Documente Academic
Documente Profesional
Documente Cultură
Ce reprezinta Java?
Java este unul dintre cele mai raspandite limbaje de nivel inalt insa acesta nu este principalul merit al sau. Acest
limbaj a revolutionat programarea, in multe sensuri pe care le vom detalia in acest curs. Scopul acestei lucrari este de a
prezenta la nivel mediu acest limbaj si de a-l utiliza pentru intelegerea conceptelor de structuri de date.
Limbajul de programare Java este acel limbaj in care se pot scrie aplicatii, applet-uri, servlet-uri. Atunci cand un
program Java este compilat, codul sursa va fi convertit in cod te tip byte code si anume un limbaj masina ce este portabil
pe orice arhitectura CPU. Acest lucru este posibil datorita existentei unei masini virtuale JVM care faciliteaza
interpretarea byte code in limbaj masina specific acelei masini pe care va fi programul va fi rulat. Platforma Java,
limbajul Java si masina virtuala Java sunt trei lucruri distincte pe care le vom detalia in cele ce urmeaza.
Platforma Java este multimea de clase Java care exista in orice kit de instalare Java. Aceste clase vor putea fi
folosite de orice aplicatie Java care ruleaza pe calculatorul unde acestea au fost instalate. Platforma Java se mai numeste
mediul Java (Java enviroment) sau kernelul Java API (Application Programing Interface). O alta denumire a aceste
multimi de clase este si cea de framework.
Clasele Java sunt grupate in colectii de clase numite pachete. Utilitatea acestora o vom detalia mai tarziu in acest
curs. Pachetele sunt de asemenea organizate dupa rolul/functia lor ca de exemplu: pachete de retele, grafica,
manipularea interfetelor cu utilizatorul, securitate, etc.
Limbajul de programare Java este limbajul OO (orientat pe obiecte) asemanator cu C++, foarte puternic si usor
de folosit si mai ales de invatat de catre programatori. Este rezultatul multor ani de lucru si inglobeaza un design elegant
si functionalitati de ultima ora ceea ce il face destul de popular printre programatori. Versatilitatea, flexibilitatea,
eficienta si portabilitatea sunt alte aspecte care propulseaza Java inaintea altora.
Pe langa acestea faptul ca programatorii pot creea programe care pot rula in cadrul unor browsere sau web
service-uri, sau ca pot creea aplicatii care sa ruleze pe diferite platforme, sau faptul ca pot creea programe ce sa ruleze
pe aproape orice dispozitiv electronic mai nou (mobile, aparate medicale, industriale, la distanta etc), fac din acest
limbaj unul foarte puternic.
Masina virtuala Java constituie elementul fundamental Java. Programele Java sunt portabile pe orice sistem de
operare, arhitectura hardware care suporta un interpretator Java. Sun, firma care realizeaza diverse kituri VM (Virtual
Machine), suporta interpretatoare pentru platforme Solaris, Microsoft si Linux. De asemenea au fost creeate
interpretatoare si pentru dispozitive ce au ca sisteme de operare Windows CE sau PalmOS.
Figura 1. Java poate rula pe orice sistem de operare/arhitectura hardware.
Una din caracteristicile de baza a tehnologiei VM este compilarea just-in-time (JIT) unde „byte code”-ul Java este
convertit la momentul executiei, in limbaj nativ.
Astfel compilarea are loc doar odata, iar interpretarea ori de cate ori ruleaza programul. Pentru a vizualiza acest lucru in
figura 2, avem cele doua evenimente schitate:
In cadrul acestei sectiuni vom urmari cateva avantaje ale acestui care incearca sa raspunda la intrebarea fireasca: de ce
sa utilizam Java cand avem alte limbaje OOP la dispozitie?
1. Scris odata va rula oriunde. Aceasta „lozinca” a firmei Sun este de fapt nucleul conceptual pe care s-a construit
platforma Java. Altfel spus odata ce aplicatia a fost scrisa, ea va rula pe orice platforma ce suporta Java, fara a fi
nevoie de modificari. Acesta este un avantaj asupra altor limbaje care trebuie rescrise (de cele mai multe ori
total) pentru a rula pe alte sisteme de operare.
2. Securitate. Platforma permite utilizatorilor sa downloadeze cod prin retea intr-un mediu sigur: codul nesigur nu
poate infecta sistemul gazda, nu poate scrie/citi fisiere pe hardisc etc. Aceasta capacitate facea ca Java sa fie
unica pana la aparitia altor platforme concurente (.NET).
3. Programare orientata catre retele. Alt principu Sun spune ca ”Reteaua este computerul”. Cei care au conceput
Java credeau in importanta comunicarii prin retea si au avut in vedere acest fapt: Java faciliteaza folosirea
resurselor prin retea si de a creea arhitecturi pe mai multe niveluri.
4. Programe dinamice. Programele scrise in Java sunt usor de extins deoarece organizarea este modulara si anume
pe clase. Clasele sunt stocate in fisiere separate si incarcate de interpretator ori de cate ori si doar atunci cand
este nevoie. Astfel o aplicatie Java apare ca o interactiune intre diverse componente software independente.
Aceasta caracteristica este net superioara aplicatiilor ce constau dintr-un cod organizat ca un bloc monolitic.
5. Performanta. Masina virtuala Java ruleaza un program interpretand instructiuni portabile byte-code. Aceasta
arhitectura inseamna ca programele Java sunt mai lente decat cele C, C++ care sunt compilate folosind cod nativ.
Totusi, pentru eficienta, anumite portiuni ale Java, cum ar fi manipularea string-urilor folosesc instructiuni cod
nativ. De la versiune la versiune acest neajuns a fost imbunatatit.
Unelte de dezvoltare: aceste unelte reprezinta cam tot de ce are nevoie un programator si anume unelte pentru
compilare, rulare, monitorizare, debug, documentare. In principiu se vor folosi javac – compilatorul, java programul ce
ruleaza aplicatiile Java, si javadoc pentru documentare.
Application Programming Interface (API): Aceasta reprezinta nucleul de functii Java. Ofera o serie de clase
folositoare ce le veti folosi in aplicatii. Acest nucleu este foarte mare, iar pentru a avea o idee despre ce contine avem
imaginea de mai jos. In figura 3 sunt prezentate doua concepte JRE (Java Runtime Enviroment) si JDK (Java Development
Kit).
JRE ofera librariile, masina virtuala Java si alte componente necesare rularii applicatiilor si a applet-urilor. Acest
mediu poate fi redistribuit odata cu aplicatiile pentru a le da autonomie.
JDK include JRE si unelte cum ar fi compilatoare sau debuger-e ce sunt utile dezvoltatorilor.
Unelte pentru interfete grafice: Swing si Java 2D sunt folosite pentru crearea de GUI (Graphical User Interfaces)
si ofera facilitati pentru un design (si nu numai) frumos al aplicatiilor.
Librarii pentru integrare: De exemplu Java IDL(Interface Definition Language) API, JDBC(Java Database
Connectivity), Java Naming and Directory InterfaceTM ("J.N.D.I.") API, Java Remote Method Invocation etc. Aceste librarii
permit accesul la baze de date sau manipularea obiectelor la distanta.
Inainte de a incepe compilarea sau rularea programelor java, trebuie sa avem acest mediu instalat pe
calculatoare. Pentru aceasta trebuie sa instalam JDK de la Sun (exista si alte pachete oferite de alte firme, insa vom folosi
pe aceasta pentru ca este gratuit si constituie autoritatea principala a limbajului Java). Acesta poate fi downloadat de pe
http://java.sun.com/
Intotdeauna pasii care vor fi urmati in crearea unui program Java vor fi :
1. Scrierea programului.
2. Compilarea acestuia.
3. Rularea programului.
Evident lucrurile se vor complica si pasii se pot detalia foarte mult. In cele ce urmeaza vom descrie pe scurt ce se
intampla si cateva indicatii pentru fiecare din cei trei pasi.
Scrierea programului
Pentru editarea unui program Java se pot folosi orice editoare: notepad, wordpad daca lucrati sub Windows, sau
joe, nano, vi daca lucrati sub Linux. De asemena se pot folosi IDE(Integrated Development Enviroment) in cazul
in care aplicatiile devin complexe.
Pentru majoritatea limbajelor, numele fisierelor ce contin codul sursa, poate fi oricare. Pentru Java numele
fisierului ce contine codul sursa este important. De exemplu in acest caz ar trebui sa fie Example.java. In Java un
fisier Java se numeste o unitate de compilare. Este un fisier text care contine una sau mai multe definitii de
clase. Compilatorul cere ca acest fisier sa aiba extensia java. Numele fisierului este Example iar acesta nu este o
coincidenta cu numele clasei. In Java totul trebuie sa fie intr-o clasa. Numele clasei publice trebuie sa fie acelasi
cu numele fisierului in care acea clasa se afla. Atentie, Java, ca si C, este case sensitive, adica Example este diferit
de example sau de eXample.
Compilarea programului
Pentru a compila un program se va executa compilatorul, javac, specificand numele fisierului sursa:
C:\>javac Example.java
Compilatorul javac creeaza o clasa Example.class care contine byte code-ul corespunzator codului sursa din
Example.java. Atentie, acest cod nu este executabil direct. El va fi executat de JVM. Pentru a rula programul, va
trebui sa utilizam interpretatorul Java, si anume java:
C:\>java Example
Atunci cand codul Java este compilat, fiecare clasa individuala este plasata in fisierul numit ca si clasa, folosind
extensia class. De aceea, este o idee buna sa dam fisierelor sursa acelasi nume ca si clasa pe care o contin.
Atunci cand rulam interpretatorul, acesta va cauta fisierul Example care are extensia .class. Il va gasi si va
executa codul continut in acea clasa.
Apoi urmeaza :
class Example {
Aceasta linie foloseste cuvantul class marcand inceputul definitie undei noi clase. Clasa este in Java, unitatea de
baza pentru incapsulare. Clasa va fi definita in blocul delimitat de „{„ si se va termina cu „}”. Momentan nu vom
intra in detalii legate de continutul unei clase, acesta fiind aspectul urmatorului capitol din curs.
Urmatorul rand din program este un comentariu pe o singura linie (cu referire la functia ce va urma).
Urmatorul rand este metoda main sau entry point:
Se numeste astfel pentru ca reprezinta functia care este prima executata atunci cand interpretatorul java
ruleaza programul. Toate aplicatiile Java incep executia cu apelul functiei main().
Cuvantul cheie public este un specificator de acces. Cuvintele cheie ale unui limbaj reprezinta acele cuvinte care
„alcatuiesc” limbajul si definesc divesele instructiuni sau operatori sau tipuri de data, etc.
Un specificator de acces determina modul in care alte clase din program pot accesa membrii unei clase.
Membrul unei clase poate fi considerat o variabila sau o functie de exemplu.
Cuvantul cheie static permite ca apelul functiei main() sa fie facut fara a fi nevoie de instantierea clasei din care
aceasta functie face parte.
Cuvantul cheie void specifica tipul de date returnat de functia main(), in cazul acesta nimic, adica spune
compilatorului ca functia nu va returna nici o valoare.
Intre parantezele functiei main() se afla lista de parametrii, in cazul de fata String args[].
Aceasta declaratie String args[] inseamna colectia de obiecte de tip String(sir de caractere).
Ultimul caracter al acestei linii este „{” si anume faptul ca incepe corpul functiei main. Corpul functiei se termina
cu „}” asemanator ca si „corpul” clasei.
System.out.println("Hello Java!");
Aceasta are ca efect – la rulare – afisarea mesajului „Hello Java!”
Afisarea are loc datorita functiei gata definite, sau predefinite, println(). Linia totusi incepe cu System.out...
In momentul acesta vom spune ca System este o clasa predefinita care ofera acces la sistem, si out este stream-
ul de iesire conectat la consola. Practic System.out este un obiect care incapsuleaza iesirea la consola. Consola
nu este des folosita in programele reale Java, sau in applet-uri, dar este utila la procesul de invatare Java.
Atunci cand scrieti un cod sursa, mai ales daca sunteti la inceput este posibil sa omiteti din neatentie sau
nestiinta anumite portiuni din cod. Din fericire, daca ceva nu este corect, compilatorul va raporta aceste probleme ca
erori de sintaxa.Compilatorul va incerca sa „dea un sens” codului sursa, indiferent in ce consta acesta. De aceea, eroarea
raportata nu reflecta de cele mai multe ori cauza problemei!
2 errors
Evident ceva nu este in regula cu mesajul pentru ca nu lipseste „;” ci „{„. Ideea este ca atunci cand programul
contine o eroare de sintaxa, mesajul nu trebuie interpretat cuvant cu cuvant. Trebuie sa ne uitam la contextul in care
apare eroarea, la liniile din jurul locului unde este indicata eroarea si sa deducem care este cauza reala a acesteia.
System.out.println(args[1]);
Ce realizeaza acest cod? Afisarea celui de al doilea argument transmis programului: argumentele sunt un sir care va fi
scris la rularea programului. Compilam si rulam:
java Example
Acest tip de exceptie apare in momentul rularii programului (de aceea se numeste runtime error) si se datoreaza
unor situatii neprevazute de programator. In cazul de fata se presupune ca avem argumente la rulare, cand realitatea
este ca sirul de argumente este gol. Accesarea argumentului cu numarul 1 este astfel o greseala! Vom detalia tot
intelesul acestei erori la momentul cuvenit.
Bazele limbajului
Inainte de a incepe sa exploram sintaxa Java, vom studia mai intai in mare alcatuirea unui program Java.
Java consta din una sau mai multe unitati de compilare, adica fisiere ce contin clase. Fiecare unitate poate
incepe cu un cuvant cheie optional package, urmata eventual de declaratii import. Acestea, asemanator directivei de
include din C permit folosirea claselor din alte pachete. Vom discuta mai tarziu despre aceste aspecte.
Apoi urmeaza definirea uneia sau mai multor clase sau interfete, dar mai nou si a structurilor enum.
In cadrul claselor putem defini campuri(variabile), metode sau constructori. Toate acestea se numesc membrii
clasei. Majoritatea metodelor vor contine instructiuni ce includ expresii, operatori, tipuri de data etc. Abordarea in
continuare va fi exact de la unitatile de baza catre pachete in final. Pachetele contin clase, clasele contin metode.
Aceasta relatie de incluziunie este marcata prin punct. Diferenta este ca in cazul pacheteleor „.” Semnifica o relatie de
incluziune a directoarelor/folderelor iar in cazul claselor/metodelor incluziunea are lor in acelasi fisier. Vom insista
asupra acestui aspect in capitolul 2.
Variabile
Variabila este un element de un anumit tip de data identificat printr-un nume, utilizat la stocarea datelor.
Numele variabilei denumit si identificator este compus din o serie de caractere ce incepe cu o litera. Tipul de data al
variabilei determina ce valori poate lua acea variabila.
Sa analizam codul de mai jos pentru o intelegere mai buna acelor descrise mai sus:
// integer
byte largestByte;
largestByte = Byte.MAX_VALUE;
// real
// afisarea
Clasa Variable contine o metoda main() si nici o variabila. Da, este corect, nici o variabila, deoarece domeniul in care cele
patru variabile sunt vizibile, este cel al functiei main().
Prin aceasta instructiune se declara o variabila numita largestByte care are tipul de data byte. Acest tip de data este o
valoare intreaga cu semn cuprinsa intre -128 si 127. Vom detalia in tabelul 1 tipurile primitive de date. Java contine doua
categorii de tipuri de date: referinta si primitive. O variabila de tip primitiv poate contine doar o singura valoare conform
cu acel tip si avand formatul acelui tip. Evident tipurile referinta sunt mai complexe si putem spune ca inglobeaza si
tipuri primitive.
Tabelul 1. Tipurile primitive
Clasele, sirurile de data si interfetele sunt tipuri de data referinta. Valoarea unei variabile de tip referinta este adresa
unui obiect. O referinta este denumita si obiect, sau adresa unei memorii in alte limbaje, insa in Java nu avem
posibilitatea de a accesa ca in C zona de memorie direct.
Pentru ca numele unei variabile sa fie valid trebuie sa indeplineasca urmatoarele conditii:
Domeniul de vizibilitate
Domeniul de vizibilitate al unei variabile este regiunea din program in care se poate face referire la acea variabila. Alt
scop al domeniului este de a determina cand sistemul creeaza sau distruge memoria alocata pentru o variabila.
Figura 6. Diverse categorii de domenii de vizibilitate ale variabilelor
Initializarea variabilelor
Variabilele locale si membre trebuie initializate inainte de a fi folosite. Initializarea inseamna atribuirea de valori
acestora:
sau
Cuvantul cheie final in cazul variabilelor semnifica faptul ca o variabila nu mai poate fi modificata odata ce a fost
initializata, fiind practic o constanta.
Operatori
Un operator indeplineste o functie, pentru unul doi sau trei operanzi. Un operator care necesita doar un
operand este denumit unar. De exemplu ++ este operator unar care incrementeaza valoarea operandului cu 1. Un
operator care necesita doi operanzi se numeste binar. De exemplu = este un operator ce asigneaza o valoare
operandului din stanga, valoarea fiind a operandului din dreapta. Un operator cu trei operanzi ar fi ?: si vom vorbi
imediat despre el.
Operatorii unari pot avea notatie prefixata ,infixata sau postfixanta:
In cele ce urmeaza vom vorbi de diversele categorii de operatori exemplificand utilizarea lor.
Operatori aritmetici
Alti operatori aritmetici sunt ++ care incrementeaza cu 1 operandul si -- care decrementeaza cu 1 operandul:
Operatori conditionali
Operatori de asignare
Operatorul = este cel implicit de asignare. De exemplu daca dorim sa adaugam o valoare la o variabila putem scrie
i = i+2;
i += 2;
Operator Descriere
?: Scurtatura la o anumita instructiune if-else
[] Folosit la lucrul cu siruri
. Pentru accesarea membrilor, a claselor.
(params ) Lista de parametrii (unei metode, sau instructiuni)
(tip ) Operator cast: schimba tipul de data al unei variabile
New Creeaza un nou obiect sau sir.
Instanceof Determina daca primul operand este instanta celui de-al doilea operand
Precedenta operatorilor
Cand folosim operatorii in combinatie cu operanzi obtinem expresii. Expresiile ajuta la calcularea si asignarea de valori
variabilelor si ajuta la contorul fluxului logic al unui program.
Expresia este o serie de variabile, operatori, apeluri care rezulta in final intr-o singura valoare. Exemple de expresii:
x * y * z
x+y/10 //ambigua
(x+y)/100 // clara, asa este recomandat
Atunci cand se va face evaluarea expresiilor se va tine cont ce operator va fi prima data evaluat:
Bloc de instructiuni
Un bloc de instructiuni este cuprins intre { si } si poate fi privit ca o singura instructiune. De exemplu:
if (i>0)
else
if (i>0)
else
Daca de exemplu dorim ca atunci cand i este mai mare ca zero, nu numai sa afisam un mesaj despre asta dar sa
decrementam aceasta valoare (motivatii pot fi multe ) atunci trebuie sa facem intr-o instructiune compusa:
if (i>0)
i--;
else
}
In cele ce urmeaza vom descrie instructiunile de baza pentru controlul logicii unui program:
While
While (expresie) {
Prima data se evalueaza expresia dintre paranteze care trebuie sa returneze o valoare booleana. Daca ea returneaza
true atunci se executa lista de instructiuni din corpul while. Se revine la evaluarea expresiei, daca ea returneaza true se
executa a doua oara lista de instructiuni pana cand expresia va returna false. In acel moment se „sare” peste corpul de
instructiuni din while la prima instructiune ce urmeaza dupa blocul while.
Exemplu:
int i=0;
while (i<10)
System.out.println(i++);
System.out.println("Dupa while");
Instructiunea do while are aproape acelasi efect singura diferenta este ca evaluarea expresiei se va face la sfarsitul
blocului de instructiuni, astfel ca acele instructiuni se vor efectua cel putin odata:
do {
… lista de instructiuni
} while (expresie);
For
Ca si while permite executarea instructiunilor in bucla. In plus permite iterarea unor variabile intr-un mod compact:
Initializari reprezinta secventa de instructiuni care se executa la intrarea in bucla; se executa doar odata la inceput.
Conditie de terminare reprezinta conditia care determina iesirea din bucla. Incrementare este o instructiune sau un set
de instructiuni care este efectuat la fiecare iteratie prin bucla.
Exemplu:
System.out.println("Dupa for");
If – else
Aceasta instructiune permite executarea unor instructiuni doar in anumite cazuri: in acest fel se poate separa logica
programului. In cea mai simpla forma instructiunea coditie poate fi scrisa asa:
if (expresie) {
Daca expresia va fi evaluata cu valoarea true, atunci lista de instructiuni este executata, altfel se sare peste. Ce se
intampla daca insa conditia este false si doar in acel caz vrem sa executam alte instructiuni? In acest caz avem
instructiunea compusa if else de exemplu:
if (response == OK) {
// code to perform OK action
} else {
// code to perform Cancel action
}
Mai departe, sa cosideram ca avem urmatorul scenariu: avem o variabila care poate lua valori de la 1 la 12 si in functie
de valoarea ei de la un moment dat vrem sa afisam luna calendaristica, corespunzatoare ei.
int month = 8;
if (month == 1)
System.out.println("Ianuarie");
else if (month == 2)
System.out.println("Februarie");
else if (month == 3)
System.out.println("Martie");
else if (month == 4)
System.out.println("Aprilie");
else if (month == 5)
System.out.println("Mai");
else if (month == 6)
System.out.println("Iunie");
else if (month == 7)
System.out.println("Iulie");
else if (month == 8)
System.out.println("August");
else if (month == 9)
System.out.println("Septembrie");
else if (month == 10)
System.out.println("Octombrie");
else if (month == 11)
System.out.println("Noiembrie");
else if (month == 12)
System.out.println("Decembrie");
Problema este ca pentru a evalua care din instructiuni trebuie executate se fac multe verificari, degeaba.Alta
instructiune care realizeaza acelasi lucru mai eficient este switch
Switch
int month = 8;
switch (month) {
case 1: System.out.println("Ianuarie "); break;
case 2: System.out.println("Februarie "); break;
case 3: System.out.println("Martie "); break;
case 4: System.out.println("Aprilie"); break;
case 5: System.out.println("Mai "); break;
case 6: System.out.println("Iunie "); break;
case 7: System.out.println("Iulie "); break;
case 8: System.out.println("August"); break;
case 9: System.out.println("Septembrie "); break;
case 10: System.out.println("Octombrie "); break;
case 11: System.out.println("Noiembrie "); break;
case 12: System.out.println("Decembrie "); break;
}
In cazul instructiunii switch se va extrage valoarea variabilei sau expresiei dintre paranteze, in cazul de fata month si in
functie de valoarea acelei expresii se va executa una din ramificatiile case. Dupa ce se executa instructiunea din dreptul
case, se va executa si instructiunea break, altfel se vor efectua si celelate instructiuni din switch care urmeaza.
Spre exemplu:
int month = 8;
switch (month) {
case 1: System.out.println("Ianuarie "); break;
case 2: System.out.println("Februarie "); break;
case 3: System.out.println("Martie "); break;
case 4: System.out.println("Aprilie"); break;
case 5: System.out.println("Mai "); break;
case 6: System.out.println("Iunie "); break;
case 7: System.out.println("Iulie "); break;
case 8: System.out.println("August");
case 9: System.out.println("Septembrie ");
case 10: System.out.println("Octombrie ");
case 11: System.out.println("Noiembrie ");break;
case 12: System.out.println("Decembrie ");
}
Va afisa August dar si lunile ce urmeaza dupa, pana la intalnirea unui break si anume Noiembrie(inclusiv).
Capitolul2
Capitolul 2: clase, obiecte, metode .......................................................................................................... 2
Alte instructiuni de control .................................................................................................................. 2
Break ............................................................................................................................................... 2
Continue .......................................................................................................................................... 3
Bucle repetitive imbricate ................................................................................................................ 4
Operatorul conditional..................................................................................................................... 5
Programare orientata pe obiecte ......................................................................................................... 5
Obiecte ............................................................................................................................................ 5
Clasa ................................................................................................................................................ 6
Forma generala a unei clase............................................................................................................. 7
Functii ........................................................................................................................................... 11
Constructori ................................................................................................................................... 16
Constructori cu unul sau mai multi parametrii ............................................................................... 17
Garbage Collection si finalizatori .................................................................................................... 19
Metoda finalize.............................................................................................................................. 20
Cuvantul cheie this ........................................................................................................................ 21
Pachete ............................................................................................................................................. 22
Definirea pachetelor ...................................................................................................................... 23
Pachete si CLASSPATH ................................................................................................................... 23
Importarea pachetelor ................................................................................................................... 25
1
Capitolul 2: clase, obiecte, metode
Break
Se poate uneori forta parasirea unei bucle repetitive(for, while), utilizand instructiunea break. In
cele ce urmeaza, conform exemplelor anterioare am realizat o bucla for, dar de data aceasta nu mai
exista conditie de continuare, deci oprirea nu poate avea loc decat din cadrul acestui for:
class BreakSample {
public static void main(String args[]) {
int num;
num = 10;
// continua la "nesfarsit", conditia de continuare nu este
for(int i=0; ; i++) {
if(i >= num) break; // i a ajuns la 10, se iese din bucla
System.out.print(i + " ");
}
System.out.println("S-a terminat bucla.");
}
}
Dupa cum se poate vedea acest tip de instructiune are loc in conjunctura cu instructiuni
conditionale: altfel s-ar executa doar odata bucla, eliminand caracterul de repetitivitate al acesteia. Alte
scopuri ale utilizarii acestei instructiuni pot fi, spre exemplu, citirea repetitiva unor caractere: in
momentul in care ceea ce am citit este egal ca valoare cu caracterul ce marcheaza oprirea citirii (de
exemplu ’q’), parasim imediat bucla repetitva in care are loc citirea.
char ch;
for( ; ; ) {
ch = (char) System.in.read(); // citesc un caracter
if(ch == 'q') break;
}
Alt mod de a folosi break, pe langa cele mentionate, este ca si instructiune de salt la o anumita
eticheta. Java nu are o instructiune clasica goto deoarece aceasta permite un flux logic nestructurat.
2
Programele care folosesc tipul acesta de instructiuni sunt greu de urmarit si de mentinut, de aceea
expunerea este pur informativa, si nu recomand folosirea break in acest scop. Forma generala a unui
break cu sens de goto este:
break eticheta;
In acest caz, eticheta este numele etichetei care identifica un bloc de instructiuni. Atunci cand se
ajunge la instructiunea break eticheta; se transfera controlul acelui bloc indicat de numele eticheta.
class BreakLabel
{
public static void main(String args[])
{
int i;
int count=0;
et1: for(i=++count; ; i++)
{
System.out.println("\n i este " + i);
if(i>2) break et1;
}
System.out.println("Dupa blocul for.");
}
}
Se poate observa ca dupa trei rulari a instructiunii for, se va executa instructiunea break cu
directionare catre eticheta et1. Se va afisa mesajele „i este 1”, „i este 2”, „i este 3” si se executa acel
break cu salt la et1. Cand are loc acest fapt, controlul se va preda blocului imediat urmator si anume
afisarea mesajului „Dupa blocul for”.
Continue
Aceasta instructiune este folosita intr-o bucla repetitiva permitand ca, in cazul in care nu vrem
sa continuam cu instructiunile din acea bucla repetitiva, instructiuni care urmeaza dupa continue, sa
trecem la pasul urmator. Practic se „sare” la urmatorul pas din instructiunea repetitva ignorand ce se
intampla dupa continue.
3
class ContinueSample
{
public static void main(String args[])
{
int i;
// afisez numere pare intre 0 si 50
for(i = 0; i<=50; i++)
{
if((i%2) != 0) continue; // daca nu e par trec la
//urmatorul pas din for
System.out.println(i);
}
}
}
In acest exemplu for fi afisate numerele pare deoarece: pe instructiunea conditionala se intra
doar daca restul impartirii numarului curent la 2 este zero. Atunci cand se intra (deci cand numarul este
impar) se efectueaza continue, adica salt la urmatorul i din for si nu se mai executa afisarea i-ului curent.
4
Operatorul conditional
Acest operator ?: este un operator ternar mostenit din C. Permite inglobarea unei conditii intr-o
expresie. Primul operand este separat de al doilea operand prin ? in timp ce al doilea operand este
separat de al treilea prin :. Primul operand trebuie sa fie de tip boolean. Al doilea si al treilea operand
vor fi de orice tip de data insa de acelasi tip de data, sau convertibil catre acelasi tip de data.
Se evalueaza primul operand, daca e true operatorul evalueaza al doilea operand si ii va folosi valoarea.
Daca este false operatorul evalueaza al treilea operand si ii returneaza valoarea. De exemplu:
int max = (x>y) ? x : y;
String nume = (nume!=null) ? nume : ” necunoscut”;
max va lua valoarea lui x daca x este mai mare ca y, altfel va lua valoarea lui y.
Obiecte
Obiectele sunt principalul concept din cadrul acestei tehnologii. Obiectele reale ce ne
inconjoara, ca masa, televizorul, unelte, animale etc, au doua caracteristici in comun: stare si
comportare. De exemplu animalele au stari (nume, colorit, specie) si comportare (mod deplasare, mod
de a respira, de a se hrani). Bicicletele au stari (viteza curenta, greutate, cadenta pedalarii) si comportare
(accelerare, franare, schimbarea treptelor de viteza).
Obiectele sunt modelate in programare prin una sau mai multe variabile, iar comportarea obiectelor
este modelata prin metode.
5
Tot ce se stie despre un obiect reprezinta starea lui exprimata prin variabile si ce poate obiectul
face, reprezinta comportamentul sau reprezentat prin metode. De exemplu un obiect software care
modeleaza o bicicleta va avea variabile ce indica starea curenta a bicicletei: viteza de 10 km/h, cadenta
pedalelor de 90 rpm, si treapta de viteza curenta (a treia). Pe langa variabile, o bicicleta reprezentata
software poate avea metode pentru a frana, schimba treapta de viteza. Totusi nu poate avea metoda de
schimbare a vitezei pentru ca viteza este consecinta cadentei de pedalare, a franarii/sau nu, a pantei.
Eventual poate avea metoda de redare a vitezei calculata pe baza acestor factori. Aceste metoda se
numesc metode de instanta pentru ca evalueaza sau modifica o stare a unei anume biciclete si nu
tuturor bicicletelor.
Diagramele de mai sus semnaleaza faptul ca variabilele formeaza centrul, nucleul obiectelor, iar
metodele inconjoara acest nucleu. Impachetarea variabilelor obiectului pentru a proteja informatia
continuta de ele, se numeste incapsulare.
Clasa
In lumea reala, multe obiecte pot fi categorizate ca fiind de acelasi tip. De exemplu automobilul
detinut de cititor este la aproximativ la fel cu orice automobil din aceasta lume. Au roti pe care se
deplaseaza, motorul cauzeaza deplasarea, si este nevoie de combustibil (sub o forma sau alta) pentru a
alimenta motorul. Asemenea si o bicicleta, va avea roti, un sistem de pedalare, una sau mai multe viteze
etc. Putem spune ca o anume bicicleta – de exemplu Atomik Mountain Bike – este o instanta a clasei
bicicleta. Se cheama ca am construit un obiect cu anume caracteristici (culoare, rezistenta) dar care
arata ca o bicicleta si se comporta asemenea.
Alta alegorie pentru a intelege mai bine diferenta intre obiect si clasa este o cel compus dintr-o
matrita de imprimat bancnote si bancnota. Hartia, bancnota propriu zisa este obiectul ca instanta a
clasei matrita care va imprima sute de hartii toate cu aceleasi caracteristici dar comportamente diferite.
Intr-o clasa, atat variabilele cat si metodele se vor numi membrii acelei clase.
6
Forma generala a unei clase
class numeclasa
{
//declar variabilelor clasei, ce vor fi disponibile in fiecare instanta
type var1;
type var2;
// ...
type varN;
// declare metodele
type metoda1(parametri)
{
//corpul metodei 1
}
type metoda2(parametri)
{
//corpul metodei 2
}
// ...
type metodaN(parametri)
{
//corpul metodei N
}
}
Acesta este sintaxa generala de definire a unei clase, se poate ca o clasa sa nu contina decat o variabila,
sau o metoda, sau niciuna.
Pentru a ilustra conceptul de clasa, vom scrie o clasa ce cuprinde informatiile despre
automobile. Aceasta clasa se numeste Automobil, si va contine trei tipuri de informatii ca: model,
consum, viteza maxima. Mai jos avem definitia clasei cu cele trei variabile:
class Automobil
{
String model; //combi, SUV, MCV, caminon etc
int consum; / 5 .. 15
int viteza; //viteza maxima ce poate fi atinsa: 180 .. 340
}
Noul tip de data se numeste Automobil, si orice obiect de acest tip va contine cele trei variabile
membru. Atentie, codul de mai sus este doar o descriere a tipului si nu prezinta crearea unui obiect.
7
Pentru a instantia un nou obiect de tipul Automobil se va folosi operatorul new:
In urma acestei instructiuni, minivan devine instanta clasei Automobil. De fiecare data cand
cream o nou instanta a unei clase, se aloca acelui obiect nou creat un spatiu de memorie necesar
membrilor clasei din care obiectul face parte. Fiecare obiect de tip Automobil va contine propriile copii
ale instantelor variabilelor model, consum, viteza. Pentru a accesa aceste variabile se va folosi
operatorul „.”. Acesta expune membrii (atat variabile cat si metode) unui obiect:
object.member
minivan.consum = 10;
In sectiunea de cod ce urmeaza vom vedea cum in alta clasa, utilizam obiecte de tip Automobil:
class Automobil
int consum; // 5 .. 15
minivan.consum =10;
minivan.viteza = 180;
8
Inainte de a trece mai departe vom prezenta un alt exemplu in care lucram cu doua obiecte,
instante ale aceleasi clase. Scopul este pentru a clarifica faptul ca variabilele continute de un obiect pot
diferi ca si valoare de variabilele continute de alt obiect, chiar daca cele doua obiecte au aceeasi clasa,
deci sunt de acelasi tip:
class Automobil
{
String model; //combi, SUV, MCV, caminon etc
int consum; // 5 .. 15
int viteza; //viteza maxima ce poate fi atinsa: 180 .. 340
}
public class AutomobilDemo
{
public static void main(String[] args)
{
Automobil minivan = new Automobil();
Automobil masinasport = new Automobil();
minivan.model = "Logan MCV";
minivan.viteza = 180;
masinasport.model = "BMW";
masinasport.viteza = 320;
9
iar apoi instantierea unui nou obiect new Automobil() pentru ca mai apoi copia noului
obiect sa fie atribuita variabilei minivan. Operatorul new aloca dinamic memorie la momentul rularii
programului, si returneaza referinta obiectului pentru care a alocat memorie. Aceasta referinta este
adresa din memorie a noului obiect alocat. Astfel in Java, toate obiectele trebuie alocate dinamic.
Pentru a disocia cele doua notiuni de declarare si instantiere separam instructiunea de mai sus:
Automobil minivan; //declaram unu obiect de tip referinta Automobil
Minivan = new Automobil(); //alocam memorie pentru un obiect de tip
//Automobil
Asignarea obiectelor
In cazul obiectelor, operatorul de asignare si anume „=” actioneaza altfel decat in vazul
variabilelor primitive de tip int. Atunci cand asignam o variabila de tip primitiv cu valoarea altei variabile
de tip primitiv, variabila din stanga operatorului primeste o copie a valorii variabilei din dreapta.
Atunci cand asignam un obiect catre alt obiect, lucrurile se schimba, deoarece obiectele
inglobeaza mai multe variabile. De exemplu:
La prima vedere, este usor de spus ca masina1 si masina2 se refera la obiecte diferite, insa nu
este deloc asa: atat masina1 cat si masina2 se vor referi la acelasi obiect. De aceea atunci cand vom afisa
cosumul ambelor vom avea „surpriza” sa constatam ca este acelasi.
System.out.println(masina1.consum);
System.out.println(masina2.consum);
10
Functii
In exemplele de mai sus, clasa Automobil contine date sub forma unor variabile, insa nici o
metoda. Desi clasele ce contin doar variabile sunt perfect valabile, majoritatea vor contine metode,
pentru asigurarea unui flux logic.
Metodele sunt functii, subrutine care trateaza, manipuleaza date definite in clase, pentru a
asigura logica dorita. O metoda consta din semnatura acesteaia si corpul ei. Semnatura metodei se
refera la tipul de data returnat, nume si lista parametrilor iar corpul inseamna blocul de instructiuni
executat ori de cate ori functia este apelata.
O metoda contine una sau mai multe instructiuni, gandite ca impreuna sa efectueze o anumita
sarcina. Metodele au nume, prin care sunt identificate in cadrul clasei, iar dupa nume intotdeauna
urmeaza paranteze. Intre paranteze sunt declarati parametrii metodei (variabile de diverse tipuri).
Aceste variabile vor lua valori in momentul apelului metodei. O metoda generala are forma:;
tip_de_returnat nume(lista de parametrii)
{
//corpul metodei
}
Tipul_de_returnat reprezinta orice tip de data valid, inclusiv clase pe care le creati; ca exemple
de tip de data avem int, Integer, String, double, Automobil. In cazul in care metoda nu returneaza nici un
tip de data acesta va fi void. Numele metodei poate fi orice identificator valabil, si respecta ca si
formare, denumirea variabilelor (a se revedea conditiile de numire a variabilelor). Lista de parametrii
este o secventa de declaratii de variabile/parametrii ce vor fi vizibili in cadrul metodei, separati prin
virgula:
int myFunction(double param1, Integer param2, Automobil minivan)
{
//... instructiuni din corpul metodei
return param2;
}
Sa luam spre exemplu clasa Automobil de mai sus, si clasa AutomobilDemo, la care mai
adaugam o metoda pe langa metoda deja existenta si anume PrintProperties. Aceasta este declarata cu
static, deoarece va fi apelata dintr-o metoda statica si anume main. Alt mod de a „rezolva” aceasta
problema era sa cream un nou obiect de tip AutomobilDemo in main si sa apelam metoda prin acel
obiect dupa cum vom vedea imediat dupa exemplul de mai jos:
11
class Automobil
{
String model; //combi, SUV, MCV, caminon etc
int consum; // 5 .. 15
int viteza; //viteza maxima ce poate fi atinsa: 180 .. 340
}
public class MethodDemo
{
public static void main(String[] args)
{
Automobil minivan = new Automobil();
Automobil masinasport = new Automobil();
minivan.model = "Logan MCV";
minivan.viteza = 180;
masinasport.model = "BMW";
masinasport.viteza = 320;
PrintProperties(minivan);
PrintProperties(masinasport);
}
static void PrintProperties(Automobil vehicul)
{
//verificam sa nu avem obiecte null
//altfel avem eroare la accesarea membrilor lui
if (vehicul == null)
{
System.out.println("Introduceti un obiect instantiat!");
return;
}
System.out.println("Masina "+ vehicul.model + " are viteza maxima
" + vehicul.viteza);
}
}
12
In acest exemplu se poate observa declararea metodei PrintProperties ce are ca parametru un obiect de
tip Automobil:
static void PrintProperties(Automobil vehicul)
Aceasta inseamna ca atunci cand vom apela aceasta metoda, va trebui sa specificam intre
paranteze doar obiecte de tip Automobil, sau care deriva din clasa Automobil (vom detalia in capitolul
urmator). Mai jos este apelul functiei cu un parametru de tipul Automobil.
PrintProperties(minivan);
In exemplul de mai sus avem o functie care nu returneaza nici o valoare. Sa vedem si un
exemplu de functie ce returneaza o valoare care va fi folosita ulterior:
class Rectangle
{
public int width; //latimea dretunghiului
public int height; //inaltimea dreptunghiului
}
static int GetPerimeter(Rectangle aRectangle)
{
if (aRectangle == null)
{
System.out.println("Introduceti un obiect instantiat!");
return -1;
}
return 2*(aRectangle.width+aRectangle.height);
}
}
13
public class MethodDemo
{
public static void main(String[] args)
{
//exemplu pentru doua dreptunghiuri diferite
Rectangle myRectangle = new Rectangle();
Rectangle myOtherRectangle = new Rectangle();
myRectangle.width = 10;
myRectangle.height = 7;
myOtherRectangle.width = 12;
myOtherRectangle.height = 20;
In acest exemplu, metodele se vor declara in clasa Rectangle si vor fi apelate din interiorul
metodei main din clasa MethodDemo. Se observa faptul ca apelul functiilor comporta in fata numelui lor
si numele clasei: Rectangle. Aceasta pentru ca metodele sunt statice, iar in acest caz o metoda statica
poate fi apelata fara sa fie nevoie de instantierea unui obiect de acel tip. Metodele statice si variabilele
statice sunt aceleasi per clasa, altfel spus variabilele membre clasei Rectangle vor avea aceleasi valori
pentru orice obiect de aceasta clasa. Daca un obiect de tip Rectangle A, modifica valoarea lui width la 5,
si width ar fi declarat static in Rectangle, atunci si un alt obiect B va avea ca width tot valoarea 5.
14
Figura 2.3 sensul cuvantului cheie static
Alt fapt de remarcat este ca valoarea returnata de functie poate fi folosita imediat in expresii
cum ar fi alcatuirea unui String sau in asignari, urmand ca variabila care ia valoarea returnata de functie
sa fie utilizata mai departe:
In exemplele de mai sus am folosit doar un singur parametru (de tip rectangle). In cele ce
urmeaza vom avea doi si chiar trei parametrii de tipuri diferite pentru a evidentia utilizarea parametrilor.
class Numbers
{
int GetSumofTwoInt(int a, int b)
{
return a+b;
}
int GetSumofThreeInt(int a, int b, int c)
{
return a+b+c;
}
15
double GetSumofTwoDouble(double a, double b)
{
return a+b;
}
}
public class MoreParameters
{
public static void main(String[] args)
{
Numbers number = new Numbers();
System.out.println("Suma 6 + 7 = " + number.GetSumofTwoInt(6,7));
System.out.println("Suma 6 + 7 +8 = " +
number.GetSumofThreeInt(6,7,8));
System.out.println("Suma 6.6 + 7.2 = " +
number.GetSumofTwoDouble(6.6,7.2));
}
}
Dupa cum se poate observa in acest exemplu toate cele trei metode sunt apelate dupa
instantierea obiectului number. Aceasta deoarece ele nu sunt statice, astfel ca acum nu mai putem
utiliza numele clasei pentru a apela functia. Ca exercitiu incercati sa refaceti metodele astfel ca ele sa fie
statice, eliminand cu totul utilizarea obiectului number.
Constructori
In exemplele anterioare, in cele in care am folosit clasa Automobil, am initializat membrii clasei
ca model, viteza in functia in care am si creat noul obiect minivan:
minivan.model = "Logan MCV";
minivan.viteza = 180;
Modul acesta de lucru nu este unul profesional, deoarece necesita o atentie in plus din partea
programatorului, acesta putand omite initializarea unor membrii (ce s-ar intampla daca avem zece
membrii in aceeasi clasa?).
Un constructor este o metoda care initializeaza un obiect atunci cand acesta este creat (cu
operatorul new). Are acelasi nume ca si clasa si nu returneaza nimic. In mod normal constructorii sunt
folositi pentru a crea proceduri de initializare pentru a „da o forma” noului obiect. Mai jos avem un
exemplu de un constructor pentru clasa Automobil:
16
class Automobil
int consum; // 5 .. 15
public Automobil()
model = "Necunoscut";
consum = 0;
viteza = 0;
...
public class AutomobilDemo
{
public static void main(String[] args)
{
Automobil minivan = new Automobil(); //Acesta este locul in care
//apelam constructorul
....
In exemplul anterior, a fost folosit un constructor fara parametrii. Desi este ok, in multe situatii,
exista anumite momente cand trebuie sa avem constructori prin care sa manipulam valorile initiale ale
unui obiect. Pentru aceasta, vom transmite aceste valori ca parametrii unui constructor al aceleasi clase,
la fel cum se intampla la apelul unor functii cu diversi parametrii:
17
class Automobil
{
String model; //combi, SUV, MCV, caminon etc
int consum; // 5 .. 15
int viteza; //viteza maxima ce poate fi atinsa: 180 .. 340
//constructor fara parametrii
public Automobil()
{
model = "Necunoscut";
consum = 0;
viteza = 0;
}
//constructor cu un parametru
public Automobil(String modelulinitial)
{
model = modelulinitial;
consum = 0;
viteza = 0;
}
//constructor cu doi parametrii
public Automobil(String modelulinitial, int vitezainitala)
{
model = modelulinitial;
consum = 0;
viteza = vitezainitala;
}
}
public class AutomobilDemo
{
public static void main(String[] args)
{
//Constructor cu un parametru
//ulterior mai trebuie sa initializez separat viteza
Automobil minivan = new Automobil("Logan MCV");
minivan.viteza = 180;
18
//Constructor cu doi parametrii, initializez tot
//direct prin apelul acestui
Automobil masinasport = new Automobil("BMW",320);
In exemplul de mai sus avem un constructor fara parametrii (implicit sau default) si doi
constructori cu unul respectiv doi parametrii. Evident tipul de data al parametrilor poate varia, exact ca
in cazul metodelor.
Cum eliminam atunci referinta unui obiect atunci cand nu mai este nevoie de el?
Atunci cand instantiem un obiect, se creează o zona de memorie care va fi alocata acelui obiect.
Memoria insa, nu este infinita, si memoria libera trebuie bine manipulata. Se poate uneori ca new sa
esueze pentru ca nu exista memorie suficienta pentru a crea obiectul. De aceea, o componenta cheie a
limbajului este un mecanism de alocare dinamic, si de recuperarea a memoriei de la obiectele
nefolosite, facand ca acea memorie sa fie disponibila altor noi obiecte. In C++, acest lucru este facut
manual, de catre programator, cu anumite instructiuni gen delete, sau free.
Java foloseste altceva si anume garbage collector (GC). GC este un mecanism ce recupereaza
memoria de la obiectele ce nu mai sunt folosite (si aici sunt mai multe scenarii), si o face disponibila
pentru alte noi obiecte. Pentru eficienta, GC va rula cand doua conditii sunt indeplinite: exista obiecte
care trebuie reciclate si este nevoie ca ele sa fie reciclate. De aceea nu se poate preciza, sau controla
cand GC va rula.
19
Metoda finalize
Se poate defini o metoda care va fi apelata automat inainte ca obiectul sa fie distrus de GC.
Aceasta se numeste finalize(), si se utilizeaza cand dorim ca obiectul sa fie distrus intr-un mod ordonat.
De exemplu se poate folosit metoda finalize() pentru a inchide un fisier eventual deschis de acel obiect.
Aici cuvantul cheie protected este un specificator de acces ce nu permite accesul la aceasta
metoda din afara clasei (decat in anumite conditii asupra carora vom reveni). In cele ce urmeaza am
prezentat un exemplu pentru a intelege mai bine aceasta metoda:
class Finalize
{
public static void main(String args[])
{
int count;
FinalizeDemo ob = new FinalizeDemo(0);
/* Generam un numar mare de obiecte, care apoi vor fi
distruse de GC la un moment dat*/
for(count=1; count < 100000; count++)
ob.generator(count);
}
}
class FinalizeDemo
{
int x;
FinalizeDemo (int i)
{
x = i;
}
20
//apelat de GC
protected void finalize()
{
System.out.println("Finalizarea lui " + x);
}
// genereaza un obiect care este imediat distrus
void generator(int i)
{
FinalizeDemo o = new FinalizeDemo(i);
}
}
In acest exemplu se creează 10000 de obiecte de tip FinalizeDemo, prin intermediul metodei
generator(). Atunci cand se paraseste domeniul de definitie al acestei metode (in cadrul for-ului din
metoda main()) practic obiectul o nu mai este folosit. Acest lucru va fi remarcat de GC atunci cand
acesta va face colectarea obiectelor care nu mai sunt folosite. Cand o este distrus se va apela automat
metoda finalize(). Aceasta metoda, pentru fiecare obiect de tip FinalizeDemo afiseaza mesajul
„Finalizarea lui ” si numarul care a fost transmis (in cadrul for-ului) ca parametru constructorului, si
anume valoarea lui x. Acest x, poate fi vazut ca un identificator de obiect in cadrul celor 10000 care vor fi
create si la un moment dat distruse.
Atunci cand apelam o metoda ce apartine unui obiect din interiorul obiectului, implicit se
considera ca referinta cu ajutorul caruia aceasta metoda s-a apelat este obiectul ce o invoca. Aceasta
referinta este this. this reprezinta instanta obiectului curent. Pentru a intelege mai bine this avem
exemplul de mai jos, in care calculam puterea unui numar:
class Power
{
double b;
int e;
double val;
Power(double base, int exp)
{
this.b = base;
this.e = exp;
this.val = 1;
if(exp==0) return;
21
for( ; exp>0; exp--)
this.val = this.val * base;
}
double get_power()
{
return this.val;
}
}
class DemoPower
{
public static void main(String args[])
{
Power x = new Power(3, 2);
System.out.println(x.b + " la " + x.e +
" este " + x.get_power());
}
}
Pachete
In programare este folositor sa grupam organizat, modulele scrise. In Java, acest lucru este
posibil datorita existentei pachetelor. Pachetul ofera un mecanism de organizare a bucatilor dintr-un
program ca un tot unitar si totodata ofera un mod de grupa colectiile de clase. Mai mult clasele definite
intr-un pachet pot fi ascunse in acel pachet si astfel nu vor fi accesibile unui alt pachet, sau in afara
pachetului in care sunt definite.
In general cand denumim o clasa, ii alocam un spatiu de nume: namespace. Acesta este ca o
regiune in cadrul careia doua clase nu pot avea acelasi nume. De ce este necesar acest namespace?
In cadrul programelor simple, ca cele prezentate mai sus numele claselor difera. In programe
mari, insa putem avea acelasi nume de clasa pentru doua scopuri diferite: spre exemplu un program
care ar efectua diverse calcule pentru un motor ar putea avea doua clase Power , una pentru calculul
puterii unui motor, iar alta pentru calculul matematic de ridicare la putere. In acest caz separam codul in
doua module/namespace-uri unul denumit eventual engine.characteristics iar altul denumit math. In
cele ce urmeaza vom analiza cum se definesc aceste nume.
22
Definirea pachetelor
Toate clasele apartin, in Java, unui pachet. Atunci cand nu apare instructiunea package pentru a
specifica in mod clar apartenenta, se foloseste de fapt pachetul default sau global.
Pentru a crea un nou pachet, comanda package va apare la inceputul fisierului sursa:
Package pachet;
Aici pachet este numele unui pachet. De exemplu, pentru crearea unui pachet Project1:
Package Project1;
Sistemul de tratare a pachetelor in Java este in felul urmator: fiecare pachet este stocat in
folderul cu numele sau. De exemplu fisierele .class declarate ca fiind din proiectul Projectul1 trebuie
salvate in folderul Project1.
Se poate crea o ierarhie de pachete. Pentru a face acest lucru fiecare pachet poate fi inclus ca un
arbore:
package pack1.pack2.pack3;
Desigur folderul pack3 va sta in folderul pack2 iar pack2 va fi subfolder al lui pack1.
Pachete si CLASSPATH
Problema care intervine este, daca vom avea o ierarhie de clase si multe foldere, de unde stie
Java sa ia aceste clase?
Se poate specifica prin intermediul CLASSPATH, care este o variabila de mediu, o serie de cai,
unde o cale va fi de forma (in Windows) C:\Pachete\Project1. Fiind o variabila de mediu, aceste cai vor fi
separate prin „;”.
23
Book(String t, String a)
{
title = t;
author = a;
}
void show()
{
System.out.println(title);
System.out.println(author);
}
}
class BookDemo
{
public static void main(String args[])
{
book0.show();
book1.show();
book2.show();
}
}
In acest exemplu, ambele clase se afla in pachetul BookPack. In urma compilarii folosind comanda
javac BookDemo.java
vom obtine doua fisiere: Book.class, BookDemo.class . Acestea trebuie sa fie in folderul BookPack
neaparat. Din folderul parinte lui BookPack vom lansa interpretatorul:
java BookPack.BookDemo
24
Importarea pachetelor
Folosind cuvantul cheie import putem „aduce” in codul curent declaratia tuturor claselor din
pachetele pe care le „importam”. Intr-un fel acest import functioneaza ca si directiva include din C++.
import pkg.classname;
unde, pkg este numele pachetului importat, iar classname este evident numele clasei ce va fi importata.
Daca vrem sa importam toate clasele, sau tot continutul unui pachet se va folosi „*” .
import pachet.MyClass;
import pachet.*;
Java contine o serie de pachete ce servesc la definirea claselor pe care le vom studia in continuare. Iata
cateva:
25
Capitolul 3
Supraincarcarea metodelor ................................................................................................................. 2
Supraincarcarea constructorilor ........................................................................................................... 4
Recursivitate........................................................................................................................................ 7
Parametrii variabili .............................................................................................................................. 8
Mostenire ............................................................................................................................................... 9
Bazele mostenirii ................................................................................................................................. 9
Specificatori de acces ........................................................................................................................ 13
Metode de get, set ............................................................................................................................ 15
Rolul constructorilor in mostenire ..................................................................................................... 17
super ................................................................................................................................................. 18
Suprascrierea metodelor ................................................................................................................... 21
final ................................................................................................................................................... 23
Clasa Object .......................................................................................................................................... 24
clone() ........................................................................................................................................... 24
toString() ....................................................................................................................................... 25
equals() ......................................................................................................................................... 26
hashcode() ..................................................................................................................................... 27
1
Supraincarcarea metodelor
In Java, se pot gasi doua sau mai multe metode in cadrul aceleiasi clase, care sa aiba acelasi
nume, atata timp cat parametrii lor sunt diferiti. In acest caz se spune ca metoda este supraincarcata, iar
procedeul se numeste de supraincarcarea metodelor. Este unul dintre modurile prin care Java suporta
polimorfismul.
Ce inseamna parametrii diferiti? In acest procedeu exista o restrictie: parametrii trebuie sa fie
diferiti: atat tipul de data al parametrilor cat si numarul lor poate sa difere. Daca tipul de data returnat
de functie si numai acesta este cel care difera, compilatorul nu poate alege care metoda trebuie apelata.
Tipurile de data returnate nu ofera informatie suficienta pentru a face diferenta. In cele ce urmeaza
vom exemplifica supraincarcarea metodelor.
class Supraincarcare
{
void functieSupraincarcata()
{
System.out.println("Fara parametrii");
}
void functieSupraincarcata(int unparam)
{
System.out.println("Un parametru: " + unparam);
}
int functieSupraincarcata(int a, double b)
{
System.out.println("Doi parametrii: " + a + " " + b);
return a + (int)b;
}
}
public class DemoSupraincarcare
{
public static void main(String args[])
{
Supraincarcare obj = new Supraincarcare();
//Apelul functiei fara nici un parametru
obj.functieSupraincarcata();
//Apelul functiei cu un parametru de tip int
obj.functieSupraincarcata(5);
//Apelul functiei cu doi parametrii
int i = obj.functieSupraincarcata(5,5.7);
System.out.println(i);
}
}
2
Dupa cum se vede una din functii nu intoarce nimic (void) pe cand cealalta returneaza o valoare
de tip int care va fi influentata de parametrii transmisi la apelul functiei:
i = obj.functieSupraincarcata(5,5.7);
Atunci cand se efectueaza acest apel, Java va chema acea functie care are ca prim parametru de
tip int, iar al doilea de tip double sau transformabil catre double. Astfel si un apel de tipul acesta este
valabil:
i = obj.functieSupraincarcata(5,6);
Dupa cum am precizat, supraincarcarea nu consta doar in schimbarea tipului de data returnat de
functie. Mai jos este un exemplu in care am definit doua functii cu acelasi parametru, dar tip de data
returnat diferit:
class Supraincarcare
{
void functieSupraincarcata(int b)
{
System.out.println("Un parametru: " + b);
}
int functieSupraincarcata(int a)
{
System.out.println("parametrul int: " + a);
return a*a;
}
}
public class DemoNoSupraincarcare
{
public static void main(String args[])
{
Supraincarcare obj = new Supraincarcare();
//Apelul functiei cu un parametru de tip int
obj.functieSupraincarcata(5);
//Apelul functiei tot cu un parametru
int i = obj.functieSupraincarcata(7);
System.out.println(i);
}
}
In exemplul acesta vom avea urmatoarele erori de compilare:
DemoNoSupraincarcare.java:8: functieSupraincarcata(int) is
already defined in Supraincarcare
int functieSupraincarcata(int a)
^
DemoNoSupraincarcare.java:22: incompatible types
found : void
required: int
int i = obj.functieSupraincarcata(7);
3
Functia functieSupraincarcata cu parametru de tip int a mai fost definita, desi cu un tip de data
returnat altul decat int, si anume void dupa cum ne dam seama din eroarea a doua.
De ce avem nevoie de supraincarcarea unor functii?
Acest mecanism este denumit si paradigma „o interfata, metode multiple”, iar in limbajele care
nu suporta acest conceput, fiecare metoda trebuie sa aiba nume diferit. Sa consideram functia abs()
care returneaza valoarea absoluta a unui numar. Numerele pot fi reprezentate in multe feluri, astfel ca
in C spre exemplu, aceasta functie ar purta denumirea de labs() in cazul in care returneaza un long, sau
fabs() in cazul in care returneaza un float. Problema este ca pentru un om, devine destul de complicat sa
retina cateva nume diferite pentru aceeasi functie, daca vorbim deja de cateva sute de functii pe care le
foloseste in mod uzual. Evident functia abs() este un singur exemplu pentru a raspunde la intrebare.
Supraincarcarea constructorilor
Constructorul este o metoda, astfel ca suporta acelasi mecanism de supraincarcare descris mai
sus. In cele ce urmeaza am ales un exemplu clasic de supraincarcare a constructorilor:
class Numbers
{
int m_int;
short m_short;
double m_double;
float m_float;
Numbers()
{
m_int = 0;
m_double =0;
m_short = 0;
m_float =0;
}
Numbers(double val)
{
m_double = val;
m_int = 0;
m_short = 0;
m_float =0;
}
Numbers(short val)
{
m_short = val;
m_int = 0;
m_double =0;
m_float =0;
4
Numbers(float val)
{
m_float = val;
m_int = 0;
m_double =0;
m_short = 0;
}
Numbers(int ival, double dval)
{
m_int = ival;
m_double = dval;
m_short = 0;
m_float =0;
}
public String toString()
{
return "int = " + m_int + " short = " + m_short
+ " double = " + m_double + " float = " +m_float;
}
}
public class OverloadingDemo
{
public static void main(String[] args)
{
Numbers number1 = new Numbers();
Numbers number2 = new Numbers(1);
Numbers number3 = new Numbers(1.1);
Numbers number4 = new Numbers(2,3);
Numbers number5 = new Numbers(2,4.4);
System.out.println(number1);
System.out.println(number2);
System.out.println(number3);
System.out.println(number4);
System.out.println(number5);
5
Deocamdata sa ignoram functia toString() care ajuta la o afisare rapida a membrilor clasei
Numbers.
In exemplul de mai sus avem sase constructori, toti cu parametrii de tipuri diferite. Acestia in interiorul
lor, initializeaza membrii, fie cu zero, fie cu valoarea corespunzatoare a parametrilor:
Numbers(int ival, float fval)
{
m_float = fval;
m_int = ival;
m_double =0;
m_short = 0;
}
De exemplu, daca am ales sa transmit un parametru int si unul float, m_int si m_float sunt
initializati cu acele valori, iar restul cu zero. Evident logica poate fi schimbata si in cazul de fata toate
variabilele intializate cu unul din parametrii constructorului. Ce este interesant, vom afla la apelul
constructorilor in interiorul clasei OverloadingDemo. Constructorul implicit este cel fara parametrii si in
cadrul lui toate variabilele se initializeaza cu zero. In cazul apelului Numbers number2 = new
Numbers(1); ar trebui sa se apeleze constructorul cu parametrul de tip int, si asa se si intampla.
Totusi in cazul celui de al treilea apel si anume: Numbers number3 = new Numbers(1.1); lucrurile
devin neclare. Numarul transmis ca parametru poate fi interpretat de Java ca si double sau ca float.
Aceeasi ambiguitate o avem si in apelurile ulterioare, pentru ca nu stim care dintre constructori vor fi
apelati, deoarece parametrii pot fi interpretati fie ca double, fie ca float si asa mai departe. Aceasta este
ceea ce interpretatorul java va „deduce”:
6
Recursivitate
O metoda se poate apela pe sine (din interiorul ei), iar acest concept se numeste recursivitate.
In general recursivitatea este folosita atunci cand definim o expresie, sau termeni care sunt
circulari prin definitie. Unul din exemplele clasice de recursivitate este factorialul, si anume produsul
numerelor naturale din 1..n. Mai jos este codul care realizeaza calculul factorialului:
class Factorial
{
int factR(int n)
{
int result;
if(n==1) return 1;
result = factR(n-1) * n;
return result;
}
}
class FactorialDemo
{
public static void main(String args[])
{
Factorial f = new Factorial();
System.out.println("Factorial de 5 este " + f.factR(5));
}
}
In cadrul clasei Factorial este definita functia factR care returneaza o valoare de tip int. In corpul
functiei are loc o conditie
if(n==1) return 1;
si anume conditia de terminare (a apelurilor recursive). Urmeaza dupa aceasta o conditie, o
instructiune care duce la apelul functiei factR:
result = factR(n-1) * n;
Mai intai se va apela functia aceasta si iar se va executa conditia de mai sus, pana cand n devine
1. In acea clipa are loc primul return din functie si anume cu valoarea 1. Se revine in functia apelata
penultima data inainte sa ajungem la n egal cu 1 si se evalueaza result ca fiind 1*2, si se returneaza
valoarea 2. Mai departe se reevalueaza valoarea lui result ca fiind 2*3 si asa mai departe.
Atunci cand o metoda se apeleaza pe sine, alti parametrii locali si variabile sunt alocate pe
stiva, metoda se executa cu acesti noi parametrii. Atunci cand se revine din apel, variabilele „vechi” si
parametrii initiali sunt refacuti, si instructiunea se reia de acolo de unde inital apelasem functia. Dupa
cum intuiti, in cazul mai multori apeluri, stiva se va „incarca” cu metode aditionale, ceea ce va duce la o
eroare cauzata de lipsa memoriei: „stack overflow”.
De ce o abordare recursiva?
Raspunsul este simplu, datorita faptului ca unii algoritmi (de exemplu QuickSort) pot fii
implementati mai usor in mod recursiv decat iterativ. Vom reveni asupra acestui subiect, cu exemple
diverse, in capitolele urmatoare.
7
Parametrii variabili
Un astfel de parametru este specificat prin trei puncte (...). De exemplu, iata o metoda ce preia
un numar variabil de parametrii:
static void TestParam(int ... p)
{
System.out.println("Numarul argumentelor: " + p.length);
for(int i=0; i < p.length; i++)
System.out.println(" argumentul " + i + ": " + p[i]);
System.out.println();
}
Aceasta declaratie semnifica faptul ca metoda TestParam poate fi apelata cu oricati parametrii
atata timp cat acestia sunt de tip int:
corect: TestParam(1,2,3);
incorect: TestParam(1,2.5,3);
In felul acesta variabila p va deveni un sir de date de tip int si va functiona ca atare.
Parametrii variabili se pot utiliza impreuna cu parametrii obisnuiti iar ca exemplu avem clasa de
mai jos:
class TestParam
{
static void TestParam(String amesage, double adouble,int ... p)
{
System.out.println("Numarul argumentelor: " + p.length);
for(int i=0; i < p.length; i++)
System.out.println(" argumentul " + i + ": " + p[i]);
System.out.println();
}
8
I:\Java\Curs3\Cod_curs3>javac Test.java
Test.java:3: ')' expected
static void TestParam(String amesage, int ... p, double
adouble)
^
Test.java:3: ';' expected
static void TestParam(String amesage, int ... p, double
adouble)
^
2 errors
De asemenea nu este permis sa avem mai multi parametrii variabili in aceeasi lista.
Mostenire
Bazele mostenirii
9
class Forma2D
{
double inaltime;
double latime;
void AfisezDimensiunile()
{
System.out.println("inaltimea este " +
inaltime + " latimea este " + latime);
}
}
Deocamdata avem o clasa, insa nici o posibilitate de a calcula aria unei forme din moment ce nu stim
despre ce fel de forma este vorba. Ca atare, clasa Forma2D nu va contine nici o functie de tip GetArea()
sau CalcArea().
}
}
Aceasta clasa va mosteni de la clasa Forma2D atat cei doi membrii inaltime si latime cat si metoda
AfisezDimensiunile(). Ca atare putem folosi membrii in interiorul unei metode cum ar fi CalcArea().
In continuare sa vedem cum pot fi folosite metodele – cele mostenite – si cele proprii:
class DemoMostenire{
public static void main(String args[])
{
Triunghi t1 = new Triunghi ();
Triunghi t2 = new Triunghi ();
t1.latime = 8.0;
t1.inaltime = 4.0;
t1.tip_triunghi = "dreptunghic";
t2.latime = 4.0;
t2.inaltime = 6.0;
t2.tip_triunghi = "isoscel";
10
System.out.println("Informatiile despre t1: ");
t1.AfisezTip();
t1.AfisezDimensiunile();
System.out.println("Aria " + t1.CalcArea());
System.out.println();
System.out.println("Informatiile despre t2: ");
t2.AfisezTip();
t2.AfisezDimensiunile();
System.out.println("Aria " + t2.CalcArea());
}
}
In functia main exista doua obiecte de tip Triunghi. Dupa cum se poate observa aceste obiecte
contin, sau au acces la membrii inaltime si latime dar si la metoda AfisezDimensiunile(). Aceasta pentru
ca clasa Triunghi mosteneste clasa Forma2D si implicit membrii ei.
Pe langa metodele si variabilele mentionate t1 si t2 au acces la variabila tip_triunghi de tip
String, dar si la AfisezTip()si CalcArea(). Inainte nu puteam implementa o metoda de calcul al
suprafetei pentru ca nu cunosteam tipul de forma (triunghi, cerc etc). In clipa aceasta, putem spune ca
clasa Forma2D prinde contur prin extinderea ei in clasa Triunghi. Vom vedea in cele ce urmeaza ca
expresia „prinde contur” poate avea mai multe intelesuri cand vom intra in detaliile polimorfismului in
capitolele urmatoare.
Deocamdata spunem ca metodele AfisezTip si CalAreasi variabila tip_triunghi sunt membrii
clasei Triunghi si numai ai clasei Triunghi, iar inaltime, latime si AfisezDimensiunile() sunt membrii clasei
Forma2D si, datorita faptului ca Triunghi mosteneste Forma2D, sunt si membrii lui Triunghi.
Pentru a duce lucrurile mai departe si a exemplifica pluralismul mecanismului de mostenire
vom mosteni din aceeasi clasa Forma2D si o clasa Dreptunghi ce seamana cu Triunghi dar are alta
implementare.
class Dreptunghi extends Forma2D
{
double CalcArea()
{
return inaltime* latime;
}
void AfisezTipDreptunghi()
{
if (latime == inaltime)
System.out.println("Dreptunghiul este patrat");
else
System.out.println("Dreptunghiul este oarecare");
}
}
class DemoMostenire{
public static void main(String args[])
{
Dreptunghi d1 = new Dreptunghi ();
Dreptunghi d2 = new Dreptunghi ();
11
d1.latime = 4.0;
d1.inaltime = 4.0;
d2.latime = 4.0;
d2.inaltime = 6.0;
System.out.println("Informatiile despre d1: ");
d1. AfisezTipDreptunghi ();
d1.AfisezDimensiunile();
System.out.println("Aria " + d1.CalcArea());
System.out.println();
System.out.println("Informatiile despre d2: ");
d2. AfisezTipDreptunghi ();
d2.AfisezDimensiunile();
System.out.println("Aria " + d2.CalcArea());
}
}
Dupa cum se observa clasa Dreptunghi nu mai are nevoie de un membru de gen tip_triunghi
pentru ca putem sa calculam foarte simplu cu ce fel de dreptunghi lucram si acest lucru il facem in
functia AfisezTipDreptunghi() a carei implementare este diferita fata de cea in Triunghi a
functiei AfisezTip().
Pentru a intelege mai bine mostenirea avem reprezentarile de mai jos:
12
In general mostenirea se va declara in urmatorul fel:
class nume_subclasa extends nume_superclasa
{
//corpul clasei
}
Java nu suporta mostenirea din mai multe superclase, spre deosebire de C++. Se poate totusi crea o
ierarhie de mosteniri in care o subclasa devine superclasa pentru alta clasa. Desigur, o clasa nu poate fi
propria superclasa.
Specificatori de acces
In Java exista la nivel de membrii ai clasei, trei tipuri de specificatori de acces: public, private si
protected.
private inseamna ca acel membru nu poate fi accesat decat in interiorul clasei, de catre
metodele din interiorul clasei.
protected inseamna ca nu poate fi accesat decat fie in interiorul clasei, de catre metodele
clasei, fie din interiorul claselor ce mostenesc clasa de baza (in care membrii au fost declarati). In cadrul
claselor din acelasi pachet general membrii declarati astfel sunt vizibili in aceste clase.
public inseamna ca membrii pot fi accesati si din afara clasei.
13
Mai jos avem implementarea claselor din aceasta diagrama
class BaseClass
{
public int i;
private int j;
protected int k;
void PrintMyVariables()
{
//membrii proprii
System.out.println(i);
System.out.println(j);
System.out.println(k);
}
}
class SubClass extends BaseClass
{
double d;
void PrintVariables()
{
//membrul propriu
System.out.println(d);
//membrii lui BaseClass
System.out.println(i);
System.out.println(k);
}
}
class AnyClass
{
float f;
//Atentie in cadrul acestei clase
//nu am acces direct asupra membrilor
//celorlalte clase pentru ca nu exista
//relatie de mostenire, ci prin instantiere
//vom accesa membrii claselor
void PrintVariablesOfBaseClass(BaseClass obj)
{
System.out.println(obj.i);
//Acest apel ar duce la eroare
//System.out.println(obj.j);
}
}
class DemoAcces
{
public static void main(String args[])
{
BaseClass objB = new BaseClass();
objB.i = 20;
14
objB.PrintMyVariables();
SubClass objS = new SubClass();
objS.d = 3.0;
objS.i= 2;
objS.k = 6;
objS.PrintVariables();
AnyClass objA = new AnyClass();
objA.PrintVariablesOfBaseClass(objB);
}
}
Membrii i si k ai clasei BaseClass vor fi vizibili atat in SubClass cat si in AnyClass dar din motive
diferite. Clasa SubClass este derivata din BaseClass, automat mosteneste si membrii public si protected
ai sai. De aceea putem face referire la membrii acestei clase fara a avea probleme. Problemele apar
daca incercam sa facem referire la membrul j care este declarat privat in clasa de baza.
Pe de alta parte in clasa AnyClass, ce nu mosteneste nici o clasa, va trebui sa folosim membrii
clasei de baza (pentru exemplificare) prin intermediul unui obiect de tip BaseClass. Acest obiect poate fi
instantiat chiar in cadrul unei metode din clasa AnyClass sau poate fi transmis ca parametru unei functii
din aceasta clasa. Evident al doilea caz l-am implementat si anume functia:
void PrintVariablesOfBaseClass(BaseClass obj)
Un alt specificator de acces ar fi cel implicit, si anume membrii declarati fara specificatori de
acces isi extind domeniul de folosire in cadrul pachetului, adica sunt vizibili in orice clasa din pachetul
clasei de baza.
Daca totusi vreau sa accesez un membru private?
In cazul in care se doreste expunerea „in exterior” a unei variabile declarata privata exista
posibilitatea implementarii unor functii pentru aceasta. Acestea se numesc getter si setter si sunt
folosite la returnarea valorii variabilei respectiv setarea valorii.
Mai mult, in cadrul acestor functii se pot verifica anumite conditii pentru evitarea aparitiei unor
erori de logica.
class ExampleGetSet
{
private String mystr;
private double a;
15
//functie de get cu acces public
public String GetString()
{
return mystr;
}
//functie de set cu acces public
}
class DemoExampleGetSet
{
public static void main(String args[])
{
ExampleGetSet obj = new ExampleGetSet();
obj.SetString("Orice fel de sir");
obj.SetVal(3.4);
System.out.println(obj.GetVal());
System.out.println(obj.GetString());
}
}
16
In exemplul de mai sus metodele SetVal si SetString vor seta membrii privati ai clasei doar in
cazul in care se „trece” de anumite conditii si anume ca String-ul transmis ca parametru sa fie diferit de
null sau ca valoarea double transmisa ca parametru sa fie valida.
Asemenea si in cazul GetString sau GetVal se pot face unele validari, insa in general cand
returnam din interior spre exterior, valorile ar trebui sa fie valide.
Nu exista regula de validare generala dar o functie Get ar trebui sa returneze un tip de data
acelasi cu variabila pe care o expune iar Set are tipul void, iar ca parametru o variabila de tip de data
acelasi cu membrul ce va fi modificat.
Nu exista reguli pentru cand o variabila trebuie declarata privata, dar sunt doua principii. Daca o
variabila este folosita doar de metodele din interiorul clasei atunci ea trebuie declarata private. Daca
valorile unei variabile trebuie sa fie intr-un interval atunci ea trebuie facuta private, altfel riscam ca
apelata in exteriorul clasei sa genereze erori de logica sau alte erori.
Intr-o ierarhie rezultata in urma mostenirii, este posibil ca atat superclasele cat si subclasele sa
aiba constructorii lor proprii. Intrebarea este: care constructor se ocupa de instantierea obiectului
subclasei? Este cel din superclasa, din subclasa, ambele? Raspunsul este urmatorul: constructorul
superclasei va ajuta la instantierea portiunii de superclasa a obiectului si constructorul subclasei va
instantia portiunea de subclasa. Pentru a fi mai explicit reluam exemplul cu Forma2D si Triunghi.
17
System.out.println("Informatiile despre t1: ");
t1.AfisezTip();
t1.AfisezDimensiunile();
System.out.println("Aria " + t1.CalcArea());
System.out.println();
System.out.println("Informatiile despre t2: ");
t2.AfisezTip();
t2.AfisezDimensiunile();
System.out.println("Aria " + t2.CalcArea());
}
}
Portiunea legata de superclasa este instatiata automat, apeland constructorul implicit al clasei Forma2D.
super
Pe langa cele prezentate mai sus, o subclasa poate apela constructorul superclasei prin utilizarea
cuvantului cheie super. Utilizarea este:
super(lista de parametrii);
Pentru a vedea cum se utilizeaza acest apel vom modifica clasa de mai sus
class Forma2D
{
double inaltime;
double latime;
public Forma2D(double a, double b)
{
inaltime =a;
latime = b;
}
void AfisezDimensiunile()
{
System.out.println("inaltimea este " +
inaltime + " latimea este " + latime);
}
}
18
void AfisezTip()
{
System.out.println("Triunghiul este " + tip_triunghi);
}
}
Am definit un constructor in clasa Forma2D cu doi parametrii de tip double. In constructorul
din Triunghi am inlocuit cele doua instructiuni ce initializau membrii din Forma2D cu apelul
constructorului clasei parinte.
inaltime =a;//initalizez portiunea legata de
latime = b;//Forma2D adica superclasa
Aceste doua instructiuni au fost inlocuite de super(a, b);
Aici clasa Triunghi apeleaza constructorul clasei Forma2D cu doi parametrii de tip double, in felul acesta
nu mai initializeaza subclasa membrii superclasei.
class Forma2D
{
double inaltime;
double latime;
public Forma2D()
{
inaltime =0; //in constructorul fara parametrii
latime = 0;//initializez membrii cu zero
}
public Forma2D(double a, double b)
{
inaltime =a;
latime = b;
}
void AfisezDimensiunile()
{
System.out.println("inaltimea este " +
inaltime + " latimea este " + latime);
}
}
class Triunghi extends Forma2D
{
String tip_triunghi;
Triunghi(String tip)
{
super(); //apelez Forma2D() si membrii superclasei vor fi zero
tip_triunghi = tip;
}
19
}
double CalcArea()
{
return inaltime* latime/ 2;
}
void AfisezTip()
{
System.out.println("Triunghiul este " + tip_triunghi);
}
}
class DemoMostenire{
public static void main(String args[])
{
Triunghi t1 = new Triunghi ("fara dimensiuni");
Triunghi t2 = new Triunghi (4,6,"isoscel");
In clasa Triunghi avem doi constructori dintre care unul doar cu un parametru de tip String. In acel
constructor vom apela constructorul clasei Forma2D super();fara parametrii si anume a carui definitie
este:
public Forma2D()
{
inaltime =0;
latime = 0;
}
20
Suprascrierea metodelor
Atunci cand o metoda dintr-o clasa copil are aceeasi semnatura (parametrii, nume si tip de data)
ca si metoda din clasa parinte, atunci metoda din copil suprascrie metoda din clasa parinte. Atunci cand
se apeleaza o metoda suprascrisa din subclasa, referirea se face doar la metoda din subclasa (ca si cum
metoda din superclasa nu ar exista)
class A
{
int i, j;
A(int a, int b)
{
i = a;
j = b;
}
//afisez i si j
void show()
{
System.out.println("i si j: " + i + " " + j);
}
}
class B extends A
{
int k;
B(int a, int b, int c)
{
super(a, b);
k = c;
}
//afisez doar k
void show()
{
System.out.println("k: " + k);
}
}
class DemoSuprascriere
{
public static void main(String args[])
{
B obj = new B(1, 2, 3);
obj.show(); // se va apela show() din B
}
}
In exemplul de mai sus evident, clasa B este copilul lui A. Metoda show() este suprascrisa in B,
adica mai este redefinita in B, desi ea exista si in parinte si anume A. In clasa DemoSuprascriere cand se
lucreaza cu obiectul obj de tip B, si se apeleaza metoda obj.show(), este ca si cum clasa A nu ar defini
21
metoda show(). Pe de alta parte, daca comentam definitia metodei show() din clasa B, si efectuam
apelul obj.show(), se vor afisa valorile lui i si j din superclasa A.
In clipa aceasta poate aparea confuzia legata de conceptul de supraincarcare. Supraincarcarea
presupune ca aceeasi functie sa aiba parametrii diferiti, ceea ce in cazul de mai sus nu se intampla.
Totusi in cele ce urmeaza vom implementa o supraincarcare a metodei show()in B pentru a
exemplifica diferenta.
class A
{
int i, j;
A(int a, int b)
{
i = a;
j = b;
}
//afisez i si j
void show()
{
System.out.println("i si j: " + i + " " + j);
}
}
class B extends A
{
int k;
B(int a, int b, int c)
{
super(a, b);
k = c;
}
void show(String str) //Metoda supraincarca show() din A
{
System.out.println(str);
}
//in clasa B nu mai am show() suprascrisa
}
class DemoSuprascriere
{
public static void main(String args[])
{
B obj = new B(1, 2, 3);
obj.show("Mesaj 1"); //apelez show() din B
obj.show(); // se va apela show() din A
}
}
22
Motivele pentru care se mentine acest mecanism de suprascriere sunt multiple, si este unul din
elementele ce contribuie la polimorfism la momentul rularii. Polimorfismul este fundamental in OOP
deoarece permite unei clase generale sa specifice metode ce vor fi aceleasi pentru toate clasele derivate
din ea, in timp ce unora din clasele copii le permite sa aiba propriile implementari pentru acele
metode.
final
Atunci cand nu se doreste ca o metoda sa poata fi suprascrisa intr-una din clasele copil derivate
din clasa parinte, se poate specifica final pentru acea metoda. Acest program
class A
{
int i, j;
A(int a, int b)
{
i = a;
j = b;
}
//afisez i si j
final void show()
{
System.out.println("i si j: " + i + " " + j);
}
}
class B extends A
{
int k;
B(int a, int b, int c)
{
super(a, b);
k = c;
}
void show()
{
System.out.println(k);
}
//in clasa B nu mai am show() suprascrisa
}
class DemoSuprascriere
{
public static void main(String args[])
{
B obj = new B(1, 2, 3);
obj.show(); // se va apela show() din B
23
}
Clasa Object
Toate clasele in Java, extind direct sau indirect, clasa Object aflata in pachetul java.lang.
Aceasta clasa defineste cateva metode importante de care trebuie sa tinem seama, pentru ca fiecare
clasa pe care o scriem poate suprascrie sau folosi aceste metode.
Metoda Scop
Object clone( ) Creeaza un obiect cu aceleasi proprietati ale obiectului clonat.
boolean equals(Object object) Determina faptul ca un obiect este/ nu este egal cu altul.
void finalize( ) Apelul inainte ca obiectul sa fie distrus.
Class<? extends Object> getClass( ) Determina clasa din care obiectul face parte.
int hashCode( ) Returneaza un id specific fiecarui obiect.
void notify( ) Reia executia firului de executie aflat in asteptare.
void notifyAll( ) Reia executia firelor de executie aflate in asteptare.
String toString( ) Returneaza un sir de caractere ce descrie obiectul.
void wait( ) Suspenda firul de executie apelant.
Metodele getClass(), notify(), notifyAll() si wait sunt declarate final. Vom reveni asupra lor la
momentul oportun. In cele ce urmeaza vom discuta despre celelalte metode.
clone( )
Metoda returneaza un obiect ce are membrii identici cu cel al obiectului curent. Metoda
functioneaza doar daca clasa implementeaza interfata Cloneable (vom aborda subiectul legat de
interfete in capitolul urmator). Metoda este declarata protected adica doar subclasele lui Object pot
suprascrie aceasta metoda. Vom reveni cu un exemplu mai elaborat cand discutam despre interfete.
class AnyClass implements Cloneable
{
int i;
String s;
public double d;
public void InitVars()
{
24
i=3;
s="Mesaj";
d= 4.1;
}
protected AnyClass clone()
{
AnyClass newobj = new AnyClass();
newobj.i=i;
newobj.s = s;
newobj.d = d;
return newobj;
}
}
class DemoObject extends AnyClass
{
public static void main(String args[])
{
AnyClass obj1 = new AnyClass();
obj1.InitVars();
AnyClass obj2 = obj1.clone();
obj2.i =1;
System.out.println(obj1);
System.out.println(obj2);
System.out.println(obj1.i + obj1.s + obj1.d);
System.out.println(obj2.i + obj2.s + obj2.d);
}
}
Pentru moment vom spune ca Cloneable este un fel de clasa parinte pentru AnyClass si metoda
clone() este mostenita si suprascrisa in AnyClass. Tipul de data returnat de clone este tot un AnyClass si
obiectul returnat este unul nou instantiat, cu valorile preluate din obiectul curent. Metoda clone()
poate fi suprascrisa in orice fel, putem returna chiar si instanta obiectului curent, insa nu acesta este
scopul.
Atunci cand rulam programul vom observa doua referinte, si anume a doua obiecte diferite,
chiar daca au initial aceleasi proprietati:
AnyClass@19821f
AnyClass@addbf1
3Mesaj4.1
1Mesaj4.1
Atunci cand rulam
System.out.println(obj1);
Rezultatul este unul oarecum ciudat si anume AnyClass@19821f.
In continuare vom lamuri aceasta „ciudatenie”
toString( )
Atunci cand apelam metoda de afisare a oricarui obiect in Java, implicit se apeleaza o metoda
toString() mostenita din cadrul clasei Object. Implicit aceasta metoda returneaza un format de genul
25
urmator: TipulClasei@hascode_al_clasei. Astfel se explica de ce cand afisam direct obiectul ca mai sus
obtinem acel rezultat.
Pentru a creea rezultate care sa insemne ceva ce poate fi interpretat de oameni, putem
suprascrie metoda toString() pentru a afisa de exemplu membrii obiectului.
class AnyClass
{
int i;
String s;
public double d;
public void InitVars()
{
i=3;
s="Mesaj";
d= 4.1;
}
public String toString()
{
String str="";
str += "i este " + i + "\n";
str += "s este " + s + "\n";
str += "d este " + d + "\n";
return str;
}
}
class DemoObject
{
public static void main(String args[])
{
AnyClass obj1 = new AnyClass();
obj1.InitVars();
//afisam obj1 prin apelului lui toString
System.out.println(obj1);
//Alt mod de a afisa continutul lui obj1
System.out.println(obj1.i + obj1.s + obj1.d);
}
}
Dupa cum se poate observa apelul inlesneste afisarea obiectului obj1. Mai mult daca am avea
membrii private metoda toString() expune valorile acestora.
equals( )
Operatorul „==” testeaza daca doua obiecte pointeaza catre aceeasi referinta. Pentru a vedea
daca doua obiecte contin valori diferite, trebuie sa folosim equals().
26
hashcode()
Atunci cand suprascriem metoda equals, trebuie sa suprascriem si metoda hashcode(). Aceasta
metoda returneaza un intreg care este folosit de java pentru a diferentia doua obiecte. Este important
ca doua obiecte egale conform metodei equals() sa aiba aceleasi hashcode-uri. De asemenea doua
obiecte diferite in acelasi sens trebuie sa aiba hashcode-uri diferite.
In continuare avem un exemplu de suprascriere a celor doua metode mai sus mentionate:
class Circle
{
private final int x, y, r;
// Constructorul de baza
public Circle(int x, int y, int r)
{
this.x = x; this.y = y; this.r = r;
}
// functii Get
public int getX()
{
return x;
}
public int getY()
{
return y;
}
public int getR()
{
return r;
}
// suprascrierea equals
@Override public boolean equals(Object o)
{
if (o == this) return true; //referinte egale
if (!(o instanceof Circle)) return false; //daca tipul de data nu
este corect
Circle that = (Circle) o; //se aplica un cast
catre tipul corect
if (this.x == that.x && this.y == that.y && this.r == that.r)
27
return true;
else
return false;
}
System.out.println(c1.equals(c2));
}
}
Specificatorul @Override intareste ideea ca metoda este suprascrisa.
In metoda equals pasii sunt tot timpul aceeasi:
1. Se verifica daca referintele sunt egale
2. Se verifica faptul ca obiectul cu care se va face comparatia sa aiba acelasi tip de data cu obiectul
curent.
3. Se compara proprietatile celor doua obiecte si se returneaza true sau false.
HashCode este mai interesanta. In cadrul acestei metode este imperativa sa implementam un algoritm
care sa produca un numar, care sa respecte conditiile mentionate la descrierea metodei hasCode. Acest
algoritm, prin inmultirea cu un numar prim si insumarea membrilor obiectului asigura acest lucru.
O suma a lui x, y si r nu ar fi suficienta deoarece trei numere pot da aceeasi suma cu alte trei numere
diferite insumate. De aceea un algoritm ar fi sa insumam cele trei numere inmultite cu numere prime
pentru a diminua aceasta posibilitate: de exemplu 23*x+31*y+17*r. Cu cat algoritmul matematic este
mai complex cu atat regulile hashCode sunt respectate in mai multe cazuri.
28
Capitolul 4
Clase si metode abstracte .................................................................................................................... 2
Interfete .............................................................................................................................................. 6
Mostenirea unor interfete ............................................................................................................... 6
Implementarea unei interfete .......................................................................................................... 7
Interfete si clase abstracte ............................................................................................................. 10
Mai multe exemple cu interfete ..................................................................................................... 10
Variabile in interfete ...................................................................................................................... 12
Exceptii .............................................................................................................................................. 13
Cum functioneaza? ........................................................................................................................ 13
Folosirea handlerului de exceptii try catch ..................................................................................... 14
Prinderea exceptiilor conform ierahiei ........................................................................................... 18
Cum aruncam o exceptie?.............................................................................................................. 19
Clasa Throwable ............................................................................................................................ 20
finally............................................................................................................................................. 22
I/O in Java.......................................................................................................................................... 27
Stream in Java................................................................................................................................ 27
Byte Stream ................................................................................................................................... 28
Character Stream ........................................................................................................................... 30
Stream-uri salvate in buffer-e ........................................................................................................ 32
1
Clase si metode abstracte
Sa reluam exemplul din capitolul anterior in care derivam din clasa Forma2D alte doua clase:
Triunghi si Dreptunghi. Sa presupunem ca vrem sa derivam un numar mai mare de clase: Dreptunghi,
Triunghi, Elipsa, Trapez si asa mai departe. Putem observa ca toate aceste clase au doua elemente
comune si anume Suprafata si Perimetrul sau Circumferinta. Pentru a putea lucra usor cu un sir de
obiecte care pot fi sau Triunghi sau Elipsa, este util ca acestea sa faca parte din clase ce au o superclasa
si anume Forma2D. In aceste conditii, ar trebui ca Forma2D sa implementeze functiile Suprafata si
Perimetrul, insa intrebarea este cum daca forma nu este una anume, deci nu stim exact cum vor fi
implementate aceste doua metode.
In aceste situatii Java permite utilizarea metodelor abstracte.
O metoda abstracta nu are un corp definit, ci doar o semnatura, iar declaratia functiei se incheie
cu „;”. Mai jos sunt o serie de reguli pentru a defini metode abstracte si clase abstracte:
Orice clasa ce contine o metoda abstracta este in mod automat abstracta, asa ca trebuie
declarata ca atare.
O clasa abstracta nu poate fi instantiata.
O subclasa a unei clase abstracte poate fi instantiata doar daca suprascrie metodele abstracte
ale parintelui si ofera o implementare pentru toate aceste metode. Aceste clase copil se numesc si
concrete pentru a sublinia ca nu sunt abstracte.
Daca o clasa copil a unei superclase abstracte nu implementeaza toate metodele abstracte pe
care le mosteneste, atunci si clasa copil este abstracta si va fi declarata ca atare.
Metodele declarate static, private sau final nu pot fi abstracte deoarece niciuna din acestea nu
poate fi suprascrisa de subclase. De asemenea o clasa declarata final nu poate contine metode
abstracte.
O clasa poate fi declarata abstract chiar daca nu contine metode abstracte. De obicei aceasta
semnifica faptul ca o clasa este incompleta si este clasa parinte pentru una sau mai multe clase copil.
2
this.raza = r;
categorie = "Cerc";
}
public double getRaza()
{
return raza;
}
//metodele abstracte care "prind forma":
3
class AbstractDemo
{
public static void main(String args[])
{
//declaram un sir de clase abstracte
Forma[] sir_forme = new Forma[4];
//instantiam diverse obiecte din sir
sir_forme[0] = new Cerc(2.0);
sir_forme[1] = new Dreptunghi(1.0, 3.0);
sir_forme[2] = new Dreptunghi(4.0, 2.0);
sir_forme[3] = new Cerc(5.0);
double suprafata_totala = 0;
for(int i = 0; i < sir_forme.length; i++)
{
suprafata_totala += sir_forme[i].Suprafata();
//putem afisa aceste date deoarece sunt din clasa
//parinte
System.out.println("Categoria: " +
sir_forme[i].categorie);
System.out.println("Perimetrul: " +
sir_forme[i].Perimetrul());
System.out.println("Suprafata: " +
sir_forme[i].Suprafata());
}
System.out.println("Suprafata totala: " +
suprafata_totala);
}
}
4
Suprafata: 78.5398163397425
Suprafata totala: 102.1061869541013
Clasa Forma este declarata abstract si ca atare nu va pute fi instantiata. Totusi obiectele, sau
sirul de obiecte este de tip Forma, pentru ca instantierea se petrece ulterior cu o subclasa a parintelui si
anume Cerc:
sir_forme[0] = new Cerc(2.0);
Clasele Cerc cat si Dreptunghi nu sunt abstracte deoarece ambele implementeaza toate
metodele declarate abstracte ale clasei Forma.
Cele doua metode Suprafata si Perimetrul sunt declarate cu un cuvant cheie abstract in clasa
Forma si nu au corp, deci se vor termina cu „;” ca orice declaratie obisnuita.
Pe de alta parte cele doua clase copil Cerc si Dreptunghi, implementeaza ambele metode:
public double Suprafata()
{
return PI*raza*raza;
}
public double Perimetrul()
{
return 2*PI*raza;
}
Pe langa aceste doua metode ambele clase pot contine propriile metode, separat una de
cealalta. De exemplu Cerc mai implementeaza urmatoarea metoda:
public double getRaza()
{
return raza;
}
Atunci cand folosim obiectele acestor clase trebuie sa avem grija, deoarece, desi toate obiectele
din sirul sir_forme sunt de tipul Forma, primul si ultimul obiect sunt de tipul Cerc, iar al doilea si al
treilea sunt de tipul Dreptunghi.
Daca un obiect este de tip Forma, nu putem apela un membru (desi public) al clasei Cerc. O
astfel de instructiune este gresita
System.out.println("PI este: " + sir_forme[i].PI);
si produce urmatoarea eroare de compilare:
Forma.java:86: cannot find symbol
symbol : variable PI
location: class Forma
System.out.println("PI este: " + sir_forme[i].PI);
Mai mult, in clasa parinte Forma, pot exista pe langa metode abstracte si alte metode concrete
ca de exemplu aceasta:
public void ShowTipForma()
{
System.out.println("Forma este de tip: " + categorie);
}
Exemplu de folosire a acestei functii:
for(int i = 0; i < sir_forme.length; i++)
5
{
sir_forme[i].ShowTipForma();
Interfete
In OOP este indicat sa definim ce trebuie sa faca o functie fara a da toate detaliile despre cum
trebuie sa faca acele sarcini. O metoda abstracta este raspunsul la aceasta problema. In unele cazuri in
care clasa nu are nevoie de metode concrete, se pot crea interfete, ce separa total implementarea unei
clase de definitia metodelor acesteia.
Interfetele sunt asemanatoare cu clasele abstracte, dar intr-o interfata, nici o metoda nu are
voie sa aiba corp, adica implementare. Mai mult, o clasa poate implementa una sau mai multe interfete.
Pentru ca implementarea unei interfete sa fie corecta, o clasa trebuie sa ofere implementari
acelor metode declarate in interfata. Evident, fiecare clasa poate avea propria implementare pentru
metodele interfetei. Mai jos este modul general de a declara o interfata:
acces interface nume_interfata
{
tip_data nume_metoda1(param-list);
tip_data nume_metoda2(param-list);
tip_data var1 = valoare;
tip-_data var2 = valoare;
// ...
tip_data nume_metodaN(param-list);
tip_data varN = valoare;
}
unde acces este public sau nu este folosit deloc. Metodele sunt declarate folosind doar
semnatura lor exact ca niste metode abstracte. Implicit metodele sunt public. Variabilele declarate intr-o
interfata nu sunt variabile de instanta, ci trebuie declarate public, final si static si trebuie initializate.
In exemplul de mai sus, clasa Forma poate implementa o interfata care poate specifica centrul
unei forme geometrice:
public interface Centrat
{
void setCentru(double x, double y);
double getCentruX();
double getCentruY();
}
Interfetele pot mosteni alte interfete, asemanator claselor, folosind cuvantul cheie extends.
Cand o interfata mosteneste o alta interfata, va mosteni toate metodele abstracte ale interfetei parinte,
putand defini si metode abstracte noi si alte constante. Spre deosebire de clase, o interfata poate
mosteni mai multe interfete:
public interface Positionabil extends Centrat
6
{
void setColDreaptaSus(double x, double y);
double getDreaptaSusX();
double getDreaptaSusY();
}
public interface Transformabil extends Scalabil, Translatabil,
Rotatabil {}
public interface Forma extends Positionabil, Transformabil {}
Asa cum o clasa foloseste extends, pentru a mosteni o clasa parinte, se poate folosi implements
pentru a mosteni interfete.
Atunci cand o clasa mosteneste o interfata, ofera o implementare tuturor metodelor interfetei.
Daca o clasa implementeaza o interfata fara a oferi o implementare pentru fiecare metoda, va mosteni
acele metode abstracte, si devine la randul sau abstracta.
Forma generala a unei implementari este:
7
return latime;
}
public double getInaltime()
{
return inaltime;
}
//metodele abstracte care "prind forma":
//constructorul
public Dreptunghi_Centrat (double cx, double cy, double latime,
double inaltime)
{
super(latime, inaltime);
this.cx = cx;
this.cy = cy;
}
8
return cx;
}
public double getCentruY()
{
return cy;
}
}
class DemoInterfete
{
public static void main(String args[])
{
Dreptunghi_Centrat drept_centrat = new
Dreptunghi_Centrat(2.4,3.5,6,9);
Centrat c = (Centrat) drept_centrat;
double cx = c.getCentruX(); //coordonatele centrului
double cy = c.getCentruY();
//calculam distanta de la origine
double dist = Math.sqrt(cx*cx + cy*cy);
System.out.println("Suprafata " +
drept_centrat.Suprafata());
System.out.println("Distanta de calibrat : " + dist);
}
}
Dupa cum se observa, interfata declarata este Centrat, iar clasa care o implementeaza este
Dreptunghi_Centrat. Deoarece vorbim despre o figura geometrica de tip dreptunghi aceasta clasa,
Dreptunghi_Centrat va mosteni de asemenea si clasa Dreptunghi.
9
Pentru a intelege mai bine toata ierarhia de clase care sunt folosite, avem diagrama din figura
4.1.
In aceasta figura se poate vedea cum interfata este implementata in Dreptunghi_centrat, iar
clasa abstracta Forma este mostenita de Dreptunghi. Implicit, atunci si Dreptunghi_centrat va mosteni
proprietatile clasei Forma.
In continuare vom prezenta o dezbatere pe seama diferentelor si asemanarilor dintre interfete
si clasele abstracte.
Atunci cand definim tipuri de data abstracte (de exemplu Forma) care pot avea subclase (de
exemplu Cerc, Dreptunghi etc) vom avea de ales daca acel tip de data este o clasa abstracta sau o
interfata.
O interfata este recomandata pentru ca orice clasa poate sa o implementeze, chiar si atunci
cand acea interfata extinde o alta superclasa care nu are nici o legatura cu interfata.
Totusi, cand o interfata contine multe metode poate deveni greoi implementarea tuturor
metodelor, in continuu pentru fiecare clasa ce o implementeaza.
Din acest punct de vedere putem spune ca o clasa abstracta nu trebuie sa fie in intregime
abstracta, ea poate contine o implementare cel putin partiala, iar subclasele o pot folosi pe aceasta. Pe
de alta parte, o clasa care extinde o clasa abstracta nu mai poate extinde (mosteni) o alta clasa, ceea ce
duce la unele dificulati.
O alta diferenta dintre clase abstracte si interfete este legata de compatibilitate. Daca definim
interfata si o adaugam intr-o librarie, iar mai apoi adaugam o metoda acelei interfete, atunci clasele care
implementau versiunea anterioara de interfata vor fi stricate (nu implementeaza corect interfata noua).
Se spune ca am stricat compatibilitatea. Daca folosim clase abstracte pe de alta parte, putem adauga
metode nonabstracte fara ca si clasele ce o mostentesc sa fie alterate de aceasta.
Desi nu putem instantia o interfata, se poate lucra cu un obiect care face referinta la o interfata.
Atunci cand apelam o metoda a unui obiect prin referinta la interfata, este de fapt vorba de metoda
implementata de obiect. Pentru a intelege mai bine conceptul avem interfata de mai jos:
10
int start;
int val;
DinDoiinDoi()
{
start = 0;
val = 0;
}
public int getNext()
{
val += 2;
return val;
}
public void reset()
{
start = 0;
val = 0;
}
public void setStart(int x)
{
start = x;
val = x;
}
}
11
}
class DemoSerii
{
public static void main(String args[])
{
DinDoiinDoi doiobj = new DinDoiinDoi ();
DinTreiinTrei treiobj = new DinTreiinTrei ();
Serii ob;
for(int i=0; i < 10; i++)
{
ob = doiobj;
System.out.println("Urmatorul numar par " +
ob.getNext()); //apelez metoda definita de interfata
//implementata in clasa DinDoiinDoi
ob = treiobj;
System.out.println("Urmatorul numar ternar " +
ob.getNext()); //apelez metoda definita de interfata
//implementata in clasa DinTreiinTrei
}
}
}
Variabile in interfete
Variabilele in interfete trebuie sa fie static, public si final. Practic este vorba de constante.
interface IConst
{
int MIN = 0;
int MAX;
String ERRORMSG = "Eroare de limita";
}
class DemoIConst implements IConst
{
public static void main(String args[])
{
int nums[] = new int[MAX];
12
for(int i=MIN; i < 11; i++)
{
if(i >= MAX) System.out.println(ERRORMSG);
else
{
nums[i] = i;
System.out.print(nums[i] + " ");
}
}
}
}
Orice incercare de modificare a valorii unei constante va esua la compilare
var.java:12: cannot assign a value to final variable MAX
MAX=20;
^
1 error
De asemena, orice nerespectare a conditiilor de mai sus va aduce cu sine aparitia diverselor
erori de compilare corespunzatoare.
Exceptii
O exceptie este o eroare care apare la un moment dat in timpul rularii programului. Folosind
mecanismul de tratare al erorilor oferit de Java, putem sa controlam logic ce se intampla atunci cand
apare o eroare. Un avantaj pe care acest mecanism il ofera este ca automatizeaza codul pentru tratarea
erorilor, care initial trebuia introdus „manual” de programator, adica prevazand fiecare situatie in parte.
De exemplu, atunci cand o metoda esueaza, este returnat un cod de eroare, iar corespunzator
acelui cod, se verifica ce anume nu a mers sau a esuat. Acest mod de rezolvare este greoi si necesita
atentie din partea programatorului. Mecanismul oferit de Java este ca o plasa de protectie, care nu
permite ca programatorul sa greseasca, deoarece cand apare o eroare, automat o functie handler de
exceptii este apelata, fara sa avem grija codului returnat de functii.
Un alt motiv pentru care tratarea exceptiilor este importanta, este ca Java defineste un standard
pentru diversele tipuri de exceptii de exemplu fisierul ce nu exista sau diviziune cu zero.
Cum functioneaza?
Atunci cand o eroare apare intr-o metoda, metoda creaza un obiect si il preda sistemului
runtime Java. Obiectul poarta denumirea de obiectul exceptiei sau mai simplu, exceptie. El contine
informatie despre eroare, inclusiv tipul erorii si starea programului atunci cand a aparut eroarea.
Creearea unui obiect exceptie si predarea acestuia catre sistem se numeste aruncarea unei exceptii.
Dupa ce o metoda arunca o exceptie, sistemul runtime incearca sa gaseasca prima functie sau
bloc care trateaza acesta eroare. Multimea de aceste posibile functii sau blocuri care sa trateze eroarea
ordonata intr-o lista a evenimentelor se numeste call stack sau stiva de apel.
13
Sistemul cauta in stiva de apel o metoda care contine un bloc ce trateaza exceptia. Acest bloc se
numeste handler de exceptie. Cautarea incepe cu metoda in care apare eroarea si continua in ordine
inversa a apelurilor cu toata stiva de apel.
Se spune ca handler-ul de exceptie „prinde” exceptia. Daca sistemul runtime a epuizat stiva de
apel fara a gasi un handler de exceptie potrivit, executia se incheie.
Sa presupunem ca apare eroarea in cadrul functiei m1. Metoda va fi aruncata in functia care a
apelat metoda m1 si anume m2. Problema este ca functia m2 nu contine nici un bloc de prindere a erorii
astfel eroarea este aruncata mai departe in functia care a apelat m2 si anume m3. Acum exista doua
posibilitati, m3 sa prinda eroarea si atat sau sa o arunce mai departe. Vom detalia in continuare.
Se recomanda ca o metoda sa prinda sau sa specifice faptul ca poate genera o anumita
exceptie, daca este cazul. Anume, prinderea erorii se specifica prin cuvantul cheie catch, iar faptul ca
arunca o exceptie se specifica prin cuvantul cheie throws.
14
In aceasta declaratie, in blocul specificat de try va sta codul obisnuit si anume instructiunile pe
care dorim sa le implementam, iar in catch care si are forma unei functii cu un parametru va sta codul de
tratare al exceptiilor. Se arata in aceasta declaratie ca pot fi mai multe handler-e asociate unui try.
Aceasta inseamna ca mai multe tipuri de erori pot sa apara intr-un bloc try iar ele sunt tratate separat.
Ne putem gandi ca in functie de tipul erorii care apare, aceste handler-e sunt suprascrise, iar apelarea
lor se face in functie de tipul exceptiei.
Sa analizam un exemplu simplu de tratare a exceptiilor
class ExceptionDemo1
{
public static void main(String args[])
{
int nums[] = new int[4];
try
{
System.out.println("Inainte de generarea erorii.");
// Generam o exceptie prin depasirea limitei sirului
nums[5] = 7;
System.out.println("Eroarea a aparut deja.
Instructiunea aceasta nu va mai fi executata.");
}
catch (ArrayIndexOutOfBoundsException exc)
{
// prindem si tratam exceptia
System.out.println("Depasirea limitei sirului!");
}
System.out.println("Dupa blocul try catch.");
}
}
Codul care monitorizeaza erorile va sta in blocul try. Atunci cand apare eroarea, adica atunci
cand se incearca executia nums[5] = 7; exceptia este este aruncata din blocul try si se iese din acest
bloc. De aceea, instructiunea ce ar fi urmat nu se mai executa, ci se va executa blocul catch aferent. Mai
precis, se executa instructiunea System.out.println("Depasirea limitei sirului!");.
Dupa ce se termina de executat blocul catch se trece la urmatoarea instructiune si anume
System.out.println("Dupa blocul try catch.");.
Mai departe putem exemplifica pentru modelul din figura 4.2 cum exceptiile pot fi prinse si
eventual, tratate in blocul catch scris in alta functie diferita de cea care genereaza eroarea:
class ExceptionDemo1
{
public static void genException()
{
int nums[] = new int[4];
System.out.println("Inainte de generarea erorii.");
// Generam o exceptie prin depasirea limitei sirului
nums[7] = 10;
15
System.out.println("Eroarea a aparut deja.
Instructiunea aceasta nu va mai fi executata?");
}
}
class ExceptionDemo2
{
public static void main(String args[])
{
try
{
ExceptionDemo1.genException();
}
catch (ArrayIndexOutOfBoundsException exc)
{
// prindem si tratam exceptia
System.out.println("Depasirea limitei sirului!");
}
System.out.println("Dupa blocul try catch.");
}
}
Acest program va produce acelasi afisaj ca cel de sus, insa, din cauze diferite. De data aceasta,
exceptia este generata in clasa ExceptionDemo1m mai exact, in functia genException. Exceptia nu
mai este tratata in cadrul functiei ci este aruncata „mai departe” in functia care efectueaza apelul, si
anume, main din clasa ExceptionDemo2.
In cazul in care o exceptie nu este prinsa de nici una din functiile din call stack, atunci exceptia
este prinsa de JVM si executia programului se incheie brusc.
Acest lucru nu este prea placut pentru utilizatorii programului scris fara un mecanism de tratare
a exceptiilor si ii va deceptiona.
Mai sus am mentionat ca exceptiile pot avea mai multe tipuri. Daca blocul de catch este scris
pentru o alta exceptie decat cea care va apare probabil in blocul try, atunci tot scopul mecanismului de
tratare a exceptiilor dispare.
class ExceptionTypeMismatch
{
public static void main(String args[])
{
int nums[] = new int[4];
try
{
System.out.println("Inainte ca exceptia sa apara.");
// generam o exceptie
nums[6] = 4;
System.out.println("nu va ajunge aici.");
}
//nu aceasta este tipul de eroare care va apare
catch (ArithmeticException exc)
16
{
//prind exceptia
System.out.println("In afara limitelor!");
}
System.out.println("Dupa try catch.");
}
}
In exemplul de mai sus se incearca tratarea exceptiei de tipul ArithmeticException si in
blocul try apare alt tip de exceptie. Ceea ce se intampla la rulare, este usor de prevazut, si anume, faptul
ca exceptia ArrayIndexOutOfBoundException ce apare va duce la oprirea programului. Este ca si cand
eroarea nu este tratata.
In cazul acesta rezolvarea rapida ar fi sa tratam ambele tipuri de exceptii. Alta rezolvare implica
modul in care sunt implementate exceptiile in Java, pe care il vom analiza mai tarziu.
class ExcMultipleDemo
{
public static void main(String args[])
{
5 / 1 este 5
impart la zero la pasul 1!
12 / 3 este 4
1 / 4 este 0
impart la zero la pasul 4!
27 / 5 este 5
17
Am depasit limita la pasul 6.
Am depasit limita la pasul 7.
In cazul de mai sus, am scris handler-ele astfel ca ele sa trateze si erorile de tipul
ArithmeticException si cele de tipul ArrayIndexOutOfBoundsException. Astfel enumerand blocurile catch
unul sub celalalat putem trata mai multe tipuri de exceptii ce pot apare pe parcurs.
Alta remarca ar fi ca in cadrul blocurilor catch regulile domeniilor de vizibilitate al variabilelor si
functiilor se mentin. Dupa cum se poate observa folosim i din cadrul for-ului pentru ca blocul try catch
este in interiorul acestui for si i este vizibil tot in interiorul for-ului.
Dupa cum se vede in figura clasa Throwable este parinte pentru orice clasa de tip Exception si
Error. Asadar, cand scriem codul catch este important sa plasam codul catch in ordinea mostenirilor
astfel ca urmatoarea ordine este recomandata:
try
{
...
}
catch (RuntimeException rex)
{
...
}
catch(Exception ex)
{
...
18
}
catch(Throwable tex)
{
...
}
Avantajul acestui cod este ca indiferent de tipul de eroare care apare in blocul try, aceasta va fi
tratata. Problema este ca tratarea este una cat mai general aproximata. Eventual putem folosi informatii
din parametrul din catch, dupa cum vom vedea in continuare.
throw exceptioObject;
19
In acest caz exceptioObject este un obiect de clasa exceptie derivat din Throwable.
Mai jos avem un exemplu pentru throw:
class ThrowDemo
{
public static void main(String args[])
{
try
{
System.out.println("inainte ca exceptia sa apara.");
throw new ArithmeticException();
}
catch (ArithmeticException exc)
{
System.out.println("Exceptie prinsa.");
}
System.out.println("Dupa blocul try catch.");
}
}
Obiectul de tip ArithmeticException a fost creat folosind operatorul new. Apoi a fost
„aruncat” folosind operatorul throw. Mai apoi a fost „prins”, adica transmis ca parametru in blocul
catch.
Clasa Throwable
Pana in acest moment singurul lucru pe care l-am facut atunci cand am prins erori a fost sa
afisam un mesaj corespunzator erorii prinse. Daca profitam de faptul ca toate exceptiile deriva din
Throwable, atunci putem folosi functiile acestei clase si anume:
Metoda Descriere
Throwable fillInStackTrace( ) Returneaza un obiect de tip Throwable care contine stiva
completa cu apelurile efectuate.
String getLocalizedMessage( ) Returneaza o descriere specifica subclasei a erorii.
String getMessage( ) Returneaza descrierea generala a erorii.
void printStackTrace( ) Afiseaza stiva.
void printStackTrace(PrintStream stream) Trimite continutul stivei unui stream.
String toString( ) Returneaza un sir ce contine descrierea erorii.
20
// Generam o exceptie prin depasirea limitei sirului
nums[7] = 10;
System.out.println("Eroarea a aparut deja. Instructiunea
aceasta nu va mai fi executata?");
}
}
class ExceptionDemo2
{
public static void main(String args[])
{
try
{
ExceptionDemo1.genException();
}
catch (ArrayIndexOutOfBoundsException exc)
{
System.out.println("Mesajul standard al exceptiei: ");
System.out.println(exc);
System.out.println("Mesajul exceptiei: ");
System.out.println(exc.getMessage());
System.out.println("Mesajul local: ");
System.out.println(exc.getLocalizedMessage());
System.out.println("\nStack trace: ");
exc.printStackTrace();
}
System.out.println("Dupa blocul try catch.");
}
}
Stack trace:
java.lang.ArrayIndexOutOfBoundsException: 7
at ExceptionDemo1.genException(ex.java:8)
at ExceptionDemo2.main(ex.java:21)
Dupa blocul try catch.
21
finally
Acest bloc din cadrul declaratiei try catch permite executia unor instructiuni inainte de iesirea din blocul
try catch. De exemplu atunci cand apare o exceptie, se poate efectua un return din functie inainte ca
instructiunile de inchidere de fisier sau de conexiuni sa aiba loc. Este recomandat ca aceste tipuri de
instructiuni sa le scriem in finally:
try
{
// blocul in care eroarea poate apare
}
catch (ExcepType1 exOb)
{
// handler pentru ExcepType1
}
finally
{
//cod care se executa tot timpul
}
class ExFinally
{
public static void genException(int p)
{
int t;
int nums[] = new int[2];
System.out.println("Am primit " + p);
try
{
switch(p)
{
case 0:
t = 6 / p;
break;
case 1:
nums[10] = 9;
break;
case 2:
// return din blocul try
return;
}
}
catch (ArithmeticException exc)
{
System.out.println("Impartire la zero!");
return; // return din catch
22
}
catch (ArrayIndexOutOfBoundsException exc)
{
System.out.println("Limite depasite.");
}
finally
{
System.out.println("Parasesc try.");
}
}
}
class FinallyDemo
{
public static void main(String args[])
{
for(int i=0; i < 3; i++)
{
ExFinally.genException(i);
System.out.println();
}
}
}
In clasa FinallyDemo se apeleaza de trei ori metoda statica genException() din clasa ExFinally.
In functie de parametru se genereaza doua exceptii si in al treilea caz se paraseste blocul try. Se observa
ca in toate cazurile, indiferent ce catch este executat, se intra pe blocul finally inainte de a reveni din
apelul functiei genException().
Am primit 0
Impartire la zero!
Parasesc try.
Am primit 1
Limite depasite.
Parasesc try.
Am primit 2
Parasesc try.
In unele cazuri, daca o metoda poate genera o exceptie pe care nu o trateaza, trebuie semnalat acest
lucru. Pentru aceasta se foloseste throws:
tip_de_data nume_metoda(lista_parametrii) throws lista_exceptii
{
//corpul metodei
}
23
lista_exceptii reprezinta una sau mai multe exceptii care sunt separate prin virgula. Aceste exceptii pot fi
generate in interiorul metodei. Exceptiile care sunt subclase ale clasei Error sau al clasei
RuntimeException nu trebuie neaparat „declarate” in acea lista. Regula este impusa tuturor celorlalte
tipuri de Exceptii.
Iata cateva clase de exceptii, subclase ale lui Exception ce trebuie declarate cu throws in metoda
apelanta:
CloneNotSupportedException, IOException, NamingException, NotBoundException.
Clasa de mai jos este gresit declarata:
import java.io.*;
class ThrowsDemo
{
public static void writeList(int[] vector)
{
PrintWriter out = new PrintWriter(new FileWriter("OutFile.txt"));
for (int i = 0; i < 10; i++)
{
out.println("valoare de la : " + i + " = " + vector[i]);
}
out.close();
}
}
Iar la compilare vom primi urmatoarea eroare:
Iata cateva exceptii, majoritatea derivate din RuntimeException care nu trebuie incluse in lista throws:
24
Exceptia Descriere
ArithmeticException Eroare de tip aritmetic, de exemplu impartire la zero.
ArrayIndexOutOfBoundsException Indexul din sir este in afara limitelor.
ArrayStoreException Se incearca atribuirea unui element din sir cu o valoare
incorecta.
ClassCastException Cast incorect.
IllegalArgumentException Parametrul transmis metodei este gresit.
IllegalMonitorStateException Operatie invalida, de exemplu, asteptarea unui fir de executie
neblocat.
IllegalStateException Aplicatia nu ruleaza in mediu corect, compatibil.
IllegalThreadStateException Operatia aplicata nu este in concordanta cu starea firului de
executie
IndexOutOfBoundsException Indexul este in afara limitelor
NegativeArraySizeException Sir creat cu limite negative.
NullPointerException Folosirea obiectului neinstantiat (egal cu null).
NumberFormatException Conversia dintr-un string intr-un numar a esuat.
SecurityException Posibila tentativa asupra securitatii.
StringIndexOutOfBounds Indexul unui string este in afara limitelor
CloneNotSupportedException Incercare de a folosi un obiect ce nu implementeaza Cloneable
IllegalAccessException Acces la clasa nepermis.
InstantiationException S-a incercat instantierea unei interfete sau clase abstracte
InterruptedException Un fir de executie a fost intrerupt
NoSuchFieldException S-a incercat accesarea unui membru care nu exista.
NoSuchMethodException S-a incercat accesarea unei metode care nu exista.
Desi Java implementeaza multe clase care se ocupa cu tratarea erorilor, mecanismul nu este limitat la
genul de liste de mai sus. Putem defini propriile noastre clase ca subclase ale parintelui Exception.
25
class CustomExceptionDemo
{
public static void main(String args[])
{
int numar[] = { 3, 6, 30, 44, 21, 50, 16 };
int deimp[] = { 3, 0, 12 , 2, 5 };
for(int i=0; i<numar.length; i++)
{
try
{
if((numar[i]%deimp[i]) != 0)
throw new MyException(numar[i], deimp[i]);
System.out.println(numar[i] + " / " + deimp[i] + "
este " +numar[i]/deimp[i]);
}
catch (ArithmeticException exc)
{
System.out.println("Impart la zero!");
}
catch (ArrayIndexOutOfBoundsException ex)
{
System.out.println("Index in afara limitelor.");
}
catch (MyException ex)
{
System.out.println(ex);
}
}
}
}
In primul rand clasa trebuie sa mosteneasca Exception. In al doilea rand aruncarea exceptiei se
face prin apelul throws cu un obiect de clasa customizata. Pentru crearea unui obiect de acest tip de
exceptie s-a folosit un constructor cu doi parametrii de tip int. Dupa ce exceptia este aruncata se poate
folosi obiectul ex care este de tip MyException, ca orice obiect din Java.
26
I/O in Java
Sistemul de tratare al intrarilor si iesirilor si al lucrului cu siruri de data in Java este extrem de
variat. In cele ce urmeaza vom incerca sa surprindem conceptele, practic sa vedem varful iceberg-ului,
pentru ca mai tarziu sa intram in detalii anumite aspecte legate de IO.
Stream in Java
In general va exista o sursa de la care vom citi si atunci vorbim de input stream si o sursa catre
care scriem si atunci vorbim de output stream:
In Java exista doua versiuni de stream: byte si caracter. Byte stream-ul este un wrapper peste un
sir de byte. Acesta se foloseste la scrierea si citirea datelor binare si la transmiterea, citirea de fisiere.
Stream-urile de caractere sunt destinate lucrului cu texte si folosesc Unicode pentru a putea fi
internationalizate.
Clasele Byte Stream sunt definite prin doua ierarhii de clase: InputStream si OutputStream din
care deriva o serie de clase pentru lucrul cu diverse dispozitive.
27
Clasele destinate lucrului cu text, si anume Character Stream, sunt Reader si Writer. Analog din
acestea deriva o serie de subclase avand particularitatile lor.
Exista stream-uri predefinite in Java pentru lucru cu intrarea standard : System.in, iesirea
standard System.out si eroarea standard System.err.
Byte Stream
28
FilterOutputStream Implementeaza OutputStream
InputStream Clasa abstracata ce descrie input stream
ObjectInputStream Input stream pentru obiecte
ObjectOutputStream Output stream pentru obiecte
OutputStream Clasa abstracata ce descrie output stream
PipedInputStream pipe pentru intrari
PipedOutputStream pipe pentru iesiri
PrintStream Output stream ce poate apela print( ) si println( )
PushbackInputStream Input stream care permite ca byte-tii sa fie returnati dintr-un stream
SequenceInputStream Input stream ce este o combinatie de mai multe stream-uri de intrari ce
vor fi citite secvential unul dupa celalalt
.
Iata si une exemplu de folosire al unora dintre clasele mai sus mentionate:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
class CopyBytes {
public static void main(String[] args) throws IOException {
FileInputStream in = null;
FileOutputStream out = null;
try {
in = new FileInputStream("filei.txt");
out = new FileOutputStream("fileo.txt");
int c;
} finally {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
}
}
}
In exemplul de mai sus este folosita functia read() care are ca scop citirea byte cu byte a intrarii.
De altfel, clasa InputStream defineste o serie de metode ce sunt mostenite si de copii sai:
29
Metoda Descriere
int available( ) Returneza numbarul de bytes disponibili de la intrare.
void close( ) Inchide sursa. Alte incercari de a citi dupa, vor genera
IOException.
void mark(int numBytes) Pune un semn la byte-ul curent care va ramane valid pana ce
vom fi citit numBytes.
boolean markSupported( ) Returneaza true daca stream-ul suporta sistemul de marcare descris.
int read( ) Returneaza un integer ce reprezinta byte-ul urmator din stream
sau –1 daca s-a intalnit sfarsit de fisier.
int read(byte buffer[ ]) Se incearca citirea unui buffer de lungimea celui specificat in
paranteze si returneaza numarul de bytes citit sau -1 daca s-a
intalnit sfarsit de fisier.
int read(byte buffer[ ], int offset,int numBytes)
Se incearca citirea unui buffer de lungime numBytes, in buffer
incepand cu offset, si returneaza numarul de bytes cititi sau -1
daca am ajuns la sfarsitul fisierului.
void reset( ) Reseteaza acel semn stabilit de mark.
long skip(long numBytes) Ignora numBytes bytes din intrare si returneaza numarul de
bytes ignorat.
Metoda Descriere
void close( ) Inchide stream-ul de scriere catre iesire, o alta scriere generand IOException.
void flush( ) Forteaza scrierea din buffer la iesire, curatind bufferul.
void write(int b) Scrie un byte la iesire. Parametru este un int.
void write(byte buffer[ ]) Scrie un sir de bytes la iesire.
void write(byte buffer[ ], int offset,
int numBytes) Scrie o portiune dintr-un buffer, delimitat de offset si numBytes.
Character Stream
Platforma Java permite utilizarea carcterelor folosind conventii Unicode. Stream-urile carcater
sunt automat transpuse intr-un format intern, care poate fi ulterior convertit la diverse formate
(depinde de locul unde va fi instalata aplicatia).
Pentru majoritatea aplicatiilor, stream-urile de carctere I/O, sunt la fel de simple ca cele byte si
regulile sunt aceleasi. Situatia se poate complica daca programul va fi unul international.
Toate clasele de acest gen deriva din Reader sau Writer. Iata mai jos ierarhia acestora:
30
Mai jos este un program pentru exemplificarea utilizarii acestor clase:
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
class CopyCharacters
{
public static void main(String[] args) throws IOException
{
FileReader inputStream = null;
FileWriter outputStream = null;
try
{
inputStream = new FileReader("filei.txt");
outputStream = new FileWriter("fileo.txt");
int c;
while ((c = inputStream.read()) != -1)
{
outputStream.write(c);
}
}
finally
{
if (inputStream != null)
31
{
inputStream.close();
}
if (outputStream != null)
{
outputStream.close();
}
}
}
}
Metodele suportate de aceste clase sunt aceleasi ca si la byte streams cu deosebirea ca aceste
metode lucreaza cu char si nu byte. De exemplu in clasa Reader, iata cateva metode ce difera sau sunt
noi:
int read(CharBuffer buffer) Se incearca citirea unui buffer de lungimea celui specificat in
paranteze si returneaza numarul de bytes citit sau -1 daca s-a
intalnit sfarsit de fisier. CharBuffer este o clasa ce incapsuleaza
o secventa de caractere asa cum face String.
int read(char buffer[ ]) Acelasi ca in cazul byte:de data aceasta se citesc caractere
32
import java.io.*;
class BufferDemo
{
public static void main(String args[]) throws Exception
{
FileReader fr = new FileReader("filei.txt");
BufferedReader br = new BufferedReader(fr);
String s;
while((s = br.readLine()) != null)
{
System.out.println(s);
}
fr.close();
}
}
Evident scopurile pot fi multiple, si in general se va folosi pentru citirea controlata in buffer-e de o
anumita dimensiune.
33
Cursul 5: Structuri de date I
Cursul 5 ................................................................................................................................................... 1
Array ................................................................................................................................................... 3
Siruri de tipuri de date primitive ...................................................................................................... 4
Siruri de date de tip referinta ........................................................................................................... 4
Siruri multidimensionale .................................................................................................................. 6
Initializarea sirurilor ......................................................................................................................... 8
Copierea, clonarea sirurilor.............................................................................................................. 9
Sirurile sunt imutabile.................................................................................................................... 11
Verificarea egalitatii a doua siruri................................................................................................... 11
Clasa Vector ...................................................................................................................................... 12
Metodele de baza .......................................................................................................................... 12
Crearea Vectorilor ......................................................................................................................... 14
Manipularea elementelor din vector.............................................................................................. 14
Adaugarea la sfarsit ................................................................................................................... 14
Adaugarea undeva in interior..................................................................................................... 15
Adaugarea unei colectii .............................................................................................................. 15
Vectori de tipuri primitive .......................................................................................................... 16
Afisarea vectorilor...................................................................................................................... 16
Stergerea elementelor ............................................................................................................... 16
Marimea unui vector ................................................................................................................. 17
Capacitatea vectorului ............................................................................................................... 18
Cautarea in vector ......................................................................................................................... 18
Preluarea dupa index ................................................................................................................. 18
Preluare dupa pozitie in sir......................................................................................................... 19
Enumerarea elementelor ........................................................................................................... 19
Gasirea elementelor intr-un sir .................................................................................................. 20
Copierea unui vectorilor ................................................................................................................ 21
Stiva .................................................................................................................................................. 22
Clasa Stack ..................................................................................................................................... 24
Adaugarea elementelor ............................................................................................................. 24
Stergerea unui element ............................................................................................................. 24
1
Enumerator ....................................................................................................................................... 25
IO: scanare si formatare..................................................................................................................... 28
Scanner ......................................................................................................................................... 28
Formatarea .................................................................................................................................... 29
Serializarea obiectelor ................................................................................................................... 31
Clasa StringBuffer .......................................................................................................................... 33
2
Array
Un sir de date sau un array este o structura ce contine valori multiple de acelasi tip de data.
Lungimea unui sir de date este stabilita la crearea array-ului si va fi fixa pe intreaga existenta a acestuia.
In figura de mai jos si anume figura 5.1 am reprezentat un array cu zece elemente si indexul
incepand cu pozitia zero. Evident ultimul element poate fi accesat la pozitia data de lungimea sirului
minus 1.
int[] un_sir_de_date;
Odata ce un sir este declarat, el poate fi instantiat. Este necesar, ca inainte de folosirea sirului,
acesta sa fie instantiat. Ca orice referinta, se va folosi operatorul new pentru instantiere, urmat de tipul
de data si numarul de elemente al sirului:
Iata o functie de exemplu ce foloseste la instantierea unui sir de o anumita marime transmisa ca
parametru:
3
Siruri de tipuri de date primitive
Atunci cand se creeaza un sir de tipuri de data primitv, sirul ca contine valorile acelor elemente.
De exemplu, pentru un sir cu patru intregi prezentat in figura de mai jos avem si durata obiectului in
memorie: in acest caz pe heap se aloca un numar fix si anume patru ori marimea tipului int.
In figura 5.2 am reprezentat cum arata un sir in heap (memoria dinamica a programului).
Acestui sir ii pot fi atribuite aceste valori in doua moduri:
int[] sir = new sir[3];
sir[0] = 23;
sir[1] = 12;
sir[2] = 45;
sir[3] = 78;
Sau
int[] sir = {23,12,45,78};
Spre deosebire de tipurile de data primitive, atunci cand cream un sir de obiecte, acestea nu
sunt stocate intr-un masiv. Array-ul va stoca doar referintele catre obiectele propriu-zise, si initial fiecare
referinta este null. Pentru a reprezenta instantierea unui sir de obiecte avem figura 5.3.
De aceasta data fiecare obiect se afla in alta zona de memorie (in heap), alta decat ce in care
este declarat sirul. Elementele din care este compus sirul sunt referintele acestor obiecte, care sunt sau
pot fi, de tipuri diferite.
4
Figura 5.3 Reprezentare Stivei si heap-ului in cazul unui sir de obiecte
Inainte de a trece mai departe sa vedem un exemplu din fiecare sir de date mentionat mai sus.
Pentru inceput vom alege sirul de primitive de tip int, si anume o simpla parcurgere a unui sir cu patru
elemente si afisarea acestora:
public class ArrayDemo {
public static void main(String[] args) {
// declar un sir de int
int[] sir = {23,12,45,78};
Dupa cum vedem in acest exemplu toate elementele din sir sunt de tipul int.
Pe de alta parte, daca utilizam un sir de obiecte, putem intalni elemente de clase diferite. Totusi,
in general un sir de referinte va contine elemente de aceeasi clasa specifica.
class Country
{
String name;
long population;
}
class Invention
{
String name;
String description;
5
}
class Painting
{
String name;
String technique;
}
}
}
Siruri multidimensionale
Din moment ce un sir de date poate contine referinte catre alte obiecte, de ce nu ar contine
referinte catre alte siruri? Atunci cand un sir se refera, sau contine alt sir avem de a face cu siruri
multidimensionale. Iata un exemplu clasic de sir multidimensional:
int matrix[][];
Pentru a instantia un astfel de sir se vor specifica, de obicei, ambele dimensiuni si anume:
In acest caz avem un sir bidimensional, astfel: 3 siruri care la randul lor contin siruri de cate doua
elemente de tip int.
6
Accesarea acestor elemente se poate face prin specificarea ambilor indici:
matrix[1][2] = 34;
Un exemplu simplu pentru a intelege mai bine lucrul cu matrici este urmatorul:
7
//mai intai parcurgem sirul "mare" pana
//la lungimea acestuia (pe linii)
for (int i = 0; i < sir.length; i++)
{
System.out.print("Element( "+ i +" ): ");
//aici parcurgem subsirurile
for (int j = 0; j < sir[i].length; j++)
{
System.out.print(" "+ sir[i][j]);
}
System.out.println();
}
}
}
Afisare:
Element( 0 ): 0.0 43.0 0.0 89.0 0.0
Element( 1 ): 2.3 5.0 6.7 11.0
Element( 2 ): 1.0 4.2 7.0 4.1 10.0 9.0
Element( 3 ): 0.0 0.0 0.0 0.0 0.0 14.0 0
In acest exemplu avem un sir de 4 subsiruri de tip float. Acestea pot avea dimensiuni diferite.
Primul subsir initializat si anume sir[0] va fi initializat ca un sir de float de 5 elemente. Vom avea
acces l a elementele acestuia de la sir[0][0]pana la sir[0][4]. Mai departe urmatorul subsir este
sir[1], care este automat instantiat si intializat cu valori astfel:
sir[1] = new float[]{2.3f,5,6.7f,11};
In acest caz subsirul de pe pozitia 1 are 4 elemente cu valorile dintre acolade.
Accesarea unui anumit element din acest sir bidimensional se face prin ambii indecsi si anume
sir[3][5] = 14;
adica elementul din subsirul 3 cu indexul 5 in acel subsir.
Apoi se poate parcurge ca in cazul matricii, insa, de data aceasta se tine cont de lungimea
fiecarui subsir in parte:
for (int j = 0; j < sir[i].length; j++)
pentru ca de aceasta data fiecare subsir are o lungime variabila.
Aplicatiile acestor siruri multidimensionale provin din diversele necesitati matematice, cum ar fi
de exemplu calculul triunghiului lui Pascal, sau reprezentarea valorilor din figuri geometrice, piramide,
trapez, triunghi etc.
Initializarea sirurilor
Atunci cand se creeaza un array, sistemul Java va initializa fiecare element al sirului cu o valoare,
zero daca este vorba de siruri numerice, false daca vorbim de siruri cu elemente de tip boolean, sau zero
pentru cele care contin referinte.
Totusi, se pot specifica valorile direct la instantierea obiectelor:
8
Acesta este un sir de String-uri care contine 4 elemente. Din acest moment el nu mai poate fi
modificat, adica nu se pot sterge sau adauga elemente in sir. El va contine pe toata durata sa fix 4
elemente. Valorile din aceste elemente se pot, totusi, modifica.
Pentru siruri multidimensionale aceasta initializare poate avea loc astfel:
String[][] sir_nume = {{"Ion","Vasile","Adrian"},{"Andrei","Radu"}};
System.out.println(sir_nume[0][2]);
In acest caz avem, doua subsiruri de String-uri, primul cu trei elemente si al doilea cu doua.
Se va afisa al treilea elment din primul subsir.
Mai mult se poate ca un sir sa fie trimis ca parametru si creeat la apelul metodei astfel:
Aceasta este functia ce primeste un sir de siruri de String-uri si ce afiseaza fiecare element din
acele subsiruri. Mai jos avem si apelul acestei functii
Inainte ca functia sa fie apelata, se creeaza un obiect de tip String[][] cu valorile dintre acolade,
si acel obiect este transmis functiei ca parametru.
Un sir poate fi copiat in alt sir, sau poate fi clonat in alt sir. Aceasta copiere se poate face in mai
multe feluri. Daca trebuie sa crestem numarul de elemente ale unui sir, trebuie sa cream un sir nou cu
numarul dorit de elemente si sa copiem element cu element vechiul sir in noul sir. Daca dorim totusi, sa
lasam marimea intacta, si sa modificam valorile din sir, trebuie sa clonam acel sir.
Metoda arraycopy() a clasei System permite copierea elementelor dintr-un sir in altul. Sirul
destinatie trebuie sa fie mai mare sau egal ca numar de elemente, cu sirul sursa. In caz contrar, se
arunca o exceptie de tip ArrayIndexOutOfBoundsException la rulare.
Metoda arraycopy() are cinci parametrii si anume:
arraycopy (Object sourceArray, int sourceOffset, Object
destinationArray, int destinationOffset, int numberOfElementsToCopy).
9
destinationArray sirul destinatie, in care se copiaza elementele
destinationOffset pozitia din destinatie de incepe copierea elementelor
numberOfElementsToCopy cate elemente vor fi copiate.
Mai jos este un exemplu de folosire a functiei arraycopy:
Atentie, atunci cand copiati elemente dintr-un sir in altul, ele trebuie sa fie de acelasi tip de
data, sau compatibile, altfel va fi aruncata o exceptie de tip ArrayStoreException.
Un exemplu de siruri incompatibile ar fi doua siruri unul de obiecte si unul de primitive.
Pentru a intelege mai bine cum se foloseste arraycopy avem clasa de mai jos:
public class copiere {
public static void main(String args[])
{
int array1[] = {1, 2, 3, 4, 5};
int array2[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
System.out.println("Original size: " + array1.length);
printArray(array1);
System.out.println("New size: " + doubleArray(array1).length);
printArray(doubleArray(array1));
System.out.println("Original size: " + array2.length);
printArray(array2);
System.out.println("New size: " + doubleArray(array2).length);
printArray(doubleArray(array2));
}
static int[] doubleArray(int original[])
{
int length = original.length;
int newArray[] = new int[length*2];
System.arraycopy(original, 0, newArray, 0, length);
return newArray;
}
public static void printArray(int[] sir)
{
for(int i=0;i<sir.length;i++)
{
System.out.print(" " +sir[i]);
}
System.out.println();
}
}
Sirurile sunt obiecte, deci respecta regulile privind egalitatea. Atunci cand se foloseste
operatorul == pentru aceasta verificare se vor compara referintele.
Verificarea corecta a valorilor dintr-un sir se face folosind equals. Aceasta metoda nu este cea
suprascrisa din Object ci este din clasa java.util.Array. Astfel comparatia de mai sus devine:
Aceasta clasa mai contine metode de sortare a elementelor unui sir, de umplere a sirurilor si de
cautare binara.
11
Sirurile sunt foarte potrivite cand stim apriori cate elemente ne trebuie pentru a rezolva o
anumita problema. Dar daca nu stim? Putem crea siruri de lungime zero si sa folosim arraycopy pentru a
redimensiona. Totusi aceasta implica crearea altor siruri, ocuparea de memorie in plus. Pentru aceasta
Java ofera o alta clasa si anume Vector.
Clasa Vector
Clasa Vector face parte din pachetul java.util si are urmatoarea declaratie:
public class Vector<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
Astfel putem concluziona sirul de mosteniri, si legatura dintre Vector si Stack astfel:
Pe langa aceste clase pe care le mosteneste, Vector mai implementeaza si interfetele Cloneable
si Serialize. Interfata Serialize permite ca orice obiect care o implementeaza sa poata fi serializat pentru
a fi transmis catre diverse destinatii, sau deserializat, pentru a putea fi corect preluat.
Metodele de baza
Mai jos avem variabilele clasei Vector si descrierea pentru fiecare dintre ele:
12
Variabila Descriere
capacityIncrement Marimea pentru incrementarea capacitatii unui vector
elementCount Numarul de elemente dintr-un vector.
elementData sirul de date intern al vectorului.
modCount mostenita din AbstractList: folosit de Iterator pentru a verifica modificari
concurente.
Metoda Descriere
add() Adauga un element intr-un vector.
addAll() Adauga o colectie de elemente intr-un vector.
addElement() asemenea metodei add()
capacity() Returneaza capacitatea adica marimea sirului intern.
clear() sterge toate elementele unui vector.
clone() Creeaza o clona a vectorului.
contains() Verifica daca un vector contine un element.
containsAll() Verifica daca un vector contine o colectie.
copyInto() Copiaza elementele unui vector intr-un array.
elementAt() Returneaza elementul de la pozitia specificata.
elements() Returneaza un obiect al vectorului care permite vizitarea tuturor cheilor
vectorului.
ensureCapacity() Verifica si se asigura ca marimea buffer-ului intern sa fie de o anumita marime.
equals() verifica egalitatea cu un obiect.
firstElement() returneaza primul element.
get() returneaza un element de la o anumita pozitie.
indexOf() cauta prima aparitie a unui element in sir.
insertElementAt() Insereaza un element in sir.
isEmpty() verifica daca un vector este gol.
iterator() returneaza un iterator, adica un obiect ce permite vizitarea elementelor din sir
lastElement() Returneaza ultimul element din sir
lastIndexOf() Cauta pozitia ultimului element din sir care este egal cu obiectul specificat
listIterator() Returneaza un obiect care permite ca toate elementele sa fie vizitate secvential.
remove() Sterge un anumit element din vector.
removeAll() Sterge toate elementele specificate in colectia data ca parametru.
removeAllElements() Sterge toate elementele din sir si seteaza marimea acestui cu zero.
set() Schimba un element de la o anumita pozitie.
setElementAt() Acelasi lucru ca si set.
setSize() modifica marimea buffer-ului intern
size() returneaza numarul de elemente din sir
subList() returneaza o sectiune din sir.
toArray() returneaza elementele vectorului ca array.
trimToSize() „taie” o portiune din sir, astfel ca el sa ramana de marimea specificata.
13
Crearea Vectorilor
Se pot folosi patru constructori pentru a creea un Vector. Folosind unul din primii trei
constructori, se poate creea un vector gol, cu o capacitate specificata. Cand spatiul devine insuficient,
vectorul isi va dubla marimea.
public Vector()
public Vector(int initialCapacity)
public Vector(int initialCapacity, int capacityIncrement)
Motivul pentru care exista cei doi constructori cu parametru, este de a permite initializarea cu
un numar de elemente a sirului, in cazul in care stim apriori de cate elemente este nevoie.
Crearea unui nou sir si copierea elementelor ocupa timp, de aceea este mai eficient sa
redimensionam un array. Daca marimea vectorului specificata prin initialCapacity este negativa se
arunca o exceptie de tip IllegalArgumentException.
Al patrulea constructor se refera la utilizarea unei colectii pentru initalizarea vectorului. Practic,
se iau elementele din colectie si se construieste un array cu acestea. Vom aborda subiectul colectii in
cele ce urmeaza.
public Vector(Collection c)
Vectorul nou creeat are cu 10% mai mult decat numarul de elemente din colectia c.
Cum copiem atunci rapid un sir clasic intr-un Vector?
Pentru aceasta, se poate transforma sirul intr-o colectie cu functia Arrays.asList() si apoi folosi
constructorul de mai sus:
Pentru a adauga elemente avem mai multe „oferte” din partea Java, si a clasei Vector.
Adaugarea la sfarsit
Pentru a demonstra adaugarea la sfarsit vom copia dintr-un sir de String-uri intr-un vector, unul
cate unul adaugand la sfarsit:
import java.util.Vector;
public class InsertVector
{
public static void main (String args[])
14
{
Vector v = new Vector();
for (int i=0, n=args.length; i<n; i++)
{
v.add(args[i]);
}
}
}
Exista momente, cand dorim sa inseram elementele la anumite pozitii in sir, mutand celelalte
elemente mai la dreapta. Exista doua functii care fac acest lucru:
Motivul pentru care exista doua doua metode care fac acelasi lucru, este ca, clasa Vector
implementeaza interfata List, deci este parte din Collection. Aceeasi eroare va fi aruncata daca indexul
este negativ, si anume IllegalArgumentException.
Trebuie facuta diferenta intre add si set: ultima metoda doar modifica elementul de la pozitia
specificata. Pentru a intelege mai bine adaugarea am luat un vector cu patru elemente.
Ce se intampla de exemplu cand inseram un element nou in sirul de mai jos:
vector.add(2,”nou”);
se obtine:
Practic se copiaza elementele dintr-un obiect de tip Collection in vector. Aceste elemente se pot
adauga fie la sfarsitul vectorului, folosind prima metoda, fie se pot insera la incepand cu un anumit
15
index, folosind cea de a doua metoda. Este mai eficient sa adaugam toate elementele dintr-un bloc de
elemente decat sa incercam sa le adaugam unul cate unul.
Daca indexul este invalid atunci se va furniza o exceptie de tip IllegalArgumentException.
Deoarece vectorii au fost ganditi sa lucreze cu obiecte, pentru a lucra cu primitive trebuie sa mai
depunem un efort.
Pentru a lucra cu valori de tip primitv trebuie sa convertim acestea in clase wrapper. Spre
exemplu int are clasa wrapper Integer, sau float are clasa wrapper Float. Aceste specificatii sunt in cursul
numarul 1. Exemplul de mai jos prezinta modul de lucru cu tipuri primitive:
import java.util.Vector;
public class PrimVector
{
public static void main (String args[])
{
Vector v = new Vector();
for (int i=1; i<=10; i++)
{
v.add(new Integer(i));
}
}
}
La preluarea valorilor din vector va trebui de asemenea sa transformam din Integer in int, deci
din tip referinta in primitiv.
Afisarea vectorilor
Ca orice Object, si clasa vector implementeaza metoda toString(), ceea ce inseamna ca putem
folosi direct obiectul de tip vector pentru afisare:
System.out.println(v);
unde v este vectorul din exemplul de mai sus. Aceasta comanda va afisa urmatorul sir:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Stergerea elementelor
Ca si in cazul adaugarii, exista multe feluri in care se pot sterge elemente din vectori, si vom
discuta despre acestea in cele ce urmeaza.
Cel mai simplu mod de a sterge, este de a elimina toate elementele folosind:
16
public void clear()
public void removeAllElements()
Atata timp, cat indexul este mai mare ca zero si nu depaseste lungimea sirului, nu vom primi o
exceptie de tip ArrayIndexOutOfBoundsException.
Atunci cand stergem un element, ramane un loc vid, si intregul subsir ce urmeaza elementului
sters va fi deplasat la stanga:
Daca nu se cunoaste pozitia, indexul elementul in sir, dar se cunoaste valoarea acestuia, atunci
se va folosi una din metodele:
Aceste metode cauta in sir, primul obiect care este egal cu obiectul dat ca parametru. Pentru
comparatia obiectelor (cele din sir si parametru) se foloseste metoda equals.
Pe langa aceste metode de stergere, mai exista una care permite eliminarea unei colectii de
elemente:
Metoda va prelua ca parametru colectia c si va sterge fiecare element din vector care se gaseste
in colectia c.
Alta metoda de a sterge, mai precisa, este removeRange si permite stergerea elementelor din
subvectorul delimitat de doua margini: fromIndex si toIndex:
17
public int size()
public void setSize(int newSize)
Prima metoda returneaza marimea sirului, adica numarul de elemente, iar a doua permite
setarea marimii vectorului la un numar de elemente specificat ca parametru. Vom vedea ca marimea
unui sir poate fi diferita de capacitatea acestuia.
Capacitatea vectorului
Daca marimea vectorului trebuie sa fie mai mare decat capacitatea lui, vectorul va creste
dinamic. Atunci cand trebuie sa adaugam elemente in vector, este bine sa verificam daca avem
capacitatea necesara. Pentru aceasta exista metoda ensureCapacity care tocmai asta face:
Daca deja capacitatea este suficient de mare, nu se intampla mai nimic. Daca in schimb,
capacitatea este mai mica decat numarul de elemente ce se va obtine in urma adaugarii, vectorul isi va
dubla marimea. Se poate insa stabili si numarul de elemente care va fi atribuit marimii vectorului in
cazul apelarii metodei ensureCapacity.
Cautarea in vector
Aceasta cautare si redare a unui element se poate face in mai multe feluri.
Pentru a prelua pe baza indexului un element dintr-un vector exista doua metode:
18
Preluare dupa pozitie in sir
Pentru aceasta exista doua functii pentru returnarea primului si ultimului element din sir:
Enumerarea elementelor
Pentru ca si cautarea sa aiba loc cat mai eficient, si anume sa parcurgem tot sirul pe indecsi
pentru a gasi un element, exista un mod mai flexibil, si anume folosirea unui obiect de tip Enumeration.
Obiectele de tip Enumeration au doua functii nextElement() si hasMoreElement() si sunt ideale
pentru iterari prin colectii.
Pentru a transforma un vector intr-un obiect de tip Enumeration exista metoda:
Mai jos avem un exemplu de folosire a acestui tip de obiect si anume Enumeration:
Vector v = . . .
Enumeration e = v.elements();
while (e.hasMoreElements())
{
process(e.nextElement());
}
In acest while se va parcurge atata timp cat functia e.hasMoreElements() va returna true.
process este o functie care va lucra cu elementul din sir, si anume fiecare element pana la epuizarea
marimii sirului.
Pentru a intelege si mai bine lucrul cu Enumeration avem exemplul de mai jos:
import java.util.Enumeration;
import java.util.Vector;
class enumerare
{
public static void main(String args[])
{
Vector v=new Vector(10,10);
for(int i=0;i<20;i++)
v.addElement(new Integer(i));
System.out.println("Vector in ordinea originala");
//se initializeaza obiectul e cu v.elements
//astfel ca putem parcurge enumeratorul e
for(Enumeration e=v.elements();e.hasMoreElements();)
{
//nu exista conditie de incrementare in for
//dar e.nextElmement() se deplaseaza
19
//la urmatorul element
System.out.print(e.nextElement()+" ");
}
System.out.println();
//afisarea vectorului original in ordine inversa
System.out.println("\nVector in ordinea inversa");
for(int i=v.size()-1;i>=0;i--)
System.out.print(v.elementAt(i)+" ");
}
}
Mai intai exista functia contains care verifica daca obiectul dat ca parametru exista in sir:
Exista doua functii care se ocupa cu gasirea unui obiect in sir si anume:
Incepand cu primul element din sir (in cazul primei functii), sau cu elementul de la pozitia index
(in cazul celei de-a doua), se cauta obiectul element in sir si se returneaza indexul unde a fost gasit
prima data.
Aproape acelasi lucru este realizat de cele doua functii de mai jos, cu precizarea ca, cautarea
incepe de la sfarsit catre primul element din sir.
Evident pentru a compara doua elemente si anume parametrul si obiectele din sir se foloseste
metoda equals(). Mai jos avem un exemplu pentru cautarea in vectori:
20
System.out.println(v);
System.out.println("Contine UnNume?: " +
v.contains("UnNume"));
System.out.println("Contains Mircea?: " +
v.contains("Mircea"));
System.out.println("Unde e Radu?: " +
v.indexOf("Radu"));
System.out.println("Unde e DAvid?: " +
v.indexOf("DAvid"));
System.out.println("Unde e Radu de la sfarsit?: " +
v.lastIndexOf("Radu"));
}
}
Exista mai multe feluri de copiere a unui vector. Se poate clona ca orice obiect, sau se poate
copia. Mai intai sa vedem cum se cloneaza:
Vector v1 = . . .;
Vector v2 = (Vector)v1.clone();
Adica, vom converti un vector in array si apoi copiere in alt vector prin copyInto.
Mai jos avem transformarea unui vector in sir:
Vector v = . . .;
String array[] = new String[0];
array = (String[])v.toArray(array);
dupa ce apeleaza metoda toArray(), sirul este redimensionat cu numarul de elemente din v.
Apoi se poate transforma din sir in vector astfel:
21
Vector v = . . .;
String array[] = new String[v.size()];
array = (String[])v.toArray(array);
Vector v = . . .;
String array[] = new String[v.size()];
v.copyInto(array);
Pe langa vectori putem folosi structuri dedicate de date cum ar fi stiva sau coada, pe care le vom
analiza in cele ce urmeaza.
Stiva
O stiva este o structura de tip LIFO, ultimul intrat primul iesit. Mai intai sa vedem cum
functioneaza o stiva. Exista doua functii de introducere in stiva push si pop de eliminare a unui element
din stiva. Introducerea unui element se va face „peste ultimul” element din stiva iar eliminarea va fi a
ultimului element din stiva:
Figura 5.4 Stiva este goala, se introduce un elment folosind functia push.
22
Figura 5.5 Stiva contine un element, se introduce al doilea element folosind functia push. Apoi se
introduce al treilea element.
Figura 5.6 Se scoate elementul din varf cu functia pop, stiva va contine doua elemente.
Din cele trei figuri se poate intelege functionarea unei stive. Evident se pot scoate si celelalte
elemente, adica sa revenim la starea initiala si anume stiva goala. Daca se incearca introducerea unui
element atunci cand deja stiva este plina, va apare o eroare care sa semnaleze acest lucru. Evident,
incercarea de a scoate dintr-o stiva goala duce la o eroare.
23
Clasa Stack
Clasa Stack in Java, este clasa copil a clasei Vector. Totusi exista cateva operatiuni specifice clasei
Stack si anume:
Adaugarea elementelor
Aceasta va prelua primul element din stiva, il va sterge din stiva, si va returna valoarea lui. Daca
stiva este goala vom primi o eroare de tipul EmptyStackException.
Pentru a verifica daca o stiva este goala sau nu avem functia:
Spre deosebire de lucrul cu vectori sau siruri, acest mecanism al stivei functioneaza astfel:
Elementul din varf este pe pozitia 1, pe pozitia 2 fiind elementul de dedesubt si asa mai departe.
Daca nu este gasit obiectul cautat atunci o eroare corespunzatoare este returnata. Un exemplu pentru a
ilustra mai bine folosirea stivelor este dat mai jos:
import java.util.Stack;
public class Stiva
{
public static void main (String args[])
{
Stack s = new Stack();
s.push("Primul element");
24
s.push("Al doilea element");
s.push("Al treilea element");
System.out.println("Next: " + s.peek());
s.push("Al patrulea element");
System.out.println(s.pop());
s.push("Al cincilea element");
s.push("Al saselea element");
//afisarea stivei
System.out.println(s);
// Gasim al doilea element
int count = s.search("Al doilea element");
// scot din stiva tot pana la el
while (count != -1 && count > 1)
{
s.pop();
count--;
}
// scot din stiva pe el si il afisez
System.out.println(s.pop());
//este stiva goala?
System.out.println(s.empty());
System.out.println(s);
}
}
Enumerator
Acest mecanism este unul eficient, si comporta doar doua metode: hasMoreElements() ce
verifica daca mai exista elemente in enumeratie si returneaza un boolean. Cealalta metoda
nextElement(), returneaza urmatorul element din colectie ca si Object. Daca nu mai sunt elemente si
totusi este apelata metoda nextElement(), atunci este aruncata exceptia NoSuchElementException.
25
Enumeration enum = . . .;
while (enum.hasMoreElements()) {
Object o = enum.nextElement();
processObject(o);
}
import java.util.*;
26
{
return java.lang.reflect.Array.get(array, cursor++);
}
}
class Enumerare
{
public static void main(String args[])
{
Enumeration enumerare = new ArrayEnumeration(args);
//parcurg sirul de argumente
while (enumerare.hasMoreElements())
{
System.out.println(enumerare.nextElement());
}
Object obj = new int[] {2, 3, 5, 8, 13, 21};
enumerare = new ArrayEnumeration(obj);
//parcurg un sir de obiecte
while (enumerare.hasMoreElements())
{
System.out.println(enumerare.nextElement());
}
try
{
//incerc sa instantiez o enumerare
//ce are la baza un obiect ce nu e de tip Array
enumerare = new ArrayEnumeration(ArrayEnumeration.class);
}
catch (IllegalArgumentException e)
{
System.out.println("Oops: " + e.getMessage());
}
}
}
O clasa utila care implementeaza aceasta interfata si anume Enumeration este StringTokenizer.
Scopul aceste clase este de a imparti un String in diferite elemente, iar delimitarea se face pe baza unui
caracter anume (de obicei spatiu sau virgula).
Astfel poate fi parcurs acest sir de siruri obtinut:
27
Pe langa metodele hasMoreElements si nextElement pe care le mosteneste din Enumeration, clasa
StringTokenizer mai are si metodele hasMoreTokens si nextToken care fac cam acelasi lucru, in plus
expun in loc de Object, direct String-uri.
Scanner
Scanarea, sau preluarea de informatii dintr-o sursa poate fi facuta mai elegant folosind clasa Scanner.
Un obiect Scanner fragmenteaza intrarea in subelemente numite token, pe baza unor delimitator. Un
exemplu de a citi numere din System.in:
Scanner sc = new Scanner(System.in);
int i = sc.nextInt();
Acest obiect poate folosi si alti delimitatori diferiti de spatiu. Acest exemplu citeste elementele
dintr-un sir separate prin linia:
import java.io.*;
import java.util.Scanner;
28
Mai mult se pot folosi expresii regulate pentru a marca delimitatorii:
Formatarea
Obiectele stream care implementeaza formatarea sunt fie PrintWriter fie PrintStream.
Pentru afisarea datelor exista doua niveluri de formatare:
· print si println – modul standard
· format – formateaza orice numar pe baza unui string
i = 5;
r = Math.sqrt(i);
System.out.println("The square root of " + i + " is " + r + ".");
}
}
Aceasta produce o afisare bruta a datelor:
29
public class Root {
public static void main(String[] args) {
int i = 2;
double r = Math.sqrt(i);
Iata un exemplu mai complex despre formatarea unui numar real ca float:
3.141593, +00000003.1415926536
30
Serializarea obiectelor
Cheia pentru a scrie intr-un obiect este de a reprezenta starea lui intr-o forma serializabila, cu
informatii suficiente pentru a reconstrui acel obiect la citire. De aceea citirea obiectelor si scrierea lor se
numeste serializare.
Serializarea se poate folosi astfel:
1. Remote method invocation (RMI) - comunicatii intre obiecte via soketi.
2. Persitenta usoara – arhivarea unui obiect pentru invocarea ulterioare in program.
ObjectOutputStream trebuie sa fie construit pe baza altui stream. In acest caz se construieste
pe baza unui FileOutputStream care directioneaza catre „theTime”
Metoda writeObject serializeaza obiectul, si scrie acest stream obtinut catre destinatie,
mentinand relatiile dintre obiecte.
Odata ce obiectele au fost scrise intr-un stream, acestea se pot citi pentru a reconstrui
obiectele. Mai jos sunt citite obiectele care au fost scrise prin codul de mai sus:
Metoda readObject deserializeaza urmatorul obiect din stream si recursiv toate referintele lui
pentru ca starea obiectului sa fie aceeasi ca la momentul serializarii.
Un obiect este serializabil doare daca clasa din care face parte, implementeaza interfata
Serializable. Serializarea se poate personaliza, prin implementarea a doua metode din interfata
Serializable: readObject si writeObject.
Pentru un control cat mai bun al relatiilor dintre clasa si superclasa, trebuie implementata si
interfata Externalizable.
Mai jos avem un exemplu pentru a demonstra serializarea obiectelor
31
import java.io.*;
public class Serializare
{
public static void main(String args[])
{
// Object serialization
try
{
MyClassToSerialize objectOut = new
MyClassToSerialize("Hello", 48, 34.658);
System.out.println("object out: " + objectOut);
FileOutputStream fos = new FileOutputStream("serial");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(objectOut);
oos.flush();
oos.close();
}
catch(Exception e)
{
System.out.println("Exception during serialization: " + e);
System.exit(0);
}
// Object deserialization
try
{
MyClassToSerialize objectIn;
FileInputStream fis = new FileInputStream("serial");
ObjectInputStream ois = new ObjectInputStream(fis);
objectIn = (MyClassToSerialize)ois.readObject();
ois.close();
System.out.println("object2: " + objectIn);
}
catch(Exception e)
{
System.out.println("Exception during deserialization: " +
e);
System.exit(0);
}
}
}
class MyClassToSerialize implements Serializable
{
String s;
int i;
double d;
public MyClassToSerialize(String s, int i, double d)
{
this.s = s;
this.i = i;
32
this.d = d;
}
public String toString()
{
return "s=" + s + "; i=" + i + "; d=" + d;
}
}
Clasa StringBuffer
Am vazut mai sus ca sirurile in general sunt imutabile. Ceea ce inseamna ca odata
declarate cu un numar de caractere, ele raman fix cu acel numar de caractere. Clasa String este
implementata tot ca un sir de caractere, deci este imutabila. Un StringBuffer este asemenea
unui String, dar poate fi modificat, si anume lungimea acestui string poate fi modificata prin
cateva functii pe care le vom studia.
StringBuffer-ele sunt sigure in multithreading, adica pot fi folosite de mai multe fire de
executie, fara a genera un comportament aleator.
String-urile pot fi concatenate si aceasta operatie reda la final un singur string format
din string-urile alipite:
x = "a" + 4 + "c"
Problema aici este ca pentru a realiza aceasta operatie este nevoie de patru obiecte si
anume sirurile ”a”, ”4”,”c” si x. Aceasta creare de obiecte, a caror referinta se pierde, duce la
aparitia unui overhead (instructiuni, operatiuni auxiliare pentru indeplinirea unei operatiuni
principale), ceea ce inseamna lipsa de eficienta ca timp si ca memorie.
Modul in care se lucreaza elegant cu String-uri este urmatorul:
33
Iata un alt exemplu pentru functia append care suporta diverse supraincarcari pentru mai multe
tipuri de data:
char[] c1 = new char[] {'S','i','r','d','e','c','h','a','r'};
StringBuffer sbc = new StringBuffer("Sirul nou format : ");
sbc.append(c1);
System.out.println(sbc);
Pe langa aceste functii, mai sunt o serie de metode pentru inserare, de cautare, redimensionare
etc.
34
Curs 6
Liste inlantuite ..................................................................................................................................... 3
Reprezentarea listelor...................................................................................................................... 4
Operatiuni cu liste simplu înlăntuite ................................................................................................ 8
Liste dublu inlantuite ..................................................................................................................... 14
FrameWork-ul Java pentru Colectii .................................................................................................... 14
Bazele Framework-ului .................................................................................................................. 15
Interfetele.................................................................................................................................. 15
Clasele Framework-ului.............................................................................................................. 16
Algoritmii Framework-ului ......................................................................................................... 16
Interfata Collection ........................................................................................................................ 16
Adaugarea elmentelor ............................................................................................................... 17
Stergerea elementelor ............................................................................................................... 17
Retinerea colectiei ..................................................................................................................... 18
Alte Operatiuni .............................................................................................................................. 18
Returnarea elementelor............................................................................................................. 18
Cautarea elementelor ................................................................................................................ 19
Clonarea colectiilor ................................................................................................................... 19
Interfata Iterator............................................................................................................................ 20
Iterator pentru filtrare ................................................................................................................... 21
Exceptiile ce pot apare in colectii ................................................................................................... 23
Exceptii de modificare concurentiala.......................................................................................... 23
Exceptii pentru operatii neimplementate ................................................................................... 24
Liste: List ........................................................................................................................................... 24
Metodele List................................................................................................................................. 25
ArrayList ........................................................................................................................................ 25
Crearea unui ArrayList................................................................................................................ 25
Adaugarea unor elemente ......................................................................................................... 26
Returnarea unui element ........................................................................................................... 26
Stergerea unui element ............................................................................................................. 26
Retinerea unei colectii ............................................................................................................... 27
Stergerea unui interval............................................................................................................... 27
Operatii cu Liste ......................................................................................................................... 28
LinkedList ...................................................................................................................................... 31
Crearea unei LinkedList .............................................................................................................. 31
1
Adaugarea in LinkedList ............................................................................................................. 31
Stergerea elementelor ............................................................................................................... 32
Iteratorul LinkedList ................................................................................................................... 32
Multimi: Set....................................................................................................................................... 34
HashSet ......................................................................................................................................... 34
Crearea unui HashSet................................................................................................................. 34
Adaugarea elementelor ............................................................................................................. 34
Stergerea elementelor ............................................................................................................... 35
Liste Generice ................................................................................................................................ 37
2
Liste inlantuite
Lista simplu înlănţuita este cea mai simpla structura de data înlănţuita. O astfel de lista este o
secvenţa de obiecte alocate dinamic, fiecare obiect având referinţa către succesorul sau in lista. Exista o
multitudine de moduri de a implementa aceasta structura de date. In figura 6.1 avem câteva
reprezentări mai cunoscute a listelor simplu înlănţuie.
Cea mai simpla lista dublu înlănţuia este in figura 6.1 1) in care fiecare element din lista indica
spre succesorul lui, iar ultimul element către null. Variabila cap are rolul de a păstra primul element din
lista. Acest tip de lista este ineficienta atunci când se dorește adăugarea elementelor la ambele capete
ale listei. Pentru a adăuga un element la capătul listei, este necesar sa localizam ultimul element.
Aceasta presupune parcurgerea întregii liste pentru o simpla operaţie de adăugare.
In figura 61. 2) avem reprezentarea unei liste având cap si coada, tocmai pentru a eficientiza
adăugarea elementelor in lista. Singura problema ar fi de spaţiu pentru variabila coada.
Lista simplu înlănţuită din figura 6.1 3) prezintă doua ajustări. Exista aici un element nou,
santinela care nu este folosit pentru a conţine date, dar este întotdeauna prezent. Avantajul principal in
folosirea santinelei este ca simplifica anumite operaţii. De exemplu, din cauza acestui element, nu va
trebui niciodată sa modificam variabila cap. De asemenea ultimul element va indica spre aceasta
santinela, in loc sa indice către null; vorbim astfel despre o lista circulara. Avantajul este ca, inserarea la
capul listei, inserarea la coada listei sunt operaţii identice.
Se poate realiza o lista simplu înlănţuită care nu are nevoie de o santinela. Figura 6.1 4) prezintă
o variaţie in care, doar o variabila este folosita pentru a tine lista, de aceasta data coada, si anume
ultimul element din lista.
3
Mai jos avem o reprezentarea acestor liste atunci când nu conţin nici un element.
Reprezentarea listelor
class LinkedList
{
int data;
LinkedList next;
public LinkedList()
{
next = null;
data =0;
}
}
Se poate observa ca aceasta clasa conţine un membru next tot de același tip ca si clasa. Aceasta
pentru ca, elementul din lista va indica spre următorul element din lista care este tot de același tip de
data. Pe lângă acest membru, clasa mai conţine un element de tip int data, care retine informaţii despre
nodul curent din lista. Bineînţeles ca pe lângă acest membru, se pot declara o serie de alţi membrii care
pot retine diverse informaţie, in funcţie de necesitaţi.
4
class LinkedListDemo
{
public static void main(String args[])
{
LinkedList list = new LinkedList();
list.data =23;
list.next = new LinkedList();
list.next.data = 10;
list.next.next = new LinkedList();
list.next.next.data = 49;
//acum parcurgem lista de la capat
//si anume din list
LinkedList iterator = list;
do
{
System.out.println(iterator.data);
}
while((iterator = iterator.next)!=null);
iterator = list;
do
{
System.out.println(iterator.data);
}
while((iterator = iterator.next)!=null);
}
}
In clasa LinkedDemo avem un exemplu de folosire a listelor simplu înlănţuite. Se declara prima
data un obiect de tip LinkedList cu un constructor fără parametrii. Apoi se instanţiază membrul next. In
acest moment se numește ca am alipit primului element din lista un nou element si anume: list.next.
Acest nou element este instanţiat la rândul lui tot cu un obiect de tip LinkedList: list.next = new
LinkedList();. Din acest moment avem posibilitatea de a modifica datele acestui nou obiect cat si
de a alipi un nou element acestuia, s.a.m.d. Vom vedea in continuare si cum se face adăugarea intr-un
mod elegant.
Parcurgerea este un proces interesant. Desigur ca putem accesa elementele următoare prin
sintaxe de genul list.next.next.data. Însa practica, nu ne permite acest mod de acces: daca avem
o lista cu sute sau mii de elemente? De aceea o parcurgere presupune existenta unui iterator, sau
element care sa se „transforme” in succesorul sau. Practic se trece de la element la element prin
5
instrucţiunea iterator = iterator.next. Aceasta se poate face intr-un while, pana când
următorul element devine null, adică am ajuns la capătul listei.
In exemplul de mai sus, pentru a parcurge lista de doua ori, a trebuit sa mă folosesc de un
element auxiliar. Daca as fi parcurs lista direct, folosind obiectul list, a doua oara nu as mai putea
„reveni” de la începutul listei. De aceea este indicat sa ţinem un element pe post de „capul” listei.
Mai jos avem un exemplu de lista simplu înlănţuită in care fiecare element tine o referinţă către
capul listei. Acest fapt este avantajos mai ale in cazul in care ștergem un element, sau accesam un
element in interiorul listei.
class LinkedList
{
int data;
LinkedList next;
LinkedList head;
public LinkedList()
{
head = this;
next = null;
data =0;
}
6
list.next = new LinkedList(list);
list.next.data = 10;
list.next.next = new LinkedList(list);
list.next.next.data = 49;
//acum parcurgem lista de la capat
//si anume din list
while (list.next !=null)
{
System.out.println(list.data);
list = list.next;
}
System.out.println(list.data);
list = list.head;
while (list.next !=null)
{
System.out.println(list.data);
list = list.next;
}
System.out.println(list.data);
}
}
In legătura cu utilizarea listelor de acest fel, putem remarca faptul ca lista se poate folosi si după
o parcurgere list = list.head;, însa ce e mai interesant este ca se poate relua aceasta parcurgere
din orice element al listei, din moment ce fiecare tine o referinţă către capul listei.
Sunt multe erori care pot apare, încă de la începutul definiţiei unei liste, astfel ca e bine sa
încercam sa prevedem situaţiile nefericite ce pot sa apară. De exemplu daca se încearcă accesarea unui
element null, ca de exemplu primul element al listei, sa aruncam o excepţie. Pentru aceasta membrii
listei trebuie sa fie private sau protected (daca lista va fi moștenita), iar excepţiile vor apare in get-eri si
set-eri.
Iată o parte a clasei LinkedList cu modificările de mai sus:
class LinkedList
{
int data;
protected LinkedList next;
protected LinkedList head;
public LinkedList GetNextElement()
{
7
//Aici nu se impune sa aruncam o exceptie
//metoda apelanta va decide in functie de
//faptul ca next este null sau nu
return next;
}
public int GetNextElementData()
{
if (next==null)
throw new NullPointerException("Elementul urmator este
null!");
return next.data;
}
Pentru a înţelege mai bine toate operaţiunile descrise in continuare vom reface structura
elementelor de compun o lista si anume cele de clasa Nod:
public class Nod
{
private Nod next;
private int data;
public Nod(int data, Nod next)
{
this.data = data;
this.next = next;
}
public int getData ()
{
return data;
}
public Nod getNext ()
{
return next;
}
8
public void setData (int newData)
{
data = newData;
}
public void setLink (Nod newNext)
{
next = newNext;
}
public void AddNodeAfter(int element)
{
next = new Nod(element,next);
}
public void RemoveNodeAfter()
{
next = next.next;
}
//iterare prin lista de la prima pozitie pana la
//pozitia idicata
public static Nod PositionInList (Nod start, int index) throws Exception
{
int i;
Nod iterator;
if (!(index>0)) throw new Exception("Index incorect!");
iterator = start;
for(i=1;(i<index && iterator!=null);i++)
{
iterator = iterator.next;
}
return iterator;
}
public static Nod SearchInList(Nod start, int target)
{
Nod iterator = start;
for(;iterator!=null;iterator=iterator.next)
{
if (iterator.data == target)
return iterator;
}
return null;
}
public String ListToString()
{
Nod iterator = this;
StringBuffer str=new StringBuffer();
for(;iterator!=null;iterator=iterator.next)
{
str.append(iterator.data);
str.append(" ");
9
}
return str.toString();
}
public String toString()
{
return data +"";
}
}
Pe lângă cei doi membrii si anume next si data, se pot observa o serie de funcţii pentru a înlesni
adăugarea, căutarea, ștergerea elementelor dintr-o lista. Clasa Nod reprezintă un element din lista, însă
prin intermediul acelui nod din lista putem de exemplu adăuga element imediat dupa capul listei:
public void AddNodeAfter(int element)
{
next = new Nod(element,next);
}
Figura de mai jos explica ce se întâmpla când adăugam un nou element il lista:
Se poate remarca faptul ca noul element este introdus intre cap si vechea santinela, urmând ca
noul element sa fie succesorul capului, ceea ce îl face pe acesta noua santinela.
Ștergerea va fi tot a acestei santinele, care înseamnă primul element dupa capul listei. Cand ștergem
santinela, de fapt, sărim peste aceasta in felul următor: next = next.next; ceea ce înseamnă ca de
acum capul listei va indica spre elementul ce urma santinelei. Acest lucru este reprezentat in figura 6.4.
10
Figura 6.4 Ștergerea unui element din lista
iterator = iterator.next;
O serie de alte funcţii sau de adăugări sau de ștergeri pot fi implementate, in aceasta clasa
pentru ca operaţiile sa fie cat mai ușor realizate. Iată un exemplu de inserare după un element din lista.
public void InsertAfterAnElement(int dataVal, Nod start, int newVal)
{
Nod tmp = new Nod(newVal,null);
Nod prev = start;
Nod iterator = start;
do
{
if (iterator.data == dataVal)
{
prev = iterator.next;
break;
}
iterator=iterator.next;
}
while(iterator!=null);
tmp.next = prev;
iterator.next = tmp;
}
11
Aceasta metoda realizează inserarea după un element specificat ca prim parametru. Valoarea noului
parametru este al treilea parametru. tmp reprezintă elementul nou, de aceea la creare nu specificam
next-ul lui. Apoi parcurgem lista pana la elementul după care inseram, reţinem următorul acelui element
in prev si la sfârșit tmp va indica spre prev iar tmp devine succesorul elementului de dinaintea lui
prev. Pentru o mai buna înţelegere a situaţiei avem imaginea de mai jos figura 6.5.
12
Ca si in cazul inserării, se parcurge lista de la primul element pana la cel căutat, si se sare peste
succesorul acestuia, marcând astfel ștergerea din lista: iterator.next = prev.next;
Pentru o mai buna înţelegere a acestei operaţii avem figura 6.6 mai jos:
In continuare vom vedea modul in care aceste structuri de date sunt implementate in Java, si
mai ales Framework-ul pe care acestea sunt construite. Mai jos avem un exemplu de folosire a clasei de
mai sus, cu toate metodele discutate:
13
System.out.println(Nod.SearchInList(head, 40));
head.RemoveNodeAfter();
System.out.println(head.ListToString());
In cazul in care dorim sa parcurgem ușor lista si in celalalt sens (si nu doar de la predecesor la
succesor), avem lista dublu înlănţuită.
Aceasta păstrează conceptele listei simplu înlănţuite, cu specificarea ca fiecare element al listei
mai conţine o referinţă către predecesorul, sau anteriorul sau, așa cum apare in figura 6.7
Framework, in general înseamnă o serie de clase, librarii care joaca rol de „schelet” intr-o aplicaţie,
permiţând extinderea şi dezvoltarea ei pe baza acestor elemente. In Java , Framework-ul este
asemănător cu Standard Template Library (STL) din C++. Exista aproximativ douazecisicinci de clase,
interfeţe, care constituie acest nucleu.
14
Bazele Framework-ului
Acest Framework de colecţii costa din trei părţi: interfeţe, implementări si algoritmi. Implementările
sunt acele clase concrete pe care Framework-ul le are, iar algoritmii sunt acţiuni, metode predefinite
care pot exista in clase.
Interfetele
Exista mai multe interfeţe in acest Framework si anume: Collection, List, Set, Map, SortedSet, si
SortedMap.
Ierarhia acestora este prezentata in figura de mai jos:
Se poate observa care este ierarhia de mosteniri in aceasta figură. Collection este un grup de
date generic, in care fiecare membru este un element. Un tip Collection poate contine duplicate si poate
sa nu aiba elementele sortate. Interfata List este o colectie specializata, in sensul ca, defineste o anumita
ordinde pentru elementele sale. Pot exista duplicate, dar important este ca exista o ordine. Set-ul este
acea colectie ce simuleaza o multime matematica. Daca avem nevoie de o multime sortata, avem la
dispozitie SortedSet.
Cealalta ierarhie de clase este Maps si anume multimi de perechi cheie-valoare. Deoarece map-urile au
un element compus din doua parti, au o alta implementare decat Collection. In caz ca lucram cu o
colectie de chei sortate putem folosi SortedMap.
15
Clasele Framework-ului
Mai jos este un tabel cu aceste clase concrete care moștenesc interfeţele prezentate mai sus:
Clase concrete
Interfeţe HashTable Array mutabil Arbore Lista înlănţuita Altele
balansat
Set HashSet TreeSet
SortedSet TreeSet
List ArrayList LinkedList Vector, Stack
Map HashMap TreeMap Hashtable,
Properties
SortedMap TreeMap
Toate clasele implementează ori interfaţa Collection, ori interfaţa Map. Pentru a înţelege acest tabel el
va trebui citit de la stânga la dreapta si anume: pe al doilea rând: care sunt clasele ce implementează
interfaţa Set? Pe fiecare coloana vom găsi acea clasa ce implementează interfaţa, daca este cazul.
Vom analiza pe rând, toate aceste clase si interfeţe. Daca dorim sa creăm clase ce implementează aceste
interfeţe este bine sa ţinem cont de câteva aspecte:
- Toate implementările sunt desincronizate. Se poate adăuga acces sincron, dar nu este
necesar.
- Toate clasele oferă iteratori fail-fast. Daca o colecţie este modificata in timp ce iteram prin
elementele acesteia, va fi aruncata o excepţie de tip ConcurrentModificationException.
- Toate implementările lucrează cu elemente null.
- Clasele se bazează pe un concept de metode opţionale in interfete. Daca o clasa nu suporta
o anumita operaţiune, va arunca o excepţie numita UnsupportedOperation Exception.
Algoritmii Framework-ului
Exista o serie de algoritmi predefinit care se găsesc in Collections si in Arrays, ce ajuta in lucrul cu
aceste colecţii si pe care ii vom analiza in curând.
Interfata Collection
Aceasta interfaţa consta din o serie de metode necesare in lucru cu o colecţie si anume:
16
Metoda Descrierea
add() Adaugă un element intr-o colecţie
addAll() Adaugă o colecţie in alta colecţie
clear() Șterge toate elementele dintr-o colecţie
contains() Verifica daca un element se afla intr-o colecţie
containsAll() Verifica daca o colecţie se afla intr-o alta colecţie
equals() Verifica egalitatea a doua colecţii
hashCode() Returnează un id specific unei colecţii
isEmpty() Verifica daca o colecţie este goala
iterator() Returnează un obiect dintr-o colecţie ce permite vizitarea elementelor acesteia
remove() Șterge un element dintr-o colecţie
removeAll() Șterge elementele unei colecţii din colecţia curenta
retainAll() Șterge elementele dintr-o colecţie care nu sunt in alta colecţie
size() Returnează numărul de elemente dintr-o colecţie
toArray() Returnează elementele dintr-o colecţie ca sir.
Adaugarea elmentelor
Putem adăuga doar un element folosind metoda add. In mod normal, daca nu se arunca nici o excepţie,
acest element va fi in colecţie după return-ul din funcţie. In acest caz valoare returnata este true.
Fiecare element din colecţia c va fi adăugat in colecţia ce apelează metoda. Daca in urma
apelului, colecţia se modifica, metoda returnează true, altfel este returnat false. Daca apar duplicate, iar
acest lucru nu este suportat de acel tip de colecţie, va fi aruncata excepţia de tip
UnsuporedOperationException.
Stergerea elementelor
17
Pentru a determina daca un element este in colecţie sau nu, trebuie sa ne bazam pe metoda
equals. Atunci cand se determina care este elementul din colecţie ce va fi șters, metoda equals va fi
invocata. Daca ștergerea nu este permisa, vom primi excepţia de mai sus.
Se pot șterge si elementele unei colecţii folosind funcţia:
Metoda șterge toate instanţele din colecţia sursa, care se afla si in colecţia c. Daca avem
elementele:
[1,5,8,1,2,4,2,5,7,6]
si colecţia c este:
[1,5]
colecţia rămasă în urma ștergerii este:
[8,2,4,2,7,6]
Retinerea colectiei
Aceasta operaţiune este inversa lui removeAll si anume presupune ca doar elementele din
colecţia c sunt păstrate in colecţia originală:
Practic metoda funcţionează ca o intersecţie de mulţimi. Daca aplicam aceasta operaţie pentru
colecţia:
[1,5,8,1,2,4,2,5,7,6]
si c este:
[1,5,23,29]
colecţia ce rezulta este:
[1,5]
Alte Operatiuni
Returnarea elementelor
Exista o singura metoda pentru a returna elemente din colecţie. Prin folosirea unui iterator, si
anume prin apelul metodei iterator() putem vizita toate elementele unei colecţii:
18
Cautarea elementelor
Daca obiectul element este găsit, atunci funcţia va returna true, altfel va returna false. Ca si in
cazul remove(), se folosește metoda equals pentru comparare.
Se poate de asemenea verifica daca o colecţie conţine alta colecţie prin funcţia:
Aceasta metoda va căuta subsetul c in colecţia curenta. Daca doar un element din c nu este găsit
in colecţia curenta se returnează false.
Funcţia pentru aflarea mărimii unei colecţii este:
Clonarea colectiilor
Prima metoda va returna un sir de obiecte si anume elementele din colecţie. Deoarece metoda
returnează un Object[], de fiecare data când avem nevoie sa luam un element din sir va trebui sa il
transformam către tipul de baza folosind operatorul de cast.
A doua versiune a metodei este folositoare când vrem sa determinam mărimea șirului de
returnat pe baza șirului pasat ca parametru. Daca mărimea colecţiei este mai mica decât a.length atunci
elementele sunt puse in sir si returnate. Daca șirul e prea mic, se creează un nou sir cu mărimea egala cu
cea a colecţiei, si acel sir este returnat. Daca șirul dat ca parametru este prea mare atunci metoda
înlocuiește cu null, elementele de după ultimul element copiat: a[collection.size()]=null.
19
Iată un mod de folosire a acestei funcţii:
Interfata Iterator
Iteratorul este acel obiect ce permite parcurgerea colecţiilor. Pentru aceasta el trebuie sa
implementeze interfaţa Iterator.
Interfaţa Iterator conţine trei metode:
hasNext() – ce verifica daca mai sunt elemente de iterat
next() – returnează următorul element din lista
remove() – șterge un element din iterator
Collection c = ...
Iterator i = c.iterator();
while (i.hasNext())
{
process(i.next());
}
Se verifica daca mai avem element de iterat prin hasNext() si apoi se preia acest element cu
next(), pentru a fi utilizat mai departe.
Metoda remove() este o noutate si nu are echivalent in Enumeration. Atunci când este apelata,
va șterge din colecţia sursa, in cazul in care aceasta operaţie este suportata. Atenţie, daca se iterează in
colecţie cu metoda next() vom primi o excepţie de tipul ConcurrentModificationException.
20
Iterator pentru filtrare
Pe lângă posibilitatea de a parcurge elementele din colecţie, putem aplica un predicat, si anume
interfaţa având o metoda ce filtrează elementele din colecţie.
interface Predicate
{
boolean predicate(Object element);
}
Atunci când apelam metoda next() a iteratorului, va fi returnat acel următor element din
colecţie care îndeplinește condiţia data de metoda predicate().
Mai jos avem un exemplu de folosire al acestui mecanism:
interface IPredicate
{
public boolean predicate(Object o);
}
class Predicate implements IPredicate
{
public boolean predicate(Object o)
{
return o.toString().startsWith("A");
}
}
import java.util.*;
public class IteratorWithPredicate implements Iterator
{
//elementul cu ajutorul caruia parcurgem
//colectia care va folosi IteratorWithPredicate
Iterator iter;
21
//adica daca mai am element de returnat
boolean doneNext = false;
public IteratorWithPredicate(Iterator iter, Predicate pred)
{
this.iter = iter;
this.pred = pred;
}
public void remove()
{
//inca nu oferim cod valid pentru aceasta metoda
throw new UnsupportedOperationException();
}
//implementam metoda hasNext
public boolean hasNext()
{
doneNext = true;
boolean hasNext;
while (hasNext = iter.hasNext())
{
next = iter.next();
if (pred.predicate(next))
{
break;
}
}
return hasNext;
}
//si metoda next ale interfetei Iterator
public Object next()
{
if (!doneNext)
{
boolean has = hasNext();
if (!has)
{
throw new NoSuchElementException();
}
}
doneNext = false;
return next;
}
}
22
care este un iterator Java, si se aplica funcţia predicate cu parametru obiectul obţinut prin iter. Daca
funcţia returnează true, nu vom trece mai departe, rămânem pe elementul găsit si se returnează false in
cazul in care mai avem elemente sau true daca am ajuns la finalul colecţiei. Daca însa predicate
returnează false, mergem mai departe in colecţie, pana când funcţia va returna true, sau am ajuns la
finalul colecţiei.
In continuare avem un mic exemplu de folosire a acestor clase, si cu precădere a iteratorului
personalizat.
class PredTest
{
static Predicate pred = new Predicate();
public static void main (String args[])
{
String[] str ={"Anca","Razvan","Maria","Daniela","Paul",
"Adrian","Dorin"};
List list = Arrays.asList(str);
Iterator i1 = list.iterator();
Iterator i = new IteratorWithPredicate(i1, pred);
while (i.hasNext())
{
System.out.println(i.next());
}
}
}
Folosind acest iterator se parcurge lista returnându-se elementele pe baza filtrului din Predicate,
si anume acele elemente ce încep cu „A”.
23
{
public static void main (String args[])
{
String[] str = {"1","a","et"};
List list = new ArrayList(Arrays.asList(str));
Iterator i = list.iterator();
while (i.hasNext())
{
System.out.println(i.next());
list.add("Element");
}
}
}
Problema este ca dorim sa adăugăm un element in colecţia list in momentul parcurgerii
acesteia: list.add("Element");, si atunci va apare excepţia:
1
Exception in thread "main" java.util.ConcurrentModificationException
at
java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)
at java.util.AbstractList$Itr.next(AbstractList.java:343)
at curs6.CollectionException.main(CollectionException.java:23)
Metodele de modificare a colecţiei sunt:
add()
addAll()
clear()
remove()
removeAll()
retainAll()
Liste: List
List este in Java o interfaţă ce implementează Collection. Exista doua clase ce implementează
aceasta interfaţa: ArrayList si LinkedList. Interfata List oferă posibilitatea de a lucra ordonat, deci
permite păstrarea secvenţiala a elementelor dintr-o colecţie.
24
Metodele List
Metoda Descriere
indexOf() Cauta un element in lista
lastIndexOf() Cauta un element in lista începând de la sfârșitul acesteia
listIterator() returnează iteratorul personalizat al listei
set() modifica un element specificat din lista
get() returnează un element din lista
remove() șterge un anumit element din lista
subList() returnează o parte din lista
Fiecare din clasele concrete vor implementa aceste metode. Pe lângă, vor avea si metode
specifice in funcţie de clasa.
ArrayList
Aceasta clasa este echivalentul clasei Vector, dar sub forma unei colecţii. Un ArrayList este o
colecţie de elemente indexate intr-o anumită ordine, dar nu neapărat sortate.
Aceasta indexare permite accesul rapid la date, dar o insertie si stergere mai lenta.
Iată câteva dintre funcţiile oferite in plus de ArrayList:
Metoda Descriere
ArrayList() Constructorul pentru o lista goala
ensureCapacity() Creează un buffer intern cu o capacitate dubla fata de cea anterioara
removeRange() Șterge un anumit interval de elemente din lista
trimToSize() limitează capacitatea buffer-ului intern la mărimea specificata
public ArrayList()
public ArrayList (int initialCapacity)
Primul este pentru a instantia un obiect cu o lista goala. Al doilea constructor instanţiază o
colecţie de elemente null, cu dimensiunea specificata in parametru.
De exemplu:
25
Adaugarea unor elemente
Prima funcţie adăuga un obiect in lista la sfârșitul listei. A doua funcţie, ce supraîncărca
funcţia add, permite adăugarea la indexul specificat si elementele de după, sunt împinse mai la
dreapta cu o unitate. Indexarea pornește de la zero.
De asemenea, ArrayList fiind o colecţie, putem adăuga prin metodele de addAll, colecţii:
fiecare element din colecţia, data ca argument, va fi pusa in lista, prin apelul metodei add(), in
cazul primei metode addAll(). In cazul in care folosim si un parametru index, inserarea in lista va avea loc
de la indexul specificat. Daca lista, cu ajutorul căruia apelam aceste metode se modifica, atunci metoda
va returna true. Daca adăugarea nu este suportata, va apare o excepţie de tipul
UnsupportedOperationException.
26
De asemenea se pot șterge elemente specifice din lista:
A doua metoda șterge un element de la o anumita poziţie, daca poziţia este valida. Prima
metoda șterge un element din lista comparând elementele listei cu parametrul. Pentru a verifica
egalitatea se folosește metoda equals(). In acest caz se șterge primul element din lista egal cu
parametrul.
Se poate șterge si o colecţie prin metoda
Aceasta metoda va șterge toate instanţele obiectelor din lista găsita in colecţia c. De exemplu,
daca lista originala era:
{”element”,”index”,”element”,”sir”,”clasa”}
{”lista”,”multime”,”element”}
{”index”,”sir”,”clasa”}
Funcţia retainAll este prezentata si mai sus, când am vorbit despre colecţii:
Prin aceasta funcţie se reţin in colecţie doar acele elemente comune listei si colecţiei c.
Evident, nu poate fi folosita decat in clase ce extind ArrayList si are ca efect ștergerea
elementelor cuprinse intre fromIndex si toIndex.
27
Operatii cu Liste
Aceasta se va realiza prin intermediul iteratorului sau a metodei listIterator(), specifica doar
listelor:
Al treilea obiect, are ca parametru un index, pentru ca sa putem începe căutarea elementelor
de la o anumita poziţie. De exemplu:
Gasirea elementelor
Înainte de a returna un element in lista putem apela metoda contains, pentru a verifica daca un
element se alfa in lista sau nu:
Prima metoda iterează începând cu primul element si in momentul in care a găsit elementul egal
cu parametrul returnează poziţia acestuia in lista. Compararea se face pe baza metodei equals.
A doua metoda are același comportament ca si prima, cu menţiunea ca, căutarea începe cu
ultimul element.
De asemenea se poate folosi si funcţia containsAll in condiţiile mai sus menţionate.
28
public Object set(int index, Object element)
Se poate astfel modifica valoarea unui element de la o poziţie specificata prin index.
Folosind metoda size() se poate afla cate elemente sunt intr-o lista. Pe deasupra, prin metoda
ensureCapacity(), se poate redimensiona buffer-ul intern care conţine lista.
Pentru a micșora numărul de elemente din lista avem funcţia trimToSize.
Pentru a concluziona cele prezentate mai sus avem un mic exemplu de folosirea acestor tipuri
de liste:
Egalitate in liste
De mai multe ori am afirmat ca egalitatea se verifica prin metoda equals. Aceasta poate fi
suprascrisa, in cazul in care lucram cu o lista particularizata după cum vom vedea in continuare.
Pe lângă metoda equals, atunci când se compara doua elemente, se compara si id-urile lor adică
ceea ce returnează funcţia hashCode(). Am precizat in cursul anterior ca aceasta funcţie returnează un
int ce reprezintă codul unic al obiectului, si aceasta funcţie respecta mai multe conditii.
Mai jos avem declaraţia acesteia
Pentru a înţelege cum se folosesc aceste funcţii avem exemplul de mai jos:
29
import java.util.ArrayList;
class Point
{
public int x;
public int y;
}
}
30
Întotdeauna funcţia hashcode() va trebui sa returneze un număr diferit daca obiectele sunt
diferite. Spre exemplu :
list.add(new Point(1,2));
list.add(new Point(2,1));
Avem doua obiecte diferite. Daca in funcţia hashCode() s-ar fi returnat simplu suma celor doua
coordonate, obiectele ar fi fost egale. De aceea se încearcă o formula care sa evite aceasta situaţie, si
anume prin înmulţire cu numere prime si apoi însumare. In urma rulării acestui program, acesta este
rezultatul:
false
true
true
[x = 1 y = 2 id = 106
, x = 3 y = 4 id = 186
, x = 2 y = 1 id = 100
, un punct]
Evident obiectul de tip String „un punct”, nu face parte din clasa Point, deci nu va respecta acele
metode equals si hashCode(). Pentru a forţa ca intr-o lista sa fie acceptate doar elemente de un anumit
tip de data, se folosesc liste generice despre care vom vorbi imediat.
In continuare vom studia cealaltă clasa ce implementează ArrayList si anume LinkedList
LinkedList
Aceasta este implementarea listei înlănţuite descrise la începutul acestui curs si anume lista
dublu înlănţuita. Vom discuta in continuare doar de metodele specifice LinkedList, celelalte metode fiind
implementate din interfaţa Collection.
public LinkedList()
public LinkedList(Collection col)
Adaugarea in LinkedList
31
Într-adevăr, se pot adăuga elemente la un anumit index, pentru ca si intr-o lista inlantuita
elementele sunt ordonate. De asemenea o lista simplu înlănţuita poate funcţiona ca o stiva sau ca o
coada, ca atare implementează metodele:
Cele doua metode adăugare permit plasarea elementului, fie la sfârșitul listei fie la capul ei.
Pentru a obţine elementele de la capete avem:
Stergerea elementelor
Iteratorul LinkedList
Iteratorul se numește ListIterator si extinde Iterator. Deoarece listele dublu înlănţuite conţin
elemente cu referinţa către next si prev, iteratorul va permite deplasarea in ambele direcţii.
Metoda Descriere
hasNext verifica pe direcţia înainte, daca mai sunt elemente
hasPrevious verifica pe direcţia înapoi, daca mai sunt elemente
next returnează următorul elemente
nextIndex returnează indexul din colecţie al următorului element
previousIndex returnează indexul din colecţie al elementului anterior
remove șterge un element din iterator
set modifica elementul curent
32
Pentru a înţelege mai bine lucrul cu aceste liste avem următorul exemplu:
33
Multimi: Set
Interfaţa Set reprezintă un grup de elemente fără duplicate. Nu exista o condiţie anume ce
impune acest lucru: adică sa nu fie duplicate elementele unui Set, ci implementările din clasele Set, sunt
cele care impun aceasta condiţie.
Interfaţa Set deriva din Collections, deci va implementa aceleași metode ca si aceasta interfaţa,
cu specificarea ca elementele trebuie sa fie unice. De asemenea un element, aflat deja in mulţime, nu
poate fi modificat. Aceasta interfaţa va fi implementata de doua clase: TreeSet si HashSet.
HashSet
Înainte de a prezenta aceasta clasa, vom spune ca HashSet este implementată ca un HashMap,
sau un hashtable. Vom insista asupra acestor colecţii in cursul următor. In continuare, vom studia
operaţiunile principale cu acest tip de data.
public HashSet()
public HashSet(int initialCapacity)
public HashSet(int initialCapacity, int loadFactor)
Primii doi constructori sunt asemănători celor studiaţi. In cazul celui de-al treilea constructor, se
poate specifica un factor de mărire a capacitaţii, in cazul in care dorim acest lucru. De exemplu, daca nu
dorim sa mărim cu 100% colecţia, atunci când mărim capacitatea, putem specifica 75%, 50% etc.
Iată mai jos un exemplu de instanţiere a unui HashSet:
Adaugarea elementelor
Metoda are ca efect adăugarea elementului, daca acesta nu este in Set, in caz contrar nu se
adăuga elementul si metoda returnează false. Comparaţia intre doua elemente se realizează cu ajutorul
metodei equals. Se poate adăuga si o colecţie de elemente cu:
34
Același regim se va aplica si aici, adică doar elemente unice din colecţie, care nu sunt in mulţime,
vor fi adăugate.
Stergerea elementelor
Efectul acestei funcţii este de a elimina din mulţime doar o singura data, elementele găsite in
colecţia c. de exemplu daca avem mulţimea:
{"Roman","Italian"}
Celelalte operaţii din mulţime sunt perfect asemănătoare cu cele din ArrayList, cu menţiunea ca
se va respecta condiţia ca elementele sa fie unice.
Insistam asupra metodei hashCode si equals:
Aceste doua metode sunt cele prin care se verifica daca un element este deja in mulţime sau nu.
Pentru o buna funcţionare a verificării daca un obiect este in mulţime sau nu, va trebui sa suprascriem
corect ambele metode.
Folosind exact clasa Point, din exemplul de mai sus, iată cum se pot folosi mulţimile:
35
public class Multimi
{
public static void main(String args[])
{
// Create the set
Set set = new HashSet();
// Adaug in multime
set.add(new Point(1,2));
set.add(new Point(2,1));
set.add("c");
// Sterg un element din multime
set.remove("c");
//Marimea unei multimi
int size = set.size();
System.out.println(size);
// Adaug un element ce exista deja
set.add(new Point(1,2));
//fara a avea insa efect
size = set.size();
System.out.println(size);
//Verificam daca un element este deja
//in multime
boolean b = set.contains(new Point(2,1)); // true
System.out.println(b);
b = set.contains("c"); // false
System.out.println(b);
// Parcurgem multimea
Iterator it = set.iterator();
while (it.hasNext())
{
//si afisam elementele
Object element = it.next();
System.out.println(element);
}
}
}
36
Liste Generice
Se declara folosind „<” si „>” pentru a specifica tipul de data ce va fi admis in aceste colecţii.
In rest comportamentul este exact același. Reluam un exemplu de la ArrayList folosind liste
generice.
List<Point> list = new ArrayList();
list.add(new Point(1,2));
list.add(new Point(3,4));
list.add(new Point(2,1));
list.add(new String("un punct"));
Point pt = new Point(-1,-1);
…
Eroarea de sintaxa, va apare atunci când încercăm sa adăugăm un element de tip String, altul
decât cel admis in listă. Listele generice admit doar referinţe, si nu admit tipuri primitive.
37
Curs7
TreeSet ................................................................................................................................................ 3
Crearea unui TreeSet ....................................................................................................................... 4
Adăugarea elementelor ............................................................................................................... 5
Comparatorul .............................................................................................................................. 5
Returnarea elementelor............................................................................................................... 6
Folosirea submulţimilor ............................................................................................................... 7
Sortarea colecţiilor .............................................................................................................................. 8
Interfaţa Comparable....................................................................................................................... 8
Comparator ................................................................................................................................... 11
Dictionary, HashTable şi Properties.................................................................................................... 13
Clasa Dictionary ............................................................................................................................. 13
Clasa HashTable ............................................................................................................................. 14
Creearea HashTable ................................................................................................................... 16
Adăugarea perechilor de chei-valoare ........................................................................................ 16
Ştergerea unei perechi .................................................................................................................. 16
Mărimea unui HashTable ........................................................................................................... 17
Operaţii cu HashTable .................................................................................................................... 17
Returnarea obiectelor din HashTable ......................................................................................... 17
Căutarea elementelor ................................................................................................................ 18
Verificarea egalităţii între HashTable.......................................................................................... 19
Clasa Properties ............................................................................................................................. 20
Setarea şi preluarea elmentelor ................................................................................................. 21
Încărcarea şi salvarea datelor ..................................................................................................... 21
Map................................................................................................................................................... 22
Interfaţa Map.Entry ....................................................................................................................... 23
Clasa HashMap .............................................................................................................................. 23
Creearea unui HashMap............................................................................................................. 24
Adăugarea în HashMap .............................................................................................................. 24
Ştergerea unui element ............................................................................................................. 24
Operaţii cu HashMap ................................................................................................................. 25
Clasa WeakHashMap ..................................................................................................................... 25
Clasa TreeMap ............................................................................................................................... 27
Crearea unui TreeMap ............................................................................................................... 27
Operaţiuni cu Map ..................................................................................................................... 28
1
Clasa Collections ................................................................................................................................ 30
Sortarea......................................................................................................................................... 30
Căutarea ........................................................................................................................................ 31
2
TreeSet
Implementările interfeţei Set, sunt HashSet şi TreeSet. Mai jos este o ierarhie completă a
claselor abstracte şi interfeţelor.
3
Figura 7.2 Arbore roşu-negru
public TreeSet()
public TreeSet(Comparator comp)
4
Pentru a menţine o anumită ordine în această structură, elementele adăugate în arbore trebuie
să fie sortate într-un anume fel. Al doilea constructor permite specificarea obiectului de tip Comparator,
ce va ajuta la sortarea acestei structuri.
Al doilea set de constructori, constituie constructori de copiere:
Dacă o colecţie transmisă ca parametru, este de tip SortedSet, atunci clasa TreeSet va efectua
unele optimizări, la adăugarea elementelor.
Adăugarea elementelor
Metoda add este aceeaşi ca şi pentru HashSet. Diferenţa majoră este că, elementele ce sunt
adăugate trebuie să implementeze interfaţa Comparable sau constructorul TreeSet trebuie să aibă un
parametru de tip Comparator. Dacă nici una din aceste condiţii nu este adevărată, atunci va fi aruncată
excepţia ClassCastException. Fiecare element din această colecţie trebuie să fie comparabile.
Comparatorul
Această metodă nu este apelată frecvent, dar dacă ne interesează ce tip de comparator este
folosit, poate fi utilă. Metoda va returna null, în cazul în care se alege ordinea naturală de
sortare a elementelor.
Mai jos este un exemplu de sortare:
import java.util.*;
public class Comparare
{
public static void main (String args[]) throws Exception
{
String elements[] = {"Radu", "Andrei", "Ion","Vasile",
"Mircea"};
//creearea unui set cu un comparator
Set set = new TreeSet(Collections.reverseOrder());
for (int i=0, n=elements.length; i<n; i++) {
set.add(elements[i]);
}
//afisez elementele setului
System.out.println(set);
5
//afisez comparatorul actualului set
System.out.println(((TreeSet)set).comparator());
}
}
Returnarea elementelor
Se pot folosi metodele first() şi last() pentru a returna primul sau ultimul element din set:
Din moment ce elemente sunt sortate, metoda va returna elementele în ordinea din arbore.
Mai jos este un exemplu pentru a parcurge elementele unui set:
import java.util.*;
public class Iterare
{
public static void main(String args[])
{
String elements[] = {"Radu", "Andrei", "Ion","Vasile",
"Mircea"};
Set set = new TreeSet(Arrays.asList(elements));
Iterator iter = set.iterator();
while (iter.hasNext())
{
System.out.println(iter.next());
}
}
}
6
Acest exemplu are ca rezultat:
Andrei
Ion
Mircea
Radu
Vasile
După cum se poate vedea, folosirea iteratorului este aceeaşi cu utilizările prezentate în cazul
celorlalte colecţii.
Folosirea submulţimilor
Din moment ce un TreeSet, este ordonat, un subarbore va fi tot ordonat. Iată două metode ce
returnează un subarbore format din elementele celui original:
Toate metodele vor returna un obiect, o interfaţă a arborelui original care, reflectă elementele
din colecţia originală. Altfel spus, dacă ștergem un obiect din submulţime, el va dispare şi din arborele
original. Dacă adăugam un element subarborelui, el va fi adăugat în arborele original. Mai jos avem un
exemplu de utilizare a acestei funcţii:
Rezultatul va fi:
7
Dacă dorim ca toElement să fie în subarbore, va trebui, fie să transmitem ca parametru,
următorul element din colecţie, fie să folosim un truc, şi anume să adăugăm ceva la sfârşit:
SortedSet headSet = set.headSet(toElement+"\0");
Dacă nu vrem ca primul element să fie în subarbore, va trebui să apelăm la un truc asemănător:
Sortarea colecţiilor
În acest capitol vom vedea ce mecanism implementează colecţiile, pentru a sorta elementele,
într-un mod cât mai eficient. Există în principiu două modalităţi de a sorta colecţiile: implementând
interfaţa Comparable sau printr-un Comparator personalizat.
Interfaţa Comparable
Clasele definite de Java au deja implementate interfaţa Comparable. Interfaţa Comparable are o
metodă şi anume compareTo(), ce defineşte modul în care se compară şi implicit, se sortează obiectele.
Mai jos avem o serie de clase ce implementează natural aceasta interfaţă:
Există câteva condiţii pe care trebuie să le respectăm atunci când scriem o metodă compareTo:
8
1. Elementele trebuie să fie comparabile mutual. Dacă două elemente sunt comparabile
mutual, înseamnă că au variabile ce pot fi distinctive. Pe de altă parte există obiecte ce nu
pot fi comparabile mutual ca de exemplu un File, un TreeSet etc. În cazul acesta vom primi
excepţia ClassCastException atunci când implementăm interfaţa Comparable.
2. Valoarea de returnat exprimă poziţia relativă într-o ordonare naturală. Metoda compareTo
poate returna trei valori. Va returna un număr negativ, dacă obiectul curent vine înaintea
obiectului cu care se face comparaţia. Va returna un număr negativ, dacă obiectul curent
ajunge în colecţie după elementul cu care se face comparaţia. Va returna zero dacă
obiectele sunt egale.
3. Ordonarea naturală se va face pe baza metodei equals(). Faptul că două elemente sunt
egale sau nu, ar trebui definit prin suprascrierea metodei equals, aşa cum am văzut şi în
cadrul colecţiilor. Această condiţie este mai mult o recomandare.
4. Niciodată nu se apelează metoda direct. Acest mecanism de sortare, este implementat în
cadrul FrameWork-ului şi nu avem acces direct. Singurul lucru pe care trebuie să îl facem
este sa implementăm metoda, şi ea va fi automat apelată la operaţiile aplicate pe colecţii.
import java.util.*;
public class Angajat implements Comparable
{
String departament;
String nume;
public Angajat(String departament, String nume)
{
this.departament = departament;
this.nume = nume;
}
public String getdepartament()
{
return departament;
}
public String getnume()
{
return nume;
}
public String toString()
{
return "[dep=" + departament + ",nume=" + nume + "]";
}
public int compareTo(Object obj)
{
Angajat decomparat = (Angajat)obj;
//daca sunt din departamente diferite
//vom compara departamentele
int deptComp =
departament.compareTo(decomparat.getdepartament());
//daca sunt din acelasi departament
9
//vom compara numele angajatilor
return ((deptComp == 0) ? nume.compareTo(decomparat.getnume())
: deptComp);
}
public boolean equals(Object obj)
{
if (!(obj instanceof Angajat))
{
return false;
}
Angajat a = (Angajat)obj;
return departament.equals(a.getdepartament()) &&
nume.equals(a.getnume());
}
public int hashCode()
{
return 43*departament.hashCode() + nume.hashCode();
}
}
În acest exemplu avem o clasă cu două variabile membru şi anume departament si nume.
Aceste două elemente sunt comparabile, deci le vom putea utiliza în sortare.
Metoda compareTo() va returna implementa logica dorită şi anume de a sorta mai întâi după
departament şi apoi după nume. Pentru aceasta se va calcula un număr deptComp pe baza comparării
departamentului obiectului transmis ca parametru şi al obiectului actual.
Vom folosi aceasta valoare deptComp în rezultatul final astfel:
adică, în cazul in care deptComp este diferit de zero, deci avem două departamente diferite,
returnează rezultatul comparării acestora. Altfel returnează rezultatul comparării obiectelor nume de tip
String. Se observă că logica poate fi extinsă în cazul în care avem mai multe proprietăţi de comparat şi
se poate stabili o prioritate în această operaţie.
Pentru a observa cum sunt folosite aceste mecanisme de comparaţie avem exemplul de mai jos:
class Companie
{
public static void main (String args[])
{
Angajat angajati[] = {
new Angajat("HR", "Irina"),
new Angajat("HR", "Cristina"),
new Angajat("Ingineri", "Daniel"),
new Angajat("Ingineri", "Vlad"),
new Angajat("Ingineri", "Octavian"),
new Angajat("Vanzari", "Emil"),
10
new Angajat("Vanzari", "Eugen"),
new Angajat("RC", "Bogdan "),
new Angajat("RC", "Avram"),
};
Set set = new TreeSet(Arrays.asList(angajati));
//parcurgem multimea sortata deja
//insa ea contine obiecte
for(Object o : set)
{
//asa ca va trebui sa apelam cast
Angajat a = (Angajat)o;
System.out.println(a);
}
}
}
Automat, la constituirea noii colecţii formată din obiecte de tip Angajat, se apelează metoda de sortare
şi anume compareTo. După cum se observă această metodă nu este apelată explicit. Rezultatul este:
[dep=HR,nume=Cristina]
[dep=HR,nume=Irina]
[dep=Ingineri,nume=Daniel]
[dep=Ingineri,nume=Octavian]
[dep=Ingineri,nume=Vlad]
[dep=RC,nume=Avram]
[dep=RC,nume=Bogdan ]
[dep=Vanzari,nume=Emil]
[dep=Vanzari,nume=Eugen]
Această metodă de sortare este una complexă şi acoperă multe cazuri, însă obligă ca obiectele
ce vor fi sortate să implementeze interfaţa Comparable. De asemenea pot fi comparate, doar obiecte de
acelaşi tip, ceea ce pentru majoritatea cazurilor este suficient.
Comparator
Atunci când nu dorim să impunem ordonarea naturală a claselor, sau clasele nu implementează
interfaţa Comparable, avem la dispoziţie metoda compare din interfaţa Comparator:
De asemenea trebuie suprascrisă metoda equals(), în cazul în care vorbim despre obiecte
complexe (cu mai multe variabile membru).
Anterior am folosit deja un Comparator pentru a demonstra lucrul cu TreeSet:
11
Pentru a exemplifica lucrul cu această interfaţă avem următorul scenariu: un manager nou,
inspectează compania descrisă anterior, şi doreşte să afle mai întâi numele angajatului şi apoi
departamentul. Cum această clasă poate implementa doar o singură interfaţă Comparable cu o singură
metodă compareTo va trebui să implementăm o clasă separat de clasa de bază şi anume un Comparator.
Mai jos avem un exemplu pentru o astfel de clasă:
Aceasta este clasa care poate constitui un comparator pentru o nouă listă după cum vom vedea
mai jos. De această dată, metoda compare are două obiecte obj1 şi obj2 pe care le va utiliza pentru
comparaţie. Logica este inversă faţă de exemplul anterior, şi anume se verifică mai întâi numele
angajatului şi apoi se verifică numele departamentului din care acesta face parte. Utilizarea acestui
Comparator este în funcţia main rescrisă:
12
//asa ca va trebui sa apelam cast
Angajat a = (Angajat)o;
System.out.println(a);
}
}
In continuare vom discuta despre colecţii ce conţin, nu doar un element, ci pe fiecare poziţie sunt câte
două elemente, adică o pereche formată din cheie şi valoare.
Aceste clase ajută în lucrul cu perechi de chei şi valori, în foarte multe aplicaţii acesta fiind o necesitate.
Mai jos avem diagrama ce exprimă ierarhia acestor clase:
Un obiect Dictionary funcţionează ca o carte de telefoane. Caut un anumit număr după numele
persoanei. Numele este cheia din dicţionar şi telefonul este valoarea. Presupunem în cadrul acestei
comparaţii, că numele persoanei este unic, deoarece, după cum vom vedea cheia trebuie să fie unică.
Clasa Dictionary
Dictionary este o clasă abstractă ce conţine doar metode abstracte. Este alcătuit din perechi:
<cheie> - valoare
13
Mai jos sunt descrise metodele din această clasă:
Deoarece Dictionary este o clasă abstractă, ea nu va fi folosită direct, ci vor exista implementări ale
acesteia, ca de exemplu HashTable.
Clasa HashTable
14
Avantajul principal al acestei colecţii este că inserarea într-o astfel de structură este făcută în
timp O(1) . Nu contează cât de mare este structura, va lua cam acelaşi timp, pentru inserarea unui
element. Cum se poate acest lucru?
Atunci când folosim perechi chei-valoare, cheile sunt convertite în cod hash, folosind un
algoritm de hash. Apoi acest cod este redus la o structură internă, pentru a fi folosit ca un index. Pentru
două elemente egale, codul va fi acelaşi iar pentru două elemente diferite codul este diferit.
Dacă hashcode-ul este acelaşi pentru elemente diferite, discutăm de coliziune. Dacă sunt multe
coliziuni, timpul de inserţie şi căutare, creşte şi se pierde astfel avantajul principal. Dacă există elemente
multiple cu aceeaşi cheie, va trebui traversată o listă înlănţuită pentru a ajunge la valori. Mai jos avem
un HashTable cu mai multe coliziuni:
15
În cadrul acestei funcţii se calculează suma codurilor ASCII ale fiecărui caracter din nume. La rularea
acestui program avem următoarele coduri:
101
101
114
114
Creearea HashTable
Există patru constructori pentru un HashTable, dintre care primii trei sunt:
public Hashtable()
public Hashtable(int initialCapacity)
public Hashtable(int initialCapacity, float loadFactor)
Ultimii doi constructori permit specificarea unei capacităţi iniţiale. Rata de mărire a capacităţii
implicite, care poate fi specificată în cel de-al doilea constructor este de dublul capacităţii actuale +1. Se
poate modifica prin ajustarea acelui procent.
Al patrulea constructor iniţializează HashTable prin copierea unei colecţii în tabelul actual.
public Hashtable(Map t)
Spre deosebire de clasele studiate anterior, trebuie să oferim o cheie dar şi o valoare:
într-un HashTable, cheile sau valorile nu pot fi null. Încercarea de introducere o cheie null în
HashTable va produce eroarea NullPointerException. Introducerea unei perechi în care cheia se afla deja
în colecţie, va avea ca efect înlocuirea valorii de la acea cheie cu valoarea nouă. Obiectul returnat va fi
vechea valoare, sau dacă obiectul nu era în colecţie, obiectul returnat este null.
Afişarea unui HashTable se face natural pentru ca implementează metoda toString().
16
Dacă cheia key este prezentă în colecţie, atunci va fi eliminata perechea cheie-valoare
specificată prin key şi valoarea este returnată. O altă metodă de a şterge elementele dintr-o colecţie
de acest tip, este clear():
Putem controla mărimea unui HashTable doar după crearea acestuia. Mai jos sunt câteva funcţii
pentru controlul mărimii unui HashTable:
Aceste funcţii returnează mărimea colecţiei şi verifică dacă HashTable este gol sau nu.
Există o funcţie de redimensionare a colecţiei, dar este protected:
Aceasta permite crearea unui nou şir intern mai mare, inserând toate valorile din şirul deja
existent.
Operaţii cu HashTable
Există câteva moduri de a returna date din HashTable. Cel mai simplu este apelând get:
dacă este găsită cheia dată ca parametru, atunci funcţia va returna valoarea corespunzătoare
cheii. Dacă nu este găsită, atunci funcţia va returna null.
Dacă vrem să aflăm cheile din colecţie putem folosi:
diferenţa constă în modul în care sunt returnate cheile. Prima metodă returnează ca un
Enumeration. A doua metodă, returnează colecţia sub forma unui Set. Pe lângă chei, se pot returna şi
valorile din HashTable:
17
Metoda elements() returnează o mulţime de valori sub forma unei Enumeration. Metoda
values() returnează aceleaşi date, dar sub forma unei Collection.
Cea mai complexă metodă de returnare a elementelor dintr-o colecţie HashTable este entrySet()
Aceasta returnează o colecţie de tip Set ce conţine perechile de chei-valori. Acest tip de data
returnat este Map, şi vom discuta despre el în cele ce urmează. Iată două moduri de a accesa cheile unei
astfel de colecţii:
Căutarea elementelor
Clasa Hashtable conţine trei metode, care ne permit căutarea unei chei sau a unei valori în
colecţie. Cea mai simplă rămâne metoda get() discutată mai sus.
Această funcţie verifică existenţa unei chei în colecţie. Alte două metode vor verifica existenţa
unei valori în colecţie:
Ambele realizează acelaşi lucru, însă pentru că HashTable implementează şi interfaţa Map, avem
această duplicitate. Este de recomandat să folosim aceste două funcţii cât mai puţin posibil, deoarece se
parcurge întreaga colecţie, algoritmul având cel un ordin O(n).
18
Verificarea egalităţii între HashTable
Egalitatea este definită de interfaţa Map, iar nu la nivelul HashTable. În consecinţă, HashTable
poate fi egal cu orice alt Map şi nu doar un alt HashTable. Regula de bază este că, două HashMap-uri
sunt egale atunci când au aceleaşi perechi cheie-valoare. Ordinea nu contează.
HashTable este imutabil. Dacă un HashTable este iniţializat cu un set de elemente, atunci pentru
redimensionare va trebui să iniţializăm un nou obiect. Pentru a obţine un Map, ce nu este imutabil avem
următoarea funcţie unmodifiableMap:
Mai jos avem un exemplu de folosire a HashTable pentru a număra cuvintele dintr-un fișier.
import java.io.*;
import java.util.*;
public class Cuvinte
{
static final Integer ONE = new Integer(1);
public static void main (String args[]) throws IOException
{
Hashtable map = new Hashtable();
FileReader fr = new FileReader(args[0]);
BufferedReader br = new BufferedReader(fr);
String line;
while ((line = br.readLine()) != null)
{
processLine(line, map);
}
Enumeration en = map.keys();
while (en.hasMoreElements())
{
String key = (String)en.nextElement();
System.out.println(key + " : " + map.get(key));
}
}
static void processLine(String line, Map map)
{
StringTokenizer st = new StringTokenizer(line);
while (st.hasMoreTokens())
{
//map este cheia
19
//iar st.nextToken() este valoare
addWord(map, st.nextToken());
}
}
static void addWord(Map map, String word)
{
Object obj = map.get(word);
if (obj == null)
{
map.put(word, ONE);
}
else
{
int i = ((Integer)obj).intValue() + 1;
map.put(word, new Integer(i));
}
}
}
Ideea acestui program este ca in map, variabilă de tip HashTable să ţinem o pereche de chei
valori, unde cheia reprezintă cuvântul citit din fişier, iar valoarea reprezintă numărul de apariţii al acelui
cuvânt.
Metoda processLine() împarte linia citită în cuvinte separate prin spaţiu, şi apelează metoda
addWord. Aici se verifică dacă mai avem înregistrat cuvântul în colecţie, dacă nu adăugam perechea
(cuvânt nou, 1) semn că apare o dată. Altfel, dacă a mai fost înregistrat, incrementăm valoarea
corespunzătoare cheii, cu unu.
Pentru un fișier simplu ca mai jos:
rezultatul este:
fff : 2
eeds : 2
eee : 1
aaa : 2
Clasa Properties
Clasa Properties reprezintă un HashTable specializat. În această clasă atât cheile cât şi valorile
sunt String. Atunci când lucrăm şi cu HashTable şi cu Properties, va trebui să ţinem cont de ierarhie: un
Properties poate fi un HashTable, însă un HashTable nu poate funcţiona ca un Properties.
20
Mai jos avem metodele specifice acestei clase:
Nume Descriere
Properties constructorul clasei
getProperty() returnează o valoare pentru o cheie din listă
list() listează proprietăţile li valorile aferente
load () încarcă proprietăţile dintr-un stream
propertyNames() returnează o colecţie de chei din listă
setProperty() setează o pereche cheie-valoare în listă
store() salvează lista de proprietăţi într-un stream
prima metodă va modifica cheia key cu valoarea value. Metoda getProperty returnează
valoarea de la cheia key. A doua metodă de getProperty va returna valoarea de la cheia key, iar
dacă aceasta este null, va returna valoarea defaultValue. Pe lângă aceste metode, există o funcţie ce
returnează o listă cu cheile din Properties:
Aceste două operaţii sunt foarte importante deoarece simplifică exportarea şi importarea
structurilor de date de acest tip:
Iată de exemplu, cum se poate salva în fişier toate datele unei astfel de colecţii:
import java.io.*;
import java.util.*;
public class Proprietati
{
public static void main(String args[]) throws IOException
{
Properties prop = new Properties();
FileOutputStream fo = new FileOutputStream(args[0]);
prop.setProperty("cheia1", "valoarea1");
21
prop.setProperty("cheia2", "valoarea2");
prop.setProperty("cheia3", "valoarea3");
prop.setProperty("cheia1", "valoarea");
prop.save(fo, "Aici vin comentarii");
}
}
Ceea ce se salvează în fişier este:
Metoda load() va încerca să găsească în fişierul sursă, o asemenea structură, de aceea este bine
să respectăm formatul, deşi metoda va putea să încarce diferite formate, însă nu de fiecare dată cu
rezultatul aşteptat.
Map
Interfaţa Map aparţine framework-ului Collections şi vine să înlocuiască clasa Dictionary. În timp
ce Dictionary este o clasă abstractă, era imperativ ca metodele ei să se afle într-o interfaţă. Aceasta este
Map, şi suportă lucrul cu perechi cheie-valoare.
Deşi face parte din FrameWork, interfaţa Map nu extinde Collection, ci este rădăcina unei noi
ierarhii. Sunt patru clase ce implementează această interfaţă: HashMap, WeakHashMap, TreeMap şi
HashTable. Toate elementele din aceste colecţii sunt de tip Map.Entry. Figura de mai jos reprezintă
ierarhia acestor clase şi interfeţe.
22
Interfaţa Map.Entry
Elementele unei Map sunt de tip Map.Entry, o interfaţă conţinută în Map. Fiecare pereche de
cheie-valoare este o instanţă a acestei interfaţă. Nu vom crea propriu-zis instanţe, dar clasele concrete
vor returna obiecte ce deja au implementat această interfaţă.
Mai jos avem funcţiile acestei interfeţe:
Metoda Descriere
equals() verifică egalitatea cu un alt obiect
getKey() returnează cheia din Map.Entry
getValue() returnează valoarea din Map.Entry
hashCode() calculează codul hash pentru obiectul actual
setValue() schimbă valoarea din Map.Entry
Iată cum se pot apela aceste metode pentru a afişa entităţile dintr-o colecţie de tip Properties:
Acelaşi cod poate fi scris cu vechile metode din Properties şi Enumeration astfel:
Clasa HashMap
Clasa HashMap este folosită la implementarea interfeţei Map şi anume o colecţie de perechi-
valori în care elementele nu sunt ordonate. Mai jos sunt metodele implementate:
23
get() Returnează valoarea pentru o anumită cheie
isEmpty() Verifică dacă o colecţie este goală sau nu
keySet() Returnează toate cheile din hash map
put() Plasează o pereche în hash map
putAll() Introduce in hash map o colecţie de perechi cheie-valoare
remove() Scoate din colecţie o pereche
size() Returnează numărul de elemente dintr-un hash map
values() Returnează o colecţie de valori din hash map
public HashMap()
public HashMap(int initialCapacity)
public HashMap(int initialCapacity, float loadFactor)
public HashMap(Map map)
În timp ce primii trei vor iniţializa o colecţie goală, al patrulea va crea hash map-ul pe baza
elementelor din parametru.
Adăugarea în HashMap
Spre deosebire de HashTable, atât cheia cât şi valoarea unui element nou introdus pot fi null.
Pentru a copia o colecţie de perechi de la un Map la altul se foloseşte a doua metodă. Afişarea
elementelor unui HashMap se face natural, deoarece suprascrie metoda toString().
Dacă cheia se află în HashMap, perechea va fi ştearsă, şi valoarea obiectului va fi returnată. Dacă
obiectul nu se află în colecţie atunci valoarea null, va fi returnată. Atenţie, şi null poate fi valoare
legitimă în colecţie. De aceea este de dorit să evităm plasarea null ca valoare în colecţie.
Altă metodă de a şterge toate elementele din colecţie este:
public void clear()
24
Operaţii cu HashMap
Se pot returna obiectele din colecţie, iar pentru aceasta avem o serie de metode:
Dacă cheia nu este găsită în colecţie, se returnează null. Altfel se va returna valoarea de la cheia
specificată ca parametru. Aceeaşi remarcă ar fi de făcut, că elementele într-un HashMap pot fi null. O
alta metodă este :
şi returnează colecţia de chei sub forma unei mulţimi. Pentru a prelua aceste chei ca o colecţie generală
avem metoda:
pentru a returna elementele complete, adică perechea de cheie-valoare, avem la dispoziţie metoda:
Prima metodă este asemănătoare metodei get(), dar în loc de a returna o valoare a obiectului,
vom avea o valoare booleană, true dacă cheia se află în colecţie sau false în caz contrar.
A doua metodă, verifică existenţa unei valori specifice în HashMap. Valorile sunt comparate, şi
această comparare este făcută în timp liniar, de aceea este mai bine să folosim prima metodă.
Clasa WeakHashMap
25
3. Referinţe slabe: acestea sunt mai slabe decât cele soft. Dacă singura referinţă a unui obiect
sunt doar de tip slab, GC va putea şterge obiectul oricând.
4. Referinţe fantomă. Acestea permite o notificare înainte ca GC să apeleze finalizarea pe
obiectul în cauză.
Mai jos avem o exemplificare a acestui mecanism şi cum se lucrează cu referinţe slabe:
import java.util.*;
public class Weak
{
private static Map map;
public static void main (String args[])
{
map = new WeakHashMap();
map.put(new String("Cheie"), "Valoare");
//o metoda ce se executa pe un alt fir
//decat cel curent
Runnable runner = new Runnable()
{
public void run()
{
while (map.containsKey("Cheie"))
{
try
{
//intra in standby 0.5 secunde
Thread.sleep(500);
}
catch (InterruptedException ignored)
{
}
System.out.println("Thread waiting");
//sistemul GC va rula fortat
System.gc();
}
}
};
Thread t = new Thread(runner);
System.out.println(map);
//incep procedura run
t.start();
//firul principal sta si asteapta ca
System.out.println("Main waiting");
try
{
//cel nou sa isi incheie executia
t.join();
//dupa ce thread-ul t nu mai este
System.out.println(map);
26
}
catch (InterruptedException ignored)
{
}
}
}
Exemplul poate fi greu de înţeles acum pentru că nu am prezentat încă firele de execuţie (Thread).
Totuşi concluzia este că după apelarea Garbage Collector-ului, se pierd legăturile slabe, deci se pierd
elementele din map. Acesta este rezultatul rulării programului:
{Cheie=Valoare}
Main waiting
Thread waiting
{}
Clasa TreeMap
Ultima implementare a interfeţei Map este un TreeMap. Un TreeMap este un Map care menţine cheile
într-o ordine prin intermediul unui arbore balansat, de tip roşu-negru. Pe lângă metodele expuse de
Map şi discutate mai sus avem următoarele:
Metoda Descriere
firstKey() Returnează prima cheie din Map
headMap() Returnează sub map-ul de la începutul map-ului original
lastKey() Returnează ultima cheie din colecţie
subMap() Returnează un subarbore oarecare din colecţie
tailMap() Returnează subarborele de la sfârşitul celui original
Acestea provin tocmai din faptul că TreeMap implementează interfaţa SortedMap, lucru ce impune
existenţa acestor metode.
Există patru constructori pentru un TreeMap. Primul este fără argument creează un Map gol, al doilea
este un constructor de copiere:
public TreeMap()
public TreeMap(Map map)
Pe lângă aceştia, mai există doi constructori: unul acceptă un Comparator pentru a defini un mod de
ordonare personalizat şi al doilea acceptă un SortedMap, fiind un constructor de copiere optimizat.
27
Operaţiuni cu Map
Pentru a vizualiza elementele unui arbore avem la dispoziţie metodele de mai jos:
Pentru a specifica subarborele, pentru headMap avem parametrul toKey ce indică subarborele de la
început până la acea cheie, iar pentru tailMap avem parametrul fromKey ce indică subarborele de la
fromKey până la sfârşit. Pentru a include şi elementele de la capăt avem următorul truc:
În cazul celei de-a treia metodă şi anume subMap, se va returna un arbore cuprins între:
Metodele firstKey() şi lastKey() permit accesarea rapidă a primului, respectiv ultimului element din map:
Object firstKey()
Object lastKey()
import java.util.*;
}
public class Studenti
{
public static void main (String[ ] args)
{
Map<String, Double> students = new TreeMap<String, Double>(new
MyComparator());
students.put ("Sebastian",6.0);
students.put ("Bogdan", 7.8);
students.put ("Andrei", 4.67);
28
students.put ("Remus", 8.96);
students.put ("Catalin", 8.55);
System.out.println (students);
System.out.println (students);
}
}
Aceasta are loc tocmai din modul în care este construit acest arbore, si anume pe baza unui comparator,
MyComparator, care va inversa ordinea firească a cheilor. De aceea, va trebui să considerăm care este
rădăcina arborelui şi să alegem subarborele corect:
În cazul acesta va fi format dintr-un singur element, cel marcat cu font italic:
29
{Sebastian=6.0, Remus=8.96, Catalin=8.55, Bogdan=7.8}
Pentru a include şi capetele intervalului va trebui să folosim acel truc descris mai sus.
Clasa Collections
Clasa Collections este o clasa a FrameWork-ului ce constă din metode statice şi obiecte pentru a
înlesni lucrul cu colecţii. Clasa Arrays este clasa corespondentă, ce lucrează cu şiruri.
Toţi membrii acestei clase sunt descrişi mai jos.
Metoda Descrierea
EMPTY_LIST Reprezintă o listă goală imutabilă
EMPTY_MAP Reprezintă o colecţie Map goală imutabilă
EMPTY_SET Reprezintă o mulţime goală imutabilă
binary_search() caută un element în listă folosind tehnica de căutare binară
copy() copiază elementele dintre două liste
enumeration() converteşte o listă la un Enumeration
fill() umple lista cu un element anume
max() caută maximul dintr-o colecţie
min() caută minimul dintr-o colecţie
nCopies() creează o listă imutabilă cu multiple copii ale unui element
reverse() inversează elementele într-o listă
reverseOrder() returnează un comparator ce inversează ordinea elementelor
shuffle() reordonează elementele aleatoriu
singleton() returnează un set imutabil de un element
singletonList() returnează o listă imutabilă de un element
singletonMap() returnează un Map imutabil de un element
sort() reordonează elementele în listă
Pe lângă acestea, mai sunt o serie de metode ce creează colecţii thread-safe, însă nu vom
discuta despre aceasta acum. În continuare vom prezenta câteva din metodele de mai sus, pe cele mai
importante.
Sortarea
30
Iată un exemplu pentru folosirea acestei metode:
import java.util.*;
public class Colectii
{
public static void main(String args[]) throws Exception
{
List list = Arrays.asList(args);
Collections.sort(list);
for (int i=0, n=list.size(); i<n; i++)
{
if (i != 0) System.out.print(", ");
System.out.print(list.get(i));
}
System.out.println();
}
}
Lista obţinută din șirul argumentelor este cel mai probabil nesortată. Prin apelarea acestei metode vom
ordona elementele listei. O altă metodă ar fi de a folosi ordinea inversă şi anume:
Collections.sort(list, Collections.reverseOrder());
Căutarea
Căutarea unui element este cea binară, şi este implementată prin cele două metode:
Căutarea binară constă în împărţirea unui sir sortat în două şi compararea elementului de căutat
cu mijlocul șirului. Dacă nu găsim elementul, căutăm mai departe. Cum? Dacă elementul din mijloc este
mai mic decât numărul de căutat, vom căuta în dreapta mijlocului, dacă nu vom căuta în stânga. Adică,
vom căuta fie in subşirul delimitat de 0 şi mijloc adică stânga, fie în subşirul delimitat de mijloc si
sir.lentgh adică dreapta. Aplicăm recursiv aceasta metodă până când elementul căutat este egal cu
mijlocul sau nu mai avem unde căuta pentru ca subşirul a devenit gol. Iată mai jos un exemplu pentru
această căutare:
31
import java.util.*;
public class Colectii
{
public static void main(String args[]) throws Exception
{
String nume[] = {"George", "Victor", "Laura", "Raluca",
"Costel", "Maria", "Dorel"};
// Convertim la colectii
List list = new ArrayList(Arrays.asList(nume));
// sortam lista
Collections.sort(list);
System.out.println("Sorted list: [length: " + list.size() +
"]");
System.out.println(list);
// caut un element
int index = Collections.binarySearch(list, "Victor");
System.out.println("Found Victor @ " + index);
// Search for element not in list
index = Collections.binarySearch(list, "fals");
System.out.println("Didn't find fals @ " + index);
// Insert
int newIndex = -index -1;
list.add(newIndex, "false");
System.out.println(list);
}
}
Iată rezultatul:
32
Curs 8
Bazele multithreading.......................................................................................................................... 2
Clasa Thread şi interfaţa Runnable....................................................................................................... 4
Crearea unui fir de execuţie ................................................................................................................. 4
Interfaţa Runnable ........................................................................................................................... 4
Îmbunătăţiri aduse exemplului ........................................................................................................ 7
Moştenirea unui Thread .................................................................................................................. 9
Crearea mai multor fire de execuţie................................................................................................... 11
Când se încheie un fir de execuţie? ................................................................................................ 14
Priorităţi în fire de execuţie ............................................................................................................... 16
Sincronizarea ..................................................................................................................................... 18
Folosirea metodelor sincronizate ................................................................................................... 19
Blocul synchronized ....................................................................................................................... 21
Comunicarea între fire de execuţie .................................................................................................... 22
Suspendarea, reluarea şi oprirea firelor de execuţie .......................................................................... 26
Grupuri de fire de execuţie ................................................................................................................ 29
Alte metode................................................................................................................................... 30
Bazele multithreading
Există doua tipuri distincte de multitasking: unul bazat pe procese şi al doilea bazat pe fire de
execuţie sau thread-uri. Este important să facem distincţia dintre cele două. Un proces este, în esenţă,
un program ce se află în execuţie. De aceea multitasking bazat pe procese înseamnă faptul că două sau
mai multe programe rulează în acelaşi timp. De exemplu, compilatorul Java va permite crearea unor noi
programe, în timp ce folosiţi un editor de text, sau navigaţi pe Internet.
În cazul multitasking bazat pe thread-uri, acesta este cea mai mică unitate ce va fi programată
de dispecer. Aceasta înseamnă că un program poate realiza mai multe lucruri simultan. De exemplu
aplicaţia de mail Outlook, sau un client de mail va permite si trimiterea/primirea de mail-uri in timp ce
compuneţi un nou mail, sau verificaţi lista de priorităţi dintr-o zi, sau căutaţi un anumit mail, etc.
Avantajul principal al multithreading este că permite scrierea unor programe foarte eficiente,
deoarece se elimină spaţiile inutile temporale prezente în multe programe, atunci când acesta este idle.
Majoritatea dispozitivelor I/O sunt mai lente decât CPU. De aceea programul va petrece majoritatea
execuţiei aşteptând informaţia de la aceste dispozitive. În acest timp, în cazul folosirii multithreading, se
pot executa instrucţiuni care să realizeze alte sarcini. De exemplu, în timp ce un program trimite un fişier
pe Internet, altă parte a programului poate citi de la tastatura, următoarea bucată ce va fi trimisă.
Un fir de execuţie este o parte dintr-un proces executat secvenţial, o serie de instrucţiuni ce vor
fi executate secvenţial. Folosirea a două sau mai multe thread-uri duce la execuţia în paralel a acestor
serii de instrucţiuni în cadrul unui proces. Mai jos avem reprezentat modul de funcţionare al unui proces
pe un singur thread.
Adevărata utilitate a unui thread intervine în cazul în care mai multe fire de execuţie coexistă în
acelaşi proces.
Pe lângă aceste caracteristici, mai este nevoie de o anumită funcţionalitate, numită sincronizare,
ce permite execuţia firelor într-o manieră controlată.
Faţă de modul în care limbajul C tratează această problemă a multithreading-ului , Java ascunde unele
detalii , tocmai pentru a înlesni şi a face cât mai convenient lucrul cu thread-uri.
Clasa Thread şi interfaţa Runnable
În Java, sistemul multithreading este construit pe clasa Thread şi interfaţa Runnable. Pentru a
crea un nou fir de execuţie, va trebui să extindem clasa Thread sau să implementăm interfaţa Runnable.
Clasa Thread defineşte câteva metode care ajută la tratarea situaţiilor ce intervin în lucrul cu
thread-uri. Iată cele mai folosite:
Medoda Descriere
Toate procesele au cel puţin un fir de execuţie ce se numeşte firul principal sau main thread.
Acesta se va executa înainte celorlalte eventual fire de execuţie ce vor fi create ulterior.
Un fir de execuţie se poate crea prin instanţierea unui obiect de tip Thread. Clasa Thread
încapsulează un obiect ce implementează interfaţa Runnable. Există două moduri de a crea un fir:
1. Implementarea interfeţei Runnable
2. Extinderea (moştenirea) clasei Thread
Interfaţa Runnable
În cadrul acestei metode, veţi defini codul ce constituie noul fir de execuţie. Metoda run() poate
apela alte metode, folosi alte clase, şi declara variabile ca orice metodă. Diferenţa majoră este că run()
reprezintă entry point pentru un nou thread din cadrul procesului. Acest thread îşi încheie execuţia
atunci când se iese din funcţia run (cu return).
Modul de lucru este următorul:
1. Se creează o clasă ce implementează interfaţa Runnable.
2. Se instanţiază un obiect de tip Thread, într-una din metodele din acea clasă ( chiar si in
constructorul clasei ). Constructorul unui Thread va fi:
Thread(Runnable threadOb)
3. Odată creat, noul obiect de tip Thread, nu va porni până când nu apelăm metoda start(). În
principiu apelul metodei start() înseamnă apelul metodei run(). Apelul metodei start este:
void start()
Prima dată, clasa MyThread implementează Runnable. Aceasta înseamnă că un obiect de tipul
MyThread va fi folosit pentru a crea un thread şi deci, va fi parametrul unui constructor Thread.
În interiorul metodei run(), există o buclă while, ce contorizează de la 0 la 9. Metoda
Thread.sleep(500); are ca scop, suspendarea firului de execuţie curent timp de 500 de
milisecunde.
În clasa DemoThread, în metoda main(), se creează un nou obiect, de tip MyThread care va fi
apelat ulterior de newThrd:
MyThread mt = new MyThread("Child #1");
Thread newThrd = new Thread(mt);
// incepe noul fir de execute
newThrd.start();
Obiectul mt este folosit pentru a crea un Thread, iar acest lucru este posibil pentru că MyThread
implementează Runnable . Execuţia unui thread începe cu start. Pe firul principal de execuţie,
instrucţiunile vor fi rulate ca şi când start() ar fi o metodă obişnuită. Firul principal de execuţie va rula o
buclă while în care aşteaptă ca mt.count să ajungă la 10. Acest lucru se va întâmpla, datorită faptului
că în thread-ul secundar count creşte.
Iată rezultatul rulării acestui program:
Modificările majore sunt: mutarea obiectului de tip Thread în cadrul clasei MyThread şi pornirea
acestui fir din constructorul clasei MyThread.
Implementarea interfeţei Runnable este un mod de a crea o clasă ce poate instanţia fire. A doua
ar fi extinderea unei clase numită Thread. Atunci când o clasă extinde Thread, va trebui să suprascrie
metoda run(), care este un entry point pentru noul fir. De asemenea trebuie să apeleze start() pentru
ca noul fir să îşi înceapă execuţia.
Pornind de la exemplul anterior, putem transforma acesta aplicând moştenirea lui Thread:
1. Se schimba declaraţia lui MyThread ca să extindă Thread:
Thread thread;
Variabila thread nu mai are nici o utilitate, din moment ce MyThread este un Thread.
Apelul lui super(name); este în fapt, apelul unui Thread cu parametru un String:
Thread(String name);
Exemplele anterioare conţineau doar un singur fir de execuţie. Totuşi, programul poate crea
oricâte astfel de fire. Următorul program creează trei thread-uri:
}
catch(InterruptedException exc)
{
System.out.println("Main thread interrupted.");
}
} while (mt1.count < 10 || mt2.count < 10 || mt3.count < 10);
System.out.println("Main thread ending.");
}
}
Cele trei fire de execuţie vor rula în paralel cu firul principal, acesta aşteptând ca cele trei sa îşi încheie
execuţia. O diagramă a acestei funcţionalităţi este în figura de mai jos:
Este util să ştim când un fir de execuţie s-a încheiat, pentru a controla logica şi fluxul
programului. În exemplele anterioare, acest lucru a fost posibil datorită unei variabile de control şi
anume count. Aceasta este o soluţie slabă din punct de vedere tehnic. Clasa Thread oferă două moduri
de a determina faptul că un fir de execuţie s-a încheiat. Primul este prin funcţia isAllive():
Această metodă returnează true, dacă firul de execuţie, pentru care a fost apelată, încă rulează,
şi false altfel. Pentru a verifica funcţionalitatea isAlive(), vom modifica versiunea clasei
ThreeChildThreads.
class ThreeChildThreads {
public static void main(String args[])
{
System.out.println("Main thread starting.");
//cream trei fire de execuţie
MyThread mt1 = new MyThread("Child #1");
MyThread mt2 = new MyThread("Child #2");
MyThread mt3 = new MyThread("Child #3");
//care vor fi automat lansate
//in bucla while, thread-ul principal
//aşteaptă ca toate firele sa se încheie
do
{
System.out.print(".");
try
{
Thread.sleep(100);
}
catch(InterruptedException exc)
{
System.out.println("Main thread interrupted.");
}
} while (mt1.thread.isAlive() || mt2.thread.isAlive() ||
mt3.thread.isAlive() );
System.out.println("Main thread ending.");
}
}
După cum se poate observa, acest program este la fel ca şi cel anterior, cu o modificare: condiţia
din while se bazează pe această funcţie, oferind robusteţe aplicaţiei.
Cel de-al doilea mod de a aştepta ca un fir să îşi încheie execuţia este join():
class ThreeChildThreads
{
public static void main(String args[])
{
System.out.println("Main thread starting.");
//cream trei fire de execuţie
MyThread mt1 = new MyThread("Child #1");
MyThread mt2 = new MyThread("Child #2");
MyThread mt3 = new MyThread("Child #3");
//care vor fi automat lansate
//in bucla while, thread-ul principal
//aşteaptă ca toate firele sa se încheie
try
{
mt1.thread.join();
System.out.println("Child 1 joined.");
mt2.thread.join();
System.out.println("Child 2 joined.");
mt3.thread.join();
System.out.println("Child 3 joined.");
}
catch(InterruptedException exc)
{
System.out.println("Main thread interrupted.");
}
System.out.println("Main thread ending.");
}
}
Fiecare fir de execuţie are asociat o anumită prioritate. Aceasta este determinată de cât timp în
CPU este alocat acelui thread. În general priorităţile joase denotă puţin timp, iar cele mari denotă mai
mult timp în CPU. Evident, cât de mult timp va avea acces la CPU, un fir de execuţie, influenţează direct,
fluxul logic al programului.
Este important să înţelegem factorii care influenţează accesul la CPU. De exemplu, dacă un fir cu
prioritate mare aşteaptă o resursă auxiliară, atunci el va fi blocat, şi alt fir cu o prioritate mai mică va
avea acces. Totuşi odată ce thread-ul cu prioritatea mai mare are resursa, va opri execuţia firului cu
prioritate mai mică pentru aşi relua propria execuţie. Alt factor care poate afecta dispecerul de fire de
execuţie, este modul în care sistemul de operare implementează multitasking. De aceea, doar prin
asignarea de priorităţi mai mici, mai mari, nu va influenţa neapărat rapiditatea cu care un fir rulează.
Prioritatea înseamnă un acces probabil mai mic sau mai mare la CPU.
Atunci când un fir de execuţie porneşte, prioritatea sa este egală cu cea a thread-ului părinte.
Valoarea nivelului de prioritate trebuie să fie între MIN_PRIORITY şi MAX_PRIORITY. În mod normal,
aceste valori sunt 1 şi 10. Pentru a returna un fir cu prioritate implicită, avem NORM_PRIORITY ce
înseamnă 5. Aceste valori sunt constante în clasa Thread. Se poate obţine prioritatea actuală a unui fior
de execuţie apelând metoda getPriority():
Exemplul următor demonstrează folosirea a două fire de prioritate diferită. Metoda run()
conţine o bulcă while care contorizează iteraţii. Această bulcă se opreşte când numărul de iteraţii
depăşeşte 1000000 sau variabila stop a fost setată pe true. Iniţial stop este setată pe false, dar primul
thread care termină de iterat, o va seta pe true. Evident aceasta va face ca şi al doilea thread să încheie
bucla. La fiecare parcurgere a buclei, variabila string, currentName, este comparată cu firul aflat în
execuţie. Dacă este diferită, se realizează modificarea acestei variabile. Aceasta permite vizualizarea
accesării CPU a unui anumit thread.
Sincronizarea
Atunci când folosim mai multe fire de execuţie, este necesar uneori, să coordonăm fluxul logic,
acest proces numindu-se sincronizare. Cel mai simplu motiv pentru această sincronizare, este ca două
sau mai multe fire să aibă acces la o resursă comună, dar doar un singur fir să acceseze la un moment
dat acea resursă. De exemplu scrierea într-un fişier, efectuată din două fire de execuţie trebuie
controlată. Aceasta se realizează astfel: un fir este pus în stare de aşteptare până când firul care are
acces la resursă termină acţiunea, urmând ca firul suspendat să îşi reia execuţia.
Sincronizarea în Java este realizată prin intermediul conceptului de monitor, ce controlează
accesul la un obiect. Monitorul funcţionează implementând conceptul de blocare. Când un obiect este
blocat de un fir de execuţie, nici un alt fir de execuţie nu are acces la acel obiect. Când firul de execuţie
eliberează acel lock, obiectul devine disponibil pentru celelalte fire de execuţie.
Toate obiectele în Java au un monitor. Această caracteristică este implementată în limbaj. De
aceea toate obiectele pot fi sincronizate. Acest lucru se va realiza prin cuvântul cheie synchronized şi alte
câteva metode pe care toate obiectele le au. Există două metode prin care se poate sincroniza fluxul
logic al unui program.
Folosirea metodelor sincronizate
Se poate sincroniza accesul la o metodă folosind cuvântul cheie synchronized. Atunci când acea
metodă este apelată , firul de execuţie în care s-a făcut apelul intră în obiectul monitor, care blochează
obiectul. În timpul blocării, nici un alt thread nu poate intra în metodă, sau accesa orice metodă
sincronizată. Atunci când firul de execuţie revine din metodă, se realizează deblocarea.
Următorul program demonstrează folosirea acestei metode de sincronizare:
Child #2 starting.
Running total for Child #2 is 1
Child #1 starting.
Running total for Child #1 is 1
Running total for Child #2 is 3
Running total for Child #1 is 5
Running total for Child #2 is 8
Running total for Child #1 is 11
Running total for Child #2 is 15
Running total for Child #1 is 19
Running total for Child #1 is 24
Running total for Child #2 is 29
Sum for Child #1 is 29
Child #1 terminating.
Sum for Child #2 is 29
Child #2 terminating.
Evident, rezultatele pot varia în funcţie de maşină, sistem de operare, etc.
Blocul synchronized
Deşi crearea metodelor sincronizate, în cadrul claselor, este un mod eficient şi uşor de
sincronizare, nu funcţionează pentru orice caz. De exemplu, să presupunem că dorim accesul sincronizat
la o metodă care nu este declarată ca atare. Acest lucru se poate întâmpla în cazul în care clasa a fost
creată de altcineva şi nu avem acces la codul sursă. Pentru a accesa obiecte care nu au fost anterior
sincronizate, avem la dispoziţie blocuri sincronizate:
synchronized(object)
{
//instrucţiunile ce trebuie sincronizate
}
Aici object este referinţa către obiectul de sincronizat. Un bloc sincronizat asigură faptul că
apelul unei metode membră a obiectului object, va avea loc într-un monitor al firului de execuţie
apelant. De exemplu putem reedita programul mai sus considerând că metoda a fost declarată fără
cuvânt cheie synchronized:
În acest fel se asigură un calcul predictiv al sumei format din elementele lui sa.
Să considerăm următoarea situaţie. Un fir numit T execută o metodă sincronizată şi are nevoie
de o resursă R, care este temporar indisponibilă. Ce ar trebui să facă T? Dacă T intră într-o buclă de
aşteptare a lui R, blochează obiectul prevenind alte fire să o acceseze. Această abordare este
consumatoare de timp şi poate duce la deadlock-uri adică blocare permanentă. O altă soluţie ar fi ca T
să elibereze temporar controlul obiectului, permiţând altui fir să ruleze. Când R devine disponibilă, T
poate fi notificat şi îşi poate relua execuţia. Acest tip de abordare, presupune existenţa unei comunicări
între fire diferite. În Java aceasta se face prin metodele wait(), notify() şi notifyAll().
Aceste metode aparţin tuturor obiectelor, deoarece sunt implementate în clasa Object. Aceste
metode pot fi apelate dintr-o metodă sincronizată. Atunci când un thread este blocat temporar,
apelează wait(). Aceasta va face ca firul să intre în sleep, şi monitorul pentru acel obiect să fie eliberat,
permiţând altui fir să folosească obiectul. Mai târziu, firul ce era oprit, este trezit, atunci când un alt fir
care va intra în acelaşi monitor, apelează metoda notify(), sau notifyAll(). Un apel la metoda notify() va
reporni firul oprit.
Iată diversele forme ale metodei wait():
Ultimele două forme ale metodei semnifică aşteptarea până când apare o notificare sau până
când o perioada de timp, dată de parametrii, trece. Mai jos avem formele funcţiilor de notificare:
class TicTac
{
synchronized void tic(boolean running)
{
if(!running)
{ //opresc metoda
notify(); //anunţ celălalt thread
return;
}
System.out.print("Tic ");
notify(); //il las pe tac sa ruleze
try
{
wait(); // aştept pe tac
}
catch(InterruptedException exc)
{
System.out.println("Thread interrupted.");
}
}
synchronized void tac(boolean running)
{
if(!running)
{ // opresc pe tac
notify(); // anunţ pe tic
return; // adică celălalt fir
}
System.out.println("Tac");
notify(); // il las pe tic sa ruleze
try
{
wait(); // aştept ca tic sa ruleze
}
catch(InterruptedException exc)
{
System.out.println("Thread interrupted.");
}
}
}
class MyThread implements Runnable
{
Thread thread;
TicTac ttOb;
// un nou fir de execuţie
MyThread(String name, TicTac tt)
{
thread = new Thread(this, name);
ttOb = tt;
thread.start(); // pornesc firul
}
// încep execuţia firului
public void run()
{
if(thread.getName().compareTo("Tic") == 0)
{
// sunt in thread-ul tic
for(int i=0; i<5; i++) ttOb.tic(true);
//la sfarsit opresc tic
ttOb.tic(false);
}
else
{
// sunt in thread-ul tac
for(int i=0; i<5; i++) ttOb.tac(true);
//la sfarsit opresc tac
ttOb.tac(false);
}
}
}
class ThreadsDemo
{
public static void main(String args[])
{
TicTac tt = new TicTac();
MyThread mt1 = new MyThread("Tic", tt);
MyThread mt2 = new MyThread("Tac", tt);
try
{
mt1.thread.join();
mt2.thread.join();
}
catch(InterruptedException exc)
{
System.out.println("Main thread interrupted.");
}
}
}
În acest exemplu există trei clase, TicTac, MyThread, ThreadsDemo. Prima clasa, conţine
două metode sincronizate:
synchronized void tic(boolean running)
synchronized void tac(boolean running)
Acestea, au următoarea logică:
if(!running)
{ //opresc metoda
notify(); //anunţ celălalt thread
return;
}
System.out.print("Tic ");
notify(); //il las pe tac sa ruleze
wait(); // aştept pe tac
Dacă parametrul transmis nu este încă false (mai am drept să rulez metoda) atunci afișez Tic şi
dau dreptul lui Tac, urmând să aştept până când thread-ul ce rulează Tac, să mă înştiinţeze că a rulat în
monitor. Dacă parametrul running este false, atunci anunţ celălalt fir de execuţie şi părăsesc metoda.
Acelaşi lucru are loc şi pentru metoda tac, evident referitor la thread-ul Tic.
Clasa MyThread, ce implementează interfaţa Runnable, reprezintă cele două fire de execuţie
suport pentru metodele tic() şi tac(). În firul de execuţie pentru Tic se apelează de cinci ori metoda tic()
cu parametru true, şi ultima dată cu parametru false, pentru a permite deblocarea firului care executa
tac(). Acelaşi lucru este valabil viceversa pentru Tac.
În clasa ThreadsDemo se creează cele două fire de execuţie, se lansează şi se aşteaptă
terminarea lor. Iată ce se întâmplă la rularea programului:
Tic Tac
Tic Tac
Tic Tac
Tic Tac
Tic Tac
Dacă modificăm însă, metodele tac() şi tic() eliminând metodele de comunicare şi anume
wait() şi notify()rezultatul poate fi:
Uneori este util să suspendăm execuţia unui thread. De exemplu, un fir de execuţie poate fi
folosit pentru afişarea orei. Dacă utilizatorul nu doreşte vizualizarea orei, acest fir va fi suspendat. El va
putea fi repornit (resume) ulterior. Metodele ce realizează acest lucru sunt:
Totuşi folosirea acestora este îngrădită, de unele probleme cauzate de suspend() în trecut. De
exemplu, dacă un thread obţine un lock pe o zonă critică, şi el este suspendat, acele lock-uri sunt eterne.
În acest timp, alte fire de execuţie aşteaptă deblocarea lor. Metoda resume() este de asemenea
învechită. Nu cauzează probleme, dar nu poate fi folosită fără metoda suspend(). De asemenea metoda
stop() a fost considerată învechită.
Următorul exemplu reprezintă un mod în care se pot folosi aceste funcţii:
Clasa MyThread, defineşte două variabile booleane, suspended şi stopped. Metoda run()
conţine blocul sincronizat ce verifică variabila suspended. Dacă este true atunci metoda wait() este
apelată şi suspendă execuţia firului. Metoda care stabileşte valoarea variabilei la true, este
mysuspend(). Pentru a relua firul, avem myresume(), care setează variabila suspended pe true şi
apelează notify(), pentru a relua execuţia firului. Ultima metodă este mystop() şi conţine paşii care duc la
oprirea firului de execuţie. În clasa SuspendDemo, metodele descrise sunt apelate în alternanţă pentru
a opri/relua execuţia firului de câteva ori. Rezultatul rulării acestui program este:
My Thread starting.
1 2 3 4 5 6 7 8 9 10
11 12 13 14 15 16 17 18 19 20
21 22 23 24 25 26 27 28 29 30
31 32 33 34 35 36 37 38 39 40
Suspending thread.
Resuming thread.
41 42 43 44 45 46 47 48 49 50
51 52 53 54 55 56 57 58 59 60
61 62 63 64 65 66 67 68 69 70
71 72 73 74 75 76 77 78 79 80
Suspending thread.
Resuming thread.
81 82 83 84 85 86 87 88 89 90
91 92 93 94 95 96 97 98 99 100
101 102 103 104 105 106 107 108 109 110
111 112 113 114 115 116 117 118 119 120
Stopping thread.
My Thread exiting.
Main thread exiting.
Fiecare fir de execuţie este membru al unui grup de fire. Un grup de thread-uri oferă un
mecanism pentru colectarea multiplelor fire într-un singur obiect pentru a manipula aceste fire. De
exemplu, se pot suspenda toate firele printr-o singură instrucţiune.
Dacă vom crea un fir fără a specifica grupul din care face parte, automat va fi pus în acelaşi grup
cu cel al firului părinte. Acest grup se mai numeşte şi current thread group. Atunci când o aplicaţie este
pornită, Java creează un TreadGroup numit main. Pentru a crea un grup separat de acesta, explicit, avem
la îndemână următoarele construcţii:
theGroup = myThread.getThreadGroup();
Alte metode
Iată, spre exemplu, cum se poate stabili prioritatea maximă, atât pe fir cât şi pe grup:
class MaxPriorityDemo
{
public static void main(String[] args)
{
1
Introducere in applet-uri
Applet-ul este poate cea mai importanta dintre aplicaţiile Java, si acest subiect nu poate fi
cuprins intr-un capitol. Aşa că vom prezenta doar o privire de ansamblu asupra programării applet-uri.
Applet-urile folosesc o arhitectură unică, ce necesită folosirea unor tehnici de programare
speciale. Una din aceste tehnici este tratarea evenimentelor. Evenimentele sunt modul in care un applet
primeşte o intrare de la orice context exterior. Atât conceptul de applet cât şi cel de eveniment este
foarte mare. În cele ce urmează vor fi tratate doar fundamentele acestor două concepte.
Applet-urile diferă de programele precedente deoarece ele sunt mici programe a căror scop este
să ruleze pe Web. Iată un mic exemplu de acest tip de program:
import java.awt.*;
import java.applet.*;
public class OneApplet extends Applet
{
public void init()
{}
Acest program începe cu două declaraţii de import. Prima importă clasele din pachetul AWT
adică Abstract Window Toolkit. Applet-urile interacţionează cu utilizatorul folosind aceste clase AWT, iar
nu prim consolă. AWT conţine o serie de clase ce permit lucrul cu ferestre şi o desenarea unei interfeţe
grafice.
Următoarea incluziune import se referă la pachetul applet. Acest pachet conţine clasa Applet,
iar orice applet creat trebuie să moştenească această clasă.
Clasa ce moşteneşte clasa Applet, este OneApplet. Această clasă ar trebui să fie declarată public,
deoarece va fi accesată din afara ei.
Clasa conţine o metodă paint(). Această metodă este definită de clasa Component din AWT, ce
este o clasă părinte pentru Applet şi trebuie să fie suprascrisă de OneApplet. Metoda paint() este apelată
ori de câte ori va avea loc reafişarea. Această reafişare poate surveni din mai multe motive. De exemplu,
fereastra în care applet-ul rulează, este suprascrisa de alta fereastră, sau este mişcată. Sau poate
fereastra este minimizată, şi apoi restaurată.
De asemenea paint() este apelată la pornirea applet-ului. Metoda paint() are un parametru de
tip Graphics. Acest parametru conţine contextul grafic în care rulează applet-ul.
În interiorul paint(), se află o metodă drawString() , care este un membru al clasei Graphics.
Această metodă afişează un String la coordonatele X,Y. Forma ei generală este:
Mesajul message va fi afişat pe fereastră la coordonatele x,y. Colţul stânga sus are
coordonatele 0,0. De asemenea, de reţinut că, applet-ul nu conţine o metodă main().
2
Spre deosebire de majoritatea programelor, execuţia unui applet nu începe cu main. Pentru a
putea porni un applet, acesta trebuie inclus într-un fișier HTML, pentru a rula în browser, sau se poate
folosi un program auxiliar appletviewer.
Pentru a include un applet într-un browser, trebuie să modificăm un fișier HTML, adăugând un
tag APPLET, astfel:
<html>
<applet code="OneApplet" width=200 height=60>
</applet>
</html>
javac OneApplet.java
Pe lângă acest nume, mai există width=200 height=60 ce înseamnă dimensiunea suprafeţei
de afişare alocată acestui applet. Pentru a executa programul OneApplet, se poate apela următoarea
comandă:
C:\>appletviewer OneApplet.html
Pentru a eficientiza rularea acestui applet, există o cale şi mai simplă: se include codul HTML în
codul sursă, astfel:
import java.awt.*;
import java.applet.*;
/*
<applet code="OneApplet" width=200 height=60>
</applet>
*/
public class OneApplet extends Applet
{
public void init()
{}
C:\>appletviewer OneApplet.java
3
Astfel, în urma compilării şi rulării,
După cum reiese şi din figura de mai jos, este un fel de Panel, el moştenind această clasă din
pachetul java.awt. ca orice Panel, un Applet, poate conţine componente de UI - user interface şi poate
folosi toate modelele de desenare şi tratare a evenimentelor, specifice unei clase Component.
4
Pe lângă clasele AWT, există o librărie despre care vom vorbi în următorul capitol pe larg,
denumită Swing. Aceasta este o alternativă la componentele AWT. De exemplu, pe lângă butoane,
etichete, etc, Swing oferă paneluri pe care se poate efectua scroll, arbori, şi tabele. Practic este o
extensie a acestor componente AWT.
Organizarea applet-urilor
Un applet este un proces bazat pe ferestre. În astfel de arhitecturi, există câteva concepte, ce nu
sunt prezente în programele ce rulează în consolă.
În primul rând, applet-urile sunt bazate pe evenimente şi seamănă cu un set de funcţii de
întrerupere. Un applet va aştepta până când un eveniment apare. Sistemul va notifica acest applet,
apelând o metodă de tratare a evenimentului apărut. Odată ce aceasta are loc, applet-ul va efectua
instrucţiunile din metodă, şi va preda înapoi controlul sistemului. În situaţiile în care un applet trebuie să
efectueze mai multe sarcini, până să returneze controlul sistemului, pentru a evita „îngheţarea”
ferestrei, putem lucra, sau efectua acele sarcini pe un alt fir de execuţie.
În al doilea rând, utilizatorul este acela care interacţionează cu applet-ul. În programe ce rulează
în consolă, programul aşteaptă intrări, şi îl notifică pe utilizator. Aici, utilizatorul va interacţiona cu
applet-ul, după bunul lui plac. Aceste interacţiuni sunt transmise applet-ului sub forma unor
evenimente. De exemplu, când utilizatorul apasă un buton al mouse-ului, se generează un eveniment de
mouse. Dacă utilizatorul apasă o tastă când focus-ul este pe fereastra applet-ului, se va genera un
eveniment de tastatură. Atunci când utilizatorul interacţionează cu un buton, sau un check-box, sau
orice alt control, se va genera un eveniment corespunzător.
Deşi OneApplet, prezentat mai sus, este un applet real, el nu este complet, pentru că nu conţine
elementele necesare majorităţii applet-urilor. Metodele întâlnite în majoritatea acestor applet-uri sunt:
init(), start(), stop() şi distroy(). Acestea sunt metodele definite de clasa Applet. A cincea metodă este
paint(), şi este moştenită din clasa Component.
Mai jos avem un exemplu de implementare a unui Applet:
5
public void init()
{
// iniţializări
}
public void start()
{
// sart sau reiau execuţia
}
// apelată la oprirea appletului.
public void stop()
{
// suspendă execuţia
}
//apelată la terminarea execuţiei
//este ultima metodă apelată
public void destroy()
{
//activităţi de oprire
}
// la redesenarea ferestrei
public void paint(Graphics g)
{
//reafişarea conţinutului ferestrei
}
}
Aceste metode au in dreptul lor un comentariu cu referire la scopul lor ( ce ar trebui sa facă
atunci când sunt apelate ) şi in ce context sunt apelate.
6
Redesenarea
Ca regulă generală, un applet va scrie în fereastra doar când metoda paint() este apelată de
sistem. Problema care se pune este următoarea: cum poate un applet să cauzeze aceste update-uri? De
exemplu, dacă un applet afişează un banner în mişcare, ce mecanism permite acelui applet să updateze
fereastra de fiecare dată? Nu se poate crea o buclă infinită în metoda paint(), pentru că ar bloca astfel
sistemul. Metoda care permite redesenarea unor informaţii, sau actualizarea lor, este repaint().
Metoda repaint() este definită în clasa Component din AWT. Aceasta face ca sistemul să execute
un apel al funcţiei update(), metodă ce va apela paint(). Dacă o parte a applet-ului trebuie să afişeze un
String, poate stoca acest mesaj într-o variabilă şi apela repaint(). Metoda paint() va prelua acea variabilă
şi o va afişa folosind drawString(). Există două metode de redesenare:
void repaint()
void repaint(int left, int top, int width, int height)
În a doua metodă, coordonatele din colţul stânga sus al regiunii ce va fi redesenată, sunt date de
primii doi parametrii. Dimensiunea regiunii este dată de ultimii doi parametrii. Aceste dimensiuni sunt
specificare în pixeli.
Metoda update() este definită în Clasa Component, şi este apelată când applet-ul a cerut ca o
porţiune din fereastră să fie redesenată. Există metode de a suprascrie această funcţie, astfel încât
funcţionalitatea să fie mult mai mult decât un simplu apel ulterior al metodei paint().
import java.awt.*;
import java.applet.*;
/*
<applet code="Banner" width=300 height=50>
</applet>
*/
//clasa implementeaza Runnable
//pentru ca vom lucra cu thread-uri
public class Banner extends Applet implements Runnable
{
String msg = " Java permite afişarea unui banner.";
Thread t;
boolean stopFlag;
// Iniţializarea threadului cu null
public void init()
{
t = null;
}
// start al appletului
//folosit pentru a porni threadul t
public void start()
{
t = new Thread(this);
7
stopFlag = false;
t.start();
}
//când threadul rulează
public void run()
{
char ch;
//Se afişează bannerul
for( ; ; )
{
try
{
repaint();
Thread.sleep(90);
ch = msg.charAt(0);
msg = msg.substring(1, msg.length());
msg += ch;
if(stopFlag)
break;
}
catch(InterruptedException exc)
{}
}
}
// pauza pe banner
public void stop()
{
stopFlag = true;
t = null;
}
// afişarea banner-ului
public void paint(Graphics g)
{
g.drawString(msg, 50, 30);
}
}
Clasa Banner extinde clasa Applet, dar implementează şi Runnable. Aceasta pentru că, applet-ul
va crea un al doilea thread ce va fi folosit la actualizarea mesajului. Mesajul care va fi afişat, este cel din
variabila String msg. Firul care se ocupă cu aceste modificări este dat de variabila t.
Variabila booleană stopFlag, este folosită pentru a opri applet-ul. În interiorul init() variabila este
iniţializată cu null, urmând ca la pornirea applet-ului şi anume la apelul funcţiei start(), această variabilă
sa fie false iar thread-ul să pornească.
Metoda care este cea mai interesantă este run(). Aici se va modifica variabila msg, astfel ca
primul caracter din String să fie pus pe ultima poziţie. Aceste acţiuni au loc într-o buclă infinită, ceea ce
ar trebui sa „îngheţe” fereastra. Cum firul care se ocupa cu apelul metodei paint() este cel principal,
funcţionarea nu va avea acest impediment. Când se încheie execuţia applet-ului, respectiv se apelează
metoda stop(), acelaşi lucru se întâmplă şi cu thread-ul t, datorită variabilei stopFlag.
8
Cum se executa un applet
Un applet rulează în general într-un browser. Plugin-ul Java din browser controlează lansarea şi
execuţia applet-ului. Browser-ul are de obicei şi un interpretator JavaScript, care poate rula cod
JavaScript în pagină.
Plugin-ul Java
Acest software, creează un thread pentru fiecare applet. Va lansa un applet într-o instanţă de
Java Runtime Enviroment (JRE). În mod normal, toate applet-urile rulează în aceeaşi instanţă de JRE.
Plugin-ul de Java, va porni o instanţă de JRE în următoarele cazuri:
1. Când un applet cere să fie executat în versiunea corespunzătoare deJRE
2. Când un applet specifică proprii parametrii pentru JRE, de exemplu, mărimea heap-ului. Un
nou applet foloseşte JRE existent, dacă specificaţiile sunt o submulţime a specificaţiilor JRE
existent.
Un applet va rula într-o instanţă de JRE existentă, dacă toate condiiţiile sunt îndeplinite:
1. Versiunea de JRE cerută de applet se potriveşte cu cea existentă.
2. Parametrii de start ai JRE, satisfac cerinţele applet-ului.
9
Applet-urile pot invoca funcţii prezente în pagina web. Funcţiile JavaScript pot de asemenea,
invoca metode ale unui applet, încorporat în aceeaşi pagină web. Plugin-ul Java şi interpretatorul
JavaScript vor orchestra aceste apeluri din cod Java în cod JavaScript şi viceversa.
Plugin-ul Java este multi-threaded, în timp ce interpretatorul JavaScript rulează pe un singur fir.
Pentru a evita probleme legate de fire de execuţie, mai ales când mai multe applet-uri rulează simultan,
apelurile între cele două limbaje trebuie să fie scurte, şi să nu conţină, pe cât posibil bucle.
Pe lângă afişarea informaţiilor într-o fereastra, un applet poate afişa mesaje şi în bara de status
a ferestrei în care applet-ul rulează. Pentru a realiza acest lucru, se apelează metoda showStatus(), care
este definită de clasa Applet. Apelul aceste metode este:
import java.awt.*;
import java.applet.*;
/*
<applet code="StatusWindow" width=300 height=50>
</applet>
*/
public class StatusWindow extends Applet
{
//afişarea unui mesaj in status
public void paint(Graphics g)
{
g.drawString("Acesta este un mesaj afişat in fereastra.", 10,
20);
showStatus("Acesta este un mesaj afişat în status bar");
}
}
10
Figura 5. Mesaj in status bar
Pentru a transmite parametrii unui applet, se foloseşte atributul PARAM al tag-ului APPLET.
Acest atribut va specifica numele parametrului şi valoarea sa. Pentru a afla valoarea unui parametru, se
foloseşte getParameter() definită în clasa Applet. Apelul acestei metode este:
Aici, paramName este numele parametrului. Funcţia va returna valoarea parametrului specificat,
sub forma unui obiect String. Pentru valori booleane, va trebui să convertim reprezentările String într-un
format intern. Dacă un parametru nu este găsit, valoarea null este returnată.
Iată un exemplu de folosire a parametrilor:
11
if(author == null) author = "not found";
purpose = getParameter("purpose");
if(purpose == null) purpose = "not found";
temp = getParameter("version");
try
{
if(temp != null)
ver = Integer.parseInt(temp);
else
ver = 0;
}
catch(NumberFormatException exc)
{
ver = -1; // error code
}
}
public void paint(Graphics g)
{
g.drawString("Purpose: " + purpose, 10, 20);
g.drawString("By: " + author, 10, 40);
g.drawString("Version: " + ver, 10, 60);
}
}
Locul în care sunt declaraţi parametrii ce vor fi transmiși applet-ului, la pornirea acestuia este în
cadrul tag-ului <applet>:
Odată ce parametrii au fost preluaţi la startul applet-ului, valorile lor vor fi folosite la afișarea pe
ecran, in metoda paint(). Iată rezultatul:
12
Clasa Applet
Toate applet-urile moştenesc clasa Applet. La rândul ei, clasa Applet moștenește următoarele
clase definite în AWT: Component, Container şi Panel. În acest fel, un applet poate avea acces la
funcţionalitatea completă oferită de AWT.
Pe lângă aceste metode, Applet mai conţine câteva funcţii ce permit controlul total al applet-ului
funcţii ce sunt descrise mai jos:
Metoda Descriere
void destroy() Apelată de browser înainte de terminarea applet-ului.
Applet-ul va suprascrie această metodă pentru a curăţa alte obiecte.
AccessibleContext getAccessibleContext( )
Metodă ce returnează contextul de accesibilitate al obiectului ce invocă
această metodă.
AppletContext getAppletContext( )
Metodă ce returnează contextul asociat unui applet.
String getAppletInfo() Returnează un string ce descrie applet-ul
AudioClip getAudioClip(URL url ) Returnează un obiect de tip AudioClip ce încapsulează clipul audio găsit
la locaţia specificată prin url.
AudioClip getAudioClip(URL url, Returnează un obiect de tip AudioClip ce încapsulează clipul audio găsit
String clipName) la locaţia specificată prin url şi având numele clipName.
URL getCodeBase( ) Returnează URL asociat cu applet-ul.
URL getDocumentBase( ) Returnează URL al documentului HTML ce invocă applet-ul.
Image getImage(URL url ) Returnează un obiect imagine ce încapsulează imaginea găsită, la o
locaţie specificată prin url.
Image getImage(URL url, Returnează un obiect imagine ce încapsulează imaginea găsită, la o
String imageName locaţie specificată prin url şi având ca nume imageName.
Locale getLocale() Returnează un obiect Locale ce este folosit în clase şi metode ce
folosesc setările specifice unei regiuni.
String getParameter(String paramName)
Această metodă returnează un parametru asociat cu paramName sau
null dacă nu este găsit acel parametru.
String[ ] [ ] getParameterInfo( ) Returnează o tabelă String ce descrie parametrii recunoscuţi de către
applet. Fiecare element din tabelă trebuie să fie format din trei string-
uri ce conţin numele parametrului, descrierea tipului de dată, şi
explicaţii cu privire la scopul lui.
void init() această metodă este apelată la începutul execuţiei unui applet şi este
prima metodă apelată.
boolean isActive() Această metodă returnează true dacă applet-ul a pornit, sau false dacă a
fost oprit.
static final AudioClip Această metodă returnează un AudioClip ce încapsulează clipul audio
newAudioClip(URL url ) găsit la url-ul specificat. Această metodă este similară cu getAudioClip,
însă este statică.
13
void play(URL url) Dacă un audio clip este găsit la locaţia specificată de url, atunci clipul
este rulat.
void play(URL, String clipName) Dacă un audio clip este găsit la locaţia specificată de url, cu numele
specificat de clipName, atunci clipul este rulat.
void resize(Dimension dim) Redimensionează applet-ul conform dimensiunilor specificate de dim.
Dimension este o clasă ce provine din java.awt. Conţine două câmpuri:
width şi height.
void resize(int width, int height) Redimensionează applet-ul conform dimensiunilor width şi height.
final void setStub(AppletStub stubObj)
Creează obiectul stubObj, ce reprezintă un locţiitor de cod, pentru
applet. Această metodă este folosită de sistemul run-time şi nu este
apelată de applet-ul în sine. Un locţiitor sau stub este o bucată de cod,
ce asigură o legătură dintre applet şi browser.
void showStatus(String str) Afişează str în fereastra status a browser-ului sau a ferestrei de afişare a
applet-ului.
void start() apelat de browser atunci când un applet trebuie să fi pornit. Este apelat
automat după metoda init().
void stop() apelat de browser atunci când un applet trebuie să îşi suspende
activitatea. Odată oprit, un applet poate fi repornit prin start().
Clasa Graphics are o mulţime de funcţii pentru desenare. Pentru fiecare figură geometrică, dreptunghi,
arc, elipsa, poligon, există o metodă specifică ce desenează fie conturul, fie interiorul acelei figuri.
Iată mai jos un exemplu de desenare a unui dreptunghi umplut cu o culoare:
14
g.setColor(Color.red);
g.fillRect(rectX, rectY, rectWidth, rectHeight);
}
public Dimension getPreferredSize( )
{
return new Dimension(100, 200);
}
}
După cum se vede desenarea are loc în metoda paint, dar redimensionarea în init. Dacă am
plasa redimensionarea în paint() ar fi practic imposibil redimensionarea ferestrei. Se pune problema,
unde trebuie să desenăm, în paint() sau altundeva?
Când să desenăm?
Există mai multe opţiuni, pe care le putem alege când efectuăm desenarea unui obiect. Cea mai
corectă metodă este însă de a desena în metoda paint(), deoarece nu se poate desena într-o fereastră
până când aceasta nu este creată şi plasată pe ecran. În plus, se poate ca metodele să dureze mult, ceea
ce blochează afişarea unor obiecte pe fereastra. Este recomandat ca în celelalte metode să se modifice
parametrii obiectelor ce vor fi afişate, şi să se recheme paint() prin intermediul metodei de actualizare.
Utilizarea fonturilor
Pentru lucrul cu fonturi, în Java există două clase principale, Font şi FontMetrics. Prima serveşte
la setarea şi lucrului concret cu anumite fonturi, redimensionarea lor, etc. A doua serveşte la preluarea
măsurilor unui font şi a altor dimensiuni ale fontului.
Mai jos se află un exemplu pentru utilizarea acestor clase, însă este doar începutul, sau o primă
privire aruncată asupra acestor aspecte, pentru că există foarte multe alte funcţionalităţi care nu sunt
abordate aici.
// demonstrarea modului corect de desenare
import java.awt.*;
import java.applet.*;
public class DrawStringDemo extends Applet
{
String message = "Hello Java";
//aici vom seta fontul folosit in applet
public void init()
{
Font f = new Font("Calibri" , Font.BOLD|Font.ITALIC, 24);
setFont(f);
}
//aici va avea loc desenarea
public void paint(Graphics g)
{
//preluarea fontului curent si a dimensiunilor
FontMetrics fm = getFontMetrics(getFont( ));
15
//folosirea dimensiunilor fontului, si a mesajului
//pentru a afla poziţia la care începem sa desenam
int textX = (getSize( ).width - fm.stringWidth(message))/2;
if (textX<0) // If dacă string-ul este prea mare
textX = 0;
//acelaşi lucru si pentru înălţime
int textY = (getSize().height - fm.getLeading( ))/2;
if (textY<0)
textY = 0;
//aici se desenează textul pe fereastra
g.drawString(message, textX, text);
}
}
In cele ce urmează este exemplificat modul in care putem adăuga, efecte, cum ar fi umbra unui
text. De asemenea, scopul este de a exemplifica modul de lucru cu parametrii, deoarece datele legate de
font sunt preluate din parametrii transmişi applet-ului:
import java.applet.*;
import java.awt.*;
/**
<applet code="DropShadow" width=300 height=80>
<param name=label value="Eticheta"/>
<param name=fontname value="Arial"/>
<param name=fontsize value=43 />
</applet>
*/
class Main extends Frame
{
public static void main(String args[])
{
//aici cream un nou applet
DropShadow dp = new DropShadow();
//pe care îl pornim
dp.start();
}
}
public class DropShadow extends Applet
{
/*eticheta ce va apare in fereastra */
protected String theLabel = null;
/*Lăţimea si înălţimea */
protected int width, height;
/*Numele fontului*/
protected String fontName;
/*variabila ce modifică fontul */
protected Font theFont;
/*mărimea fontului */
protected int fontSize = 18;
16
/*deplasamentul umbrei*/
protected int theOffset = 3;
/*daca am primit toţi parametrii */
protected boolean inittedOK = false;
/*pentru a verifica dacă iniţializarea e ok*/
public void init( )
{
theLabel = getParameter("label");
if (theLabel == null)
throw new IllegalArgumentException("LABEL is REQUIRED");
//ne ocupam de font
fontName = getParameter("fontname");
if (fontName == null)
throw new IllegalArgumentException("FONTNAME is REQUIRED");
String s;
if ((s = getParameter("fontsize")) != null)
fontSize = Integer.parseInt(s);
if (fontName != null || fontSize != 0)
{
theFont = new Font(fontName, Font.BOLD + Font.ITALIC,
fontSize);
System.out.println("Name " + fontName + ", font " +
theFont);
}
if ((s = getParameter("offset")) != null)
theOffset = Integer.parseInt(s);
setBackground(Color.green);
inittedOK = true;
this.resize(getPreferredSize());
}
public Dimension getPreferredSize( )
{
return new Dimension(200, 100);
}
/** Paint method showing drop shadow effect */
public void paint(Graphics g)
{
if (!inittedOK)
return;
g.setFont(theFont);
g.setColor(Color.black);
g.drawString(theLabel, theOffset+30, theOffset+50);
g.setColor(Color.white);
g.drawString(theLabel, 30, 50);
}
//ce fel de parametri sunt necesari
public String[][] getParameterInfo( )
{
17
String info[][] = {
{ "label", "string", "Text to display" },
{ "fontname", "name", "Font to display it in" },
{ "fontsize", "10-30?", "Size to display it at" },
};
return info;
}
}
Totodată, în exemplul de mai sus, se vedea un alt mod de a porni un applet, din cadrul unei
metode main(). Se renunţă astfel la browser, sau aplicaţia ajutătoare appletviewer:
Pe lângă aceste funcţionalităţi, există foarte multe alte componente de GUI pe care le vom
aborda într-un capitol următor. În cele ce urmează vom aborda modul în care applet-urile tratează
evenimentele ce pot apare.
Tratarea evenimentelor
Modelul delegării
18
Evenimente
Sursele evenimentelor
O sursă a unui eveniment este un obiect ce generează acel eveniment. O sursă trebuie să
înregistreze ascultătorii pentru a aceştia să primească notificări legate de un eveniment. Aceasta este
metoda care va realiza înregistrarea, sau abonarea:
Aici Type este numele evenimentului, şi el este referinţa la un ascultător. De exemplu metoda
care înregistrează un ascultător la un eveniment de tastatură este addKeyListenter(). Metoda care
înregistrează mişcarea unui mouse este addMouseMotionListener(). Atunci când un eveniment are loc,
toţi ascultătorii care s-au abonat la acel eveniment sunt notificaţi, primind o copie a obiectului
eveniment.
O sursă va trebui să ofere şi o metodă ce permite anularea înregistrării de la un anumit
eveniment. Forma generală a metodei care realizează acest lucru este:
Aici Type este numele evenimentului de la care se face deînregistrarea. De exemplu, dacă se
doreşte ştergerea unui ascultător de la tastatură, se va apela metoda removeKeyListener().
Metodele ce adaugă sau şterge un eveniment, sunt oferite de sursele ce generează aceste
evenimente. De exemplu, clasa Component, oferă metode pentru adăugarea şi ştergerea ascultătorilor
la tastatură şi mouse.
Ascultători de evenimente
Un ascultător, este un obiect ce este notificat atunci când un eveniment are loc. Acest ascultător
trebuie să îndeplinească două condiţii:
1. Să fie înregistrat cu una sau mai multe surse pentru a primi notificări despre anumite
evenimente.
2. Să implementeze metode ce să proceseze aceste notificări.
Metodele care primesc şi procesează notificări sunt definite într-o mulţime de interfeţe din
pachetul java.awt.event. de exemplu interfaţa MouseMotionListener defineşte metode pentru
a primi notificări atunci când mouse-ul este mutat sau tastat. Orice obiect poate primi şi
procesa aceste evenimente.
19
Clase de evenimente
Pentru toate sursele mai sus menţionate există p serie de ascultători, şi anume interfeţe ce
folosesc la captarea notificărilor evenimentelor produse.
Acestea sunt : ActionListener, AdjustmentListener, …, WindowListener.
20
Tratarea evenimentelor de mouse
int getX()
intgetY()
Mai jos se află un eveniment, ce demonstrează folosirea evenimentelor de mouse. Acest applet
va afişa coordonatele actuale ale mouse-ului în cadrul ferestrei. De fiecare dată când un buton este
apăsat, este afişat cuvântul „Down”, iar când este eliberat, se afişează cuvântul „Up”. Dacă un buton
este apăsat se va afişa în colţul stânga sus mesajul „Mouse clicked”.
Atunci când mouse-ul se află deasupra ferestrei, mesajul de notificare apare în colţul stânga sus.
Atunci când se trasează mouse-ul, se afişează un mesaj „*” în coordonatele actuale ale mouse-ului.
În toate aceste modificări, coordonatele mouse-ului vor fi salvate în mouseX şi mouseY. Aceste
Variabile vor fi utilizate în metoda paint() pentru a afişa diversele mesaje.
import java.awt.event.*;
import java.applet.*;
import java.awt.*;
/*
<applet code="MouseWatcher" width=300 height=100>
</applet>
*/
public class MouseWatcher extends Applet
21
implements MouseListener, MouseMotionListener
{
String msg = "";
int mouseX = 0, mouseY = 0; //coordonatele
Image image;
public void init()
{
//la iniţializare
//ne abonam pentru a fi notificaţi
//la evenimentele mouse-ului
addMouseListener(this);
addMouseMotionListener(this);
}
// Tratare eveniment click de mouse
public void mouseClicked(MouseEvent me)
{
mouseX = 0;
mouseY = 10;
msg = "Mouse clicked.";
repaint();
}
// Tratare eveniment de intrare mouse
public void mouseEntered(MouseEvent me)
{
mouseX = 0;
mouseY = 10;
msg = "Mouse entered.";
repaint();
}
// Tratare eveniment de ieşire muse
public void mouseExited(MouseEvent me)
{
mouseX = 0;
mouseY = 10;
msg = "Mouse exited.";
repaint();
}
// Tratare eveniment de apăsare mouse
public void mousePressed(MouseEvent me)
{
// salvare coordonate
mouseX = me.getX();
mouseY = me.getY();
msg = "Down";
repaint();
}
// Tratare eveniment de eliberare mouse
22
public void mouseReleased(MouseEvent me)
{
// salvare coordonate
mouseX = me.getX();
mouseY = me.getY();
msg = "Up";
repaint();
}
// Tratare eveniment de trasare mouse
public void mouseDragged(MouseEvent me)
{
//salvare coordonate
mouseX = me.getX();
mouseY = me.getY();
msg = "*";
showStatus("Dragging mouse at " + mouseX + ", " + mouseY);
repaint();
}
// Tratare eveniment de mişcare mouse
public void mouseMoved(MouseEvent me)
{
//afişarea pe status
showStatus("Moving mouse at " + me.getX() + ", " +
me.getY());
}
// se afişează mesajul la coordonatele salvate
public void paint(Graphics g)
{
g.drawString(msg, mouseX, mouseY);
}
}
In figura de mai jos se poate vedea rezultatul acţiunilor, notificate către handlerele de mouse, ce
modifică variabila de mesaj şi coordonatele.
23
Ultimul exemplu se referă la tratarea evenimentelor tastaturii, şi este asemănător celui anterior.
Diferenţa este că, abonarea se va face la evenimentele de tastatură, şi evident nu vom avea
coordonate, de această dată. Totuşi, cele două programe pot fi combinate astfel ca ambele tipuri de
evenimente să fie tratate.
import java.awt.event.*;
import java.applet.*;
import java.awt.*;
/*
<applet code="KeyWatcher" width=300 height=100>
</applet>
*/
public class KeyWatcher extends Applet
implements KeyListener
{
String msg = "";
Image image;
public void init()
{
//la initializare
//ne abonam pentru a fi notificati
//la evenimentele tastaturii
addKeyListener(this);
resize(800,100);
}
/* tratare evetiment tastare*/
public void keyTyped(KeyEvent e) {
msg = "KEY TYPED: " + e.paramString();
repaint();
}
/* tratare eveniment apasare tasta */
public void keyPressed(KeyEvent e) {
msg = "KEY PRESSED: " + e.paramString();
repaint();
}
24
AWT
Introducere, concepte ......................................................................................................................... 2
Componente.................................................................................................................................... 2
Evenimente ......................................................................................................................................... 7
Modelul vechi de evenimente Java .................................................................................................. 7
Identificarea țintei ........................................................................................................................... 7
Tratarea evenimentelor ................................................................................................................... 8
Clasa Event .......................................................................................................................................... 8
Variabilele ....................................................................................................................................... 8
Constante ........................................................................................................................................ 9
Evenimente de fereastră................................................................................................................ 10
Evenimente de scroll...................................................................................................................... 11
Evenimente de focus ..................................................................................................................... 11
Metodele clasei Event.................................................................................................................... 12
Modelul nou de evenimente.............................................................................................................. 12
Clasa AWTEvent................................................................................................................................. 14
Variabile ........................................................................................................................................ 14
Constante ...................................................................................................................................... 14
Constructori ................................................................................................................................... 15
Metode.......................................................................................................................................... 15
Clasa AWTEventMulticaster ........................................................................................................... 16
Componente ..................................................................................................................................... 19
Clasa Component........................................................................................................................... 19
Evenimentele unei Component...................................................................................................... 24
Etichete ............................................................................................................................................. 25
Constante ...................................................................................................................................... 25
Metode.......................................................................................................................................... 26
Butoane............................................................................................................................................. 26
Metode.......................................................................................................................................... 26
Clasa Canvas ...................................................................................................................................... 28
Clasa Cursor....................................................................................................................................... 29
TextComponent................................................................................................................................. 30
Clasa Container.................................................................................................................................. 33
Container....................................................................................................................................... 33
Panel ................................................................................................................................................. 38
Window............................................................................................................................................. 40
1
Introducere, concepte
Librăria AWT (Abstract Window Toolkit) oferă o interfaţă grafică pentru programele Java.
Această librărie oferă unelte folosite pentru comunicarea program – utilizator.
Problema acestei librării este că Java 1.0.2 a fost înlocuit de versiuni ca 1.1 şi 1.2, versiuni ce au
introdus alte caracteristici. De foarte mulţi ani, programatorii au trecut prin mari bătăi de cap în
încercările de a porta un program scris pentru sisteme Unix, Linux pe sistem Windows sau Macintosh. În
1995, Sun anunţa o posibilă soluţie: Java. Pentru sisteme de operare bazate pe ferestre, portabilitatea
era o problemă. Când transferăm un program scris pentru Windows pe sisteme Macintosh, codul care
tratează lucrul cu interfaţa grafică trebuie rescris complet. În Java însă, acest neajuns cauzat de
portabilitate, este rezolvat parţial prin AWT. Prin această librărie, programele se pot rula în aceeași
manieră în orice sistem de operare, ele vor arata la fel. De exemplu dacă aplicaţia folosește liste de tip
Combobox, acestea vor arăta ca o listă de tip Windows, dacă aplicaţia rulează sub Windows, sau ca o
lista de Unix dacă aplicaţia rulează sub Unix.
În construcţia acestei librarii există câteva concepte de bază pe care le vom detalia în acest prim
capitol şi anume: componente, membrii, aspect, recipient. În continuare vom vorbi mai pe larg despre
fiecare.
Componente
Text static
Clasa Label oferă un mod de a afişa o linie de text pe ecran. Se poate controla fontul folosit
pentru text şi culoarea. Mai jos este un exemplu de etichete desenate pe ecran:
2
Componente pentru introducere date
Java oferă câteva moduri de a permite utilizatorului de a introduce date într-o aplicaţie.
Utilizatorul poate tipări informaţia, sau o poate selecta dintr-o listă de opţiuni prestabilită.
Există două componente pentru a putea introduce date de la tastatură: TextField pentru o singură linie şi
TextArea pentru linii multiple. Această oferă şi mijloace pentru diferite validări a textului introdus de
utilizator. În figura de mai jos sunt câteva exemple pentru aceste componente:
3
Figura 10.3 Căsuţe de opţiuni
Problema cu clasele Checkbox şi CheckboxGroup este că atunci când listele sunt prea mari, si
opţiunile sunt multe, nu există loc pentru a plasa aceste butoane radio. În acest caz se folosesc
componentele Choice, care reprezintă o listă de mai multe elemente, din care se poate selecta doar unul
singur. În figura de mai jos este reprezentat un astfel de element.
Clasa List
Totuşi dacă dorim sa afişăm integral elementele unei liste, şi să oferim posibilitatea de a selecta
multiple elemente, avem la dispoziţie clasa List. În figura de mai jos avem o astfel de componentă.
4
Figura 10.5 Clasa List
Meniuri
Interfeţele moderne folosesc aceste elemente, însă în Java în această librărie, meniurile pot
apare doar într-un Frame. O clasă Menu, este complexă şi comportă multe părţi: bare de meniu,
elemente de meniu, etc.
Clasa PopupMenu
Meniurile Pop-up sunt folosite în funcţie de context, mai ales când mouse-ul se află într-o
regiune a ferestrei. Mai jos sunt prezentate ambele tipuri de meniuri:
a) b)
Figura 10.6 a) Meniuri b)Meniuri Pop-up
5
Declanşatoare de evenimente
Java oferă două componente a căror scop este de a declanşa acţiuni pe ecran: Button şi
Scrollbar. Ele permit utilizatorului să specifice momentul realizării unei acţiuni.
Clasa Scrollbar
Într-un program gen Word sau într-un browser Web, atunci când o imagine sau text este prea
mare pentru a fi afişată în pagină, acest element permite deplasarea utilizatorului în diverse părţi ale
ecranului. Atunci când se efectuează click pe Scrollbar efectul este exact deplasare într-o anumită
direcţie pentru a viziona conţinutul paginii. În figura 10.7 este reprezentat acest element.
Unele componente ca TextArea sau List conţin deja aceste Scrollbar-uri pentru a diminua efortul
programatorului. Pentru alte elemente ca Panel sau Frame va trebui implementat de la zero
funcţionalitatea pentru ScrollBar-uri.
Clasa Buton
Butonul este cel mai cunoscut element pentru declanşarea unor evenimente şi cel mai intuitiv.
Java permite si butoane cu imagini: ImageButton.
Clasa Canvas
Această clasă reprezintă o suprafaţă goală: nu are nici o vizualizare predefinită. Se pot folosi
Canvas pentru desenarea imaginilor, construirea componentelor personalizate, sau a super-
componentelor ce conţin alte componente predefinite.
6
În continuare vom descrie în detaliu elementele amintite mai sus pentru a oferi o perspectivă
asupra librăriei AWT.
Evenimente
Am descris în cursul anterior câteva evenimente, precum şi conceptul din spatele acestui
mecanism. În cele ce urmează vom aprofunda aceste concepte.
Modelul este destul de simplu: atunci când se recepţionează un eveniment iniţiat de utilizator,
sistemul generează o instanţă de tip Event şi o transmite programului. Programul identifică ţinta (de
exemplu componenta în care a avut loc evenimentul) şi permite acelei componente să trateze
evenimentul. Dacă ţinta nu poate trata evenimentul, se încearcă găsirea unei componente care va
putea.
Identificarea țintei
Evenimentele au loc într-o clasă Component. Programul decide care componentă va primi
evenimentul, pornind de la nivelul cel mai înalt. În exemplul ales, acţiunea de a efectua click pe frame-ul
de mai jos, mai exact pe butonul Blood, va declanșa metoda deliverEvent() din cadrul acestei clase.
Procesul va continua până când ţinta găsită este butonul Blood, buton ce nu mai conţine alte
componente, așa că se va executa evenimentul aferent lui.
Mai jos este prezentată ierarhia de apeluri a metodei deliverEvent, până când evenimentul este livrat
ţintei.
7
Figura 10.9 Livrarea evenimentului
Tratarea evenimentelor
Odată ce metoda deliverEvent() identifică ţinta, se va apela metoda handleEvent() a ţintei. Dacă
ţinta nu are suprascrisă această metodă, se apelează implementarea implicită moștenită de la obiectul
Component moștenit, și nu se întâmplă nimic. În continuare vom descrie ierarhia claselor ce se ocupă de
evenimente.
Clasa Event
Atunci când apare sau este declanșat un eveniment, este de responsabilitatea programatorului
să capteze acel eveniment. Se poate decide netratarea lui, așa că evenimentul va trece ca și când nu ar fi
avut loc. Atunci când se dorește transmiterea unui eveniment mai departe, sau tratarea lui, trebuie să
înţelegem cum se comportă un obiect de acest tip. Înainte de a începe prezentarea acestei clase, trebuie
spus că ea a fost înlocuită de AWTEvent pe care o vom descrie imediat după. Motivul pentru care o
descriem aici este mai mult informativ și pentru a putea face legătura cronologic și conceptual între
aceste două moduri de a soluţiona problema legată de evenimente.
Variabilele
Clasa Event conţine multe variabile ce oferă informaţie despre evenimentul ce are loc. Vom discuta
despre câteva.
Acest membru al clasei Event, permite verificarea evenimentului de double-click. Câmpul este relevant
doar pentru evenimente de tip MOUSE_DOWN.
8
public Event evt
Acest câmp este folosit pentru a transmite evenimente, după cum am văzut mai sus. Programul
poate trata evenimentul transmis, sau evenimentul poate fi trimis unui handler personalizat:
public int id
WINDOW_DESTROY MOUSE_ENTER
WINDOW_EXPOSE MOUSE_EXIT
WINDOW_ICONIFY MOUSE_DRAG
WINDOW_DEICONIFY SCROLL_LINE_UP
KEY_PRESS SCROLL_LINE_DOWN
KEY_RELEASE SCROLL_PAGE_UP
KEY_ACTION SCROLL_PAGE_DOWN
KEY_ACTION_RELEASE SCROLL_ABSOLUTE
MOUSE_DOWN LIST_SELECT
MOUSE_UP LIST_DESELECT
MOUSE_MOVE ACTION_EVENT
Câmpul target conţine referinţa către un obiect care produce acest eveniment. De exemplu,
dacă utilizatorul selectează un buton, butonul este ţinta evenimentului. Dacă utilizatorul mișcă mouse-ul
pe un obiect Frame, atunci Frame este ţinta. Target va indica locul unde a avut loc evenimentul, și nu
neapărat componenta care tratează evenimentul.
Această variabilă conţine durata unui eveniment exprimată în milisecunde. Codul transformă o
valoare long a acestei variabile în tipul Date pentru a examina ulterior durata:
Constante
9
Clasa Event conţine nenumărate constante, unele pentru a desemna ce eveniment a avut loc,
altele pentru a ajuta la determinarea tastei apăsate.
Constantele ce se referă la taste se împart în două, după tipul de eveniment ce este generat:
KEY_ACTION - și se numesc taste pentru acţiune
KEY_PRESS – se desemnează alfanumericele.
În tabelul de mai jos sunt prezentate câteva constante și tipul de eveniment care folosește aceste
constante:
Constanta – Tip eveniment Constanta – Tip eveniment
HOME KEY_ACTION F9 KEY_ACTION
END KEY_ACTION F10 KEY_ACTION
PGUP KEY_ACTION F11 KEY_ACTION
PGDN KEY_ACTION F12 KEY_ACTION
UP KEY_ACTION PRINT_SCREEN KEY_ACTION
DOWN KEY_ACTION SCROLL_LOCK KEY_ACTION
LEFT KEY_ACTION CAPS_LOCK KEY_ACTION
RIGHT KEY_ACTION NUM_LOCK KEY_ACTION
F1 KEY_ACTION PAUSE KEY_ACTION
F2 KEY_ACTION INSERT KEY_ACTION
F3 KEY_ACTION ENTER (\n) KEY_PRESS
F4 KEY_ACTION BACK_SPACE (\b) KEY_PRESS
F5 KEY_ACTION TAB (\t) KEY_PRESS
F6 KEY_ACTION ESCAPE KEY_PRESS
F7 KEY_ACTION DELETE KEY_PRESS
Există și modificatori pentru tastele Shift, Alt, Control. Atunci când utilizatorul apasă o tastă sau
generează alt tip de eveniment, se poate verifica dacă a fost apăsată simultan și una din tastele mai sus
menţionate. Pentru aceasta avem la dispoziţie modificatorii:
Atunci când raportează un eveniment, sistemul setează câmpul modifiers ce poate fi verificat ulterior.
Evenimente de fereastră
Aceste evenimente au loc pentru componentele ce aparţin unui Window. Câteva din aceste
evenimente sunt valabile doar pentru anumite platforme OS.
Acest eveniment este produs când sistemul spune unei ferestre să se autodistrugă. Aceasta
intervine de obicei când utilizatorul selectează Close sau Quit. Implicit, instanţele Frame nu tratează
acest eveniment.
10
public static final int WINDOW_EXPOSE
Acest eveniment este transmis atunci când o parte a ferestrei devine vizibilă. Pentru a afla ce
parte a ferestrei a fost descoperită, se poate folosi getClipRect(), metodă ce aparţine instanţa clasei
Graphics.
Evenimente de scroll
Această constantă este transmisă unui eveniment care are loc atunci când utilizatorul apasă
săgeata de sus a unui scrollbar vertical sau pe săgeata din stânga a unui scrollbar orizontal.
Această constantă este setată unui eveniment care are loc atunci când utilizatorul apasă săgeata
de jos a unui scrollbar vertical sau pe săgeata din dreapta a unui scrollbar orizontal.
Această constantă este setată unui eveniment care are loc atunci când utilizatorul apasă lângă
săgeata de sus a unui scrollbar vertical sau lângă săgeata din stânga a unui scrollbar orizontal, cauzând
deplasarea unei pagini întregi în acea direcţie.
Această constantă este setată unui eveniment care are loc atunci când utilizatorul apasă lângă
săgeata de jos a unui scrollbar vertical sau lângă săgeata din dreapta a unui scrollbar orizontal, cauzând
deplasarea unei pagini întregi în acea direcţie.
Evenimente de focus
11
Acest eveniment apare când o anumită componentă este selectată. Metoda
FocusListener.focusLost() poate fi folosită pentru tratarea acestui eveniment.
De obicei evenimentele vor fi declanșate de contextul exterior (apăsarea unui buton, a unei
taste, etc). totuși dacă ne creăm propriile componente, sau dorim să comunicăm între thread-uri, atunci
va trebui să ne creăm propriile noastre evenimente. Clasa Event are o serie de constructori și anume:
public Event (Object target, long when, int id, int x, int y, int key,
int modifiers, Object arg)
Primul constructor este și cel mai complet, și iniţializează toate câmpurile obiectului cu
parametrii specificaţi. Ceilalţi doi constructori vor omite câteva iniţializări dupa cum urmează:
public Event (Object target, long when, int id, int x, int y, int key, int
modifiers)
public Event (Object target, int id, Object arg)
Metoda shiftDown() returnează true dacă tasta Shift a fost apăsată sau false în caz contrar.
Metode asemănătoare există și pentru celelalte taste și anume Ctrl sau Alt.
Deși pare mai complex (modelul este format din mai multe părţi) este mai simplu și mai eficient.
Acest model solicită obiectelor să se aboneze pentru a primi evenimente. Acest model se numește
“delegare” și implementează modelul Observer-Observable descris în cursul anterior. Reluând, fiecare
componentă este o sursă de evenimente ce poate genera diverse tipuri de evenimente, care sunt de
fapt, subclase a clasei AWTEvent. Obiectele interesate de un eveniment se numesc ascultători. Fiecare
tip de eveniment corespunde unei interfeţe de ascultare ce specifică metodele care sunt apelate atunci
când un eveniment are loc.
Ierarhia de evenimente și clasele cu rol de ascultători pentru acestea, este reprezentată în figura
de mai jos:
12
Figura 10.10 Modelul AWTEvent
13
Unele interfeţe de ascultători sunt construite astfel încât să trateze mai multe evenimente. De
exemplu, interfaţa MouseInterface declară cinci metode pentru a trata diverse evenimente de mouse.
Aceasta înseamnă că un obiect interesat de evenimente de mouse trebuie să implementeze interfaţa
MouseListener și va trebui să implementeze toate cele cinci metode.
Ce se întâmplă dacă dorim să tratăm doar un eveniment de mouse? Suntem obligaţi să scriem
cod de patru ori in plus? Din fericire pachetul java.awt.event include clase adapter, ce sunt scurtături ce
permit scrierea facilă a acestor handler-e de evenimente. Clasa adapter oferă implementări nule a
tuturor metodelor interfeţei. De exemplu MouseAdapter oferă implementări implicite alea metodelor
mouseEntered(), mouseExisted(), etc. Putem astfel să ne concentrăm pe metoda care ne interesează la
un moment dat și anume mouseClicked().
Clasa AWTEvent
Variabile
protected int id
Câmpul id al clasei AWTEvent este accesibil prin intermediul getID(). Este identificatorul tipului
de eveniment, ca de exemplu ACTION_PERFORMED al evenimentului ActionEvent sau MOUSE_MOVE.
Constante
Constantele clasei AWTEvent sunt folosite pentru a determina ce tip de tratare de eveniment va
fi folosită și ce eveniment este procesat. Mai jos este o listă cu acestea:
14
Constructori
Clasa AWTEvent este abstractă, constructorii nu pot fi folosiţi imediat. Ei sunt apelaţi la instanţierea unei
clase copil.
Primul constructor creează un eveniment de tip AWTEvent folosindu-se de unul din versiunea veche
Java. Al doilea creează un AWTEvent folosind o sursă dată. Parametrul id folosește ca identificator al
tipului de eveniment.
Metode
15
ItemEvent Este clasa ce permite tratarea evenimentelor ce au loc atunci când utilizatorul
Selectează un checkbox sau un buton radio etc.
TextEvent Încapsulează evenimentele ce au loc atunci când conţinutul unui
TextComponent s-a schimbat.
Pentru toate aceste elemente descrise există interfeţe de ascultători şi clase adaptori.
De exemplu pentru clasa ActionEvent există interfaţa ActionListener ce conţine metode pentru tratarea
evenimentelor de acest tip.
Pentru clasa ComponentEvent există interfaţa ComponentListener ce conţine patru metode
pentru mutarea şi redimensionarea componentei. Clasa ComponentAdapter este adaptorul
corespunzător, folosit mai ales când se doreşte ascultarea unui eveniment specific.
Clasa AWTEventMulticaster
Aceasta este folosită de AWT pentru a trata cozile de ascultători pentru diverse evenimente, şi
pentru a trimite evenimente tuturor ascultătorilor interesaţi (multicasting). Iată cum se foloseşte
această clasă:
actionListenerChain=AWTEventMulticaster.add(actionListenerChain,
newActionListener);
actionListenerChain.actionPerformed(new ActionEvent(...));
16
public void keyReleased(KeyEvent e)
public void keyTyped(KeyEvent e)
public void mouseClicked(MouseEvent e)
public void mouseDragged(MouseEvent e)
public void mouseEntered(MouseEvent e)
public void mouseExited(MouseEvent e)
public void mouseMoved(MouseEvent e)
public void mousePressed(MouseEvent e)
public void mouseReleased(MouseEvent e)
public void textValueChanged(TextEvent e)
public void windowActivated(WindowEvent e)
public void windowClosed(WindowEvent e)
public void windowClosing(WindowEvent e)
public void windowDeactivated(WindowEvent e)
public void windowDeiconified(WindowEvent e)
public void windowIconified(WindowEvent e)
public void windowOpened(WindowEvent e)
import java.awt.*;
import java.awt.event.*;
class ItemEventComponent extends Component implements ItemSelectable
{
boolean selected;
int i = 0;
ItemListener itemListener = null;
ItemEventComponent ()
{
enableEvents (AWTEvent.MOUSE_EVENT_MASK);
}
public Object[] getSelectedObjects()
{
Object o[] = new Object[1];
o[0] = new Integer (i);
return o;
}
public void addItemListener (ItemListener l)
{
itemListener = AWTEventMulticaster.add (itemListener, l);
}
public void removeItemListener (ItemListener l)
{
itemListener = AWTEventMulticaster.remove (itemListener, l);
}
17
if (e.getID() == MouseEvent.MOUSE_PRESSED)
{
if (itemListener != null)
{
selected = !selected;
i++;
itemListener.itemStateChanged (
new ItemEvent (this, ItemEvent.ITEM_STATE_CHANGED,
getSelectedObjects(),
(selected?ItemEvent.SELECTED:ItemEvent.DESELECTED)));
}
}
}
}
public class Multicasting extends Frame implements ItemListener
{
Multicasting ()
{
super ("Listening In");
ItemEventComponent c = new ItemEventComponent ();
add (c, "Center");
c.addItemListener (this);
c.setBackground (SystemColor.control);
setSize (200, 200);
}
public void itemStateChanged (ItemEvent e)
{
Object[] o = e.getItemSelectable().getSelectedObjects();
Integer i = (Integer)o[0];
System.out.println (i);
}
public static void main (String args[])
{
Multicasting f = new Multicasting();
f.show();
}
}
18
Componente
În continuare vom prezenta clasa Component, împreună cu unele componente specifice cum ar fi Label,
Button și Canvas. De asemenea vom arunca o privire și asupra clasei Cursor.
Clasa Component
Fiecare aplicaţie cu interfaţă grafică constă dintr-un set de obiecte. Aceste obiecte sunt de tip
Component, iar cele mai frecvent derivate din această clasă, sunt butoanele, câmpurile de text și
container-ele.
Constante
Acestea sunt folosite pentru a specifica cum ar trebui aliniată componenta curentă:
Metode
protected Component()
Metoda returnează Toolkit-ul actual al componentei. Prin acest Toolkit avem acces la
detaliile platformei curente (ca rezoluţia, mărimea ecranului, sau fonturi disponibile).
Metoda returnează culoarea de prim plan. Dacă nu există o culoare setată, se va lua culoarea
părintelui componentei actuale. Dacă nici părintele nu are culoare de prim plan se returnează null.
19
public void setBackground (Color c)
Aceste două metode realizează același lucru ca cele de mai sus, dar se referă la culoarea
fundalului.
Sunt metodele pentru preluarea/setarea fontului care va fi valabil pentru componenta curentă.
Metoda getGraphics preia contextul grafic al componentei. Cele mai multe componente nu
specifică, corect acest context și vor arunca excepţia InternalError la apelul acestei metode.
Aceste metode modifică sau ajută la preluarea setării Locale a componentei curente. Localizarea
este o parte dintr-un subiect al internaţionalizării programelor Java și nu îl vom aborda acum.
Aceste metode returnează poziţia curentă a Component-ei, poziţie relativă la părintele acestei
structuri. Punctul returnat prin Point este colţul stânga sus ce delimitează dreptunghiul componentei.
Primele două metode ajută la preluarea dimensiunii componentei, în timp ce următoarele două,
setează noua dimensiune a componentei.
20
public float getAlignmentX ()
public float getAlignmentY ()
Aceste două metode returnează aliniamentul componentei referitor la axa X respectiv Y. acest
aliniament poate fi folosit ulterior de managerul de aspect pentru a poziţiona componente relativ la
altele. Valoarea returnată este între 0 și 1, unde o valoare apropiată de 0 indică o apropiere de stânga în
timp ce o valoare apropiată de 1 indică o deplasare către dreapta.
Aceste două metode verifică dacă, punctul de coordonate x și y se află în cadrul dreptunghiului
ce delimitează componenta. Dacă această componentă nu este rectangulară, metoda acţionează ca și
când ar fi delimitată de un dreptunghi.
Metodele folosesc contains pentru a verifică dacă x și y se află în cadrul componentei. Dacă
dac, metoda returnează instanţa componentei. Dacă nu, returnează null.
Există o serie de funcţii de redesenare, care în principiu realizează același lucru: redesenarea
componentei cât mai repede cu putinţă.
Apelul implicit al acestei metode este către paint. Dar se poate imprima conţinutul unei
componente, dacă parametrul Graphics implementează PrintGraphics.
21
Metode de vizualizare
Pachetul AWT conţine de asemenea clasa abstractă Image, care se ocupă cu tratarea imaginilor,
provenite din diverse surse, cele mai întâlnite fiind fișierele de imagini.
Un mic exemplu pentru folosirea acestei clase, pentru încărcarea unei imagini și afișarea ei se află mai
jos:
import java.awt.*;
public class Imaging extends Frame
{
Image im;
Imaging ()
{
im = Toolkit.getDefaultToolkit().getImage ("c:\\1.jpg");
}
public void paint (Graphics g)
{
g.drawImage (im, 0, 0, 175, 225, this);
}
public boolean mouseDown (Event e, int x, int y)
{
im.flush();
repaint();
return true;
}
public static void main (String [] args)
{
Frame f = new Imaging();
f.setVisible(true);
}
}
Revenind la clasa Component, aceasta suportă lucrul cu imagini prin intermediul clasei Image și
a câtorva funcţii ce vor fi descrise în cele ce urmează.
public boolean imageUpdate (Image image, int infoflags, int x, int y, int
width, int height)
22
Metoda creează o imagine goală de mărime width și height. Imaginea returnată este o imagine
stocată în memorie. Dacă nu poate fi creată, atunci apelul metodei returnează null.
Metoda forţează încărcarea unei imagini, asincron, în alt thread. Obiectul observer este
componenta care va fi afișată de imaginea image la încărcarea imaginii. Dacă image a fost deja
încărcată atunci funcţia returnează true. în caz contrar se returnează false. Din moment de image este
încărcată asincron, prepareImage returnează imediat ce este apelată.
Metoda este suprascrisă de fiecare componentă în parte. Atunci când este apelată componenta
este invalidată. Metoda este apelată de sistem la crearea componentei, sau atunci când componenta
este adăugată la un Container și acesta era deja afișat.
Cele două metode, din care ultima este invechită, afișează pe ecran componenta (în cazul
metodei setVisible doar dacă variabila booleană este setată pe true). Părintele Container, trece în
starea invalid, deoarece cel puţin un copil a modificat aspectul.
Metodele stabilesc dacă utilizatorul poate accesa/interacţiona cu aceste componente sau nu.
23
public void requestFocus ()
Această metodă verifică dacă acea componentă este capabilă de a recepţiona focus sau nu.
O clasă Component suportă metodele învechite de control al evenimentelor, discutate mai sus:
deliverEvent, postEvent și handleEvent, dar și metode noi de a posta noi evenimente ca:
Pe lângă acestea există o serie de funcţii ce sunt învechite cum ar fi keyUp, sau mouseDown ce pot fi
folosite pentru captarea evenimentelor de buton sau taste. Pentru tratarea evenimentelor apărute în
cadrul unui Component există modelul delegat și o serie de funcţii pentru înregistrarea sau
deînregistrarea ascultătorilor:
Pentru a putea trata evenimentele (asta dacă nu dorim neapărat înregistrarea la un anumit
eveniment) trebuie să permitem captarea acestora. Aceasta se realizează prin funcţia:
Clasa AWTEvent permite definirea unor constant ce permit verificarea tipului de eveniment ce a
avut loc. Tipul evenimentului este dat de variabila eventsToEnable.
24
Această metodă permite oprirea recepţionării unor evenimente pentru componenta curentă.
Metoda este apelată atunci când componenta primește focus. Suprascrierea acestei metode
este echivalentă cu suprascrierea metodelor gotFocus() sau lostFocus().
Etichete
O etichetă, este reprezentată de clasa Label, ce derivă din Component și afișează o linie de text. Este
folosită mai ales la stabilirea unor titluri sau a atașa text altei componente.
Constante
25
Acestea oferă aliniamentul textului din etichetă.
Metode
public Label ()
public Label (String label)
public Label (String label, int alignment)
Aceștia sunt constructorii acestei clase, prin care se permite instanţierea unei etichete, și
specificarea textului afișat label sau a aliniamentului.
Butoane
Clasa Button derivă de asemenea din Component și se folosește pentru declanșarea unor acţiuni.
Metode
public Button ()
public Button (String label)
Aceștia sunt constructorii, ultimul permiţând crearea unui buton a cărui text este label.
26
Fiecare buton poate avea două nume. Unul este ceea ce vede utilizatorul și celălalt este folosit
de programator și se numește comanda butonului. Aceasta a fost introdusă pentru internaţionalizarea
programelor. De exemplu daca eticheta butonului conţine Yes însă programul rulează pe sistem francez
sau german, eticheta va afișa Oui sau Ja. Oricare ar fi sistemul pe care rulează comanda butonului poate
rămâne Yes.
Evenimentele unui buton sunt cele de mouse, de focus de tastatură practic cele prezentate mai
sus în clasa Component.
Pentru a înţelege mai bine evenimentele și diferenţa între numele comenzii și numele afișat
avem următorul cod:
import java.applet.*;
import java.awt.event.*;
import java.awt.*;
public class Buton extends Applet implements ActionListener
{
Button b;
public void init ()
{
add (b = new Button ("One"));
b.addActionListener (this);
add (b = new Button ("Two"));
b.addActionListener (this);
add (b = new Button ());
b.setLabel("Drei");
b.setActionCommand("Three");
b.addActionListener (this);
add (b = new Button ());
b.setLabel("Quatre");
b.setActionCommand("Four");
b.addActionListener (this);
}
public void actionPerformed (ActionEvent e)
{
String s = e.getActionCommand();
if ("One".equals(s))
{
System.out.println ("Pressed One");
}else if ("Two".equals(s)) {
System.out.println ("Pressed Two");
} else if ("Three".equals(s))
{
27
System.out.println ("Pressed Three");
} else if ("Four".equals(s))
{
System.out.println ("Pressed Four");
}
}
}
Deși etichetele ultimelor butoane sunt altfel decât Four sau Three mesajul va fi afișat pentru că
se compară numele acţiunii butonului.
Clasa Canvas
Aceasta este o clasă pe servește ca suport pentru noi componente ce pot fi create pe lângă cele
deja oferite de librăria AWT. Canvas poate fi folosită și ca loc de a desena componente adiţionale pe
ecran, sau folosind Graphics se pot desena pe un Canvas diverse figuri.
Cel mai bine este ca un obiect Canvas să stea în interiorul unui Container în cazul aplicaţiilor mai
complexe. Mai jos este un exemplu de folosirea a aceste clase.
import java.awt.Canvas;
import java.awt.Graphics;
import java.applet.Applet;
În cadrul acestui exemplu clasa DrawingRegion ce moștenește Canvas este folosită în cadrul unui
Applet, mai exact adăugată în cadrul ferestrei principale.
28
Clasa Cursor
Aceasta oferă diferite forme și dimensiuni pentru a reprezenta pointer-ul mouse-ului. Iată mai jos
constantele ce reprezintă acele forme
Metode
Metoda returnează tipul cursorului și este egal ca valoare cu una din constantele clasei.
Metoda returnează un cursor predefinit. Dacă tipul nu este egal cu una din constante, metoda
aruncă excepţia IllegalArgumentException. Metoda verifică dacă obiectul de tip Cursor există deja
și dacă da returnează referinţa la obiectul existent.
Iată un exemplu pentru utilizarea acestei componente:
import java.awt.*;
import java.awt.event.*;
29
Button comp1 = new Button("Ok");
Button comp2 = new Button("Cancel");
panel.add(comp1);
panel.add(comp2);
f.add(panel,BorderLayout.CENTER);
f.setSize(200,200);
f.setVisible(true);
Cursor cur = comp1.getCursor();
Cursor cur1 = comp2.getCursor();
comp1.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
comp2.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
f.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent we){
System.exit(0);
}
});
}
}
TextComponent
Există două moduri de a introduce date: de la tastatură sau prin mișcările mouse-ului. Pentru
introducerea de caractere există două clase TextField și TextArea. Acestea fac parte din clasa părinte
TextComponent.
Metodele ajută la preluarea/modificarea textului din cadrul acestei componente. Dacă este
vorba de o componentă de tip TextArea, atunci sunt permise și caracterele \n astfel că textul va apare
pe mai multe linii.
Utilizatorii pot selecta textul din cadrul acestei componente folosind mouse-ul sau tastatura.
Pentru a lucra cu textul selectat avem următoarele funcţii:
Metoda returnează poziţia iniţială a textului selectat. Poziţia poate fi considerată numărul de
caractere ce precedă primul caracter selectat. Dacă nu este selectat text, se returnează poziţia
cursorului. Valoarea de start de la începutul textului este 0.
30
Această metodă returnează poziţia cursorului ce indică sfârșitul selecţiei curente. Dacă nu este
selectat nimic atunci se va returna poziţia curentă a cursorului.
Această metodă returnează textul selectat sub forma unui String, sau null dacă nu este nimic
selectat.
Aceste două metode modifică selecţia actuala a textului după parametrul position.
Aceste metode sunt pentru a activa sau dezactiva un TextComponent. Mai jos este un exemplu
pentru a demonstra folosirea acestor clase:
import java.awt.*;
import java.applet.*;
public class TestText extends Applet
{
TextArea area;
Label label;
public void init () {
setLayout (new BorderLayout (10, 10));
add ("South", new Button ("toggleState"));
add ("Center", area = new TextArea ("Area to write", 5, 10));
add ("North", label = new Label ("Editable", Label.CENTER));
}
public boolean action (Event e, Object o)
{
if (e.target instanceof Button)
{
if ("toggleState".equals(o))
{
area.setEditable (!area.isEditable ());
label.setText ((area.isEditable () ? "Editable" : "Read-
only"));
return true;
}
}
return false;
}
}
31
Pe lângă evenimentele cunoscute se poate trata modificarea textului componentei folosind următoarea
funcţie:
Această metodă permite înregistrarea obiectului listener pentru a primi notificări atunci când
are loc un eveniment de tip TextEvent. Metoda listener.textValueChanged() este apelată atunci când
aceste evenimente au loc. Mai jos este un exemplu pentru folosirea acestora:
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
class TextFieldSetter implements ActionListener
{
TextField tf;
TextFieldSetter (TextField tf)
{
this.tf = tf;
}
public void actionPerformed(ActionEvent e)
{
if (e.getActionCommand().equals ("Set"))
{
tf.setText ("You Just Say Hello");
}
}
}
public class TextEvent1 extends Applet implements TextListener
{
TextField tf;
int i=0;
public void init ()
{
Button b;
tf = new TextField ("Hello", 20);
add (tf);
tf.addTextListener (this);
add (b = new Button ("Set"));
b.addActionListener (new TextFieldSetter (tf));
}
public void textValueChanged(TextEvent e)
{
System.out.println (++i + ": " + e);
}
}
32
Celelalte tipuri de componente, precum listele, checkbox-urile sau combobox-urile vor fi
abordate în cursul următor, urmând ca acum să studiem cum se pot grupa aceste componente.
Clasa Container
Clasa Container este o subclasă a Component, ce conţine alte componente, inclusiv alte
recipiente. Container permite crearea de grupuri de obiecte ce apar pe ecran. în continuare vor fi atinse
câteva din aceste clase. Fiecare obiect Container are un o schiţă, o reprezentare ce permite organizarea
componentelor din acel recipient. Toate aceste aspecte vor fi detaliate mai târziu când vom vorbi despre
Layout.
Container
Container este o clasă abstractă ce oferă suport pentru alte obiecte de tip Component. Această
clasă conţine metode pentru a grupa elementele de tip Component şi a trata evenimentele care apar în
cadrul ei. Deoarece este o clasă abstractă, nu poate fi instanţiat un obiect de acest tip, de aceea ea
trebuie derivată şi apoi utilizată.
Constructori
Constructorul clasei ce moşteneşte Container permite instanţierea unui obiect şi asocierea unui
Layout şi anume folosind un manager pentru aspect. Următorul cod exemplifică acest lucru:
import java.awt.*;
public class LightweightPanel extends Container
{
LightweightPanel () {}
LightweightPanel (LayoutManager lm)
{
setLayout(lm);
}
}
Gruparea elementelor
Metoda getComponentCount() returnează numărul de componente din cadrul unui Container iar
countComponents() este o veche metodă dintr-o versiune Java 1.0.
33
public Component getComponent (int position)
public Component[] getComponents()
Prima metodă returnează o componentă cu un anumit index. Dacă poziţia este incorectă va fi aruncată
următoarea excepţie: ArrayIndexOutOfBoundsException.
A doua metodă returnează un şir ce conţine toate componentele din Container-ul curent. Orice
modificare a oricărui element din acest şir va apare imediat pe ecran.
Adăugarea elementelor
Metoda add() adaugă o componentă în Container, la o anumită poziţie. Dacă position este -1
inserarea va avea loc la sfârşit. Dacă position este incorect, metoda va arunca excepţia
IllegalArgumentException. Dacă se încearcă adăugarea Container-ului curent la sine însuşi metoda va
arunca aceeaşi excepţie. Dacă totul merge perfect, componenta este adăugată în recipient, şi metoda
returnează obiectul Component adăugat.
Această metodă adaugă un component ca ultim obiect al Container-ului . Aceasta se face prin apelul
metodei anterioare cu position egal cu -1.
Această metodă este necesară pentru situaţiile în care avem componente Layout ce solicită
informaţii în plus pentru a dispune componentele pe ecran. Informaţia adiţională este specificată prin
parametrul constraints. Parametrul constraints depinde de managerul de interfaţă:
LayoutManager. Poate fi folosit pentru a denumi Container-e din cadrul unui CardLayout, sau specifica
suprafaţa unui BorderLayout, etc.
Această metodă permite folosirea metodei anterioare, în care String-ul folosit defineşte o
anumită constrângere. Apelarea aceste metode va genera un ContainerEvent cu id-ul
COMPONENT_ADDED. Acestea sunt câteva din metodele de adăugare, însă mai sunt şi altele care pot fi
folosite.
Ştergerea elementelor
34
public void removeAll ()
Metoda va şterge toate componentele din container. Atunci când este apelată, va genera un
ContainerEvent cu id-ul COMPONENT_REMOVED.
Alte medode
Metoda verifică faptul că component este părinte a acestui Container. Va returna true în caz
afirmativ, altfel va returna false.
Metode de Layout
35
public float getAlignmentX ()
public float getAlignmentY ()
Evenimente
Această metodă este apelată atunci când eveniment de tip Event are loc. Metoda încearcă să
localizeze o componentă din Container pentru a primi evenimentul aferent ei. Dacă este găsită, atunci
coordonatele x şi y sunt transmise noii ţinte, evenimentul e de tip Event este astfel livrat.
Această metodă apelează metoda contains() din cadrul fiecărei componente din Container
pentru a vedea dacă, coordonatele x şi y sunt în interiorul componentei. Dacă da, acea componentă este
returnată. Dacă nu se găseşte nici o componentă care să respecte această cerinţă atunci obiectul
Container este returnat. Dacă aceste coordonate sunt în afara Container-ului atunci se returnează null.
36
Această metodă este identică cu cea anterioară, doar că locaţia se specifică printr-un obiect de
tip Point.
Ascultători
Metoda înregistrează un obiect listener interesat de a primi notificări atunci când un obiect
de tip ContainerEvent trece prin coada de evenimente la care Container-ul curent este abonat.
Atunci când aceste evenimente au loc, metodele listener.componentAdded() sau
listener.componentRemoved() sunt apelate. Evident că şi alţi ascultători pot fi de asemenea înregistraţi.
În codul ce urmează a fi prezentat, este exemplificată folosirea unui ContainerListener pentru a
înregistra ascultători pentru toate butoanele adăugate unui applet. Ceea ce face ca acest cod să
funcţioneze, este apelul funcţiei enableEvents() care are ca efect livrarea evenimentelor în absenţa
ascultătorilor.
/*
<applet code="UsingContainer" width=300 height=50>
</applet>
*/
import java.awt.*;
import java.applet.*;
import java.awt.event.*;
37
public void actionPerformed (ActionEvent e)
{
System.out.println ("Selected: " + e.getActionCommand());
}
}
Această metodă şterge un listener, adică va face ca acel obiect să nu mai fie abonat. Dacă
listener nu este înregistrat, nu se întâmplă nimic.
Panel
Această clasă oferă un Container generic pentru afișarea unei suprafeţe. Este cel mai simplu
dintre toate Container-ele, fiind doar o suprafaţă rectangulară.
Constructorii acestei clase sunt:
public Panel ()
public Panel (LayoutManager layout)
Ultimul constructor permite setarea unui manager de aspect iniţial și anume layout.
Evenimentele acestei clase sunt în concordanţă cu cele amintite la clasa Container, din moment
ce Panel moștenește un Container.
import java.awt.*;
import java.awt.event.*;
38
private Button copyButton;
private Button cutButton;
private Button pasteButton;
private Button exitButton;
public PanelTest() {
super("Test Panel");
setSize(450, 250);
addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
}
);
Panel toolbarPanel = new Panel();
toolbarPanel.setBackground(new Color(20, 20, 10));
toolbarPanel.setLayout(new FlowLayout(FlowLayout.CENTER));
copyButton = new Button("Copy");
toolbarPanel.add(copyButton);
cutButton = new Button("Cut");
toolbarPanel.add(cutButton);
pasteButton = new Button("Paste");
toolbarPanel.add(pasteButton);
add(toolbarPanel, BorderLayout.EAST);
// Bottom Panel
Panel bottomPanel = new Panel();
bottomPanel.setBackground(new Color(100, 120, 10));
exitButton = new Button("Exit");
bottomPanel.add(exitButton);
add(bottomPanel, BorderLayout.SOUTH);
}
public static void main(String[] args) {
39
Window
Un Window este o suprafaţă de afișare de top în afara unui browser sau a unui applet. Frame
este o subclasă a Window ce conţine limite, bara de titlu, etc. în mod normal Window se folosește
pentru a permite crearea meniurilor pop-up sau a altor componente ce necesită acest spaţiu. Clasa are o
serie de metode ce influenţează aparenţa ferestrei reprezentate.
Este constructorul ce poate specifica părintele ferestrei, adică în cadrul cărei ferestre va activa
această fereastră. Atunci când părintele este minimizat, același lucru se întâmplă cu copilul.
Metoda show afișează fereastra. Atunci când fereastra este creată, ea este implicit ascunsă.
Pentru a aduce în prim plan fereastra, se poate apela metoda toFront().
Metoda returnează Toolkit-ul curent al ferestrei, adică obiectul ce oferă informaţii despre
platforma pe ca rulează programul. Aceasta va permite redimensionarea ferestrei sau alegerea de
imagini pentru aplicatie, etc.
40
import java.awt.*;
public class WindowTest extends Frame
{
Window w = new PopupWindow (this);
WindowTest ()
{
super ("Window Example");
resize (250, 100);
show();
}
public static void main (String args[])
{
Frame f = new WindowTest ();
}
public boolean mouseDown (Event e, int x, int y)
{
if (e.modifiers == Event.META_MASK)
{
w.move (location().x+x, location().y+y);
w.show();
return true;
}
return false;
}
}
class PopupWindow extends Window
{
PopupWindow (Frame f)
{
super (f);
Panel p = new Panel ();
p.add (new Button ("About"));
p.add (new Button ("Save"));
p.add (new Button ("Quit"));
add ("North", p);
setBackground (Color.gray);
pack();
}
public boolean action (Event e, Object o)
{
if ("About".equals (o))
System.out.println ("About");
else if ("Save".equals (o))
System.out.println ("Save Me");
else if ("Quit".equals (o))
System.exit (0);
//fereastra este acunsa la
//apasarea oricarui buton
hide();
return true;
}
}
41
AWT II
Componente ....................................................................................................................................... 2
Clasa Frame ..................................................................................................................................... 2
Cum construim din Window o noua componentă ............................................................................ 5
Dialoguri .......................................................................................................................................... 6
Clasa FileDialog .............................................................................................................................. 11
Layout ............................................................................................................................................... 16
Interfața LayoutManager ............................................................................................................... 16
Interfața LayoutManager2 ............................................................................................................. 17
FlowLayout .................................................................................................................................... 18
BorderLayout ................................................................................................................................. 22
Componente tip lista ......................................................................................................................... 25
Choice ........................................................................................................................................... 25
Liste ............................................................................................................................................... 28
Meniuri ............................................................................................................................................. 33
MenuComponent .......................................................................................................................... 34
MenuContainer ............................................................................................................................. 35
MenuShortcut ............................................................................................................................... 36
MenuItem...................................................................................................................................... 36
Menu ............................................................................................................................................. 38
MenuBar ....................................................................................................................................... 39
1
Componente
Clasa Frame
Frame-ul este un fel de Window care permite adăugarea unui MenuBar, a titlului ferestrei, și
altor caracteristici ale unei ferestre (cum ar fi redimensionarea, maximizarea, minimizarea, meniu de
fereastră etc).
Clasa conţine o serie de constante pentru a specifica tipul cursorului. În acest scop se poate
folosi metoda Component.setCursor() pentru a schimba cursorul mouse-ului atunci când acesta se află
deasupra frame-ului. Mai jos este o listă cu aceste constante:
public Frame ()
Constructorul implicit creează o fereastră ascunsă cu numele “Untitled” sau un String gol. Ca și
în cazul clasei Window, managerul de aspect implicit, al Frame-ului comportă cursorul BorderLayout.
DEFAULT_CURSOR. Deoarece Frame este ascunsă la creare, trebuie apelată metoda show() sau
setVisible(true) pentru a permite vizualizarea acesteia.
Această versiune de constructor este identică primei, dar permite setarea titlului ferestrei prin
specificarea parametrului de tip String.
2
Metodele clasei Frame
Prima metodă returnează bara de meniu a Frame-ului curent. A doua metodă modifică bara de
meniu cu parametrul bar. Se poate ca o aplicaţie să aibă mai multe bare de meniu, aceasta depinzând
de context.
Această metodă șterge componenta specificată ca parametru, din cadrul Frame-ului, în cazul în
care component este bara de meniuri a Frame-ului. Aceasta este echivalent cu apelul metodei
setMenuBar cu parametru null.
Metoda dispose() eliberează resursele folosite de Frame. Dacă vre-una din clasele Dialog
sau Window sunt asociate acestui Frame, atunci și resursele acestora sunt de asemenea eliberate. Se
obișnuiește ascunderea ferestrei înainte de eliberarea resurselor acesteia, pentru ca utilizatorii sa nu
realizeze acest proces.
Prima metodă returnează faptul că Frame-ul curent este sau nu redimensionabil. A doua
metodă modifică starea Frame-ului. O valoare true a parametrului înseamnă că starea ferestrei este
redimensionabilă în timp ce false semnifică faptul că utilizatorul nu poate redimensiona fereastra.
3
Variabila cursorType trebuie să fie una din constantele de mai sus. Dacă se specifică altă valoare în
locul acestor constante, va fi aruncată excepţia IllegalArgumentException.
A doua metodă permite preluarea cursorului actual.
Această metodă creează un peer pentru Frame-ul curent. Fiecare componentă de interfaţă AWT
are propriul ei peer. Un peer este implementarea acelei componente în mediul nativ. De exemplu,
componenta Choice corespunde unor obiecte ce permit utilizatorului să selecteze un obiect dintr-o listă.
Metoda addNotify creează peer pentru Frame automat, la apelul metodei show(). Se poate
suprascrie această metodă însă trebuie să apelăm mai întâi super.addNotify().
Evenimentele Frame-ului
Evenimentele care pot fi generate de un Frame, sunt cele generate de Component, deoarece
Frame moștenește această clasă. Pe lângă acestea, Frame poate genera evenimente de tip Window.
Acestea sunt: WINDOW_DESTROY, WINDOW_EXPOSE, WINDOW_ICONIFY, WINDOW_DEICONIFY, și
WINDOW_MOVED.
Un eveniment des întâlnit, WINDOW_DESTROY, este generat atunci când utilizatorul încearcă să
închidă Frame-ul, selectând Quit, sau Exit din meniu. Implicit acest eveniment nu face nimic. Trebuie să
oferiţi un handler, adică o metodă care să trateze acest eveniment.
enableEvents (AWTEvent.WINDOW_EVENT_MASK);
Metoda garantează primirea tuturor evenimentelor de fereastră, chiar dacă nu sunt ascultători
abonaţi. Pentru a închide o fereastră se poate folosi metoda processWindowEvent și în cazul Frame-ului:
4
Cum construim din Window o noua componentă
Am discutat despre clasa Window și clasa Frame, iar acum putem să vedem cum sunt folosite în
exemplul de mai jos. Construim o serie de butoane pop-up. De asemenea este folosit Toolkit-ul unui
Frame pentru a încărca imagini într-o aplicaţie. Butoanele apar atunci când utilizatorul apasă butonul
dreapta al mouse-ului, asemănător exemplului din cursul precedent.
import java.awt.*;
import java.awt.image.ImageObserver;
public class PopupButtonFrame extends Frame implements ImageObserver
{
Image im;
Window w = new PopupWindow (this);
PopupButtonFrame ()
{
super ("PopupButton Example");
im = getToolkit().getImage ("1.jpg");
5
}
class PopupWindow extends Window
{
PopupWindow (Frame f)
{
super (f);
Panel p = new Panel ();
p.add (new Button ("About"));
p.add (new Button ("Save"));
p.add (new Button ("Quit"));
add ("North", p);
setBackground (Color.gray);
pack();
}
public boolean action (Event e, Object o)
{
if ("About".equals (o))
System.out.println ("About");
else if ("Save".equals (o))
System.out.println ("Save Me");
else if ("Quit".equals (o))
System.exit (0);
hide();
return true;
}
}
Noutatea faţă de programul din cursul anterior, este că, încărcăm o imagine în cadrul Frame-
ului, folosind un obiect de tip Image ce este desenat pe metoda de paint().
Dialoguri
Clasa Dialog oferă implementarea unei ferestre speciale, care este în mod normal, folosită la
mesaje pop-up pentru ca utilizatorul să poată introduce date. Ea trebuie asociată cu Frame, și ori de câte
ori ceva se întâmplă Frame-ului, același eveniment va avea loc și pe Dialog. De exemplu, dacă părintele
Frame are iconiţă, Dialog-ul dispare până când iconiţa este ștearsă din Frame.
Dialog-ul poate fi modal sau amodal. Un Dialog amodal este acela în care utilizatorul poate
interacţiona cu Frame-ul și cu Dialogul în același timp. Un Dialog modal este acela care blochează orice
interacţiune cu alte componente ale aplicaţiei, inclusiv Frame-ul asociat cu acesta.
În figura de mai jos este exemplificat un astfel de dialog.
6
Figura 11.1 Exemplu de Dialog
Constructor și metode
Primul constructor creează o instanţă de Dialog, fără titlu cu părintele dat de parent. Acest
dialog nu este modal și este redimensionabil.
Al doilea constructor specifică părintele prin parametrul parent. Se poate specifica și faptul că
este sau nu modal. Iniţial, dialogul este redimensionabil.
Al treilea constructor permite specificarea titlului dialogului. Dialogul nu este modal.
Al patrulea constructor este cel mai complex și permite specificarea atât a titlului cât și a
faptului că este sau nu modal.
Cele două metode permit aflarea titlului dialogului curent, respectiv modificarea titlului.
Aceste două metode permit aflarea faptului că dialogul curent este redimensionabil sau nu,
respectiv setarea acestui fapt.
Aceste două metode permit interogarea/setarea faptului că Dialog-ul curent este modal sau nu.
Data viitoare când dialogul este afișat folosind metoda show(), dacă este setat modal, doar el va fi activ,
în caz contrar și alte componente sunt accesibile.
7
import java.awt.*;
interface DialogHandler
{
void dialogCreator (Object o);
}
8
(e.id == Event.ACTION_EVENT))
{
//cand are loc un eveniment este transmis
//parintelui pentru afisarea datelor citite
((DialogHandler)getParent ()).dialogCreator(e.arg);
}
return super.handleEvent (e);
}
}
public class modeFrame extends Frame implements DialogHandler
{
modeTest d;
modeFrame (String s)
{
super (s);
resize (100, 100);
d = new modeTest (this);
d.show ();
}
public static void main (String []args)
{
Frame f = new modeFrame ("Frame");
}
public boolean handleEvent (Event e)
{
if (e.id == Event.WINDOW_DESTROY)
{
hide();
dispose();
System.exit (0);
}
return super.handleEvent (e);
}
public void dialogCreator(Object o)
{
// crearea unui dialog
// in care sunt adaugate ca etichete
// valorile text ale membrilor lui modeTest
d.dispose();
add ("North", new Label (d.user.getText()));
add ("South", new Label (d.pass.getText()));
show ();
}
}
Explicaţiile despre modul cum funcţionează acest program sunt date ca și comentariu. După cum
se poate observa, tratarea evenimentelor și unele modele conceptuale sunt conform modelului Java 1.0,
așa că acest exemplu este mai mult informativ, nu trebuie utilizat la implementare.
9
Mai jos este un exemplu, de asemenea cu explicaţii, în care tratarea evenimentelor și modul de
lucru este mai aproape de cel versiunilor actuale de JDK.
import java.awt.*;
//se renunta la interfata DialogHandler
class modeTest11 extends Dialog
{
TextField user;
TextField pass;
//Crearea GUI al aplicatiei
modeTest11 (Frame parent)
{
super (parent, "Mode Test", true);
add ("North", new Label ("Please enter username/password"));
//adaugarea panel-elor in mod asemanator
Panel left = new Panel ();
left.setLayout (new BorderLayout ());
left.add ("North", new Label ("Username"));
left.add ("South", new Label ("Password"));
add ("West", left);
Panel right = new Panel ();
right.setLayout (new BorderLayout ());
user = new TextField (15);
pass = new TextField (15);
pass.setEchoCharacter ('*');
right.add ("North", user);
right.add ("South", pass);
add ("East", right);
add ("South", new Button ("Okay"));
resize (250, 125);
}
//suprascrierea functiei de tratare a
//evenimentelor de fereastra
public boolean handleEvent (Event e)
{
if (e.id == Event.WINDOW_DESTROY) {
dispose();
return true;
} else if ((e.target instanceof Button) &&
(e.id == Event.ACTION_EVENT)) {
hide();
}
10
{
modeFrame11 (String s)
{
super (s);
resize (100, 100);
}
public static void main (String []args)
{
Frame f = new modeFrame11 ("Frame");
modeTest11 d;
//dialogul este afisat primul
//el este "pornit" de frame
d = new modeTest11 (f);
//f.show ();
//ce se intampla daca as decomenta instructiunea
//de mai sus. De ce nu am acces la frame?
//Pentru ca dialogul este modal.Daca ar fi amodal?
d.show ();
d.dispose();
//dupa ce dialogul este inlaturat
//este afisat frame-ul
f.add ("North", new Label (d.user.getText()));
f.add ("South", new Label (d.pass.getText()));
f.show ();
}
//aici sunt tratate evenimentele frame-ului
public boolean handleEvent (Event e)
{
if (e.id == Event.WINDOW_DESTROY)
{
hide();
dispose();
System.exit (0);
}
return super.handleEvent (e);
}
}
Clasa FileDialog
Aceasta este o subclasă a Dialog-ului ce permite selectarea fișierelor sau salvarea acestora.
FileDialog este întotdeauna un Dialog modal, cu alte cuvinte aplicaţia apelantă va fi blocată până la
selectarea unui fișier sau până la închiderea dialogului de fișier.
11
Metodele clasei FileDialog
Un FileDialog are două moduri: unul pentru încărcarea fișierelor și unul pentru salvarea
acestora. Următoarele variabile descrise oferă posibilitatea specificării acestui mod de lucru. Diferenţa
vizibilă este că pe ecran va apare Load sau Save.
Primul constructor creează un dialog pentru fișiere ce are specificat Frame-ul părinte ca
parametru. Titlul este iniţial gol.
Al doilea constructor permite specificarea și a titlului iniţial.
Al treilea constructor permite specificarea, pe lângă parametrii descriși, și a modului dialogului:
Save sau Load.
Aceste două metode permit preluarea sau setarea a directorului curent în care se va face
căutarea fișierului. Metoda setDirectory trebuie apelată înainte de afișarea dialogului.
Unele dintre cele mai importante metode, acestea permit preluarea numelui fișierului selectat
(în cazul metodei getFile). Metoda setFile permite setarea numelui fișierului selectat implicit la
afișarea dialogului.
Aceste două metode permit preluarea / setarea modului de lucru: Save sau Load.
12
Mai jos avem un exemplu pentru folosirea acestor tipuri de dialog, foarte folositoare în cazul
aplicaţiilor ce lucrează cu fișiere.
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.*;
public class FileDialogTest extends Frame implements ActionListener
{
//zona de editarea textului
TextArea myTextArea;
Label myLabel;
//butonul pentru deshiderea dialocului Load
Button loadButton;
//butonul pentru deshiderea dialocului Save
Button saveButton;
//se construieste fereastra de editare text
FileDialogTest ()
{
super ("File Dialog Tester");
Panel p = new Panel ();
p.add (loadButton = new Button ("Load"));
p.add (saveButton = new Button ("Save"));
//butoanele sunt abonate la evenimentul
//de apasare
loadButton.addActionListener(this);
saveButton.addActionListener(this);
add ("North", myLabel = new Label ());
add ("South", p);
add ("Center", myTextArea = new TextArea (10, 40));
pack();
}
public static void main (String args[])
{
//instantierea ferestrei principale
FileDialogTest f = new FileDialogTest();
//si afisarea acesteia
f.show();
}
public boolean handleEvent (Event e)
{
if (e.id == Event.WINDOW_DESTROY)
{
hide();
dispose ();
System.exit(0);
return true; // never gets here
}
13
return super.handleEvent (e);
}
//cand apare un eveniment
public void actionPerformed(ActionEvent e)
{
//daca sursa evenimentului este de un buton
if (e.getSource() instanceof Button)
{
int state;
String msg;
//daca este cel de Load
if (e.getSource() == loadButton)
{
state = FileDialog.LOAD;
msg = "Load File";
}
else //sau este cel de Save
{// if (e.target == saveButton)
state = FileDialog.SAVE;
msg = "Save File";
}
//Se deschide un dialog de fisier
//cu parintele this
FileDialog file = new FileDialog (this, msg, state);
file.setFile ("*.java"); // filtrul este extensia java
file.show(); // afisarea dialogului si asteptare
String curentFile;
byte[] data = null;
//daca s-a selectat un fisier se va prelua numele lui
if ((curentFile = file.getFile()) != null)
{
//se compune calea completa a fisierului
String filename = file.getDirectory() + curentFile;
setCursor (Frame.WAIT_CURSOR);
//daca dialogul a fost de Load
if (state == FileDialog.LOAD)
{
try
{
//se incearca deschiderea fisierului
//si citirea datelor din el
data =ReadFile(filename);
}
catch (FileNotFoundException exc)
{
System.out.println("File Not Found: " + filename);
}
catch (IOException exc)
14
{
System.out.println("IOException: " + filename);
}
myLabel.setText ("Load: " + filename);
}
else//trebuie sa salvam...
{
//Stergem".*.*" daca apare acest text
//inseamna ca fisierul nu exista
if (filename.indexOf (".*.*") != -1) {
filename = filename.substring (0, filename.length()-4);
}
try
{
//se incearca scrierea in fisier
data = WriteInFile(filename);
}
catch (IOException exc)
{
System.out.println("IOException: " + filename);
}
myLabel.setText ("Save: " + filename);
//numele fisierului este afisat la sfarsit
}
//daca s-a scris sau s-a citit corect
if (data!=null)
//putem plasa acea informatie in casuta de text
myTextArea.setText (new String (data, 0));
//revenim la cursorul initial
setCursor (Frame.DEFAULT_CURSOR);
}
}
}
//metoda citeste din fisierul dat ca parametru
//si returneaza ceea ce a reusit sa citeasca
public byte[] ReadFile(String filename) throws IOException
{
File f = new File (filename);
byte[] data;
FileInputStream fin = new FileInputStream (f);
int filesize = (int)f.length();
data = new byte[filesize];
fin.read (data, 0, filesize);
return data;
}
//metoda scrie din fisierul dat ca parametru
//si returneaza ceea ce a reusit sa scrie
15
public byte[] WriteInFile(String filename) throws IOException
{
byte[] data;
File f = new File (filename);
FileOutputStream fon = new FileOutputStream (f);
String text = myTextArea.getText();
int textsize = text.length();
data = new byte[textsize];
//se scrie in data ce se afla in myTextArea
text.getBytes (0, textsize, data, 0);
fon.write (data);
fon.close ();
return data;
}
}
Aplicaţia permite introducerea într-o fereastră ca cea de mai jos a unui text, care poate fi
“încărcat” dintr-un fișier sau poate salva textul editat într-un fișier. Explicaţiile despre cum funcţionează
acest program se găsesc sub forma unor comentarii inserate în dreptul instrucţiunilor.
Layout
Fiecare Container are un manager de layout ( adică al aspectului componentelor ). Acesta este
responsabil pentru poziţionarea componentelor în cadrul recipientului, indiferent de platformă sau
mărimea ecranului. Practic aceste clase elimină necesitatea de a calcula poziţia în care plasăm o
componentă. Chiar și pentru o componentă simplă cum ar fi un buton, calcularea poziţiei și a
dimensiunilor relative la fereastra curentă, se poate întinde pe zeci poate sute de linii de cod. În
continuare vom descrie câteva managere de aspect care ușurează poziţionarea acestor componente pe
ecran.
Interfața LayoutManager
Această interfaţă definește responsabilităţile unei clase ce va afișa componente din cadrul unui
Container. Sarcina acestei interfeţe este să determine poziţia și mărimea fiecărei componente din cadrul
Container-ului. Nu se apela direct metoda unui LayoutManager, deoarece apelurile acestor clase sunt
ascunse de programatori. Metodele acestei interfeţe se pot suprascrie atunci când implementăm un nou
manager de aspect, diferit de cele oferite de Java.
16
Această metodă este apelată când programul atribuie un nume componentei atunci când o
adaugă Layout-ului (de exemplu când apelează add(String,Component) sau add(Component,Object)).
Metoda va șterge componenta component din memorie. Aceasta este apelată de obicei la
eliberarea Container-ului în care se află componenta.
Interfața LayoutManager2
Odată cu introducerea Java 1.1, au apărut modificări interfeţei mai sus menţionată.
Noua interfaţă rezolvă o problemă care apare atunci când lucrăm cu GridBagLayout. Deși
metoda addLayoutComponent(String, Component), funcţionează bine pentru BorderLayout și
CardLayout, nu poate fi folosită pentru GridBagLayout. Poziţia unei componente într-un GridBagLayout
este controlată de un număr de constrângeri, care sunt încapsulate în obiectul de tip
GridBagConstraints. Pentru a asocia aceste constrângeri, trebuie apelată metoda setConstraints().
Interfaţa LayoutManager2 definește o versiune a metodei addLayoutComponent() ce poate fi
folosită de toţi managerii de aspect, indiferent de constrângerile particulare. Metoda are un parametru
de tip Object care poate reprezenta orice tip de informaţie, cum ar fi tipul de constrângeri de mai sus.
Metoda este apelată când programul asignează constraints componentei comp atunci când
o adaugă layout-ului. Este treaba layout-ului să decidă ce va face cu constraints.
Această metodă returnează mărimea maximă a target în cadrul acestui manager de layout.
17
Anterior, doar mărimea minimă sau cea preferată se putea obţine.
Metodele returnează aliniamentul target de-a lungul axei X respectiv Y. Valoarea returnată
este între 0.0 și 1. Valorile apropiate de 0 înseamnă o poziţionare mai aproape de stânga, dacă vorbim
de prima metodă, în timp ce valorile mai aproape de 1 semnifică o poziţionare mai la dreapta. Dacă ne
referim la a doua metodă, o valoare aproape de 0 înseamnă o poziţionare mai sus, în timp ce o valoare
mai aproape de jos.
Metoda spune managerului de aspect că orice informaţie de layout, care are ca ţintă target
este invalidată.
FlowLayout
Acesta este managerul de Layout implicit pentru un Panel. Un FlowLayout adaugă componente
container-ului curent, pe rânduri, de la stânga la dreapta. Dacă nu se mai găsesc componente în rând, ca
începe un nou rând. Atunci când Container-ul este redimensionat, componentele sunt repoziţionate pe
același principiu.
Metodele FlowLayout
public FlowLayout ()
public FlowLayout (int alignment)
public FlowLayout (int alignment, int hgap, int vgap)
Primul constructor creează un layout de acest tip, folosind setări implicite, aliniament centrat cu
o pauză orizontală și verticală între componente de cinci pixeli. De obicei acest constructor este apelat în
cadrul metodei setLayout():setLayout (new FlowLayout()).
Al doilea constructor permite setarea aliniamentului pentru componentele adăugate. Astfel în
figura de mai jos avem exemple pentru cele trei tipuri de aliniamente.
18
Figura 11.2 FlowLayout ( A) stânga B) centrat C) dreapta)
Cele două metode permit returnarea, respectiv setarea aliniamentului layout-ului curent.
Aliniamentul poate fi una din cele trei constante mai sus menţionate.
Aceste metode permit preluarea respectiv setarea spaţiului vertical și orizontal dintre
componente. După apelul metodei setVgap sau setHgap trebuie apelată metoda de validate() a
Container-ului. Începând cu versiunea 1.2, Java admite alte două constante pentru orientare și anume
TRAILING și LEADING.
19
În exemplul de mai jos se poate înţelege cum se efectuează afișarea componentelor pe rânduri.
Pentru o mai bună înţelegere a acestor aliniamente recomand redimensionarea ferestrei.
import java.awt.*;
import java.awt.event.*;
20
//constructorul Frame-ului
public FlowLayoutDemo(String name)
{
super(name);
addComponentsToFrame();
}
public void addComponentsToFrame()
{
//panelul primeste ca layout experimentLayout
compsToExperiment.setLayout(experimentLayout);
experimentLayout.setAlignment(FlowLayout.TRAILING);
//in acest panel vor sta butoanele din
//partea de jos a ferestrei
Panel controls = new Panel();
controls.setLayout(new FlowLayout());
controls.add(LtoRbutton);
controls.add(RtoLbutton);
//se adauga panelul compsToExperiment in centrul frame-ului
this.add(compsToExperiment, BorderLayout.CENTER);
//se adauga panelul controls in partea de jos a frame-ului
this.add(controls, BorderLayout.SOUTH);
}
private static void createAndShowGUI() {
//se instantiaza si ruleaza fereastra
FlowLayoutDemo frame = new FlowLayoutDemo("FlowLayoutDemo");
frame.pack();
frame.setVisible(true);
}
21
public static void main(String[] args) {
createAndShowGUI();
}
BorderLayout
Acesta este managerul implicit pentru un Window. Oferă un mod ușor de a poziţiona
componentele conform unor limite ale ferestrei, de unde vine și numele.
Următorul apel: setLayout(newBorderLayout() modifică managerul de aspect astfel încât layout-
ul să fie unul de tip BorderLayout.
Constantele BorderLayout
Aceste constante vor indica poziţia cardinală a locului unde va fi plasat pe Container, noua
componentă.
Metodele BorderLayout
public BorderLayout ()
public BorderLayout (int hgap, int vgap)
22
În general metodele acestui tip de Layout sunt aceleași ca și în cazul FlowLayout, diferă doar
comportamentul aspectului și anume faptul a acest Layout poziţionează pe regiuni cardinale,
componentele pe Container-ul pe care îl tratează.
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
23
button = new Button("Button 2 (CENTER)");
button.setPreferredSize(new Dimension(200, 100));
pane.add(button, BorderLayout.CENTER);
Mai există o serie de alte clase Layout: GridLayout ,CardLayout ce pot fi studiate mai departe. Aceste
Layout-uri pot fi utilizate în combinaţie, în funcţie de context. Iată un mic exemplu de cum putem
combina mai multe aspecte:
import java.awt.*;
public class ManyLayouts extends java.applet.Applet
{
public void init()
{
Panel s = new Panel();
Panel e = new Panel();
setLayout (new BorderLayout ());
add ("North", new Label ("Enter text", Label.CENTER));
add ("Center", new TextArea ());
e.setLayout (new GridLayout (0,1));
e.add (new Button ("Reformat"));
e.add (new Button ("Spell Check"));
e.add (new Button ("Options"));
add ("East", e);
24
s.setLayout (new FlowLayout ());
s.add (new Button ("Save"));
s.add (new Button ("Cancel"));
s.add (new Button ("Help"));
add ("South", s);
}
}
Vom discuta în continuare despre trei componente AWT: Choice, List și CheckBox. Toate cele trei
clase implementează interfaţa ItemSelectable. Choice și List sunt similare, ambele oferă posibilitatea
alegerii unui obiect dintr-o listă. Tipul de listă este însă diferit, Choice fiind o list de tip pull-down iar List
fiind una pe care poate fi efectuată acţiunea de scroll.
Metodele Choice
public Choice ()
Există un singur constructor pentru această clasă. Componenta, la creare, este iniţial goală. La
adăugarea unui item, folosind addItem() sau add(), atunci acesta poate fi vizualizat și selectat.
Această metodă returnează textul unui item de la poziţia dată de index. Dacă index este mai
mic ca zero sau mai mare ca numărul de elemente selectabile atunci va fi aruncată excepţia
ArrayIndexOutOfBoundsException.
Aceste metode permit adăugarea unui nou element în lista Choice-ului. Dacă parametrul este un
null, atunci va fi aruncată excepţia NullPointerException.
Metoda permite adăugarea unui nou element la poziţia indicată de index. Dacă index este
ilegal, atunci va fi aruncată excepţia IllegalArgumentException..
25
public synchronized void remove (String item)
public synchronized void remove (int position)
public synchronized void removeAll ()
Metodele permit ștergerea unui element sau a tuturora din lista de elemente disponibile. Prima
metodă permite selectarea elementului de șters, după numele dat ca parametru.
A doua metodă permite ștergerea de la o anumită poziţie, atâta timp cât este una validă.
Ultima metodă va șterge toate elementele din lista Choice.
Metoda getSelectedItem() returnează elementul selectat, sub forma unui String. Dacă
Choice este goală funcţia va returna null.
Metoda permite aflarea elementului selectat sub forma unui șir de Object. Metoda este impusă
de interfaţa ItemSelectable, și este folositoare cu adevărat în cazul clasei List.
Metoda returnează poziţia din listă a elementului selectat. Primul element se află pe poziţia 0.
Același efect este obţinut doar că elementul este specificat prin numele său.
Evenimentele Choice
Evenimentul cel mai des întâlnit este cel de selectarea a unui element. Aceasta poate fi captat
printr-un ItemListener. De asemenea se pot folosi evenimente de mouse sau tastatură.
Metodele sunt apelate atunci când utilizatorul apasă o tastă și focusul este pe Choice. Prin
obiectul e de tip Event se poate interoga date specifice despre eveniment, iar variabila key poate furniza
date despre tasta apăsată.
26
public void addItemListener(ItemListener listener)
public void removeItemListener(ItemListener listener)
Poate cele mai importante metode, permit inregistrarea/deînregistrarea unui obiect de tip
Choice la/de la ascultarea evenimentelor de modificare a acestui obiect.
Metoda este apelată atunci când un eveniment de tip AWTEvent are loc. Suprascrierea acestei
este echivalentă cu suprascrierea handleEvent() din vechea versiune Java.
În exemplul de mai jos se poate observa adăugarea unor elemente în lista Choice, precum și
tratarea evenimentelor de selecţie ce apar.
import java.awt.*;
import java.applet.*;
import java.awt.event.*;
class MyChoice extends Choice
{
MyChoice ()
{
super ();
//trebuie sa
//activam ascultarea evenimentelor
enableEvents (AWTEvent.ITEM_EVENT_MASK);
}
//suprascriem processItemEvent
protected void processItemEvent(ItemEvent e)
{
//atunci cand are loc un eveniment
ItemSelectable ie = e.getItemSelectable();
System.out.println ("Item Selected: " + ie.getSelectedObjects()[0]);
super.processItemEvent (e);
}
}
public class ChoiceDemo extends Applet implements ItemListener
{
Choice c;
public void init ()
{
String []fonts;
//preluam fonturile existente pe sistem
//pentru a fi incarcare in Choice
fonts = Toolkit.getDefaultToolkit().getFontList();
c = new MyChoice();
for (int i = 0; i < fonts.length; i++)
{
//metoda de adaugare de elemente
//in ComboList
27
c.add (fonts[i]);
}
//componenta Choice este adaugata Applet-ului
add (c);
//ascultam un eveniment de selectie
c.addItemListener (this);
}
//ce se intampla cand selectam un element
public void itemStateChanged(ItemEvent e)
{
ItemSelectable ie = e.getItemSelectable();
System.out.println ("State Change: " + ie.getSelectedObjects()[0]);
}
}
Liste
Clasa List oferă un mod de a afișa o secvenţă de elemente, din care utilizatorul poate selecta
unul sau mai multe. În mod normal, un scrollbar este asociat acestei componente.
Metode
public List ()
public List (int rows)
public List (int rows, boolean multipleSelections)
Primul constructor creează o listă goală cu patru linii vizibile. Utilizatorul poate selecta în acest
caz, un singur element.
Al doilea constructor permite specificarea numărului de linii vizibile, prin parametrul rows.
Al treilea constructor permite de asemenea specificarea numărului de linii, și totodată,
specificarea faptului că utilizatorul poate selecta mai multe elemente sau nu.
Cele două metode returnează numărul de elemente din listă. A doua metodă este numele
metodei din versiunea veche.
28
Nu contează dacă elementul este sau nu selectat.
Primele două metode permit adăugarea unui item la sfârșitul listei. Ultimele două elemente
permit adăugarea unui element pe o poziţie specificată de index. Dacă indexul este mai mic ca zero sau
mai mare ca numărul de elemente de listă, elementul este adăugat la sfârșit.
Prima metodă permite ștergerea unui element după numele său, iar celelalte două permit
ștergerea unui element după poziţia lui în listă.
Aceste metode sunt corespondentele metodelor de mai sus, returnând numele elementelor
selectate.
Metoda returnează elementele selectate sub forma unui șir de obiecte, conform interfeţei
ItemSelectable. Dacă nici un item nu este selectat, șirul este gol.
29
Dacă lista este setată pe selecţie unică, la apelul metodei select(), elementul anterior selectat
este automat deselectat. Dacă lista este de tipul multiselecţie, atunci metoda select() nu are acest
efect. Metoda deselect() deselectează elementul de la poziţia index.
Metodele permit interogarea unui element cu privire la faptul că este sau nu selectat.
Metodele returnează starea curentă a listei, daca este de tipul multiselecţie sau nu.
Metodele permit schimbarea stării curente a listei: dacă valoarea parametrului este true atunci
lista va trece în mod multiselecţie, dacă valoarea parametrului este false, atunci lista va fi cu selecţie
unică.
Metoda returnează numărul de rânduri care a fost transmis la instanţierea listei. Nu returnează
numărul de rânduri vizibile.
Metodele returnează dimensiunea preferată a listei. Pentru a calcula această dimensiune, se vor
folosi numărul de rânduri specificate la instanţierea listei.
Evenimentele listei
30
Metoda este apelată atunci când utilizatorul efectuează un dublu-click pe un item din listă, iar o
reprezintă acel obiect. Dacă lista este de tip multiselecţie, prinderea acestui eveniment poate duce la
confuzii din cauză că nu este clar dacă utilizatorul a vrut să selecteze elementul sau selecţia se referă la
toate elementele alese. Următorul exemplu prezintă un mod de a rezolva această situaţie, prin
implicarea unui alt element și anume un buton.
import java.awt.*;
import java.applet.*;
public class ListDemo extends Applet
{
List l;
public void init ()
{
String fonts[];
//se preiau fonturile din sistem
fonts = Toolkit.getDefaultToolkit().getFontList();
//se creaza o lista cu multiselectie
l = new List(4, true);
for (int i = 0; i < fonts.length; i++)
{
//se adauga elementele in lista
l.add(fonts[i]);
}
setLayout (new BorderLayout (10, 10));
//se adauga componentele pe fereastra
add ("North", new Label ("Pick Font Set"));
add ("Center", l);
add ("South", new Button ("Submit"));
resize (preferredSize());
validate();
}
public boolean action (Event e, Object o)
{
// la apasarea butonlui
if (e.target instanceof Button)
{
//se preiau elementele selectate
String chosen[] = l.getSelectedItems();
for (int i=0;i<chosen.length;i++)
//pentru a fi afisate
System.out.println (chosen[i]);
}
return false;
}
}
31
Modelul nou de tratare a evenimentelor implică folosirea unor ascultători, iar pentru aceasta
avem metodele de abonare/dezabonare la evenimentele de tip item.
32
//se preia indexul acestuia
Selection = Selector.getSelectedIndex();
//si in functie de elementul selectat
//se modifica fundalul formei
if (Selection == 0) {
setBackground (Color.red);
} else if (Selection == 1) {
setBackground (Color.magenta);
} else if (Selection == 2) {
setBackground (Color.blue);
} else if (Selection == 3) {
setBackground (Color.cyan);
} else if (Selection == 4) {
setBackground (Color.green);
} else if (Selection == 5) {
setBackground (Color.yellow);
} else if (Selection == 6) {
setBackground (Color.white);
} else if (Selection == 7) {
setBackground (Color.gray);
} else if (Selection == 8) {
setBackground (Color.black);
}
}
}
Meniuri
Mai sus, am menţionat că un Frame poate avea un meniu. Pentru a asigura un meniu aplicaţiei,
acesta va sta într-un Frame. Implementarea unui meniu în cadrul unui Frame, implică o îmbinare a mai
multor elemente și anume: MenuBar, Menu, MenuItem.
Pentru a afișa un meniu, acesta va fi pus într-un MenuBar, care la rândul lui va fi adăugat unui
Frame. Un meniu poate conţine un MenuItem, dar poate conţine și alte meniuri care formează
submeniurile sale. Componentele MenuItem sunt materialele construite ca și panel-ele care formează
cortinele. Cortina este Menu. Meniurile sunt atașate de un MenuBar. Această bară de meniuri va sta de
obicei în partea de sus a Frame-ului ca în figura de mai jos:
În figura de mai jos avem toate aceste componente plasate in ierarhia AWT.
33
Figura 11.5 Ierarhia componentelor de meniu
MenuComponent
Aceasta este o clasă abstractă ce reprezintă părintele obiectelor legate de meniuri. Nu se va crea
niciodată o instanţă a acestei clase, ci se poate eventual moșteni. Utilitatea acesteia, este aceea de a
conţine o serie de metode valabile pentru celelalte componente de care vom vorbi.
34
public Font getFont ()
public void setFont (Font f)
Cele două metode permit preluarea/setarea fontului asociat unei componente de meniu. Atunci
când se creează o componentă de meniu, fontul iniţial ala acesteia este null.
MenuContainer
Aceasta este o interfaţă implementată de patru tipuri de Container-e și anume Frame, Meniu și
MenuBar și Container.
Metodele
35
Metoda permite ștergerea componentei specificată parametru din obiectul clasei ce
implementează această metodă.
MenuShortcut
Aceasta este o clasă ce reprezintă o scurtătură de taste pentru un MenuItem. Atunci când au loc
aceste evenimente, un eveniment de acţiune este generat astfel încât el declanșează acţiunea unei
componente de meniu.
Metode
Constructorul creează un obiect MenuShortcut cu cheia key. Această cheie reprezintă valoarea
întreagă returnată de evenimentul KEY_PRESS.
Acest constructor permite, în plus, specificarea faptului că este folosită o combinaţie de taste
gen Shift sau nu.
Această metodă specifică faptul că obiectul MenuShortcut impune ca tasta Shift să fie apăsată
sau nu.
MenuItem
Metodele
public MenuItem ()
public MenuItem (String label)
public MenuItem (String label, MenuShortcut shortcut)
Prima metodă creează un item de meniu cu o etichetă goală și nici o scurtătură de taste.
Al doilea constructor creează un meniu ce permite specificarea unei etichete, în timp ce al
treilea mai adaugă și o scurtătură de taste.
36
public String getLabel ()
public void setLabel (String label)
Metoda isEnabled verifică dacă item-ul de meniu este activat. Un item activat poate fi selectat
de utilizator, iar unul dezactivat apare scris cu gri.
Metoda setEnabled activează/dezactivează item-ul de meniu, conform parametrului.
37
Menu
Acestea sunt obiectele care conţin elementele de meniu descrise mai sus.
Metodele
public Menu ()
public Menu (String label)
public Menu (String label, boolean tearOff)
Primul constructor permite instanţierea unui meniu care nu are etichetă și care nu poate fi oprit.
Al doilea constructor permite instanţierea unui meniu cu etichetă, care nu poate fi oprit.
Al treilea constructor creează un meniu cu eticheta label dar ce poate fi oprit sau nu. Aceasta
se va seta folosind al treilea parametru.
Metoda returnează elementul de la poziţia dată de index. Dacă index este invalid, atunci
metoda aruncă excepţia ArrayIndexOutOfBoundsException.
Această metodă permite adăugarea unui obiect item în meniu. Eticheta asociată cu item este
afișată în meniu. Dacă item se află deja într-un alt meniu atunci este șters de acolo.
Metoda permite crearea unui item de meniu cu eticheta label și adăugarea acestuia la meniu.
Metodele permit adăugarea unui item la indexul specificat. Dacă indexul nu este valid atunci va
fi aruncată excepţia IllegalArgumentException.
38
Meniul nu va genera un eveniment atunci când este selectat, evenimentul fiind generat de
selectarea unui MenuItem.
MenuBar
Metodele
public MenuBar()
Constructorul permite crearea unui ManuBar gol. Pentru a adăuga meniuri acestuia se va folosi
metoda add().
Metoda returnează meniul de la poziţia index. Dacă index nu este valid, metoda aruncă
excepţia ArrayIndexOutOfBoundsException.
Metoda permite adăugarea unui meniu m pe bara de meniuri. Eticheta care a fost folosită pentru
crearea lui m va fi afișată pe bara de meniuri. Dacă m se găsește pe o bară de meniuri deja, el va fi șters.
Metoda remove șterge componenta de la poziţia index, dacă este vorba de prima metodă. În
cazul celei de-a doua metodă se șterge meniul component din bara de meniuri.
Metoda returnează MenuItem asociat cu scurtătura de taste shortcut. Dacă acest item de
meniu nu există, se returnează null.
39
Pentru a afla scurtăturile de taste din obiectele asociate barei de meniuri actuale, avem metoda
shortcuts().
Metoda returnează meniul ce a fost desemnat ca meniu de ajutor prin intermediul celei de-a
doua metode. Metoda setHelpMenu setează meniul de ajutor, și anume îl va deplasa maxim la stânga
pe m.
În exemplul de mai jos sunt cuprinse toate elementele prezentate mai sus.
import java.awt.*;
import java.awt.event.*;
//clasa proprie de item de meniu
class MyMenuItem extends MenuItem
{
//in care adaugam un ascultator
//obiectului al
//care va fi unul de tip MenuDemo
public MyMenuItem (String s, ActionListener al)
{
super (s);
addActionListener (al);
}
}
public class MenuDemo extends Frame implements ActionListener, ItemListener
{
public MenuDemo ()
{
super ("MenuTest");
MenuItem mi;
//se creeaza meniu file
Menu file = new Menu ("File", true);
//caruia is se adauga un meniu Open
file.add (new MyMenuItem ("Open", this));
//si un meniu Close (care este dezactivat)
mi = file.add (new MyMenuItem ("Close", this));
mi.setEnabled (false);
//cream un submeniu Extras cu trei meniuri
Menu extras = new Menu ("Extras", false);
//unul de tip checkbox
CheckboxMenuItem ck = new CheckboxMenuItem ("What");
ck.addItemListener(this);
mi = extras.add (ck);
//iar celelalte normale ( A1 si A2)
mi = extras.add (new MyMenuItem ("Second submenu (A1)", this));
40
mi.setActionCommand ("A1");
mi = extras.add (new MyMenuItem ("Third submenu (A2)", this));
mi.setActionCommand ("A2");
file.add (extras);
//se adauga o linie de separatie
file.addSeparator();
//si ultimul meniu Quit
file.add (new MyMenuItem ("Quit", this));
//dupa meniul file, barei de meniuri
//i se adauga meniul help
Menu help = new Menu("Help");
//cu o optiune, About
help.add (new MyMenuItem ("About", this));
MenuBar mb = new MenuBar();
//adugam barei de meniuri cele doua meniuri
mb.add (file);
mb.add (help);
mb.setHelpMenu (help);
//si bara o atasam frame-ului
setMenuBar (mb);
setSize (200, 200);
enableEvents (AWTEvent.WINDOW_EVENT_MASK);
}
//atunci cand apasam unul din meniuri
public void actionPerformed(ActionEvent e)
{
if (e.getActionCommand().equals("Quit"))
{
System.exit(0);
}
//afisam selectia efectuata
System.out.println ("User selected " + e.getActionCommand());
}
public void itemStateChanged(ItemEvent e)
{
//daca meniul este de tip selectabil CheckBoxmenu
if (e.getSource() instanceof ItemSelectable)
{
ItemSelectable is = (ItemSelectable)e.getSource();
System.out.println ("The value is: " +
(is.getSelectedObjects()!=null));
}
}
protected void processWindowEvent(WindowEvent e)
{
if (e.getID() == WindowEvent.WINDOW_CLOSING)
{
41
//notificam ascultatorii de eveniment
super.processWindowEvent(e);
//si se inchide procesul
System.exit(0);
}
else
{
super.processWindowEvent(e);
}
}
public static void main (String []args)
{
MenuDemo f = new MenuDemo();
f.setVisible(true);
}
}
42
Curs de Java si structuri de date
Cuprins:
1. Introducere in Java
2. Clase si pachete
3. Polimorfism
4. Colectii
5. Exceptii si IO
6. Interfete ,clase abstracte
7. Aplicarea cunostintelor in rezolvarea unor proiecte
8. Multithreading
9. AWT / Swing
Ce este Java?
JVM – Masina
virtuala Java
Java ruleaza pe orice masina ce suporta JVM
Ruleaza pe orice sistem
Sigur
Flexibil, dinamic
Performant
Ce poate oferi Java?
Un prim exemplu
/*
Acesta este primul program
Ce va fi scris intr-un fisier Example.java
*/
class Example {
// Orice program Java incepe cu main().
public static void main(String args[]) {
System.out.println("Hello Java!");
}
}
Pasii de baza:
•Scrierea programului.
•Compilarea acestuia.
•Rularea programului.
Erori de sintaxa
Example.java: 7 ’;’ expected
Public static void main(String[] args)
Example.java:10: class, interface or enum
expected
}
^
2 errors
Erori de runtime
System.out.println(args[1]);
Operatori aritmetici
&& op1 && op2 Returneaza true daca op1 and op2 sunt true; evalueaza
conditional op2
|| op1 || op2 Returneaza true daca op1 sau op2 este true; evalueaza
conditional op2
! !op Returneaza true daca op este false
& op1 & op2 Returneaza true daca op1 si op2 sunt boolean si amandoi
sunt true; evalueaza intotdeauna op1 si op2
| op1 | op2 Returneaza true daca ambii op1 si op2 sunt de tip
boolean, si atat op1 cat si op2 sunt true; evalueaza
intotdeauna op1 si op2
^ op1 ^ op2 Returneaza true daca op1 si op2 sunt diferiti, adica un
XOR pe biti
Operatori de siftare
Operator Folosire Descriere
<< op1 << op2 Siftarea bitilor lui op1 la stanga cu o lungime data de
op2; umple cu zero bitii din partea dreapta
>> op1 >> op2 Siftarea bitilor lui op1 la dreapta cu o lungime data de
op2; umple cu bitul cel mai semnificativ (de semn) bitii
din partea stanga
>>> op1 >>> op2 Siftarea bitilor lui op1 la dreapta cu o lungime data de
op2; umple cu zero bitii din partea stanga
Operatori de prescurtare
Operator Folosire Echivalent
Scurtaturi pentru operatorii += op1 += op2 op1 = op1 + op2
aritmetici -= op1 -= op2 op1 = op1 - op2
*= op1 *= op2 op1 = op1 * op2
/= op1 /= op2 op1 = op1 / op2
%= op1 %= op2 op1 = op1 % op2
Scurtaturi pentru operatorii pe biti &= op1 &= op2 op1 = op1 & op2
|= op1 |= op2 op1 = op1 | op2
^= op1 ^= op2 op1 = op1 ^ op2
Scurtaturi pentru siftare <<= op1 <<= op2 op1 = op1 << op2
>>= op1 >>= op2 op1 = op1 >> op2
>>>= op1 >>>= op2 op1 = op1 >>> op2
Operator Descriere
?: Scurtatura la o anumita instructiune if-else
Operatori de multiplicare * / %
Operatori aditivi + -
Operatori de siftare > >>>
Operatori relationali < > <= >= instanceof
Operatori de egalitate == !=
SI pe biti &
XOR pe biti ^
SAU pe biti |
SI logic &&
SAU logic ||
Scurtatura if-else ?:
Precedenta cea mai asignare = += -= *= /= %= &= ^= |=
mica <<= >>= >>>=
Tipul de instructiune Cuvinte cheie
Bucla while, do-while, for
De decizie if-else, switch-case
Tratarea erorilor try-catch-finally, throw
Ramificare break, continue, label :, return
while
while (expresie) {
... lista de instructiuni
}
... instructiuni dupa blocul while
Exemplu
int i=0;
while (i<10)
{
System.out.println(i++);
}
System.out.println("Dupa while");
for
if (response == OK)
{ // code to perform OK action }
else
{ // code to perform Cancel action}
switch ( n )
{
case 1: System.out.println( "one" ); break;
case 2: System.out.println( "two" ); break;
default: System.out.println( "something else" );
} // end switch (n)
Capitolul 2
Alte instructiuni
Introducere in OOP
Pachete
class BreakSample {
public static void main(String args[]) {
int num;
num = 10;
// continua la "nesfarsit", conditia de continuare nu este
for(int i=0; ; i++) {
if(i >= num) break; // i a ajuns la 10, se iese din
bucla
System.out.print(i + " ");
}
System.out.println("S-a terminat bucla.");
}
}
char ch;
for( ; ; ) {
ch = (char) System.in.read(); // citesc un caracter
if(ch == 'q') break;
}
class ContinueSample
{
public static void main(String args[])
{
int i;
// afisez numere pare intre 0 si 50
for(i = 0; i<=50; i++)
{
if((i%2) != 0) continue; // daca nu e par trec la
//urmatorul pas din for
System.out.println(i);
}
}
}
Obiecte
Bicicleta
class numeclasa
{
//declar variabilelor clasei, ce vor fi disponibile in fiecare
instanta
type var1;
type var2;
// ...
type varN;
// declare metodele
type metoda1(parameteii)
{
//corpul metodei 1
}
type metoda2(parameterii)
{
//corpul metodei 2
}
// ...
type metodaN(parameterii)
{
//corpul metodei N
}
}
Automobil
class Automobil
{
String model; //combi, SUV, MCV, caminon etc
int consum; // 5 .. 15
int viteza; //viteza maxima ce poate fi atinsa: 180 .. 340
}
public class AutomobilDemo
{
public static void main(String[] args)
{
Automobil minivan = new Automobil();
minivan.model = "Logan MCV";
minivan.consum =10;
minivan.viteza = 180;
System.out.println(minivan.model + " are un consum de "
+ minivan.consum + " si viteza maxima " +
minivan.viteza);
}
} main()
AutomobilDemo
Automobil minivan = new Automobil();
Automobil masinasport = new Automobil();
minivan.model = "Logan MCV";
minivan.viteza = 180;
masinasport.model = "BMW";
masinasport.viteza = 320;
System.out.println(minivan.model +" are viteza maxima " +
minivan.viteza);
System.out.println(masinasport.model +" are viteza maxima
" + masinasport.viteza);
masina sport
minivan
Automobil
Automobil
Automobil masina1 = new Automobil();
Automobil masina2 = masina1;
Automobil masina3 = new Automobil;
masina2 = masina3;
masina2
masina1 Automobil
masina3
Automobil
Automobil
Functii
tip_de_returnat nume(lista de parametrii)
{
//corpul metodei
}
class Automobil
{
String model; //combi, SUV, MCV, caminon etc
int consum; // 5 .. 15
int viteza; //viteza maxima ce poate fi atinsa: 180 .. 340
}
public class MethodDemo
{
public static void main(String[] args)
{
Automobil minivan = new Automobil();
Automobil masinasport = new Automobil();
minivan.model = "Logan MCV";
minivan.viteza = 180;
masinasport.model = "BMW";
masinasport.viteza = 320;
PrintProperties(minivan);
PrintProperties(masinasport);
}
static void PrintProperties(Automobil vehicul)
{
//verificam sa nu avem obiecte null
//altfel avem eroare la accesarea membrilor lui
if (vehicul == null)
{
System.out.println("Introduceti un obiect instantiat!");
return;
}
System.out.println("Masina "+ vehicul.model + " are viteza maxima " +
vehicul.viteza);
}
}
Rectangle
width = 10 width=12
GerPerimeter
height = 7
R1 height = 20
width
height
R2
GetArea
class Rectangle
{
public int width; //latimea dretunghiului
public int height; //inaltimea dreptunghiului
static int GetArea(Rectangle aRectangle)
{
if (aRectangle == null)
{
System.out.println("Introduceti un obiect instantiat!");
return -1;
}
return aRectangle.width*aRectangle.height;
}
static int GetPerimeter(Rectangle aRectangle)
{
if (aRectangle == null)
{
System.out.println("Introduceti un obiect instantiat!");
return -1;
}
return 2*(aRectangle.width+aRectangle.height);
}
}
public class MethodDemo
{
public static void main(String[] args)
{
//exemplu pentru doua dreptunghiuri diferite
Rectangle myRectangle = new Rectangle();
Rectangle myOtherRectangle = new Rectangle();
myRectangle.width = 10;
myRectangle.height = 7;
myOtherRectangle.width = 12;
myOtherRectangle.height = 20;
Rectangle
GerPerimeter
width
DemoClass
height
static GetArea
main
GetPerimeter
R1
class Numbers
{
int GetSumofTwoInt(int a, int b)
{
return a+b;
}
int GetSumofThreeInt(int a, int b, int c)
{
return a+b+c;
}
double GetSumofTwoDouble(double a, double b)
{
return a+b;
}
}
public class MoreParameters
{
public static void main(String[] args)
{
Numbers number = new Numbers();
System.out.println("Suma 6 + 7 = " + number.GetSumofTwoInt(6,7));
System.out.println("Suma 6 + 7 +8 = " +
number.GetSumofThreeInt(6,7,8));
System.out.println("Suma 6.6 + 7.2 = " +
number.GetSumofTwoDouble(6.6,7.2));
}
}
Constructori
class Automobil
{
String model; //combi, SUV, MCV, caminon etc
int consum; // 5 .. 15
int viteza; //viteza maxima ce poate fi atinsa: 180 .. 340
//Acesta este constructorul clasei Automobil apelat la instantierea
//unui nou obiect de tip Automobil
public Automobil()
{
model = "Necunoscut";
consum = 0;
viteza = 0;
}
}
...
public class AutomobilDemo
{
public static void main(String[] args)
{
Automobil minivan = new Automobil(); //Acesta este locul in care
//apelam
constructorul
....
//constructor cu un parametru
public Automobil(String modelulinitial)
{
model = modelulinitial;
consum = 0;
viteza = 0;
}
//constructor cu doi parametrii
public Automobil(String modelulinitial, int vitezainitala)
{
model = modelulinitial;
consum = 0;
viteza = vitezainitala;
}
GC
R1
protected void finalize()
{
//codul la distrugerea obiectului
}
class Finalize
{
public static void main(String args[])
{
int count;
FinalizeDemo ob = new FinalizeDemo(0);
/* Generam un numar mare de obiecte, care apoi vor fi
distruse de GC la un moment dat*/
for(count=1; count < 100000; count++)
ob.generator(count);
}
}
class FinalizeDemo
{
int x;
FinalizeDemo (int i)
{
x = i;
}
//apelat de GC
protected void finalize()
{
System.out.println("Finalizarea lui " + x);
}
// genereaza un obiect care este imediat distrus
void generator(int i)
{
FinalizeDemo o = new FinalizeDemo(i);
}
}
this
class Power
{ Power(double base, int exp)
double b;
int e;
double val;
Power(double base, int exp)
{ get_power
this.b = base;
this.e = exp; Power
this.val = 1;
if(exp==0) return;
for( ; exp>0; exp--)
this.val = this.val * base; n
} e
double get_power() w
{
return this.val;
}
} this
class DemoPower
{
public static void main(String args[])
{
Power x = new Power(3, 2);
System.out.println(x.b + " la " + x.e +
" este " + x.get_power());
}
}
Pachete
package pack1.pack2.pack3;
pack2
pack3
import pachet.MyClass;
import pachet.*;
A
B
my package
package BookPack;
class Book
{
private String title;
private String author;
Book(String t, String a)
{
title = t;
author = a;
}
void show()
{
System.out.println(title);
System.out.println(author);
}
}
class BookDemo
{
public static void main(String args[])
{
javac BookDemo.java
java BookPack.BookDemo
Pachete in Java
•Supraincarcarea metodelor
•Recursivitate
•Parametrii variabili
•Bazele mostenirii
•Specificatori de acces
•Constructorii in mostenire
•super
•Suprascrierea metodelor
•Object
Supraincarcarea metodelor
variabile
Supraincarcare
class Supraincarcare
{
void functieSupraincarcata()
{
System.out.println("Fara parametrii");
}
void functieSupraincarcata(int unparam)
{
System.out.println("Un parametru: " + unparam);
}
int functieSupraincarcata(int a, double b)
{
System.out.println("Doi parametrii: " + a + " " + b);
return a + (int)b;
}
}
public class DemoSupraincarcare
{
public static void main(String args[])
{
Supraincarcare obj = new Supraincarcare();
//Apelul functiei fara nici un parametru
obj.functieSupraincarcata();
//Apelul functiei cu un parametru de tip int
obj.functieSupraincarcata(5);
//Apelul functiei cu doi parametrii
int i = obj.functieSupraincarcata(5,5.7);
System.out.println(i);
}
}
int double
class Supraincarcare
{
void functieSupraincarcata(int b)
{
System.out.println("Un parametru: " + b);
}
int functieSupraincarcata(int a)
{
System.out.println("parametrul int: " + a);
return a*a;
}
}
public class DemoNoSupraincarcare
{
public static void main(String args[])
{
Supraincarcare obj = new Supraincarcare();
//Apelul functiei cu un parametru de tip int
obj.functieSupraincarcata(5);
int i = obj.functieSupraincarcata(7);
System.out.println(i);
}
}
variabile
Numbers
public class OverloadingDemo
{ class Numbers
public static void main(String[] args) {
{ int m_int;
Numbers number1 = new Numbers(); short m_short;
Numbers number2 = new Numbers(1); double m_double;
Numbers number3 = new Numbers(1.1); float m_float;
Numbers number4 = new Numbers(2,3); Numbers()
Numbers number5 = new Numbers(2,4.4); {
System.out.println(number1); m_int = 0;
m_double =0;
System.out.println(number2); m_short = 0;
m_float =0;
System.out.println(number3); }
Numbers(double val)
System.out.println(number4); {
m_double = val;
System.out.println(number5); m_int = 0;
m_short = 0;
m_float =0;
} }
Numbers(float val) Numbers(short val)
} { {
m_float = val; m_short = val;
m_int = 0; m_int = 0;
m_double =0; m_double =0;
m_short = 0; m_float =0;
} }
Numbers(int ival, double dval)
{
m_int = ival;
m_double = dval;
m_short = 0;
m_float =0;
}
Numbers(int ival, float fval)
{
m_float = fval;
m_int = ival;
m_double =0;
m_short = 0;
}
}
int = 0 short = 0 double = 0.0 float = 0.0
int = 0 short = 0 double = 0.0 float = 1.0
int = 0 short = 0 double = 1.1 float = 0.0
int = 2 short = 0 double = 0.0 float = 3.0
int = 2 short = 0 double = 4.4 float = 0.0
factR(3)
Clasa Factorial
Parametrii variabili
corect: TestParam(1,2,3);
incorect: TestParam(1,2.5,3);
class TestParam
{
static void TestParam(String amesage, double adouble,int ... p)
{
System.out.println("Numarul argumentelor: " + p.length);
for(int i=0; i < p.length; i++)
System.out.println(" argumentul " + i + ": " + p[i]);
System.out.println();
}
public static void main(String args[])
{
TestParam("Mesaj1",2.3,3,4);
}
}
Bazele mostenirii
class Forma2D
{
double inaltime;
double latime;
void AfisezDimensiunile()
{
System.out.println("inaltimea este " +
inaltime + " latimea este " + latime);
}
}
class Triunghi extends Forma2D
{
String tip_triunghi;
double CalcArea()
{
return inaltime* latime/ 2;
}
void AfisezTip()
{
System.out.println("Triunghiul este " + tip_triunghi);
}
}
class DemoMostenire{
public static void main(String args[])
{
Triunghi t1 = new Triunghi ();
Triunghi t2 = new Triunghi ();
t1.latime = 8.0;
t1.inaltime = 4.0;
t1.tip_triunghi = "dreptunghic";
t2.latime = 4.0;
t2.inaltime = 6.0;
t2.tip_triunghi = "isoscel";
System.out.println("Informatiile despre t1: ");
t1.AfisezTip();
t1.AfisezDimensiunile();
System.out.println("Aria " + t1.CalcArea());
System.out.println();
System.out.println("Informatiile despre t2: ");
t2.AfisezTip();
t2.AfisezDimensiunile();
System.out.println("Aria " + t2.CalcArea());
}
}
class Forma2D
{
double inaltime;
double latime;
void AfisezDimensiunile()
{
System.out.println("inaltimea este " +
inaltime + " latimea este " + latime);
}
}
class Dreptunghi extends Forma2D
{
double CalcArea()
{
return inaltime* latime;
}
void AfisezTipDreptunghi()
{
if (latime == inaltime)
System.out.println("Dreptunghiul este patrat");
else
System.out.println("Dreptunghiul este oarecare");
}
}
class DemoMostenire{
public static void main(String args[])
{
Dreptunghi d1 = new Dreptunghi ();
Dreptunghi d2 = new Dreptunghi ();
d1.latime = 4.0;
d1.inaltime = 4.0;
d2.latime = 4.0;
d2.inaltime = 6.0;
System.out.println("Informatiile despre d1: ");
d1. AfisezTipDreptunghi ();
d1.AfisezDimensiunile();
...
Specificatori de acces
class BaseClass
{
public int i;
private int j;
protected int k;
void PrintMyVariables()
{
//membrii proprii
System.out.println(i);
System.out.println(j);
System.out.println(k);
}
}
class AnyClass
class SubClass extends BaseClass {
{ float f;
double d; //Atentie in cadrul acestei clase
void PrintVariables() //nu am acces direct asupra membrilor
{ //celorlalte clase pentru ca nu exista
//membrul propriu //relatie de mostenire, ci prin instantiere
System.out.println(d); //vom accesa membrii claselor
//membrii lui BaseClass void PrintVariablesOfBaseClass(BaseClass obj)
System.out.println(i); {
System.out.println(k); System.out.println(obj.i);
} //Acest apel ar duce la eroare
} //System.out.println(obj.j);
}
class DemoAcces }
{
public static void main(String args[])
{
BaseClass objB = new BaseClass();
objB.i = 20;
objB.PrintMyVariables();
SubClass objS = new SubClass();
objS.d = 3.0;
objS.i= 2;
objS.k = 6;
objS.PrintVariables();
AnyClass objA = new AnyClass();
objA.PrintVariablesOfBaseClass(objB);
}
}
Metode Get Set
class ExampleGetSet
{
private String mystr;
private double a;
}
double CalcArea()
{
return inaltime* latime/ 2;
}
void AfisezTip()
{
System.out.println("Triunghiul este " + tip_triunghi);
}
}
class DemoMostenire
{
public static void main(String args[])
{
Triunghi t1 = new Triunghi (4,8,"dreptunghic");
Triunghi t2 = new Triunghi (4,6,"isoscel");
System.out.println("Informatiile despre t1: ");
t1.AfisezTip();
t1.AfisezDimensiunile();
System.out.println("Aria " + t1.CalcArea());
System.out.println();
System.out.println("Informatiile despre t2: ");
t2.AfisezTip();
t2.AfisezDimensiunile();
System.out.println("Aria " + t2.CalcArea());
}
}
super
class Forma2D
{
double inaltime;
double latime;
public Forma2D(double a, double b)
{
inaltime =a;
latime = b;
}
void AfisezDimensiunile()
{
System.out.println("inaltimea este " +
inaltime + " latimea este " + latime);
}
}
class Triunghi extends Forma2D
{
String tip_triunghi;
Triunghi(double a, double b, String tip)
{
super(a, b);
tip_triunghi = tip;
}
double CalcArea()
{
return inaltime* latime/ 2;
}
void AfisezTip()
{
System.out.println("Triunghiul este " + tip_triunghi);
}
}
Suprascrierea metodelor
show() show()
A B
class A
{ class B extends A
int i, j; {
A(int a, int b) int k;
{ B(int a, int b, int c)
i = a; {
j = b; super(a, b);
} k = c;
//afisez i si j }
void show() //afisez doar k
{ void show()
System.out.println("i si j: " + i + " {
" + j); System.out.println("k: " + k);
} }
} }
class A
{
int i, j;
A(int a, int b)
{
i = a;
j = b;
}
//afisez i si j
void show()
{
System.out.println("i si j: " + i + " " + j);
}
}
class B extends A
{
int k;
B(int a, int b, int c)
{
super(a, b);
k = c;
}
void show(String str) //Metoda supraincarca show() din A
{
System.out.println(str);
}
//in clasa B nu mai am show() suprascrisa
}
class DemoSuprascriere
{
public static void main(String args[])
{
B obj = new B(1, 2, 3);
obj.show("Mesaj 1"); //apelez show() din B
obj.show(); // se va apela show() din A
}
}
Object
Metoda Scop
Object clone( ) Creeaza un obiect cu aceleasi proprietati ale obiectului clonat.
boolean equals(Object object) Determina faptul ca un obiect este/ nu este egal cu altul.
void finalize( ) Apelul inainte ca obiectul sa fie distrus.
Class<? extends Object> getClass( ) Determina clasa din care obiectul face parte.
int hashCode( ) Returneaza un id specific fiecarui obiect.
void notify( ) Reia executia firului de executie aflat in asteptare.
void notifyAll( ) Reia executia firelor de executie aflate in asteptare.
String toString( ) Returneaza un sir de caractere ce descrie obiectul.
void wait( ) Suspenda firul de executie apelant.
class AnyClass
{
int i;
toString()
String s;
public double d;
public void InitVars()
{
i=3;
s="Mesaj";
d= 4.1;
}
public String toString()
{
String str="";
str += "i este " + i + "\n";
str += "s este " + s + "\n";
str += "d este " + d + "\n";
return str;
}
}
class DemoObject
{
public static void main(String args[])
{
AnyClass obj1 = new AnyClass();
obj1.InitVars();
//afisam obj1 prin apelului lui toString
System.out.println(obj1);
//Alt mod de a afisa continutul lui obj1
System.out.println(obj1.i + obj1.s + obj1.d);
}
}
class Circle
{ equals()
private final int x, y, r;
// Constructorul de baza hashCode()
public Circle(int x, int y, int r)
{
this.x = x; this.y = y; this.r = r;
}
//Constructor de copiere - alternativa la clone()
public Circle(Circle original)
{
x = original.x;
y = original.y;
r = original.r;
}
// functii Get
public int getX()
{
return x;
}
public int getY()
{
return y;
}
public int getR()
{
return r;
}
// suprascrierea equals
@Override public boolean equals(Object o)
{
if (o == this) return true; //referinte egale
if (!(o instanceof Circle)) return false; //daca tipul de data nu este corect
Circle that = (Circle) o; //se aplica un cast catre tipul corect
if (this.x == that.x && this.y == that.y && this.r == that.r)
return true;
else
return false;
}
@Override public int hashCode() {
int result = 17;
result = 37*result + x;
result = 37*result + y;
result = 37*result + r;
return result;
}
}
class CircleDemo
{
public static void main(String args[])
{
Circle c1 = new Circle(1,1,4);
System.out.println(c1.hashCode());
Circle c2 = new Circle(1,2,4);
System.out.println(c2.hashCode());
System.out.println(c1.equals(c2));
}
}
Curs 4
Clase abstracte
Interfete
Exceptii
I/O
Regulile clasei abstracte
• Orice clasa ce contine o metoda abstracta este in mod automat abstracta, asa ca trebuie
declarata ca atare.
• O clasa abstracta nu poate fi instantiata.
• O subclasa a unei clase abstracte poate fi instantiata doar daca suprascrie metodele
abstracte ale parintelui si ofera o implementare pentru toate aceste metode. Aceste clase
copil se numesc si concrete pentru a sublinia ca nu sunt abstracte.
• Daca o clasa copil a unei superclase abstracte nu implementeaza toate metodele
abstracte pe care le mosteneste, atunci si clasa copil este abstracta si va fi declarata ca atare.
• Metodele declarate static, private sau final nu pot fi abstracte deoarece niciuna din
acestea nu poate fi suprascrisa de subclase. De asemenea o clasa declarata final nu poate
contine metode abstracte.
public abstract class Forma
{
public String categorie;
public abstract double Suprafata();
public abstract double Perimetrul(); //in loc de corp avem ";"
}
System.out.println("Categoria: " +
sir_forme[i].categorie);
System.out.println("Perimetrul: " +
sir_forme[i].Perimetrul());
System.out.println("Suprafata: " +
sir_forme[i].Suprafata());
//nu putem accesa aceste date, deoarece sunt specifice
//doar unei subclase si nu parintelui
//System.out.println("PI este: " + sir_forme[i].PI);
}
System.out.println("Suprafata totala: " +
suprafata_totala);
}
}
Forma.java:86: cannot find symbol
symbol : variable PI
location: class Forma
System.out.println("PI este: " + sir_forme[i].PI);
Interfete
try
{
// blocul in care eroarea poate apare
}
catch (ExcepType1 exOb)
{
// handler pentru ExcepType1
}
catch (ExcepType2 exOb)
{
// handler pentru ExcepType2
}
finally
{ …
}
class ExceptionDemo1
{
public static void main(String args[])
{
int nums[] = new int[4];
try
{
System.out.println("Inainte de generarea erorii.");
// Generam o exceptie prin depasirea limitei sirului
nums[5] = 7;
System.out.println("Eroarea a aparut deja.
Instructiunea aceasta nu va mai fi executata.");
}
catch (ArrayIndexOutOfBoundsException exc)
{
// prindem si tratam exceptia
System.out.println("Depasirea limitei sirului!");
}
System.out.println("Dupa blocul try catch.");
}
}
class ExceptionDemo1
{
public static void genException()
{
int nums[] = new int[4];
System.out.println("Inainte de generarea erorii.");
// Generam o exceptie prin depasirea limitei sirului
nums[7] = 10;
System.out.println("Eroarea a aparut deja. Instructiunea aceasta
nu va mai fi executata?");
}
}
class ExceptionDemo2
{
public static void main(String args[])
{
try
{
ExceptionDemo1.genException();
}
catch (ArrayIndexOutOfBoundsException exc)
{
// prindem si tratam exceptia
System.out.println("Depasirea limitei sirului!");
}
System.out.println("Dupa blocul try catch.");
}
}
class ExcMultipleDemo
{
public static void main(String args[])
{
int numar[] = { 5, 3, 12, 1, 20, 27, 10, 56 };
int deimp[] = { 1, 0, 3, 4, 0, 5 };
for(int i=0; i<numar.length; i++)
{
try
{
System.out.println(numar[i] + " / " + deimp[i] + " este
" + numar[i]/deimp[i]);
}
catch (ArithmeticException aex)
{
System.out.println("impart la zero la pasul "+ i+"!");
}
catch (ArrayIndexOutOfBoundsException iex)
{
System.out.println("Am depasit limita la pasul "+i+".");
}
}
}
}
5 / 1 este 5
impart la zero la pasul 1!
12 / 3 este 4
1 / 4 este 0
impart la zero la pasul 4!
27 / 5 este 5
Am depasit limita la pasul 6.
Am depasit limita la pasul 7.
try
{
...
}
catch (RuntimeException rex)
{
...
}
catch(Exception ex)
{
...
}
catch(Throwable tex)
{
...
}
AclNotFoundException, AWTException, BackingStoreException, BadLocationException, CertificateException,
ClassNotFoundException, CloneNotSupportedException, DataFormatException, DestroyFailedException, ExpandVetoException,
FontFormatException, GeneralSecurityException, GSSException, IllegalAccessException, InstantiationException,
InterruptedException, InvalidMidiDataException, InvalidPreferencesFormatException, IOException, LastOwnerException,
LineUnavailableException, MidiUnavailableException, MimeTypeParseException, NamingException,
NoninvertibleTransformException, NoSuchFieldException, NoSuchMethodException, NotBoundException, NotOwnerException,
ParseException, ParserConfigurationException, PrinterException, PrintException, PrivilegedActionException, PropertyVetoException,
RefreshFailedException, RemarshalException, RuntimeException, SAXException, ServerNotActiveException, SQLException,
TooManyListenersException, TransformerException, UnsupportedAudioFileException, UnsupportedCallbackException
import java.io.*;
class ThrowsDemo
{
public static void writeList(int[] vector)
{
PrintWriter out = new PrintWriter(new FileWriter("OutFile.txt"));
for (int i = 0; i < 10; i++)
{
out.println("valoare de la : " + i + " = " + vector[i]);
}
out.close();
}
}
Clasa Scop
BufferedInputStream input stream ca buffer
BufferedOutputStream output stream ca buffer
ByteArrayInputStream Input stream care citeste din sir de byte
ByteArrayOutputStream Output stream ce scrie in sir de byte
DataInputStream Un input stream ce contine metode pentru citirea tipurilor standard de data din Java
DataOutputStream Un output stream ce contine metode pentru scrierea tipurilor standard de data din Java
FileInputStream Input stream care citeste dintr-un fisier
FileOutputStream Output stream care scrie intr-un fisier
FilterInputStream Implementeaza InputStream
FilterOutputStream Implementeaza OutputStream
InputStream Clasa abstracata ce descrie input stream
ObjectInputStream Input stream pentru obiecte
ObjectOutputStream Output stream pentru obiecte
OutputStream Clasa abstracata ce descrie output stream
PipedInputStream pipe pentru intrari
PipedOutputStream pipe pentru iesiri
PrintStream Output stream ce poate apela print( ) si println( )
PushbackInputStream Input stream care permite ca byte-tii sa fie returnati dintr-un stream
SequenceInputStream Input stream ce este o combinatie de mai multe stream-uri de intrari ce vor fi citite secvential unul dupa celalalt
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
class CopyBytes {
public static void main(String[] args) throws IOException {
FileInputStream in = null;
FileOutputStream out = null;
try {
in = new FileInputStream("filei.txt");
out = new FileOutputStream("fileo.txt");
int c;
while ((c = in.read()) != -1) {
out.write(c);
}
} finally {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
}
}
}
InputStream
Metoda Descriere
int available( ) Returneza numbarul de bytes disponibili de la intrare.
void close( ) Inchide sursa. Alte incercari de a citi dupa, vor genera
IOException.
void mark(int numBytes) Pune un semn la byte-ul curent care va ramane valid pana ce
vom fi citit numBytes.
boolean markSupported( ) Returneaza true daca stream-ul suporta sistemul de marcare descris.
int read( ) Returneaza un integer ce reprezinta byte-ul urmator din stream
sau –1 daca s-a intalnit sfarsit de fisier.
int read(byte buffer[ ]) Se incearca citirea unui buffer de lungimea celui specificat in
paranteze si returneaza numarul de bytes citit sau -1 daca s-a
intalnit sfarsit de fisier.
int read(byte buffer[ ], int offset,int numBytes)
Se incearca citirea unui buffer de lungime numBytes, in buffer
incepand cu offset, si returneaza numarul de bytes cititi sau -1 daca am ajuns la
sfarsitul fisierului.
void reset( ) Reseteaza acel semn stabilit de mark.
long skip(long numBytes) Ignora numBytes bytes din intrare si returneaza numarul de bytes ignorat.
OutputStream
Metoda Descriere
void close( ) Inchide stream-ul de scriere catre iesire, o alta scriere generand IOException.
void flush( ) Forteaza scrierea din buffer la iesire, curatind bufferul.
void write(int b) Scrie un byte la iesire. Parametru este un int.
void write(byte buffer[ ]) Scrie un sir de bytes la iesire.
void write(byte buffer[ ], int offset,
int numBytes) Scrie o portiune dintr-un buffer, delimitat de offset si numBytes.
Character Stream
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
class CopyCharacters
{
public static void main(String[] args) throws IOException
{
FileReader inputStream = null;
FileWriter outputStream = null;
try
{
inputStream = new FileReader("filei.txt");
outputStream = new FileWriter("fileo.txt");
int c;
while ((c = inputStream.read()) != -1)
{
outputStream.write(c);
}
}
finally
{
if (inputStream != null)
{
inputStream.close();
}
if (outputStream != null)
{
outputStream.close();
}
}
}
}
Reader
int read(CharBuffer buffer) Se incearca citirea unui buffer de lungimea celui specificat in
paranteze si returneaza numarul de bytes citit sau -1 daca s-a
intalnit sfarsit de fisier. CharBuffer este o clasa ce incapsuleaza
o secventa de caractere asa cum face String.
int read(char buffer[ ]) Acelasi ca in cazul byte:de data aceasta se citesc caractere
Writer
class BufferDemo
{
public static void main(String args[]) throws Exception
{
FileReader fr = new FileReader("filei.txt");
BufferedReader br = new BufferedReader(fr);
String s;
while((s = br.readLine()) != null)
{
System.out.println(s);
}
fr.close();
}
}
import java.io.*;
class PrintStreamDemo
{
try
{
}
Curs 5
• Array
• Vector
• Stack
• Enumeration
• IO alte operatii
Array
int[] un_sir_de_date;
NegativeArraySizeException!
int[] sir = new sir[3];
sir[0] = 23; int[] sir = {23,12,45,78};
sir[1] = 12; for (int i = 0; i < sir.length; i++) {
sir[2] = 45; System.out.print(sir[i] + " ");
sir[3] = 78; }
System.out.println();
int matrix[][];
matrix[1][2] = 34;
System.out.println(java.util.Arrays.equals(array1,array3));
Variabila Descriere
capacityIncrement Marimea pentru incrementarea capacitatii unui vector
elementCount Numarul de elemente dintr-un vector.
elementData sirul de date intern al vectorului.
modCount mostenita din AbstractList: folosit de Iterator pentru a verifica modificari concurente.
Metoda Descriere
add() Adauga un element intr-un vector.
addAll() Adauga o colectie de elemente intr-un vector.
addElement() asemenea metodei add()
capacity() Returneaza capacitatea adica marimea sirului intern.
clear() sterge toate elementele unui vector.
clone() Creeaza o clona a vectorului.
contains() Verifica daca un vector contine un element.
containsAll() Verifica daca un vector contine o colectie.
copyInto() Copiaza elementele unui vector intr-un array.
elementAt() Returneaza elementul de la pozitia specificata.
elements() Returneaza un obiect al vectorului care permite vizitarea tuturor cheilor vectorului.
ensureCapacity() Verifica si se asigura ca marimea buffer-ului intern sa fie de o anumita marime.
equals() verifica egalitatea cu un obiect.
firstElement() returneaza primul element.
get() returneaza un element de la o anumita pozitie.
indexOf() cauta prima aparitie a unui element in sir.
insertElementAt() Insereaza un element in sir.
isEmpty() verifica daca un vector este gol.
iterator() returneaza un iterator, adica un obiect ce permite vizitarea elementelor din sir
lastElement() Returneaza ultimul element din sir
lastIndexOf() Cauta pozitia ultimului element din sir care este egal cu obiectul specificat
listIterator() Returneaza un obiect care permite ca toate elementele sa fie vizitate secvential.
remove() Sterge un anumit element din vector.
removeAll() Sterge toate elementele specificate in colectia data ca parametru.
removeAllElements() Sterge toate elementele din sir si seteaza marimea acestui cu zero.
set() Schimba un element de la o anumita pozitie.
setElementAt() Acelasi lucru ca si set.
setSize() modifica marimea buffer-ului intern
size() returneaza numarul de elemente din sir
subList() returneaza o sectiune din sir.
toArray() returneaza elementele vectorului ca array.
trimToSize() „taie” o portiune din sir, astfel ca el sa ramana de marimea specificata.
public Vector()
public Vector(int initialCapacity)
public Vector(int initialCapacity, int capacityIncrement)
import java.util.Vector;
public class InsertVector
{
public static void main (String args[])
{
Vector v = new Vector();
for (int i=0, n=args.length; i<n; i++)
{
v.add(args[i]);
}
}
}
vector.add(2,”nou”);
IllegalArgumentException.
Vectori de tipuri primitive
import java.util.Vector;
public class PrimVector
{
public static void main (String args[])
{
Vector v = new Vector();
for (int i=1; i<=10; i++)
{
v.add(new Integer(i));
}
}
}
Afisarea vectorilor
System.out.println(v);
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Stergerea vectorilor
vector.remove(2);
Al treilea
get
Enumeration
Vector v = . . .
Enumeration e = v.elements();
while (e.hasMoreElements())
{
process(e.nextElement());
}
import java.util.Enumeration;
import java.util.Vector;
class enumerare
{
public static void main(String args[])
{
Vector v=new Vector(10,10);
for(int i=0;i<20;i++)
v.addElement(new Integer(i));
System.out.println("Vector in ordinea originala");
//se initializeaza obiectul e cu v.elements
//astfel ca putem parcurge enumeratorul e
for(Enumeration e=v.elements();e.hasMoreElements();)
{
//nu exista conditie de incrementare in for
//dar e.nextElmement() se deplaseaza
//la urmatorul element
System.out.print(e.nextElement()+" ");
}
System.out.println();
//afisarea vectorului original in ordine inversa
System.out.println("\nVector in ordinea inversa");
for(int i=v.size()-1;i>=0;i--)
System.out.print(v.elementAt(i)+" ");
}
}
Cautare in vector
Vector v = . . .;
String array[] = new String[0];
array = (String[])v.toArray(array);
Vector v = . . .;
String array[] = new String[v.size()];
array = (String[])v.toArray(array);
Vector v = . . .;
String array[] = new String[v.size()];
v.copyInto(array);
Stiva
Operatii cu stiva
public Object pop()
Enumeration enum = . . .;
while (enum.hasMoreElements()) {
Object o = enum.nextElement();
processObject(o);
}
import java.io.*;
import java.util.Scanner;
public class ScanTest {
public static void main(String[] args) throws IOException {
Scanner s = null;
try {
InputStream input = new FileInputStream("test.txt");
s = new Scanner(input);
s.useDelimiter("linia");
while (s.hasNext()) {
System.out.println(s.next());
}
} finally {
if (s != null) {
s.close();
}
}
}
3.141593, +00000003.1415926536
Serializarea obiectelor
x = "a" + 4 + "c"
Liste inlantuite
Collection
List
Set
Liste simplu inlantuite
Reprezentarea listelor
class LinkedList
{
int data;
LinkedList next;
public LinkedList()
{
next = null;
data =0;
}
}
class LinkedListDemo
{
public static void main(String args[])
{
LinkedList list = new LinkedList();
list.data =23;
list.next = new LinkedList();
list.next.data = 10;
list.next.next = new LinkedList();
list.next.next.data = 49;
23 10 49 //acum parcurgem lista de la capat
//si anume din list
LinkedList iterator = list;
do
{
System.out.println(iterator.data);
}
while((iterator = iterator.next)!=null);
iterator = list;
do
{
System.out.println(iterator.data);
}
while((iterator = iterator.next)!=null);
}
}
class LinkedList
{
int data;
LinkedList next;
LinkedList head;
public LinkedList()
{
head = this;
next = null;
data =0;
}
public LinkedList(LinkedList head)
{ class LinkedListDemo
this.head = head; {
next = null; public static void main(String args[])
data =0; {
} LinkedList list = new LinkedList();
} list.head = list;
list.data =23;
list.next = new LinkedList(list);
list.next.data = 10;
list.next.next = new LinkedList(list);
list.next.next.data = 49;
//acum parcurgem lista de la capat
//si anume din list
while (list.next !=null)
{
System.out.println(list.data);
list = list.next;
23 10 49 }
System.out.println(list.data);
list = list.head;
while (list.next !=null)
{
System.out.println(list.data);
list = list.next;
}
System.out.println(list.data);
}
}
class LinkedList
{
int data;
protected LinkedList next;
protected LinkedList head;
public LinkedList GetNextElement()
{
//Aici nu se impune sa aruncam o exceptie
//metoda apelanta va decide in functie de
//faptul ca next este null sau nu
return next;
}
public int GetNextElementData()
{
if (next==null)
throw new NullPointerException("Elementul urmator este null!");
return next.data;
}
3 20 12 15
3 36 20 12 15
3 36 12 15
3 60 40 36 12 15
36
40
3 40 36 12 15
Liste dublu inlantuite
null null
Frameworkul de colectii Java
Clase concrete
Interfeţe HashTable Array mutabil Arbore Lista înlănţuita Altele
balansat
Set HashSet TreeSet
SortedSet TreeSet
List ArrayList LinkedList Vector, Stack
Map HashMap TreeMap Hashtable,
Properties
SortedMap TreeMap
Collection
Metoda Descrierea
add() Adaugă un element intr-o colecţie
addAll() Adaugă o colecţie in alta colecţie
clear() Șterge toate elementele dintr-o colecţie
contains() Verifica daca un element se afla intr-o colecţie
containsAll() Verifica daca o colecţie se afla intr-o alta colecţie
equals() Verifica egalitatea a doua colecţii
hashCode() Returnează un id specific unei colecţii
isEmpty() Verifica daca o colecţie este goala
iterator() Returnează un obiect dintr-o colecţie ce permite vizitarea elementelor acesteia
remove() Șterge un element dintr-o colecţie
removeAll() Șterge elementele unei colecţii din colecţia curenta
retainAll() Șterge elementele dintr-o colecţie care nu sunt in alta colecţie
size() Returnează numărul de elemente dintr-o colecţie
toArray() Returnează elementele dintr-o colecţie ca sir.
public boolean contains (Object element)
public boolean add(Object element)
[1,5,8,1,2,4,2,5,7,6]
c = [1,5]
[8,2,4,2,7,6]
Copierea colectiilor
import java.util.*;
public class CollectionException
{
public static void main (String args[])
{
String[] str = {"1","a","et"};
List list = new ArrayList(Arrays.asList(str));
Iterator i = list.iterator();
while (i.hasNext())
{
System.out.println(i.next());
list.add("Element");
}
}
}
1
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)
at java.util.AbstractList$Itr.next(AbstractList.java:343)
at curs6.CollectionException.main(CollectionException.java:23)
List list = Arrays.asList(args);
list.add("Add"); // Arunca UnsupportedOperationException
Liste: List
Metoda Descriere
indexOf() Cauta un element in lista
lastIndexOf() Cauta un element in lista începând de la sfârșitul acesteia
listIterator() returnează iteratorul personalizat al listei
set() modifica un element specificat din lista
get() returnează un element din lista
remove() șterge un anumit element din lista
subList() returnează o parte din lista
ArrayList
Metoda Descriere
ArrayList() Constructorul pentru o lista goala
ensureCapacity() Creează un buffer intern cu o capacitate dubla fata de cea anterioara
removeRange() Șterge un anumit interval de elemente din lista
trimToSize() limitează capacitatea buffer-ului intern la mărimea specificata
public ArrayList()
public ArrayList (int initialCapacity)
{”element”,”index”,”element”,”sir”,”clasa”}
C ={”lista”,”multime”,”element”}
{”index”,”sir”,”clasa”}
Iteratorul LinkedList
Metoda Descriere
hasNext verifica pe direcţia înainte, daca mai sunt elemente
hasPrevious verifica pe direcţia înapoi, daca mai sunt elemente
next returnează următorul elemente
nextIndex returnează indexul din colecţie al următorului element
previousIndex returnează indexul din colecţie al elementului anterior
remove șterge un element din iterator
set modifica elementul curent
public class LinkedLists {
public static void main(String args[])
{
LinkedList list = new LinkedList();
list.add(new Integer(2));
list.addFirst(new Double(5.6));
list.addFirst(new Double(5));
list.addFirst(new Float(3.4));
list.addLast(new Short((short)10));
ListIterator it = list.listIterator();
//parcurgere de la cap la coada
while (it.hasNext())
System.out.print(" " + it.next());
//sterg din iterator ultimul element
//care a fost returnat
System.out.println();
it.remove();
//parcurgere de la coada la cap
while (it.hasPrevious())
{
System.out.print(" "+ it.previous());
System.out.print(" " +it.previousIndex());
}
System.out.println();
System.out.println(list);
}
}
{"Roman","Italian"}
•TreeSet
•Sortarea Colecţiilor
•Dictionary, HashTable şi Properties
•Map
•Clasa Collections
TreeSet
Reguli:
•Fiecare nod este roşu sau negru
•Rădăcina este întotdeauna un nod negru
•Dacă nodul este roşu, copii lui trebuie sa fie de culoare neagră
•Fiecare cale de la rădăcină la frunze trebuie sa conţină acelaşi număr de noduri negre.
Nume metoda Descriere
TreeSet() Constructorul unui TreeSet.
add() Adaugă un element în mulţime
addAll() Adaugă o colecţie de elemente în mulţime
clear() Şterge elementele din mulţime
clone() Creează o clonă cu elementele din mulţime
comparator() Returnează un obiect de tip Comparator
contains () Verifică existenţa unui obiect în mulţime
first() Returnează primul element din mulţime
headSet() Returnează un subset de elemente de la începutul mulţimii
isEmpty() Verifică dacă mulţimea este goală.
iterator() Returnează un obiect din set ce permite vizitarea mulţimii
last() Returnează ultimul element din şir
remove() Şterge un element din mulţime
size() Returnează numărul de elemente din subset
subSet() Returnează o submulţime din mulţimea iniţială
tailSet() Returnează o submulţime de elemente de la sfârşitul mulţimii iniţiale
public TreeSet()
public TreeSet(Comparator comp)
import java.util.*;
public class Comparare
{
public static void main (String args[]) throws Exception
{
String elements[] = {"Radu", "Andrei", "Ion","Vasile", "Mircea"};
//creearea unui set cu un comparator
Set set = new TreeSet(Collections.reverseOrder());
for (int i=0, n=elements.length; i<n; i++) {
set.add(elements[i]);
}
//afisez elementele setului
System.out.println(set);
//afisez comparatorul actualului set
System.out.println(((TreeSet)set).comparator());
}
}
import java.util.*;
public class Iterare
{
public static void main(String args[])
{
String elements[] = {"Radu", "Andrei", "Ion","Vasile", "Mircea"};
Set set = new TreeSet(Arrays.asList(elements));
Iterator iter = set.iterator();
while (iter.hasNext())
{
System.out.println(iter.next());
}
}
}
Andrei
Ion
Mircea
Radu
Vasile
Subarbori
Interfaţa Comparable
public int compareTo(Object Obj)
import java.util.*;
public class Angajat implements Comparable
{
String departament;
String nume;
public Angajat(String departament, String nume)
{
this.departament = departament;
this.nume = nume;
}
public String getdepartament()
{
return departament;
}
public String getnume()
{
return nume;
}
public String toString()
{
return "[dep=" + departament + ",nume=" + nume + "]";
}
public int compareTo(Object obj)
{
Angajat decomparat = (Angajat)obj;
//daca sunt din departamente diferite
//vom compara departamentele
int deptComp = departament.compareTo(decomparat.getdepartament());
//daca sunt din acelasi departament
//vom compara numele angajatilor
return ((deptComp == 0) ? nume.compareTo(decomparat.getnume()): deptComp);
}
public boolean equals(Object obj)
{
if (!(obj instanceof Angajat))
{
return false;
}
Angajat a = (Angajat)obj;
return departament.equals(a.getdepartament()) && nume.equals(a.getnume());
}
public int hashCode()
{
return 43*departament.hashCode() + nume.hashCode();
}
}
class Companie
{
public static void main (String args[])
{
Angajat angajati[] = {
new Angajat("HR", "Irina"),
new Angajat("HR", "Cristina"),
new Angajat("Ingineri", "Daniel"),
new Angajat("Ingineri", "Vlad"),
new Angajat("Ingineri", "Octavian"),
new Angajat("Vanzari", "Emil"),
new Angajat("Vanzari", "Eugen"),
new Angajat("RC", "Bogdan "),
new Angajat("RC", "Avram"),
};
Set set = new TreeSet(Arrays.asList(angajati));
//parcurgem multimea sortata deja
//insa ea contine obiecte
for(Object o : set)
{
//asa ca va trebui sa apelam cast
Angajat a = (Angajat)o;
System.out.println(a);
} [dep=HR,nume=Cristina]
} [dep=HR,nume=Irina]
} [dep=Ingineri,nume=Daniel]
[dep=Ingineri,nume=Octavian]
[dep=Ingineri,nume=Vlad]
[dep=RC,nume=Avram]
[dep=RC,nume=Bogdan ]
[dep=Vanzari,nume=Emil]
[dep=Vanzari,nume=Eugen]
Interfaţa Comparator
class AngComparator implements Comparator
{
public int compare(Object obj1, Object obj2)
{
Angajat a1 = (Angajat)obj1;
Angajat a2 = (Angajat)obj2;
int nameComp = a1.getnume().compareTo(a2.getnume());
return ((nameComp == 0) ?
a1.getdepartament().compareTo(a2.getdepartament()) :
nameComp);
} public static void main (String args[])
} {
Angajat angajati[] = {
new Angajat("HR", "Irina"),
new Angajat("HR", "Cristina"),
new Angajat("Ingineri", "Daniel"),
new Angajat("Ingineri", "Vlad"),
new Angajat("Ingineri", "Octavian"),
new Angajat("Vanzari", "Emil"),
new Angajat("Vanzari", "Eugen"),
new Angajat("RC", "Bogdan "),
new Angajat("RC", "Avram"),
};
//noua coletie are un comparator de tip
//AngComparator
Set set = new TreeSet(new AngComparator());
set.addAll(Arrays.asList(angajati));
//parcurgem multimea sortata deja
//insa ea contine obiecte
for(Object o : set)
{
//asa ca va trebui sa apelam cast
Angajat a = (Angajat)o;
System.out.println(a);
}
}
Dictionary, HashTable şi Properties
30 11 45 19 30 11 45 19
Dictionary
elements() Returnează un obiect din dicţionar ce permite vizitarea tuturor cheilor
get() 1.0 Returnează o valoare din dicţionar
isEmpty() Verifică dacă un dicţionar este gol
keys() Returnează colecţia de chei din dicţionar
put() Introduce un element format din cheie şi valoare
remove() Şterge un element din dicţionar
size() Returnează numărul de element din dicţionar
Nume metodă Descriere
Hashtable() Constructorul colecţiei
clear() Şterge elementele din colecţie
clone() Creează un HashTable cu aceleaşi elemente
contains() Verifică existenţa unui obiect în colecţie
containsKey() Verifică existenţa unei chei în HashTable
containsValue() Verifică existenţa unei valori în HashTable
elements() Returnează un element ce permite vizitarea colecţiei
entrySet() Returnează un set de perechi cheie – valoare
equals() Verifică egalitatea între două obiecte
get() Returnează valoarea de la o anumită cheie.
hashCode() Calculează codul hash al unei colecţii
isEmpty() Verifică dacă colecţia este goală sau nu.
keys() Returnează o colecţie de chei din HashTable.
keySet() Returnează o colecţie de chei din HashTable.
put() Pune o pereche cheie-valoare în HashTable
putAll() Pune o colecţie de cheie-valoare în HashTable
rehash() Măreşte capacitatea colecţiei
remove() Şterge un element din colecţie
size() Returnează numărul de elemente din colecţie
toString() Returnează întregul HashTable ca un String
values() Returnează o colecţie de valori din HashTable
Coliziuni
101
101
114
114
public Hashtable(Map t)
import java.io.*;
import java.util.*;
public class Proprietati
{
public static void main(String args[]) throws IOException
{
Properties prop = new Properties();
FileOutputStream fo = new FileOutputStream(args[0]);
prop.setProperty("cheia1", "valoarea1");
prop.setProperty("cheia2", "valoarea2");
prop.setProperty("cheia3", "valoarea3");
prop.setProperty("cheia1", "valoarea");
prop.save(fo, "Aici vin comentarii");
}
}
Interfaţa Map.Entry
Metoda Descriere
equals() verifică egalitatea cu un alt obiect
getKey() returnează cheia din Map.Entry
getValue() returnează valoarea din Map.Entry
hashCode() calculează codul hash pentru obiectul actual
setValue() schimbă valoarea din Map.Entry
HashMap
public HashMap()
public HashMap(int initialCapacity)
public HashMap(int initialCapacity, float loadFactor)
public HashMap(Map map)
Referinţe
•Referinţe puternice, nu au o clasă specială
•Referinţe soft sunt ca un cache. Când memoria este puţină GC va elibera arbitrar aceste referinţe.
•Referinţe slabe: acestea sunt mai slabe decât cele soft. Dacă singura referinţă a unui obiect sunt doar de tip slab, GC va putea şterge obiectul oricând.
•Referinţe fantomă. Acestea permite o notificare înainte ca GC să apeleze finalizarea pe obiectul în cauză.
import java.util.*;
public class Weak
{ Thread t = new Thread(runner);
private static Map map; System.out.println(map);
public static void main (String args[]) //incep procedura run
{ t.start();
map = new WeakHashMap(); //firul principal sta si asteapta ca
map.put(new String("Cheie"), "Valoare"); System.out.println("Main waiting");
//o metoda ce se executa pe un alt fir try
//decat cel curent {
Runnable runner = new Runnable() //cel nou sa isi incheie executia
{ t.join();
public void run() //dupa ce thread-ul t nu mai este
{ System.out.println(map);
while (map.containsKey("Cheie")) }
{ catch (InterruptedException ignored)
try {
{ }
//intra in standby 0.5 secunde }
Thread.sleep(500); }
}
catch (InterruptedException ignored)
{
}
System.out.println("Thread waiting");
//sistemul GC va rula fortat
System.gc();
}
}
};
TreeMap
public TreeMap()
public TreeMap(Map map)
Object firstKey()
Object lastKey()
import java.util.*;
public class Colectii
{
public static void main(String args[]) throws Exception
{
List list = Arrays.asList(args);
Collections.sort(list);
for (int i=0, n=list.size(); i<n; i++)
{
if (i != 0) System.out.print(", ");
System.out.print(list.get(i));
}
System.out.println();
}
}
Căutarea binară
1 3 4 6 8 9 11 14 15 17 19 20 23 25
1 3 4 6 8 9 11 14
1346
•Bazele multithreading
•Clasa Thread si Interfata Runnable
•Mai multe fire de executie
•Sincronizarea
•Comunicarea intre fire de executie
•Suspendarea/reluarea firelor de executie
•Grupuri de fire de executie
Conceptul de thread
Starile unui thread
Clasa Thread
Medoda Descriere
final String getName( ) Se obţine numele thread-ului
final int getPriority( ) Se obţine prioritatea thread-ului
final boolean isAlive( ) Determină faptul că un thread mai este în execuţie sau nu.
final void join( ) Aşteaptă terminarea unui thread pentru a continua.
void run( ) Entry point pentru un thread.
static void sleep(long milliseconds) Suspendă un thread un număr de milisecunde
void start( ) Începe execuţia unui fir prin apel run()
Interfata Runnable
class ThreeChildThreads {
public static void main(String args[])
{
System.out.println("Main thread starting.");
//cream trei fire de executie
MyThread mt1 = new MyThread("Child #1");
MyThread mt2 = new MyThread("Child #2");
MyThread mt3 = new MyThread("Child #3");
//care vor fi automat lansate
//in bucla while, thread-ul principal
//asteapta ca toate firele sa se incheie
do
{
System.out.print(".");
try
{
Thread.sleep(100);
}
catch(InterruptedException exc)
{
System.out.println("Main thread interrupted.");
}
} while (mt1.count < 10 || mt2.count < 10 || mt3.count < 10);
System.out.println("Main thread ending.");
}
}
Diagrama executiei pe mai multe fire Main thread starting.
Child #1 starting.
.Child #2 starting.
Child #3 starting.
....In Child #1, count is 0
In Child #2, count is 0
In Child #3, count is 0
.....In Child #1, count is 1
In Child #3, count is 1
In Child #2, count is 1
.....In Child #1, count is 2
In Child #2, count is 2
In Child #3, count is 2
.....In Child #1, count is 3
In Child #2, count is 3
In Child #3, count is 3
.....In Child #1, count is 4
In Child #2, count is 4
In Child #3, count is 4
.....In Child #1, count is 5
In Child #2, count is 5
In Child #3, count is 5
.....In Child #1, count is 6
In Child #2, count is 6
In Child #3, count is 6
.....In Child #1, count is 7
In Child #3, count is 7
In Child #2, count is 7
.....In Child #1, count is 8
In Child #3, count is 8
In Child #2, count is 8
.....In Child #1, count is 9
Child #1 terminating.
In Child #3, count is 9
Child #3 terminating.
In Child #2, count is 9
Child #2 terminating.
Main thread ending.
isAllive
class ThreeChildThreads {
public static void main(String args[])
{
System.out.println("Main thread starting.");
//cream trei fire de executie
MyThread mt1 = new MyThread("Child #1");
MyThread mt2 = new MyThread("Child #2");
MyThread mt3 = new MyThread("Child #3");
//care vor fi automat lansate
//in bucla while, thread-ul principal
//asteapta ca toate firele sa se incheie
do
{
System.out.print(".");
try
{
Thread.sleep(100);
}
catch(InterruptedException exc)
{
System.out.println("Main thread interrupted.");
}
} while (mt1.thread.isAlive() || mt2.thread.isAlive() || mt3.thread.isAlive() );
System.out.println("Main thread ending.");
}
}
join
class ThreeChildThreads
{
public static void main(String args[])
{
System.out.println("Main thread starting.");
//cream trei fire de executie
MyThread mt1 = new MyThread("Child #1");
MyThread mt2 = new MyThread("Child #2");
MyThread mt3 = new MyThread("Child #3");
//care vor fi automat lansate
//in bucla while, thread-ul principal
//asteapta ca toate firele sa se incheie
try
{
mt1.thread.join();
System.out.println("Child 1 joined.");
mt2.thread.join();
System.out.println("Child 2 joined.");
mt3.thread.join();
System.out.println("Child 3 joined.");
}
catch(InterruptedException exc)
{
System.out.println("Main thread interrupted.");
}
System.out.println("Main thread ending.");
}
}
Prioritati
class SumArray
{
private int sum;
//metoda are specificatorul de sincronizare
synchronized int sumArray(int nums[])
{
sum = 0; // resetarea sumei
for(int i=0; i<nums.length; i++)
{
sum += nums[i];
System.out.println("Running total for " +
Thread.currentThread().getName() +
" is " + sum);
try
{
Thread.sleep(15); //timp in care
//se poate schimba de la un fir la altul
}
catch(InterruptedException exc)
{
System.out.println("Main thread interrupted.");
}
}
return sum;
}
}
//clasa care se ocupa de thread
class MyThread implements Runnable
{
Thread thread;
static SumArray sa = new SumArray();
int a[];
int answer;
// Constructorul unui nou fir
MyThread(String name, int nums[])
{
thread = new Thread(this, name);
a = nums;
thread.start(); //pornesc thread-ul
}
public void run()//incepe executia noului fir
{
int sum;
System.out.println(thread.getName() + " starting.");
answer = sa.sumArray(a);
System.out.println("Sum for " + thread.getName() +
" is " + answer);
System.out.println(thread.getName() + " terminating.");
}
}
class SyncDemo
{
public static void main(String args[])
{
int a[] = {1, 2, 3, 4, 5};
MyThread mt1 = new MyThread("Child #1", a);
MyThread mt2 = new MyThread("Child #2", a);
}
}
Child #1 starting. Child #2 starting.
Running total for Child #1 is 1 Running total for Child #2 is 1
Child #2 starting. Child #1 starting.
Running total for Child #1 is 3 Running total for Child #1 is 1
Running total for Child #1 is 6 Running total for Child #2 is 3
Running total for Child #1 is 10 Running total for Child #1 is 5
Running total for Child #1 is 15 Running total for Child #2 is 8
Running total for Child #2 is 1 Running total for Child #1 is 11
Sum for Child #1 is 15 Running total for Child #2 is 15
Child #1 terminating. Running total for Child #1 is 19
Running total for Child #2 is 3 Running total for Child #1 is 24
Running total for Child #2 is 6 Running total for Child #2 is 29
Running total for Child #2 is 10 Sum for Child #1 is 29
Running total for Child #2 is 15 Child #1 terminating.
Sum for Child #2 is 15 Sum for Child #2 is 29
Child #2 terminating. Child #2 terminating.
Blocul de sincronizare
synchronized(object)
{
//instrucţiunile ce trebuie sincronizate
}
Comunicarea in thread-uri
class MaxPriorityDemo
{
public static void main(String[] args)
{
ThreadGroup groupNORM = new ThreadGroup(
"A group with normal priority");
Thread priorityMAX = new Thread(groupNORM,
"A thread with maximum priority");
// set Thread's priority to max (10)
priorityMAX.setPriority(Thread.MAX_PRIORITY);
// set ThreadGroup's max priority to normal (5)
groupNORM.setMaxPriority(Thread.NORM_PRIORITY);
System.out.println("Group's maximum priority = "
+ groupNORM.getMaxPriority());
System.out.println("Thread's priority = "
+ priorityMAX.getPriority());
}
}
Curs 9
Introducere in applet-uri
Arhitectura unui applet
Executarea unui applet
Parametrii unui applet
Operatiuni grafice in applet
Tratarea evenimentelor
Clase de evenimente
Evenimente de mouse
Evenimente de tastatura
import java.awt.*;
import java.applet.*;
public class OneApplet extends Applet
{
public void init()
{}
public void paint(Graphics g)
{
g.drawString("First Applet.", 20, 20);
}
}
<html>
<applet code="OneApplet" width=200 height=60>
</applet>
</html>
javac OneApplet.java
C:\>appletviewer OneApplet.html
import java.awt.*;
import java.applet.*;
/*
<applet code="OneApplet" width=200 height=60>
</applet>
*/
public class OneApplet extends Applet
{
public void init()
{}
public void paint(Graphics g)
{
g.drawString("First Applet.", 20, 20);
}
}
C:\>appletviewer OneApplet.java
//Structura de baza a unui applet Structura unui applet
import java.awt.*;
import java.applet.*;
/*
<applet code="AppletStructure" width=300 height=100>
</applet>
*/
public class AppletStructure extends Applet
{
//prima metoda apelata.
public void init()
{
// initializari
}
public void start()
{
// sart sau reiau executia
}
// apelata la oprierea appletului.
public void stop()
{
// suspenda executia
}
//apelta la terminarea executiei
//este ultima metoda apelata
public void destroy()
{
//activitati de oprire
}
// la redesenarea ferestrei
public void paint(Graphics g)
{
//reafisarea continutului ferestrei
}
}
Redesenarea
void repaint()
void repaint(int left, int top, int width, int height)
Exemplu, un banner
import java.awt.*;
import java.applet.*;
/*
<applet code="Banner" width=300 height=50>
</applet>
*/
//clasa implementeaza Runnable
//pentru ca vom lucra cu thread-uri
public class Banner extends Applet implements Runnable
{
String msg = " Java permite afisarea unui banner.";
Thread t;
boolean stopFlag;
// Initializarea threadului cu null
public void init()
{
t = null;
}
// start al appletului
//folosit pentru a porni threadul t
public void start()
{
t = new Thread(this);
stopFlag = false;
t.start();
}
//cand threadul ruleaza
public void run()
{
char ch;
//Se afiseaza bannerul
for( ; ; )
{
try
{
repaint();
Thread.sleep(90);
ch = msg.charAt(0);
msg = msg.substring(1, msg.length());
msg += ch;
if(stopFlag)
break;
}
catch(InterruptedException exc)
{}
}
}
// pauza pe banner
public void stop()
{
stopFlag = true;
t = null;
}
// afisarea banner-ului
public void paint(Graphics g)
{
g.drawString(msg, 50, 30);
}
}
Executia unui applet in JRE
import java.awt.*;
import java.applet.*;
/*
<applet code="StatusWindow" width=300 height=50>
</applet>
*/
public class StatusWindow extends Applet
{
//afisarea unui mesaj in status
public void paint(Graphics g)
{
g.drawString("Acesta este un mesaj afisat in fereastra.", 10, 20);
showStatus("Acesta este un mesaj afista in status bar");
}
}
Transmiterea parametrilor
String getParameter(String paramName)
Pasii:
•Se implementează o interfaţă corespunzătoare în ascultător aşa încât va recepţiona evenimentele corespunzătoare.
•Implementarea codului astfel încât să înregistreze şi de-înregistreze (după caz) un ascultător, la şi de la anumite evenimente.
Evenimente de mouse si tastatura
repaint();
}
// Tratare eveniment de eliberare mouse
public void mouseReleased(MouseEvent me)
{
// salvare coordonate
mouseX = me.getX();
mouseY = me.getY();
msg = "Up";
repaint();
}
// Tratare eveniment de trasare mouse
public void mouseDragged(MouseEvent me)
{
//salvare coordonate
mouseX = me.getX();
mouseY = me.getY();
msg = "*";
showStatus("Dragging mouse at " + mouseX + ", " + mouseY);
repaint();
}
// Tratare eveniment de miscare mouse
public void mouseMoved(MouseEvent me)
{
//afisarea pe status
showStatus("Moving mouse at " + me.getX() + ", " +
me.getY());
}
// se afiseaza mesajul la coordonatele salvate
public void paint(Graphics g)
{
g.drawString(msg, mouseX, mouseY);
}
}
import java.awt.event.*;
import java.applet.*;
import java.awt.*;
/*
<applet code="KeyWatcher" width=300 height=100>
</applet>
*/
public class KeyWatcher extends Applet
implements KeyListener
{
String msg = "";
Image image;
public void init()
{
//la initializare
//ne abonam pentru a fi notificati
//la evenimentele tastaturii
addKeyListener(this);
resize(800,100);
}
/* tratare evetiment tastare*/
public void keyTyped(KeyEvent e)
{
msg = "KEY TYPED: " + e.paramString();
repaint();
}
/* tratare eveniment apasare tasta */
public void keyPressed(KeyEvent e) {
msg = "KEY PRESSED: " + e.paramString();
repaint();
}
/*tratare eveniment eliberare tasta*/
public void keyReleased(KeyEvent e) {
msg = "KEY RELEASED: " + e.paramString();
repaint();
}
// se afiseaza mesajul conform tastei apasate
public void paint(Graphics g)
{
g.drawString(msg,20,20);
}
}
Awt
Introducere, concepte
Evenimente
Clasa AWTEvent
Component
Label
Button
TextComponent
Container
Panel
Window
Abstract Window Toolkit
TextField
Label
Scrollbar
Butoane radio
Liste de optiuni Liste
Modelul este destul de simplu: atunci când se recepţionează un eveniment iniţiat de utilizator,
sistemul generează o instanţă de tip Event și o transmite programului.
Programul identifică ţinta (de exemplu componenta în care a avut loc evenimentul) și permite acelei componente
să trateze evenimentul. Dacă ţinta nu poate trata evenimentul, se încearcă găsirea unei componente care va putea.
Livrarea evenimentului
Identificarea ţintei
public int id
WINDOW_DESTROY MOUSE_ENTER
WINDOW_EXPOSE MOUSE_EXIT
WINDOW_ICONIFY MOUSE_DRAG
WINDOW_DEICONIFY SCROLL_LINE_UP
KEY_PRESS SCROLL_LINE_DOWN
KEY_RELEASE SCROLL_PAGE_UP
KEY_ACTION SCROLL_PAGE_DOWN
KEY_ACTION_RELEASE SCROLL_ABSOLUTE
MOUSE_DOWN LIST_SELECT
MOUSE_UP LIST_DESELECT
MOUSE_MOVE ACTION_EVENT
public Object target
}
public Object[] getSelectedObjects()
{
Object o[] = new Object[1];
o[0] = new Integer (i);
return o;
}
public void addItemListener (ItemListener l)
{
itemListener = AWTEventMulticaster.add (itemListener, l);
}
public void removeItemListener (ItemListener l)
{
itemListener = AWTEventMulticaster.remove (itemListener, l);
}
public void processEvent (AWTEvent e)
{
if (e.getID() == MouseEvent.MOUSE_PRESSED)
{
if (itemListener != null)
{
selected = !selected;
i++;
itemListener.itemStateChanged (
new ItemEvent (this, ItemEvent.ITEM_STATE_CHANGED,
getSelectedObjects(),
(selected?ItemEvent.SELECTED:ItemEvent.DESELECTED)));
}
}
}
}
public class Multicasting extends Frame implements ItemListener
{
Multicasting ()
{
super ("Listening In");
ItemEventComponent c = new ItemEventComponent ();
add (c, "Center");
c.addItemListener (this);
c.setBackground (SystemColor.control);
setSize (200, 200);
}
public void itemStateChanged (ItemEvent e)
{
Object[] o = e.getItemSelectable().getSelectedObjects();
Integer i = (Integer)o[0];
System.out.println (i);
}
public static void main (String args[])
{
Multicasting f = new Multicasting();
f.show();
}
}
Componente
public boolean imageUpdate (Image image, int infoflags, int x, int y, int width, int height)
Alte Metode
public Label ()
public Label (String label)
public Label (String label, int alignment)
import java.awt.Canvas;
import java.awt.Graphics;
import java.applet.Applet;
/*
<applet code="UsingContainer" width=300 height=50>
</applet>
*/
import java.awt.*;
import java.applet.*;
import java.awt.event.*;
public class UsingContainer extends Applet implements ActionListener
{
Button b;
public void init()
{
enableEvents (AWTEvent.CONTAINER_EVENT_MASK);
add (b = new Button ("One"));
add (b = new Button ("Two"));
add (b = new Button ("Three"));
add (b = new Button ("Four"));
}
protected void processContainerEvent (ContainerEvent e)
{
if (e.getID() == ContainerEvent.COMPONENT_ADDED)
{
if (e.getChild() instanceof Button)
{
Button b = (Button)e.getChild();
b.addActionListener (this);
}
}
}
public void actionPerformed (ActionEvent e)
{
System.out.println ("Selected: " + e.getActionCommand());
}
}
public synchronized void addContainerListener(ContainerListener listener)
public void removeContainerListener(ContainerListener listener)
protected void processEvent(AWTEvent e)
protected void processContainerEvent(ContainerEvent e)
Panel
Frame
Dialog
FileDialog
LayoutManager
FlowLayout
BorderLayout
Choice
Liste
Meniuri
Frame
Constante Metode
public Frame ()
Evenimente de fereastra
WINDOW_DESTROY
WINDOW_EXPOSE
WINDOW_ICONIFY
WINDOW_DEICONIFY
WINDOW_MOVED
enableEvents (AWTEvent.WINDOW_EVENT_MASK);
Constructori
Metode
Metode
public FileDialog (Frame parent)
public FileDialog (Frame parent, String title)
public FileDialog (Frame parent, String title, int mode)
LayoutManager2
public FlowLayout ()
public FlowLayout (int alignment)
public FlowLayout (int alignment, int hgap, int vgap)
controls.add(LtoRbutton);
controls.add(RtoLbutton);
//se adauga panelul compsToExperiment in centrul frame-ului
this.add(compsToExperiment, BorderLayout.CENTER);
//se adauga panelul controls in partea de jos a frame-ului
this.add(controls, BorderLayout.SOUTH);
}
BorderLayout
Constante
public static final String CENTER
public static final String EAST
public static final String NORTH
public static final String SOUTH
public static final String WEST
Metode
public BorderLayout ()
public BorderLayout (int hgap, int vgap)
public List ()
public List (int rows)
public List (int rows, boolean multipleSelections)
MenuContainer
MenuItem
public MenuItem ()
public MenuItem (String label)
public MenuItem (String label, MenuShortcut shortcut)
}
public void itemStateChanged(ItemEvent e)
{
//daca meniul este de tip selectabil CheckBoxmenu
if (e.getSource() instanceof ItemSelectable)
{
ItemSelectable is = (ItemSelectable)e.getSource();
System.out.println ("The value is: " + (is.getSelectedObjects()!=null));
}
}
protected void processWindowEvent(WindowEvent e)
{
if (e.getID() == WindowEvent.WINDOW_CLOSING)
{
//notificam ascultatorii de eveniment
super.processWindowEvent(e);
//si se inchide procesul
System.exit(0);
}
else
{
super.processWindowEvent(e);
}
}
public static void main (String []args)
{
MenuDemo f = new MenuDemo();
f.setVisible(true);
}
}