Documente Academic
Documente Profesional
Documente Cultură
INFORMAȚII GENERALE 3
MODULUL 1 - MEDIUL DE LUCRU IN JAVA 7
1.1. ELEMENTE GENERALE DE COMPILARE ȘI EXECUȚIE A PROGRAMELOR 7
1.2. LIMBAJUL DE PROGRAMARE JAVA 9
1.2.1. COMPILAREA ÎN JAVA 9
1.2.2. MEDIUL DE LUCRU JAVA 10
Descrierea cursului
Cursul de Programare orientată obiect î și propune să familiarizeze studen ții cu no țiunile principale ale teoriei
generale a Programării Orientate Obiect. Studen ții vor deprinde principalele no țiuni necesare pentru învă țarea
unui limbaj de programare orientat obiect. No țiunile teoretice vor fi exemplificate prin utilizarea limbajului Java.
Mediul de programare utilizat va fi NetBeans 8.2.
În suportul de curs, la finele fiecărui modul, sunt precizate atât referin țele bibliografice. Sursele bibliografice au
fost astfel stabilite astfel încât să ofere posibilitatea adâncirii nivelului de în țelegere a fiecărui aspect. Acest
suport de curs se bazeaza ca structura si continut pe cursul omonim predat studentiilor de la forma de
invatamant cu frecventa de domnul profesor univ.dr. habil. Gheorghe Cosmin Silaghi. Marea majoritate a
exemplelor de cod si a diagramelor UML asociate sunt preluate din carte „Thinking în Java” a lui Bruce Eckel.
În cadrul cursului se va apela la o serie de echipamente şi materiale precum: laptop, videoproiector, conexiune
Internet, suport de curs. Laborator va fi echipat cu NetBeans 10.0.
Calendarul cursului
Pe parcursul semestrului sunt programate 4 întâlniri faţă în faţă cu toţi studenţii. În vederea eficientizării
acestor întâlniri faţă în faţă, pentru fiecare din acestea, se recomandă parcurgerea de către student a
suportului de curs pus la dispoziţie încă de la începutul semestrului, iar ulterior întâlnirii, este indicată
rezolvarea sarcinilor şi exerciţiilor aferente fiecărui modul parcurs. De asemenea, anterior întâlnirilor
programate, studenţilor li se recomandă să parcurgă capitolele corespunzătoare temelor abordate la fiecare
întâlnire din cel puţin una din sursele bibliografice indicate. În acest mod, se va facilita orientarea cursului
asupra aspectelor de fineţe din conţinutul disciplinei şi se va permite concentrarea pe modalităţile de aplicare
la nivel practic a informaţiilor deja parcurse.
Fiecare temă presupune parcurgerea unui material didactic indicat și implementarea în Java a unui set de
probleme.
Studenţi cu dizabilităţi
Atât prin intermediul programului de întâlniri fa ță în fa ță, cât şi prin întâlnirile programate în mediul virtual,
titularul de curs se va implica şi va sprijini studenţii, astfel încât aceştia să reuşească să asimileze în mod
optim conceptele şi abilităţile aferente disciplinei. Studen ții care au o dizabilitate motorie sau de altă natură vor
putea contacta tutorii pentru adaptarea cerin țelor la specificul dizabilită ții.
Modulul 1
I. Cuprins
1. Elemente generale de compilare și
execuție a programelor
2. Limbajul Java
II. Obiective
3. Teste de evaluare
În acest modul vom prezenta
succint procesul de compilare și
execuție a unei aplicații scrise în
limbajul de programare Java
precum și mediul de lucru asociat
III.limbajului.
Cuvinte cheie
Compilare, interpretare,
preprocesare,, executie, asamblare,
limbaj de programare de nivel inalt,
mediu de lucru Java.
Compilarea într-un limbaj de genera ția a treia ca limbajul C este în general formată din trei faze independente,
care sunt realizate de trei programe ce se execută secven țial:
● Preprocesarea – se parcurg toate codurile sursa specificate linie după linie, căutând comenzi
(directive) spre execuție. Preprocesorul prime ște în intrare un text iar rezultatul lui este și el un
text. De exemplu, atunci când preprocesorul C întâlne ște directiva #include el o substituie cu
textul fișierului header, iar codul sursa introdus poate include la rândul lui alte fi șiere header;
● Compilarea propriu-zisă – fișierele preprocesate con ținând declara țiile obiectelor importate sunt
compilate separat. Rezultatul acestor compilări va fi un set de module obiect (fi șiere cu
extensia .o). Se obține câte unul pentru fiecare modul C ini țial. Tot codul C al modulului e compilat
în cod mașină însă referințele la simboluri externe din alte module sunt satisfăcute fiindcă aceste
module sunt compilate separat;
● Editarea de legături – Legarea modulelor (implicând completarea referin țelor obiectelor globale,
relocarea codului) este făcută de această ultimă fază a compilării. Aceste module obiect pot fi
obținute prin compilarea unor programe scrise în limbaje diferite, de exemplu, în asamblor pentru
a optimiza anumite operații. Rezultatul ob ținut este un program executabil dependent de platforma
pe care s-a realizat compilarea.
Preprocesarea și compilarea propriu-zisă pot fi văzute ca două etape distincte ale unei singure faze ce
transforma codul sursa în cod obiect.
● Verificarea codului intermediar – mai întâi codul intermediar Java este inspectat de un verificator.
Acesta verifică dacă instrucțiunile ce vor fi rulate pot efectua ac țiuni care sunt în mod vădit
dăunătoare. Toate clasele încărcate, cu excep ția claselor de sistem, sunt verificate.
● Încărcarea codului intermediar – clasele dintr-o aplica ție Java sunt încărcate folosind clase
speciale derivate din clas java.lang.ClassLoader.
● Just-In-Time (JIT) Compilation – compilatorul JIT este activat în mod implicit în JRE și este activat
atunci când este apelată o metodă Java. Compilatorul JIT compilează codul intermediar al acestei
metode în codul nativ, compilându-l "la timp" pentru a putea fi rulat. Atunci când o metodă a fost
compilată, JVM solicită codul compilat al acelei metode în loc să îl interpreteze si, astfel, permite
ca viteza programului Java să se apropie de cea a unei aplica ții native.
compilam fișierul MyClass.java care face referin ță la clase din directoarele /programs/ex1/ și /lecturenotes1/.
Rezultatul compilării este un fișier numit MyProg.class care con ține codul intermediar corespunzător sursei
compilate. Acest fișier este creat în mod implicit în directorul în care s-a produs compilarea. Cu toate acestea,
este foarte de dorit să nu amestecați fișierele care con țin codul sursă și cele care con țin codul intermediar. Un
director de destinație în care va fi creat fi șierul MyProg.class poate fi specificat prin op țiunea -d, ca mai jos:
Fișierele compilate pot fi regrupate în arhive JAR (Java Archive) ce conțin în mod tipic
pe lângă fișierele .class și alte fișiere auxiliare (resurse de tipul imaginilor, filmelor etc.). Fiecare
arhivă posedă un singur fișier manifest cu informații referitoare la fișierele împachetate în
arhiva .jar, de exemplu, numele clasei de pornire a aplica ției în care se găse ște o func ție
public static void main(String[] args);. JDK conține utilitarul jar pentru crearea de arhive JAR.
Colectarea fișierelor într-o singură arhivă JAR facilitează transmiterea codului executabil pe
Internet.
Clasele Java sunt organizate în ierarhi de pachete și sub-pachete ce urmează structura de directoare în care
sunt stocate clasele compilate. Toate clasele dintr-un pachet vor fi colectate într-un singur director. Dacă
pachetul conține sub-pachete acestea apar ca subdirectoare ale directorului pachetului.
Fiecare fișier .java începe cu mențiunea package: se indică numele pachetului din care vor face parte clasele.
package javalectures.exercises;
class HelloWorldApp {
public static void main(String[] args) {
System.out.println("Hello World!"); // Display the string.
}
}
Pentru ca o ierarhie de clase sa poată fi vizibila de către clase din afara ei, rădăcinile arborilor de directoare în
care sunt memorate clasele trebuie adăuga ți în variabila sistem CLASSPATH.
C:\Users\Alexandru>echo %CLASSPATH%
C:\Program Files\Java\jdk1.8.2_14\jre\lib;C:\MyApps\lib;
Compilatorul și interpretorul Java caută automat clasele și arhivele ce con țin fi șiere .class în lista specificata
de această variabilă sistem. Directorul curent .\ este întotdeauna prezent în variabila CLASSPATH.
- Numele de clase ar trebui să fie substantive. Numele de clase formate din mai multe cuvinte se fac cu
capitalizarea fiecărui cuvânt din nume.
class Raster {};
class ImageSprite {};
- Numele constantelor de clasă și ale constantelor ANSI ar trebui să fie toate scrise cu majuscule, iar cuvintele
separate de caracterul "_".
static final int MIN_WIDTH = 4;
static final int MAX_WIDTH = 999;
static final String GET_THE_CPU_TYPE = "Intel";
Întrebări recapitulative
Întrebări pentru testul scris:
1. Ce este un compilator?
2. Ce este un interpretor?
3. Ce este un asamblor?
4. Ce este compilarea in doua faze?
5. Explicati diferenta intre codul masina si codul intermediar Java.
6. Cum difera compilarea si executia din C fata de Java?
7. Detaliati caracteristicile celor cinci generatii de limbaje de programare?
8. La ce foloseste variabila CLASSPATH?
9. Ce este JVM? Dar JRE? sau JDK?
10. Prezentati conventiile de numire ale pachetelor, claselor, metodelor, constantelor si variabilelor in
Java.
2 Codul intermediar Java poarta numele de: 9 Numele unei metode ar trebui sa fie format din:
A. cod obiect A. litere
B. bytecode B. cifre
C. cod masina C. verbe
D. majuscule
3 Java este un limbaj de programare de generatia a: 10 Compilarea fisierelor Java se face prin comanda:
A. cincea A. java
B. treia B. javac
C. doua C. gcc
6 Fisierele compilate cu extensia .class sunt regrupate de 13 Programele scrise in Java sunt:
obicei in fisiere: A. mai rapide decat cele scrise in C
A. ZIP B. mai rapide decat cele scrise in limbaj de
B. GZIP asamblare
C. JAR C. mai sigure decat cele scrise in C
D. Alte tipuri de arhive
7 Limbajul Java a fost creat în anul: 14 Toate clasele dintr-un pachet vor fi colectater:
A. 1989. A. într-un singur director
B. 2001 B. in mai multe directoare
C. 1995 C. intr-un fisier ZIP
Modulul 1
Referințe bibliografice:
Modulul 2
I. Cuprins
1. Introducere în programarea
obiectulă 3. Clase interioare
2. Modele de design (Design Patterns)
4. Teste de evaluare
II. Obiective
În acest modul urmează să
prezentăm descrierea, realizarea și
implementarea conceptelor de
programare obiectuală în Java.
III.Aceste
Cuvinteconcepte
cheie sunt detaliate în
curs.obiect, incapsulare, atribute,
Clasa,
metode, compozitie, delegare,
mostenire, polimorfism, upcast,
downcast, modele de design, clase
interioare.
Light
on()
off()
brighten()
dim()
Nume tip
Interfața
Astfel, dacă lt este un obiect de tipul Ligth, atunci putem cere acestui obiect realizarea mesajului on():lt.on();
Implementarea constă în modalitatea concretă prin care se realizează func ționalitatea descrisă de interfa ță.
Astfel, în exemplul prezentat anterior, implementarea va consta în ac țiunile concrete care trebuie realizate
(programate) pentru a realiza opera ția on(). În activitatea de scriere și utilizare a programelor
informatice, se disting 2 tipuri de actori: - programatori de clase noi
- programatori care folosesc clasele create de creatorii de clase. Programele scrise de
aceștia se numesc programe client.
Scopul programatorilor de aplica ții client este de a colecta și utiliza un mediu de lucru care să
conțină cat mai multe clase, pentru a asigura dezvoltarea rapidă aplica țiilor. Scopul
programatorilor creatori de clase este de a construi clase care să furnizeze programatorilor
clienți doar ceea ce este necesar, restul componentelor clasei urmând să rămână ascunse.
Se ajunge astfel la conceptul de “ascundere a implementării”, descris în Java prin cuvintele
cheie public, private, protected. Aceste cuvinte cheie se numesc modificatori de acces.
După ce o clasă a fost creată, testată și începe să fie utilizată în programe client, se pot construi noi clase care
să conțină obiecte din clasa inițială. Astfel, se realizează “ reutilizarea implementării”, una din facilitățile
principale oferite de programarea obiectuală.
Procesul prin care se compune o nouă clasă înglobând de la clase existente se nume ște
compoziție1 (agregare).
Dacă avem creată o clasă, și dorim să creăm o clasă nouă, cu o func ționalitate similară cu a
clasei inițiale (o clasă care, eventual, să con țină aceea și func ționalitate a clasei ini țiale, extinsă
cu proprietăți noi), se folosește conceptul de moștenire. Clasa inițială de la care porne ște
procesul de moștenire se numește clasă de bază.
În cazul în care prin moștenire, clasa nouă are exact aceea și interfa ță ca și clasa de bază, având doar o
funcționalitate diferită pentru metodele din interfa ță, spunem ca obiectele din tipul clasei derivate sunt de tipul
clasei de bază. Un asemenea tip de rela ție este denumită generic rela ție de tip is-a.
Dacă însă, clasa derivată adaugă funcționalitate nouă prin crearea de noi metode în interfa ță, atunci spunem
că obiectele de tipul clasei derivate se aseamănă cu obiectele din clasa de bază. Rela ția nou creată se
numește generic relație de tipul is-like-a.
Prin moștenire, se pot crea ierarhii de clase. Între obiectele de tipul claselor din ierarhie se
pot stabili relații de tipul is-a sau is-like-a. Astfel, putem considera că obiectele din clasele derivate sunt
în același timp și obiecte din tipul clasei de bază. Programarea orientată obiect ne permite să utilizăm obiecte
de tipul claselor derivate în locul obiectelor de tipul clasei de bază. Se ob ține astfel posibilitatea schimbării
obiectelor din clase diferite între ele, realizându-se polimorfism prin interschimbare de obiecte. Prin
polimorfism, mașina virtuala Java va determina în momentul execu ției tipul din care face parte obiectul,
apelând funcționalitatea aferentă metodei solicitate.
Crearea și distrugerea obiectelor reprezintă elemente importante de care trebuie să se țină seama
la scrierea programelor. La creare obiectele necesită resurse (memorie), iar Java utilizează în mod exclusiv
alocarea dinamică a memoriei cu tip efectiv determinat la execuție (necesarul de memorie al fiecărui
obiect în parte nu se poate determina în momentul compilării). Instantierea se realizează prin intermediul
operatorului new și are ca efect crearea efectiva a obiectului cu alocarea spa țiului de memorie corespunzător
în zona de “heap” a programului.
Pentru obiectele alocate static sau pe stivă, compilatorul se ocupă de distrugerea acestora la terminarea
programului sau a unei zone de vizibilitate.
1
Composition în lb. Engleză
În Java, un obiect este o variabilă, adică un spațiu de memorie care are un identificator unic,
care păstrează date și specifică operațiile care se pot executa asupra acestor date.
Sintagma
obiect.functieMembra(listaArgumente)
înseamnă pentru JVM apelarea funcției membru pentru un obiect, iar în terminologie orientată obiect
înseamnă transmiterea unui mesaj către obiectul respectiv. Astfel, un program în Java înseamnă creare de
obiecte și transmitere de mesaje către acestea.
2.1.3.1. Încapsulare
Controlul accesului mai este denumit și “ascunderea implementării”. Încapsularea și controlul accesului
conduce la definirea unor entită ți care sunt mai mult decât simple structuri C. Astfel, prin încapsulare, putem
//definiții de metode
Stiva unei metode există doar pe durata de via ța a acestei metode: din momentul apelării până în momentul
terminării funcției (print return sau cu o excepție). Acest comportament se datorează chiar faptului ca stiva
este limitată și spațiul este pus la dispozi ția următoarei metode.
Heap-ul este un spațiu de memorie utilizat de JVM pentru a obține memorie suplimentara
la execuție (run-time) atunci când sunt create obiecte folosind cuvântul cheie new.
Variabilele de pe stivă există atâta timp cât metoda este executată. Valorile din heap există atât timp cat o
referință are ca valoare adresa acestei zone de memorie. Obiectele Java pot fi stocate și în zone de memorie
non-RAM prin operații de serializare.
Variabilele Java pot avea și tipuri primitive neobiectuale (boolean, char, byte, short, int, long, float, double sau
void). Aceste variabile de dimensiune mică sunt salvate direct pe stivă, iar ini țializarea lor nu necesită new la
alocare. Fiecare dintre acestea are un wrapper type pentru construirea obiectului din tipul obiect
corespondent. De exemplu tipului primitiv char îi corespunde tipul obiect Character. Prin autoboxing se
realizează conversia automată de la un tip primitiv la un tip wrapper asociat.
Character ch = 'x';
Bicycle.numberOfBicycles++;
Bicycle.getNumberOfBicycles();
Un exemplu recurent de astfel de metodă este main. Metodele statice nu pot accesa câmpuri/metode ne-
statice din clasa respectivă.
2.1.6. Operatori
In Java operatorii produc o valoare prin aplicarea lor. Ca și în limbajul C avem 5 tipuri de
operatori:
- aritmetici (+,+=,-,-=,*,*=,/,/=,%,%=,++,--);
- pe biți (-,&,&=,|,|=,^,^=,>>,>>=,<<,<<=,>>>,>>>=);
- relaționali (==,!=,>,<,>=,>,<=, instanceof);
- operatori logici (!, &&,||,? :).
Prioritatea operatorilor este similara celei existe în limbajul C. Ei pot produce și efecte colaterale în sensul
modificarea valorii operanzilor asupra cărora sunt aplica ți.
Operatorul de atribuire = atribuie unei variabile de tip primitiv valoarea indicată.
In cazul variabilelor de tip obiect, operatorul de atribuire face ca valoarea referin ței să indice adresa de pe
heap a obiectului. De exemplu, dacă c și d sunt obiecte, după atribuirea
c.equals(d);
La crearea unui obiect, Java apelează automat constructorul clasei respective. Constructorul default este cel
ce nu are argumente.
În C, variabilele trebuiau definite întotdeauna la începutul blocului de vizibilitate (de ex. la începutul func țiilor).
Această regulă nu se mai păstrează în Java. Astfel, obiectele pot fi definite oriunde în cod și definirea
acestora se face de regulă, cât mai aproape de locul de utilizare.
Se numește constructor implicit (default), constructorul care nu are argumente. Dacă clasa
nu are nici un constructor, atunci compilatorul va scrie (crea) automat un constructor implicit
pentru clasa respectivă. Dacă într-o clasă a fost definit cel pu țin un constructor cu parametrii,
atunci constructorul implicit (fără parametrii) nu va mai fi creat automat.
În cazul în care dorim să apelăm explicit constructorul unei clase folosim metoda
this(argumente), care apelează constructorul ce corespunde liste. Astfel, nu se repetă
secvențele de cod scrise la constructorii cu mai pu ține argumente.
Point () {
this(0,0);
}
Point (double x) {
this(x,0);
}
}
//constructor
public Point(int x, int y) {
this.x = x;
this.y = y;
}
In interiorul unei metode dintr-o clasă, la apelul unei metode din clasa curentă, nu este nevoie sa se utilizeze
this. Cuvântul cheie this este frecvent utilizat la retur, pentru a returna obiectul curent.
Dacă obiectele noastre agregă alte obiecte, colectorul se ocupă de dealocarea obiectelor agregate. Metoda
finalize se utilizează în general pentru a implementa termination conditions pentru un obiect (de
exemplu închiderea unui fișier).
Fiindcă Java nu garantează apelarea metodei finalize, pentru o ștergere corectă a obiectelor atunci când, de
exemplu, programul se termină cu o excep ție, este indicat ca codul de ștergere sa fie pus într-o metoda
dispose. Metoda dispose se apelează pe clauza finally a unui try-catch global.
Ștergerea obiectelor trebuie să se realizeze în ordine inversă creării lor, sarcină ce incumbă programatorul.
Datele membre ale unei clase pot fi ini țializate cu ajutorul blocurilor de ini țializare. Acestea pot fi statice sau
non-statice. Blocul static este apelat o singură dată, la prima creare a unui obiect din clasa respectivă sau la
apelarea unui membru statice din acea clasă. Blocul static poate să ini țializeze doar date statice.
Blocul non-static de inițializare specifică opera ții de ini țializare care trebuie efectuate indiferent de
constructorul apelat. El este necesar pentru a se permite definirea claselor interne anonime.
În descriere UML, structura de clase de mai sus poate fi reprezentată prin figura de mai jos:
Rombul plin înseamnă compoziție iar cel gol agregare. Compozi ția/Agregarea este expresia unei rela ții de tipul
“has-a”, și se traduce prin expresia: un obiect din tipul X este membru al unui obiect din
tipul Y, sau un obiect din tipul Y se compune dintr-un obiect din tipul X.
class Person {
private Heart heart;
private List<Hand> hands;
}
class City {
private List<Tree> trees;
private List<Car> cars;
}
2.1.8.2. Moștenirea
Prin moștenire se creează o clasă nouă (clasa derivată) care se aseamănă cu o clasă
existentă (clasa de bază). Moștenirea este expresia unei relații de tipul “is-like-a”.
Sintaxa moștenirii presupune indicarea clasei de bază după numele clasei care se creează,
înainte de a se trece la corpul defini ției noii clase. Java permite doar mo ștenirea simpla, adică
derivarea unei clase utilizând doar o clasă de bază.
Prin moștenire noua clasă va avea ca și membrii toți membrii clasei de bază. În plus, noua
clasă poate să își definească proprii membrii, sau să rescrie defini țiile membrilor din clasa de bază. Subsetul
de membrii din clasa de bază care vor fi mo șteni ți în clasa derivată se determină utilizând regulile de
combinare a specificatorilor de acces.
În rescriere UML, structura de clase din exemplul de mai sus este reprezentată în figura 2.2.
X
- i : int
+ X ()
+ set(in ii : int) : void
+ read() : int
+ permute() : int
Y
- i : int
+ Y ()
+ change() : int
+ set(in ii : int) : void
Noua clasă Y moștenește toate datele membre ale lui X. De fapt Y con ține un sub-obiect de tipul X ca și cum
am fi realizat agregarea tipul X în tipul Y. Astfel, la apelul sizeof se remarcă faptul că Y ocupă mai mult spa țiu
decât X. Membrii privați ai lui X există în continuare ca și membrii priva ți ai sub-obiectului, însă mecanismul
private funcționează, adică nu putem accesa ace ști membrii din afara obiectului de care apar țin (care este de
tip X). Deci, Y moștenește doar membrii din interfa ța lui X, la care se poate avea acces din afară.
Se observă modul în care metoda set din clasa Y redefinește metoda set din clasa X. în acest caz, nu e
vorba de supraîncărcare, deoarece semnăturile celor 2 func ții (cea din clasa X și cea din clasa Y) sunt
identice. La invocarea metodei set pe un obiect din clasa Y se va apela metoda set redefinită.
In Java, moștenirea se realizează ori de câte ori se creează o clasă: se mo ștene ște din java.lang.Object. De
exemplu, Fiindcă clasa nouă poate extindă comportamentul clasei vechi cuvântul cheie utilizat extends.
Pentru realizarea moștenirii, la crearea obiectului din clasa derivată, se creează un sub obiect din clasa de
bază (ca și cum ar fi realizată o compoziție către acesta). Acest obiect poate fi referit prin cuvântul cheie
super. Inițializarea sub-obiectului din clasa de bază se realizează prin apelul constructorului său.
Constructorul clasei de bază este apelat întotdeauna înaintea constructorului clasei derivate. Pentru apel al
constructorului clasei de bază cu argumente: super (argumente). Din clasa derivată, pentru a se apela o
metodă din clasa de bază se utilizează referin ța super.
class Bicycle {
// bicicleta are 2 atribute
public int gear;
public int speed;
// constructor
public Bicycle(int gear, int speed) {
this.gear = gear;
this.speed = speed;
}
// trei metode
public void applyBrake(int decrement) {
speed -= decrement;
}
//derived class
class MountainBike extends Bicycle {
// se adauga un atribut suplimentar
// constructor
public MountainBike(int gear,int speed,
int startHeight) {
// invocam constructorul clasei de baza
super(gear, speed);
seatHeight = startHeight;
}
Rulare:
No of gears are 5
speed of bicycle is 70
seat height is 25
class Delegate() {
Result doSomething() {
Result myResult = new Result();
//metoda neexpusa
Result doSomethingElse() {
Result otherResult = new Result();
return otherResult;
}
}
Principalul avantaj al delegării este flexibilitatea la runtime. Delegatul poate fi modificat cu ușurin ță la execu ție.
B
-i : int
+B()
+ ~B()
+f() : void
A C
-i : int 1 1 -a : A
+A() +C()
+~A() +~C()
+f() : void +f() : void
Se observă că de multe ori, la crearea obiectelor complexe este nevoie de apel explicit al constructorilor.
Apelul destructorilor acestor obiecte se realizează în mod automat, de către compilator. Sec țiunea următoare
va trata în detaliu ordinea de apel automat a constructorilor și destructorilor, în cazul combinării compozi ției cu
moștenirea.
Chiar dacă în constructor se apelează o metodă presupusă a se lega dinamic, în fapt, se apelează exclusiv
metoda suprascrisă (cea din clasa cu constructorul deoarece în acest caz legarea are loc la compilare).
O metodă suprascrisă dintr-o clasă derivată poate returna un tip derivat dintr-un tip returnat de metoda din
clasa de baza (care este suprascrisă). Aceasta strategie de programare poarta numele de covariant return
types.
class Base {
A method() {
System.out.println("Base method()");
return new A();
}
}
Atunci când final este precedat de static datele vor fi stocate în zona de date a programului.
class FinalVariable {
public static void main(String args[]) {
final String s = "abc";
s = "ef"; // Error because s is final.
}
}
//-----------------------------------------------------------------
---------
class SomeObject {
int i = 10;
}
class TestFinalReference {
public static void main(String args[]) {
final SomeObject t = new SomeObject();
t.i = 30; // Works correctly
}
}
Câmpurile declarate final care nu sunt inițializate, trebuie inițializate înainte de utilizare.
// a blank final static variable
static final double PI;
In cazul argumentelor declarate final, corpul metodei nu se poate schimba valoarea către care arată referin ța.
class MyClass {
private int x;
//getters and setters
void setX(int x) {
this.x = x;
}
Metode final nu pot fi suprascrise prin moștenire. Metodele private ale unei clase sunt
implicit final. Metodele final sunt tratate inline de către compilator. Codul metodei este copiat de
compilator ceea ce permite creșterea vitezei de execu ție prin evitarea generării codului pentru apel și retur
precum și a codului de introducere și extragere din stivă a parametrilor metodei.
final class A
{
In cazul clase declarate final, se inhibă mo ștenirea din clasa respectivă. Aceste clase vor avea toate metodele
implicit finale.
2.1.10. Upcast
Upcast reprezintă una din proprietățile cele mai importante introduse de paradigma obiectuală
prin conceptual de moștenire. Upcast înseamnă că obiectele din tipul claselor derivate sunt în
același timp și din tipul clasei de bază.
Să considerăm o clasă de bază Intrument, și o clasă derivată Wind. Clasa derivată moștenește toate metodele
interfeței clasei de bază. Astfel, dacă vom putea trimite un mesaj unui obiect de tip Instrument, acela și mesaj îl
vom putem trimite și unui obiect de tipul Wind. Astfel, putem spune că un obiect de tip Wind este în același
timp și de tipul Instrument. Clasa Instrument de ține o metodă play care este specifică claselor derivate,
precum clasa Wind.
Oricum, la transmiterea argumentelor pentru o func ția redefinită a clasei Instrument, compilatorul realizează
conversia automată a pointerului de tip Wind în referință de tip
Instrument. Aceasta reprezintă în fapt, opera ția de upcast.
Revenind la întrebarea referitoare la alegerea între compozi ție și mo ștenire, una din modalită țile cele mai bune
de a decide care concept să-l folosim este să încercăm să răspundem la următoarea întrebare: pe parcursul
programului, operația de upcast va fi necesară? În caz afirmativ, se recomandă utilizarea mo ștenirii. În caz
negativ, utilizarea compoziției.
enum Note {
MIDDLE_C, C_SHARP, B_FLAT; // Etc.
} ///:~
class Instrument {
public void play(Note n) {
System.out.println("Instrument.play()");
}
}
Rulare:
Wind.play() MIDDLE_C
Alternativa la early binding o reprezintă “legarea întârziată” (late binding) adică legarea
dinamică a corpului funcției de apelul acesteia exact înainte de realizarea apelului. Legarea
întârziată presupune existenta unui mecanism care să determine tipul exact al obiectului înainte
de apelul funcției. Astfel, prin determinarea exactă a tipului înainte de apel, se poate face
legare la versiunea corectă a funcției, și anume la cea redefinită în cazul în care ne situăm într-
un program cu ierarhii de clase.
Redefinirea unei funcții într-o clasă derivată se nume ște “ overriding”. în Java, mecanismul de
legare dinamică și identificarea tipului la execu ție se nume ște RTTI ( Run-Time Type
Identification). JVM utilizează legarea dinamică pentru orice apel de metodă, cu excep ția
metodelor statice și a metodelor final (private e implicit final pentru că o metodă private nu
poate fi moștenită). Legarea constructorilor este realizată și ea la compilare. Prin declararea
unei metode ca fiind final putem inhiba mecanismul de legare dinamică și cre ște eficien ța la
execuție. în ceea ce privește legarea datelor membru statice sau non-statice , acestea sunt legate (rezolvate)
la compilare.
O clasă se numește abstractă dacă are cel pu țin o metoda abstracta, adică se declară doar
semnătura ei, nu și implementarea. Pentru a exprima faptul că o metodă (si clasa care o
conține) este abstractă, Java folosește cuvântul cheie abstract.
Atunci când se moștenește dintr-o clasă abstractă, clasa derivată trebuie să implementeze toate func țiile
abstracte. În cazul în care în clasa derivată nu este implementată cel pu țin o abstractă, clasa derivată devine
la rândul său o clasă abstractă. Metodele abstracte for țează astfel să fie implementate, în clasele derivate,
care se doresc a fi clase efective.
În exemplul cu ierarhia de instrumente muzicale, metodele din interfața clasei Instrument sunt
doar de complezență, adică au fost definite deoarece este nevoie de definirea lor pentru ca programul să
funcționeze. Aceste metode nu furnizează un răspuns util. Inten ția clasei Instrument este să se creeze o
interfață pentru clasele care se vor deriva, astfel încât obiectele din clasele derivate să poată fi folosite în mod
unitar. Astfel, clasa Instrument este un bun exemplu de posibilă clasă abstractă. De obicei, se creează o clasă
abstractă în vârful unei ierarhi de clase atunci când dorim să manipulăm obiecte din diverse clase derivate
printr-o interfață comună.
Cu toate că de obicei unele metodele abstracte nu sunt implementate în clasa abstractă, este totu și posibil să
se scrie implementarea acestora. Chiar dacă o clasă abstractă va implementa toate metodele sale,
mecanismul clasei abstracte se păstrează, adică, în continuare, compilatorul împiedică crearea de obiecte din
această clasă.
De multe ori definirea tuturor metodelor unei clase abstracte este convenabilă atunci când dorim să scriem o
bucată de cod care să fie apoi apelată din toate sau din unele clase derivate, eventual din metodele care
redefinesc această metodă.
Clasele abstracte de obicei nu implementează interfa ța respectivă. în Java există conceptual distinct de
interfață. Interfețele duc conceptul de clasa abstractă cu un pas mai departe.
Se poate considera că o interfață este o clasă abstractă pură în care toate metodele sunt
abstracte și nu agregă obiecte. Dacă se poate crea o clasa de bază fără defini ții de metode și
variabile membre, se recomandă să se creeze interfe țe în locul claselor abstracte. O interfa ță
poate conține câmpuri dar acestea sunt în mod implicit static și final (mod convenabil pentru a
defini constante). Câmpurile definite în interfe țe nu pot fi blank finals, ele trebuie inițializate la
definire. Metodele declarate în interfa ță sunt în mod implicit publice.
Interfața descrie un contract între clase: o clasă care implementează o interfa ța va implementa metodele
definite în interfața. Pentru a crea o interfa ța folosim cuvântul cheie interface. Atunci când o clasă
implementează metodele unei interfețe va indica acest fapt print cuvântul cheie implements.
interface Animal {
public void eat();
public void travel();
}
Interfețele se pot combina și extinde interfe țele la fel ca și clasele. Astfel, o interfa ță poate extinde oricâte alte
interfețe (numite super-interfețe).
Nu apar coliziuni ale numelor de metode la implementarea mai multor interfe țe dacă interfe țele de bază au
același nume de metodă cu semnături diferite. Dacă însă metodele diferă doar prin tip de retur par erori la
compilare.
Interfețele reprezintă tipuri propriu-zise de date, deci pot fi definite variabile de tip interfa ță. Evident, tipul real
al variabilelor va fi cel al unei clase care implementează interfa ța.
Astfel, mecanismul de upcast este pe deplin aplicabil variabilelor de tip interfa ță: o variabilă
dintr-o anumită clasă va poseda tipul tuturor interfe țelor pe care clasa le implementează dintr-o
ierarhie de interfețe. Astfel, rolul interfe țelor este acela de a putea face upcast la mai mult decât
la o clasă de bază.
2.1.13. Downcast
Downcast reprezintă operația de conversie între un tip din partea de sus a unei ierarhii și un tip
specializat. Dow cast este operația inversă upcastului. La upcast realizarea conversiei este o
chestiune simplă, datorită faptului că se cunoa ște și se determină exact tipul destina ție a
conversiei. La downcast, problema apare datorită faptului că tipul destina ție nu este unic
determinat. La realizarea operației de downcast, programatorul trebuie să știe ca într-adevăr,
la momentul apelului, se referă un obiect din tipul derivat a șteptat. Compilatorul nu are de unde
să cunoască tipul obiectului la momentul realizării apelului. Java verifică orice conversie (cast), și Dacă nu se
poate realiza se aruncă o excepție de tipul ClassCastException
Strategy permite eliminarea unor cuplări puternice ce nu ar permite aplicarea metodei din
strategie (de exemplu, execute) decât obiectelor dintr-o anumită ierarhie, și nicidecum altor
obiecte din alte ierarhii. în cazul în care nu s-ar utiliza interfe țele, dacă am dori să aplicăm
metoda (strategia) unui obiect dintr-o clasă care nu face parte din ierarhie, nu s-ar putea.
Denumirile uzuale în exemplele acestui pattern sunt: Strategy pentru interfață sau clasa
abstractă, Concrete Strategy pentru implementarea acesteia, Context, pentru clasa care
folosește/execută strategiile.
De exemplu, să presupunem că dorim să sortăm colec ții de obiecte folosind diferite strategii de sortare.
Metoda de sortare va conține o parte fixă, ce nu se va schimba (de exemplu, anumite procesări ale datelor).
Partea variabilă va fi aici strategia de sortare (quicksort, heapsort, bubblesort, shellsort etc…) care poate fi
schimbată la execuție în funcție de natura datelor, pentru a optimiza timpul de sortare.
Figura 2.8 Diagramă UML asociată modelului de design Strategy aplicat unei probleme de sortare
Se va scrie cod care preia la intrare interfa ța din biblioteca externă și va produce interfa ța țintă
(Target) de care este nevoie în program nostru. în acest caz, adaptorul este obiectul de tip
wrapper care face posibil acest lucru. El va implementa interfa ța din programul nostru, și va
conține o referință spre un obiect (Adaptee) din librăria externă asupra căreia nu avem control.
Când îi vom apela metoda (operation), el va delega apelul spre metoda corespunzătoare
(specificOperation) a obiectul adaptat(Adaptee) asupra căruia nu avem control.
De exemplu, în figura de mai jos avem o ierarhie proprie în care toate clasele implementează interfa ța
Processor și metoda asociată process. Pe lângă această ierarhie, avem o alta asupra căreia nu avem control
și în care toate obiectele implementează interfa ța Filter, ce conține și ea o metodă asociată process. Dar o
clasă din ierarhia Filter nu este reutilizabilă din simplul motiv că interfa ță acesteia nu se potrive ște cu cea
specifică contextului în care se dore ște utilizată ( Process).
Conceptual putem însă considera că filtrele nu sunt altceva decât ni ște procesoare și am dori să le putem
folosi ca atare. Maniera în care putem atinge acest deziderat constă în crearea unui adaptor FilterAdapter ce
implementează interfața Process, înglobează un filtru propriu-zis, iar în momentul apelului metodei process
acesta delegă apelul către obiectul adaptat filtru.
Figura 2.10 Diagramă UML asociată unui exemplu de utilizare a modelului de design Adaptor
Se realizează astfel decuplarea atât dintre clasa Factory și implementările ei concrete (in diagrama de mai jos,
Implementation1Factory și Implementation2Factory), cat și dintre interfața generică a obiectului produs
(Service) și implementările sale concrete ( Implementation1 și Implementation2). Astfel se vor putea adaugă
foarte ușor noi tipuri de creatori de servicii, cu serviciile lor asociate, fără a impacta implementările deja
existente. în plus, se poate schimba la execu ție în mod dinamic tipul creatorilor de servicii.
Un alt avantaj al acestui model conceptual este legat de faptul ca anumite opera ții de control ale creării
obiectelor pot fi centralizate la nivelul clasei abstracte Factory, în corpul unor metode concrete.
Clasele interioare sunt declarate în interiorul unei alte clase (Outer Class). Clasa interioară este
un concept diferit de cel de compoziție, ce permite gruparea claselor care sunt legate din punct
de vedere logic și controlul vizibilită ții în cadrul acestora.
Clasele interioare se comporta ca un membru al clasei exterioare, astfel obiectele din aceste
clase pot accesa membrii obiectului clasei exterioare fără ca sa fie nevoie de calificare (au
dreptul de a accesa membrii clasei exterioare inclusiv cei private). Clasa interioară poate avea
modificatorii permiși atât metodelor ( public, final, abstract) cat și datelor membru
(private, protected și static). Clasele interioare cu modificatori private și protected sunt
potrivite pentru a implementa interfețe deoarece ele realizează ascunderea implementării. în
acest caz, interfețele sunt utile pentru a putea asocia claselor interioare un tip, care să ne permită folosirea
instanțelor acestora. Altfel, tipurile lor nu ar fi fost vizibile în exteriorul clasei pentru că a fost declarate private
sau protected.
interface Contents{
public int value();
}
interface Destination{
public String readLabel();
}
class TParcel {
private class PContents implements Contents {
private int i = 11;
public int value() { return i; }
}
protected class PDestination implements Destination {
private String label;
private PDestination(String whereTo) {
De obicei, clasa exterioară are o metoda care returnează un obiect din clasa interioară. Tipul
obiectului din clasa interioară se specifică prefixat de numele clasei exterioare:
OuterClass.InnerClass. Obiectul clasei interioare are o referintaa catre obiectul clasei
exterioare care l-a creat. Pentru a se obtine referinta la obiectul clasei exterioare din obiectul
clasei interioare se utilizeaza: OuterType.this. Pentru a crea un obiect din clasa inner pornind
de la un obiect din clasa outer se poate utiliza: obiectOuter.new InnerType(…).
In funcție de maniera de instatiere și rela ția cu clasa exterioară clasele interioare pot fi clasificate în patru
tipuri:
● clase interioare normale
● clase interioare anonime
● clase interioare statice (nested classes)
● clase interioare definite în interiorul metodelor sau a unui domeniu de vizibilitate
Clasele anonime sunt clase interioare fără nume create direct la momentul utilizării lor. Ele sunt
instanțiate într-un singur loc și folosite prin upcasting la o clasă de bază sau interfață. Astfel,
numele clasei nu este important, tipul fiind un subtip al unei clase sau o implementare a unei
interfețe. O clasă internă anonimă poate extinde o clasă sau implementa o singură interfa ță,
dar nu poate face ambele concomitent. De asemenea, ea nici nu poate să implementeze mai
multe interfețe.
Singurele metode care pot fi apelate pe o clasa anonimă sunt cele ale tipului pe care îl extinde sau
implementează. Caracterul ; finalizează construc ția instruc țiunii care con ține defini ția clasei anonime.
Câmpurile din clasele anonime pot fi ini țializate cu valori din domeniul unde se creează clasa. Dacă într-o
clasa anonimă se dorește a fi utilizat un argument (sau o valoare) definit în afara metodei, atunci referin ță
acestuia trebuie sa fie precedată de cuvântul cheie final.
Clasele anonime, neavând un nume, nu pot avea constructor, dar ini țializările se pot realiza în blocul non-
static de inițializare. în mod implicit, clasă de bază este creată cu constructorul default. Dacă dorim să
invocăm un alt constructor al clasei de bază se vor transmite parametrilor către constructorul clasei de bază
direct la crearea obiectului de tip clasă anonimă.
Clasele interioare pot fi create static (nested classes) dacă nu se dorește utilizarea referin ței
obiectului exterior în interiorul clasei interioare. Nu este nevoie de un obiect al clasei externe
pentru a crea un obiect al clasei interioare statice. Instan țele lor nu pot accesa câmpuri
nestatice ale clasei exterioare. Clasele interioare parte a unei interfe țe devin automat static
public. Rolul lor este acela de a crea cod comun care sa fie utilizat de toate implementările
interfeței.
Oricât de adâncă este imbricarea claselor interioare statice, acestea pot accesa obiectele membre din clasele
exterioare, indiferent de nivelul de imbricare.
Clasele interioare pot fi definite și în interiorul metodelor (sau a unui domeniu de
vizibilitate) deoarece:
● permit implementarea unei interfețe (sau extinderea unei clase). Metoda va crea și returna o
referință ce este folosită prin upcasting la clasa de bază sau interfa ță.
● permit crearea unor clase nepublice ce ajută la rezolvarea unor probleme complicate, clase ce nu au
utilitate în alte zone ale programului.
Aceste clase pot fi accesate doar din interiorul metodei (sau domeniului de vizibilitate) unde a fost definite.
Clasele locale unui domeniu de vizibilitate au acces la toate variabilele domeniului respectiv (inclusiv cele
final), însă aceste clase nu au specificator de acces, singurii modificatori care pot fi aplica ți lor sunt abstract
sau final.
Deoarece constructorul clasei interioare trebuie să fie ata șat de un obiect al clasei exterioare, la mo ștenirea
dintr-o clasa interioară, acest obiect trebuie furnizat la construc ția obiectului din clasa derivată. Acesta va
permite instantierea obiectului incorporat super din super-clasa interioară (outerClassReference.super()).
class WithInner {
class Inner {}
}
public class InheritInner extends WithInner.Inner {
//! InheritInner() {} // Nu va compila
InheritInner(WithInner wi) {
wi.super(); // aici outerclassreference.super()
}
public static void main(String[] args) {
WithInner wi = new WithInner();
InheritInner îi = new InheritInner(wi);
}
}
Clasa de tip closure furnizează o poartă spre interiorul clasei exterioare într-o manieră sigură
ce nu necesită modificarea accesul la metodele acestei clase.
Utilitatea obiectelor callback rezidă în flexibilitatea lor. Acestea permit schimbarea metodei
apelate, în mod dinamic, la execuție.
Un exemplu de implementare a obiectelor callback și closure cu ajutorul claselor interioare poate fi văzut în
codul de mai jos.
interface Incrementable {
void increment();
}
class MyIncrement {
void increment() {
System.out.println("Other operation");
}
static void f(MyIncrement mi) {
mi.increment();
}
}
class Caller {
private Incrementable callbackReference;
Caller(Incrementable cbh) {
callbackReference = cbh;
}
void go() {
callbackReference.increment();
}
}
}
}
In exemplul de mai sus, clasa Calle1 oferă o implementare standard a interfe ței Incrementable, incrementând
prin metoda increment un contor întreg. Clasa Calle2 moștenește deja o metodă increment de la clasa de
bază MyIncrement, ce are un comportament necorelat cu cel așteptat de interfața
Incrementable. Astfel, Calle2 nu poate suprascrie metoda increment pentru a implementa interfața
Incrementable, fiind forțată să ofere o implementare a acesteia într-o clasă interioară privată Closure. Astfel,
interfața clasei exterioare Calle2 rămâne nemodificată.
Accesul la instanța clasei private Closure se face prin upcast la interfața Incrementable, pe care
clasa interioara o implementează în locul clasei exterioare. Astfel, clasa Closure furnizează o poartă de intrare
spre interiorul clasei exterioare Calle2 într-o manieră sigură, referința ei oferind acces prin upcast doar la
metodele interfeței Incrementable.
Clasa Caller primește în constructor o referință către un obiect Closure ce implementează interfața
Incrementable, putând astfel sa apeleze ulterior (call back) metoda acestuia ce modifica starea obiectului
exterior Calle2.
someButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
//do something ...
}
});
Întrebări recapitulative
Întrebări pentru testul scris:
11. Care este diferenta dintre o clasa abstracta si o interfata?
12. Cum difera conceptul de compozitie de cel de agregare?
13. Ce este delegarea?
14. Ce este o clasa interioara?
15. Explicati conceptul de closure.
16. Enumerati valentele cuvantului cheie static.
17. Care sunt etapele crearii unui obiect in Java?
18. Care sunt posibilele avantaje ale utilizarii unui iterator?
19. Explicatii diferentele intre conceptele de downcast si upcast.
20. Care sunt modificatorii posibili ai unei clase interioare?
1 Cum se numeste o relatie daca obiectul continut este distrus 7 Cuvantul String in Java indica :
atunci cand obiectul ce il contine este distrus: A. O clasa
A. Agregare B. Un object
B. Compozitie C. O variabila
C. Encapsulare D. Un sir de caractere
D. Asociere
3 Din ce clasa mostenesc toate clasele din Java: 9 Care din urmatoarele afirmatii este adevarata in ceea ce
priveste clasele inner statice:
A. java.lang.class A. nu are acces la membrii ne-statici ai clasei
B. java.class.inherited exterioare
C. java.class.object B. variabilele si metodele sale trebuie sa fie statice
D. java.lang.Object C. Trebuie sa primeasca o referinta catre o instanta
din clasa exterioara pentru a se putea crea o
instanta dintr-o asemenea clasa
D. trebuie sa extinda clasa exterioara
4 Membrii statici ai unei clase se mostenesc la derivarea 10 Modelul de design iterartor e util cand:
acesteia de catre subclase: A. Colecția se bazează pe o resursă lentă, care nu
A. adevarat poate oferi toate elementele dintr-o dată
B. fals B. Colecția permite doar o parcurgerea
unidirecțională
C. Colecția este generată în mod dinamic pe măsură
ce este parcursă
D. In toate cazurile precedente
5 Ce se intampla daca o clasa implementeaza doua interfete 11 Care din urmatoarele afirmatii este adevarata atunci cand ne
ce au metode cu aceiasi semnatura: referim la o metoda a unei clase interioare locale unui
A. eroare la executie domeniu de vizibilitate:
B. eroare la compilare A. trebuie declarata final
C. nu vor aparea erori B. poate fi declarata abstract
D. prima metoda apelata este executata cu succes C. poate fi declarata static
D. poate fi declarata public
6 Care din urmatoarele afirmatii este adevarata pentru o 12 Cand un obiect este reciclat de Garbage Collector, se va
interfata: executa metoda finalize:
A. variabilele declarate in interfata sunt implicit A. da
public, static si final B. nu
B. cuvantul cheie implements indica faptul ca C. este posibila executia acesteia
interfata mosteneste din alta
C. metodele declarate in interfata sunt implicit private
D. o interfata nu poate sa extinda orice numar de
interfete
Modulul 2
Referințe bibliografice:
Modulul 3
I. Cuprins
1. Excepții
4. RTTI
2. Colecții
5. Concurența în Java
II. Obiective
3. Fluxuri de intrare-ieșire
Familarizarea cu
6. Teste de evaluare elemente
conceptuale fundamentale utilizate
în programarea Java: exceptii,
colectii generice, fluxuri de intrare-
ieșire, RTTI, fire
III.de
Cuvinte
executiecheie
exceptii, bloc try/catch, colectii
generice, serializarea obiectelor, RTTI,
reflectie, thread, partajarea resurselor,
monitoare, deadlock
Apariția unei erori este legată de anumite condi ții particulare ale variabilelor din program ( error condition), de
exemplu, o împărțire cu un operand care are valoarea 0.
Când o eroare se produce într-o metodă, ma șina virtuală Java realizează următoarele opera ții:
● creează un obiect excepție pe heap, cu new și îl pasează către sistemul de
runtime. Obiect conține informații despre tipul de excep ție și stiva de apeluri;
● Firul de execuție curent este întrerupt și referin ța la obiectul excep ție este
transmisă în afara contextului curent;
● Mecanismul de gestiune a excepțiilor caută un context potrivit pentru reluarea
execuției programului;
● Se reia execuția programului într-o por țiune de cod specială, destinată gestiunii acesteia numită
exception handler.
Procesul descris în etapele precedente poartă numele de aruncarea unei excep ții. Aruncarea explicita a unei
excepției se face folosind cuvântul cheie throw:
if (t==null)
throw new NullPointerException();
La throw se poate arunca orice obiect dintr-un tip care extind clasa Throwable (clasa rădăcină a ierarhiei
excepțiilor).
Blocul finally se dovedește foarte util când în blocurile try-catch se găsesc instrucțiuni return. El
se va executa și în acest caz, exact înainte de execuția instrucțiunii return, aceasta fiind
executată ulterior. în general, în blocul finally se eliberează resurse pentru ca acestea să nu rămână blocate în
cazul terminării intempestive a programului.
Noțiunea de excepție este foarte importantă și datorită faptului ca ea reprezintă premisa
fundamentală a tranzacțiilor. O tranzac ție e formată din una sau mai multe instruc țiuni care
compun o singură unitate logică de lucru (ori toate instrucțiunile reușesc să se execute cu
succes, ori niciuna). Tranzacția începe cu prima instruc țiune executată și se încheie în
momentul în care efectele tranzacției sunt salvate sau eliminate. O executare cu succes a unei
tranzacții înseamnă că instrucțiunile au fost executate fără erori.
Fiecare tranzacție permite două alternative. Dacă instruc țiunile dintr-o tranzac ție sunt finalizate cu succes
(commit), efectele tranzacției devin permanente (de obicei, într-o bază de date). Dacă, însă, dintr-un motiv
oarecare, instrucțiunile nu sunt finalizate cu succes (apare o eroare/excep ție), efectele tranzac ției sunt
eliminate (din baza de date) și tranzacția este derulată înapoi( rollback). Astfel, mecanismul de gestiune
a excepțiilor devine fundamental pentru a putea realiza operațiile de rollback. Folosind blocul
try-catch modelul general de tranzacție devine:
try {
// blocul de instructiuni
// din tranzactie ce pot ridica exceptii
t.begin();
instructiune 1;
...
instructiune n;
// instructiunea commit ce salveaza modificarile
// cand tranzactia se finalizeaza cu succes
t.commit();
} catch (Exception ex) {
// rollback atunci cand apare o eroare
// modificarile tranzactiei fiind pierdute
t.rollback();
}
finally {
// cod care se executa indiferent de tipul de exceptie aruncat
(sau nu)
Clasa Exception desemnează comportamentul specific pentru excep ții, fiind părintele majorită ții claselor
excepție din Java. Ea oferă printre altele metodele:
● getMessage () – moștenită de la Throwable, ce este similară cu toString pentru erori;
● printStackTrace () – ce afișează secvența de metode apelate pentru a se ajunge la instruc țiunea
ce a generat excepția. La afișări de erori se recomandă utilizarea ie șirii de eroare System.err în
loc de System.out.
Putem a loga excepțiile ce apar pe parcursul aplicației folosind java.util.logging. Mesajele
logate vor putea avea diferite nivele de gravitate : FINE, INFO, WARNING, SEVERE etc.
Dacă utilizatorul dorește să își definească propriile excep ții acestea trebuie sa mo ștenească clasa Exception.
Numele excepției utilizator trebuie să fie sugestiv pentru cauza apari ției excep ției.
Pentru prinderea oricărui tip de excep ția se va folosi în clauza catch tipul Exception.
● Checked exceptions, obiecte din clasa Exception sau subclasele sale – sunt excep ții pe care o
aplicație corect programată ar trebui să le prindă și să permită continuarea rulării programului. Ele
sunt excepțiile pe care o aplicație se a șteaptă sa le întâlnească. Fiecare subclasă a lui Exception
reprezintă un tip particular de excep ție. De exemplu, dacă într-o aplica ție se dore ște citirea unui
fișier de pe disc, dar acesta nu există, se va arunca o excep ție de tipul FileNotFoundException.
Un cod bine scris va prinde excepția, va afișa n mesaj de eroare, și va permite
probabil reintroducerea unui alt nume de fișier.
● Errors, obiecte din clasa Error sau subclasele sale – sunt folosite pentru erori de sistem sau erori
la compilare ce nu pot fi tratate de aplica ție și induc e șecul execu ției acesteia. De exemplu, dacă
heap-ul aplicației a crescut dincolo de limita admisă de ma șina virtuală și nu se mai poate aloca
spațiu pentru crearea de noi obiecte un OutOfMemoryError se va produce. Aplicația poate să
prindă această excepție pentru a afișa un mesaj de eroare. Mai apoi însă, execu ția programului va
eșua. Erorile sunt aruncate în mod automat de către Java la momentul apari ției lor ( unchecked
exceptions);
● Runtime Exceptions – ca și în cazul erorilor ele induc e șecul execu ției programului însă nu sunt
generate de factori externi ci de diverse bug-uri de programare. Ele pot fi prinse însă solu ția
evidentă constă în eliminarea din cod a bug-urile care le generează (de exemplu, division by
zero).
Excepțiile checked sunt, în general, cele prinse de blocurile try-catch. Excepțiile unchecked
(runtime, error) nu trebuie, în general, prinse în blocurile try-catch.
Excepțiile pot sa nu fie tratate în exteriorul metodei ce le generează. Pentru a indica tipurile de erori netratate
pe care o metoda le poate arunca se utilizează cuvântul cheie throws în declarația metodei. Tipurile de
excepții ce pot fi aruncate de aceasta vor fi separate prin virgulă.
Compilatorul forțează astfel programatorul să specifice excep țiile. dacă într-o metodă este posibil să apară o
excepția și programatorul nu o specifică prin throws, atunci apare o eroare la compilare. Erorile de tip
RuntimeException nu trebuie specificate prin throws, doar Checked exceptions fiind verificate la compilare.
In contextul paradigmei orientate obiect folosite în Java:
● se recomanda să nu fie aruncate excep ții în constructori, deoarece aceste metode trebuie sa î și
încheie execuția cu succes pentru a aduce obiectele într-o stare sigură;
● Dacă o metodă suprascrie o metodă din clasa de bază ea poate arunca doar excep țiile metodei
suprascrise sau clase de excepții derivate din acestea.
Rulare:
Cause : java.lang.ArrayIndexOutOfBoundsException
3.2. Colecții
Când ne referim la colecții de obiecte, tablourile(arrays) reprezintă cel mai eficient mod de a păstra
obiecte însă principalul lor neajuns este legat de dimensiunea lor fixată la compilare.
In figura 3.2 se prezintă ierarhia de interfe țe și clase ale arhitecturii. Numele interfe țelor sunt delimitate de linii
punctate iar numele claselor concrete de linii continue.
import java.util.*;
class Apple {
private static long counter;
private final long id = counter++;
public long id() { return id; }
public String toString() {
return "[ Apple id:" + id + " => " + super.toString() +
" ] ";
}
}
class Orange {}
Rulare:
Post Java Standard Edition 5 apar containerele generice în care se pot păstra doar obiecte dintr-un tipul de
bază indicat la declararea variabilei.
TipContainer<TipDeBaza> variabilaContainer;
De exemplu:
Se obține o eroare la compilare dacă se încearcă stocarea unui obiect dintr-un alt tip decât cel de bază
specificat la declarație. Nu mai este necesară opera ția de cast (conversie) la regăsirea obiectelor ( get).
Rulare:
0
1
2
[ Apple id:0 => Apple@1db9742 ]
[ Apple id:1 => Apple@106d69c ]
[ Apple id:2 => Apple@52e922e ]
Interfața java.util.Collection este implementată de majoritatea claselor ce desemnează colec ții din pachetul
java.util.
import java.util.Collection;
//...
Collection names = new ArrayList();
In colecții se pot adăuga nu doar elemente individuale ( add) ci și grupuri de elemente folosind
metodele Collection.addAll() și Collection.Arrays.asList().
import java.util.*;
public class AddingGroups {
public static void main(String[] args) {
//colectie de intregi direct initializata
Collection<Integer> collection
=new ArrayList<Integer>(Arrays.asList(1, 2,
3, 4, 5));
Integer[] moreInts = { 6, 7, 8, 9, 10 };
//metoda addAll va adauga la colectie elemente
//din vectorul moreInts
collection.addAll(Arrays.asList(moreInts));
// Runs significantly faster, but you can
}
}
List, Set, Queue și Map sunt și ele interfețe, primele trei extinzând interfața Collections.
Interfața List indica o colecție ordonată. Listele pot con ține elemente duplicate. Pe lângă opera țiile mo ștenite
de la Collection, interfața List conține operații bazate pe poziție (index), de exemplu: set,
add(la o anumita poziție), get, remove (de la o anumita poziție).
Collections.sort(list);
System.out.println(list);
Rulare:
Implicit funcția sort ordonează crescător elementele. Dacă dorim să realizăm o sortare particulară pentru un tip
de date complex, Java ne oferă interfa ța Comparator. Aceasta con ține metoda
Un obiect Map este o structură care mapează chei pe valori. Cheile nu pot fi duplicate, iar
fiecărei chei îi corespunde exact o valoare. Map modelează conceptul de func ție: prime ște în
intrare un obiect (cheia), și returnează un alt obiect (valoarea). Există trei implementări ale
interfeței Map:HashMap, TreeMap și LinkedHashMap cu particularită ți similare implementărilor
interfeței Set.
Map poate fi văzută ca:
● O pereche de colecții formată din colec ția cheilor ( keySet()) și cea a valorilor(values());
● O singură colecție de perechi (cheie, valoare) ( entrySet()).
System.out.println(persons.get("Alex"));
// adaugăm un element cu aceeași cheie
System.out.println(persons.put("Andrei", new Person("",
0)));
// put(...) întoarce elementul vechi și îl suprascrie
System.out.println(persons.get("Andrei"));
// remove(...) returnează elementul șters
System.out.println(persons.remove("Andrei"));
// afișăm structura de date
System.out.println(persons);
}
}
Rulare:
[Alex, 30]
[Andrei, 25]
[, 0]
[, 0]
{Alex=[Alex, 30], Ana=[Ana, 5]}
Interfața Map.Entry corespunde unei perechi (cheie, valoare) a unei structuri Map. Metodele sale fiind:
getKey(returnează cheia), getValue( returnează valoare), setValue (setează valoarea).
Rulare:
[9, 5, 6, 7, 10]
Construcția sintetică for-each permite și ea parcurgerea unei colecții. Ea se bazează, pe un iterator ascuns.
Astfel, elementele colecției nu pot fi șterse în timpul iterării.
sau
In cazul claselor de I/O componenta va fi una dintre cele patru clase abstracte (InputStream OutputStream,
Reader sau Writer). Toate implementările claselor concrete au constructori care iau ca parametru un obiect
din unul din aceste patru tipuri abstracte. Aceasta este caracteristica fundamentală a design pattern-ului
Decorator. Astfel, fiecare obiect de tipul de bază InputStream va încapsula un alt obiect de acela și tip, iar
apelurile către metodele din interfața sa vor fi delegate obiectului încapsulat, cu unele modificări în
comportament. în cazul claselor de I/O nivelul de delegare este de cele mai multe destul de adânc, în sensul
în care un decorator se va regăsi în interiorul unui alt decorator care la rândul său se regăse ște în interiorul
unui al treilea decorator etc.
Decoratorul are aceeași interfață cu obiectele decorate; poate însă să extindă interfa ța prin expunerea unor
metode specifice. Clasele Filter (FilterInputStream, FilterOutputStream, FilterReader, FilterWriter) sunt
rădăcina abstractă a claselor decorator din ierarhiile claselor de I/O.
Tipul efectiv al unui obiect deserializat se poate obține prin metoda getClass.
System.out.println(o.getClass() == SomeType.class);
Pe lângă metodele de scriere/citire a obiectelor cele două clase pun la dispozi ție și metode pentru scrierea
tipurilor primitive de date. Serializarea atributelor de tip primitiv din interiorul obiectelor se face automat. Atunci
când un obiect agregat este serializat întregul graf de obiecte ce este referit direct sau indirect de acesta va fi
serializat, cu condiția ca clasele obiectelor din graf să implementeze interfa ța Serializable. Procesul de
serializare este recursiv deoarece obiectele referite pot referi la rândul lor alte obiecte și
așa mai departe.
Există cazuri când dorim ca unele variabile membre sau sub-obiecte ale unui obiect să nu fie salvate automat
în procesul de serializare, sau dorim să recreăm sub-obiecte de la zero. Dacă atributele reprezintă informa ții
confidențiale sau variabile auxiliare nu dorim în general să le salvăm. Personalizarea serializarii anumitor date
se poate face implementând în clasa serializabilă metodele private writeObject și readObject. Acestea sunt
3.4. RTTI
Există două maniere de identificare a tipurilor variabilelor unui program:
● cea statică, în care identificarea tipurilor variabilelor are loc la compilare (tipul obiectelor trebuie să
fie definit și identificabil la momentul compilării);
● cea dinamică bazată pe reflecție, în care identificarea tipurilor are loc la rulare (clasele ce descriu
aceste tipuri pot fi descoperite și încărcate doar la momentul execu ției).
In Java, prin mecanismul de RTTI (Runtime Type Identification) se poate identifica tipul exact al
unui obiect la execuție (limbajul de programare are metode pentru a realiza acest lucru). Există
însă o restricție în sensul în care tipurile detectabile prin RTTI trebuie să fie cunoscute la
momentul compilării.
RTTI joacă un rol primordial și în implementarea upcast, atunci când este necesară
identificarea automată a tipului variabilei la execu ție pentru a putea invoca polimorfic metoda
suprascrisă în clasa derivată.
La încărcarea unei clase, se verifică dacă obiectul Class al tipului respectiv este încărcat, dacă nu, se
identifică fișierul .class pe disc, și octe ții acestuia sunt verifica ți înainte să fie încărca ți.
Pregătirea obiectelor unei clase pentru utilizare se realizează automat de JVM în 3 pa și:
● Încărcarea clasei: realizată de class loader, se găse ște byte codul pe disc și se creează obiectul
Class corespunzător tipului;
class Candy {
static { System.out.println("Loading Candy"); }
}
class Gum {
static { System.out.println("Loading Gum"); }
}
class Cookie {
static { System.out.println("Loading Cookie"); }
}
public class SweetShop {
public static void main(String[] args) {
System.out.println("inside main");
new Candy();
System.out.println("After creating Candy");
try {
Class.forName("Gum");
} catch(ClassNotFoundException e) {
System.out.println("Couldn’t find Gum");
}
System.out.println("After
Class.forName(\"Gum\")");
new Cookie();
System.out.println("After creating Cookie");
}
}
● Metoda object.getClass(): returnează obiectul de tip Class aferent unui tip deja încărcat de către
JVM;
● Metode ale obiectului Class: getName, getSimpleName, getCanonicalName, isInterface,
getInterfaces, getSuperclass etc.
class Toy {
// Comment out the following default constructor
// to see NoSuchMethodError from (*1*)
Toy() {}
Toy(int i) {}
}
interface HasBatteries {}
interface Waterproof {}
interface Shoots {}
Referința spre obiectul de tip Class poate fi obținută și printr-un literal astfel: NumeTip.class. Aceasta
referință este sigură în sensul în care validitatea ei poate fi verificată la momentul compilării, spre deosebire de
metoda classForName care va returna o excepție la rulare dacă clasa nu există. Crearea unei referin țe print-
un literal nu inițializează obiectul de tip Class corespunzător.
Pentru tipurile wrapper de tipuri primitive, există atributul TYPE care oferă referin ța spre obiectul class asociat
(astfel, boolean.class == Boolean.TYPE).
Dacă dorim să declarăm doar referințe spre obiectul Class al unui tip anume, variabila în cauză se va declara
de tipul generic: Class<Tip> referință. în acest caz, variabila va putea referi doar obiectul Class al tipului Tip,
nu obiecte Class ale subtipurilor acestuia. Astfel, chiar dacă clasa Integer este derivată din clasa Number,
Class<Interger> nu este subclasă a lui Class<Number>. Pentru a putea salva referințe spre obiecte
RTTI permite realizarea conversiei de tip cu ajutorul obiectului Class prin metoda obiectClass.cast(referinta).
Conversia la compilare se face folosind operatorul de cast (TipConversie)obiect însă ea poate genera
ClassCastException dacă tipurile nu sunt compatibile. Pentru a evita ridicarea unei astfel de excep ții se
utilizează operatorul instanceof de verificare a tipului.
if (x instanceof Dog)
((Dog)x).bark(); //se evita ClassCastException
try {
method = dogObject.getClass().getMethod("bark", null);
try {
method.invoke(dogObject, null);
} catch (IllegalAccessException | IllegalArgumentException
| InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
In exemplu de mai sus se presupune existenta unui obiect dogObject ce con ține o metodă bark(). Se
utilizează reflecția pentru a apela metoda bark().
Spre deosebire de un Proxy static, un Proxy dinamic se creează prin reflec ție în momentul execu ției. Clasa
unui Proxy dinamic va implementa o listă de interfe țe specificate la execu ție, iar fiecare apel spre una din
metodele interfeței va fi delegat spre un alt obiect printr-o interfa ță uniformă ce con ține metoda invoke.
Apelurile garantează conformitatea semnăturii metodelor (nume metodă, parametrii și tip de retur).Un Proxy
dinamic poate fi asimilat unei forme de fa țadă ce oferă poten țial implementarea oricărei interfe țe.
Unele probleme (precum cele de simulare) sunt concurente prin defini ția lor. Astfel, chiar dacă sistemul pe
care programul se executa are un singur CPU, programele concurente au o claritate mai mare.
Creșterea puterii de calcul este susținută de emergen ța calculatoarelor cu un număr din ce în ce mai mare de
core-uri, în condițiile plafonării vitezei chipurilor. Acestea permit prin distribuirea taskurilor pe procesoare
majorarea numărului de operații elementare pe unitatea de timp ( throughput). Chiar în condițiile sistemelor
mono-core, programele concurente se pot executa mai rapid prin exploatarea situa țiilor în care anumite
procese sunt blocate. Dacă un task (sau thread) nu poate continua (de exemplu, din cauza unor opera ții de
I/O), se spune că acel task este blocat. în acest timp, un sistem de operare multi-tasking va putea aloca
procesorul unor alte taskuri neblocate.
Abordare concurentă este utilă și în cazul programelor bazate pe evenimente permi țând realizarea de interfe țe
grafice reactive, în condițiile în care prelucrările asociate unor evenimente durează perioade de timp
semnificative. Pentru a realiza un program care răspunde inputului utilizatorului, taskul de lunga durată trebuie
rulat într-un nou fir de execuție.
Implementarea unor strategii de execu ție concurentă este foarte utilă nu doar la nivelul sistemului de operare
(prin multi-tasking) ci și la nivelul mediului de programare. Ea presupune partajarea de către firele de execu ție
a unor resurse comune, coordonarea accesului la acestea și transmiterea de informa ții intre diferitele procese
ale sistemului.
Exista doua mari clase de sisteme concurente: distribuite și concurente. Sistemele concurente pot fi clasificate
ca fiind distribuite sau paralele după cum urmează:
● Într-un sistem distribuit, fiecare procesor are memorie proprie (model de memorie distribuită).
Taskurile concurente sunt izolate unele de altele, fiecare func ție concurentă nu
produce efecte colaterale. Schimbul de informa ții se face prin trimiterea de
mesaje între procesoare. Prin messaging se realizează coordonarea taskurilor
fără să fie nevoie de partajarea de resurse;
● Într-un sistem paralel, toate procesoarele au acces la o memorie partajată.
Memorie partajată poate fi utilizat pentru schimbul de informa ții între procesoare. Majoritatea
programelor concurente scrise în C/C++ sau Java folosesc modelul de memorie partajata. în
C/C++ prin primitiva fork se creează procese separate gestionate de către sistemul de operare.
în Java în cadrul unui singur proces gestionat de către sistemul de operare, se creează mai
multe taskuri prin threading.
Java threading este preemptiv în sensul în care mecanismul de scheduling furnizează o felie de timp procesor
fiecărui fir de execuție, prin întreruperea periodică a threadului care se execută și schimbarea contextului de
execuție către un alt thread. JVM suportă un număr limitat de threaduri (de ordinul zecilor) ceea poate fi
limitativ în anumite situații.
Alternativa, neimplementată în Java, ar fi utilizarea multi-threading-ului cooperativ. Acesta nu impune o limită
a taskurilor independente care pot rula la un moment dat, fiecare thread cedând controlul(yield) către alt
thread la un anume moment convenabil.
Chiar dacă firele de execuție din Java dispun de o primitivă yield, apelul acesteia nu garantează schimbarea
contextului de execuție către un alt thread.
Pentru a crea un thread, trebuie furnizat un obiect de tip Runnable în constructorul unui obiect de tip Thread.
Metoda start a clasei Thread realizează toate ini țializările necesare și apoi apelează metoda run a obiectului
Runnable agregat. Metoda run este apelată într-un fir nou de execu ție. Mecanismul pentru lansarea thread-
urilor în execuție este non-determinist (nu putem garanta ordinea în care scheduler-ul alocă procesorul
diverselor taskuri).
Prin folosirea metodei yield() se solicită JVM să dea controlul unui alt thread din programul curent prin
invocarea scheduler-ului. Garbage collector-ul nu va colecta un thread ne-referit decât după ce acesta î și
termină execuția propriului run.
Există trei maniere de creare a unui thread:
● Se creează o clasă care implementează Runnable. Un obiect din aceasta clasă este transmis în
constructorul unui obiect de tip Thread;
● Se moștenește din clasa Thread, care ea însă și implementează Runnable, și se suprascrie
metoda run. Clasa care instantazaa fire de execu ție nu poate avea deja o superclasă, fiindcă Java
nu permite moștenirea multiplă;
● Se creează o clasă care implementează Runnable. Această clasă agregă un obiect de tip Thread
construit prin new Thread(this).
In cazul ultimelor doua metode taskurile sunt pornite din constructor (cu start) ceea ce este problematic,
deoarece thread-ul își poate începe execu ția înainte ca constructorul să se termine. Se preferă prima variantă
și utilizarea executorilor.
Clasele care implementează Runnable sau extind Thread pot fi scrise ca și clase interioare (eventual
anonime).
Java furnizează un nivel intermediar intre un client și taskurile pe care acesta le execută. în loc ca clientul să
apeleze taskurile în mod direct, un obiect intermediar (executor) va apela aceste taskuri indirect. Executorii
Crearea specializată a thread-urilor se realizează cu ajutorul interfe ței ThreadFactory. Thread-urile pot fi
personalizate prin implementarea metodei newThread. Obiecte implementând ThreadFactory pot fi furnizate
ca și constructori pentru Executori. ExecutorService-ul rezultat va utiliza acest threadFactory pentru crearea
noilor thread-uri (prin metoda newThread).
executorService.submit(new Callable<Integer>() {
@Override
public Integer call(){
System.out.println("Starting");
return new Random().nextInt(4000);
}}
);
Metoda submit produce un obiect de tip Future parametrizat cu tipul rezultatului specific returnat din task.
Obiectul Future poate fi interogat cu metoda isDone() pentru a vedea dacă metoda call a produs rezultatul.
Metoda get() a obiectului Future obține rezultatul produs de metoda call. Dacă rezultatul nu este
disponibil, metoda get blochează execuția thread-ului apelant până când rezultatul devine disponibil.
O clasă aparte de thread-uri sunt cele Daemon care sunt thread-uri de serviciu (furnizând un serviciu general
altor fire de execuție). Dacă un thread daemon creează alte thread-uri, acestea devin daemon la rândul lor.
Un deadlock apare atunci când următoarele 4 condi ții sunt simultan îndeplinite:
● Excluderea mutuală: cel puțin una din resursele folosite de către taskuri nu pot fi utilizate în
comun;
● Cel puțin un task deține o astfel de resursă și a șteaptă sa ac ționeze o altă asemenea resursă
deținută de alt task;
● astfel de resursă nu poate fi luata în mod for țat de la un task;
● poate apărea un wait circular.
Rezolvarea situației de deadlock presupune împiedecarea realizării uneia din cele 4 condi ții de mai sus.
Problema filosofilor (Dijkstra) este un exemplu clasic de program concurent ce poate genera un
deadlock. Ea modelează procese care își dispută accesul exclusiv la un număr limitat de
resurse. La o masă circulară stau N filozofi; în fa ța fiecăruia este o farfurie cu spaghete; între
oricare doi filozofi este o furculi ță; fiecare are nevoie de ambele furculi țe alăturate pentru a
mânca apoi eliberează furculițele utilizate. Filosofii desfă șoară alternant următoarele activită ți:
mănâncă si gândesc. Dacă toți filozofii iau fiecare furculi ța din stânga simultan, niciunul nu va mai putea lua
furculița din dreapta, rezultând o situa ție de deadlock.
Întrebări recapitulative
Întrebări pentru testul scris:
1 Cate stari primare contine ciclul de viata al unui thread in 7 Care din clasele de mai jos nu e in legatura cu ierarhia de
Java: clase Input/OutputStream:
A. o stare A. File
B. cinci stari B. Writer
C. trei stari C. InputStream
D. patru stari D. Reader
2 Care dintre urmatoarele clase implementeaza un tablou 8 Ce atribute nu sunt niciodata serializate:
dinamic: A. private
A. AbstractList B. protected
B. LinkedList C. static
C. ArrayList D. public
D. AbstractSet
3 Care dintre urmatoarele reprezinta o interfata de control a 9 Care din următoarele alternative sunt adevărate?:
procesului de serializare si deserializare: A. Fiecare thread are propriul spatiu de adrese
A. Serializable (propriile variabile)
B. Externalization B. Pentru a crea un thread trebuie in mod obligatoriu
C. FileFilter sa extindem clasa Thread
D. ObjectInput C. Threadurile permit unei aplicatii sa execute mai
multe taskuri in acelasi timp
D. Pentru a crea un thread trebuie sa implementam
interfata Runnable
E. niciuna din cele de mai sus
5 Care dintre următoarele clase nu apartine pachetului java.io: 11 Care din clauzele de mai jos e executata chiar daca nu se
A. String gasesc exceptii:
B. StringReader A. throws
C. Writer B. finally
D. File C. throw
D. catch
6 Care din metodele de mai jos trebuie implementate pentru 12 Cate metode are interfata Externalizable:
interfata Runnable: A. 0
A. stop() B. 1
B. run() C. 5
C. runThread() D. 2
D. stopThread()
Modulul 3
Referințe bibliografice: