Sunteți pe pagina 1din 35

Clase si obiecte, mostenire, polimorfism

Introducere
In programarea Obiect Orientata (OO) clasele se comporta ca si template-uri care
specifica caracteristicile si comportamentul obiectelor pe care clasele le poseda.

Putem crea un obiect oricand dorim sa cream o instanta specifica a unei clase.

Clasele pot crea doua tipuri de variabile:


- variabile instanta: Valoarea unei variabile instanta este asociata cu o anumita
instanta, numit si obiect, a unei clase. Valorile variabilelor instanta sunt
atribuite doar atunci cand obiectul este creat, sau instantiat, dintr-o clasa
- variabile clasa: Acestea nu sunt asociate unei anumite instante a clasei, ci sunt
disponibile chiar si atunci cand nici un obiect nu a fost instantiat dintr-o clasa.
Toate variabilele de acest fel impart (share-uiesc) o singura copie a clasei.

Intr-un mod asemanator celui anterior putem categorisi metodele in:


- metode instanta: sunt associate unei anumite instante a clasei. O metoda
instanta se poate apela doar prin obiectul instantiat
- metode clasa: se pot apela chiar daca nu s-a creat nici o instanta a clasei,
folosind numele clasei pentru a le accesa. Nu putem accesa variabile instanta
in cadrul metodelor clasa.

Incapsulare
Obiectele din viata reala au o stare, informatia asociata obiectului, si un
comportament.

Ca si in viata reala, un obiect software detine o stare si un comportament:


- starea unui obiect este pastrata de valorile atributelor clasei
- comportamentul este implementat de metode
-
Procedeul de includere a variabilelor si metodelor asociate, in interiorul aceleiasi
clase, se numeste incapsulare.

Folosirea incapsularii in programarea OO contrasteaza cu programarea traditionala,


procedurala, care tratateaza datele si codul care lucreaza cu ele ca si entitati separate.

In plus, fata de viata reala, folosirea incapsularii ofera doua avantaje majore
programatorilor:
- ascunderea informatiei: inseamna ca un obiect distinge intre membrii publici,
atributele si metode sunt disponibile tuturor obiectelor, si membrii privati,
adica atributele si metode private, care nu sunt disponibile tuturor obiectelor.
o ascunderea informatiei semnifica de fapt ca starea unui obiect poate fi
modificata doar prin intermediul metodelor publice, cu foarte putine
exceptii, in care atributele pot fi accesate direct.
o permite schimbarea codului privat al unui obiect, fara afectarea
celorlalte obiecte care interactioneaza cu el
- modularizarea: inseamna ca putem intretine codul pentru un obiect separat de
codul aferent altor obiecte. Aceasta presupune impartirea problemei ce trebuie
rezolvata in probleme mai mici. Fiecare modul, problema mai mica, reprezinta
un subprogram implementat pintr-o functie care contribuie la rezultatul final.

Crearea Claselor Java


Sintaxa generala este:

[public][abstract|final] class Identificator [extends


ClasaDeBaza][implements Interfete]{
// body
}

Observatie: intre paranteze drepte sunt trecute elementele optionale

Declararea unei clase:


- declararea unei clase Java se compune din header si body. Header-ul
precizeaza numele clasei, superclasa numita si clasa parinte, daca exista
vreuna, pe care o extinde, si intefetele pe care le implementeaza
- toate clasele Java extind in mod explicit clasa parinte Object
- daca nu se declara in mod explicit elementele optionale, compilatorul Java ia
in considerare valorile predefinite. Aceasta inseamna ca clasa curenta este o
sublasa a clasei Object, este non-publica, non-abstracta si non-finala, si nu
implementeaza nici o interfata.
- acoladele desemneaza continutul clasei sau definirea acesteia

In definirea unei clase avem mai multe elemente si anume:


- public: o clasa publica este accesibila in afara pachetului in care este
declarata. Declararea unei clase fara cuvantul cheie public face ca aceasta sa
fie accesibila doar in interiorul pachetului in care a fost declarata. Intr-un fisier
poate exista o singura clasa publica, dar multiple clase non-publice. In acest
caz fisierul trebuie sa aiba acelasi nume cu al clasei publice. Clasele Java care
nu au interfete publice se pot numi oricum, dar nu sunt executabile direct. Ele
pot fi executat indirect din alta clasa din acelasi pachet
- abstract: cuvant cheie ce se foloseste pentru a declara o clasa abstracta. O
clasa abstracta nu poate fi instantiata, asadar ea trebuie extinsa pentru a putea
fi folosita. Daca o clasa contine macar o metoda abstracta, metoda care nu
detine o implementare concreta, atunci ea trebuie declarata abstracta. O clasa
abstracta nu poate fi declarata final
- final: o clasa declarata final nu poate fi extinsa. Folosirea acestui cuvant
cheie face ca clasa definita astfel sa fie imposibil de modificat accidental sau
in mod deliberat. Cateva clase utilitare din libraria standard Java, precum
String sau StringBuffer, sunt definite final din motive de securitate.
De asemenea, clasele finale nu pot fi declarate abstract. O eroare de
compilare va aparea daca o clasa este declarata si final si abstract.
- class: cuvant cheie folosit pentru a declara o clasa. O clasa contine metode si
atribute, impreuna formand membrii clasei. O clasa poate contine ca atribute
alte clase
- Identificator reprezinta numele clasei, nu poate fi acelasi cu numele unui
cuvant cheie. Spre exemplu, nu putem numi o clasa short sau static. Ca si
conventie Java, numele clasei incepe cu o litera mare (Spre exemplu:
Customer sau Employee) si nu poate fi acelasi cu numele unei alte clase sau
interfate din pachet
- extends: specifica faptul ca clasa curenta extinde o clasa, devenind subclasa.
Procesul se numeste mostenire. In Java putem extinde o singura clasa
(mostenire simpla). Daca omitem extends inseamna ca clasa curenta extinde
in mod implicit clasa Object
- implements: indica faptul ca clasa implementeaza o interfata, sau lista de
interfete, separate prin virgula.

O clasa poate contine metode speciale numite constructori, care sunt apelate atunci
cand cream o instanta a clasei. Se folosesc de obicei pentru a initializa valorile
atributelor pentru variabilele instanta, dar pot fi folositi si pentru alte task-uri legate de
crearea obiectelor precum conectarea la un server sau efectuarea unor calcule intiale.

Cand declaram un constructor, numele acestuia trebuie sa fie acelasi cu numele clasei.
De asemenea, nu putem specifica un tip returnat, pentru ca asta ar transforma
constructorul intr-o metoda.

Se pot declara oricati constructori dorim intr-o clasa, atata timp cat ei au parametri
diferiti.

O data ce constructorul a fost definit, el este intotdeauna apelat cand un obiect este
creat. Daca nu se specifica un constructor in definitia clasei, Java creaza un
constructor default, fara parametri, care initializeaza toate atributele la 0 sau null.

Pentru toate clasele, cu exceptia clasei Object, constructorul default invoca


constructorul fara parametri al superclasei.

Daca cream cel putin un constructor, atunci Java nu va mai apela constructorul
predefinit. In cazul lipsei unui constructor fara parametri va trebui, in momentul
mostenirii, sa creem un constructor explicit. Aceasta pentru ca constructorul sau
predefinit, fara argumente, va esua sa se compileze, din moment ce va incerca sa
apeleze un constructor fara argumente, inexistent in clasa de baza.

Crearea metodelor
Declararea unei metode are doua parti: un header si un body. Acoadele include
continutul metodei, numit si body.

[nivelDeAcces] [synchronized] [static] [native] [final|abstract]


tipReturnat nume([listaParametri]) [throws Exceptii] {
// body
}

Prima parte a metodei, header-ul, se compune din mai multi modificatori optionali:
- nivelDeAcces: este dat de modificatori care sunt componente optionale din
header-ul metodei. Modificatorii de acces guverneaza posibilitatea de acces al
metodei de catre alte obiecte. Modificatorii de acess sunt:
o private
o protected
o public
o default
- syncronized: declararea unei metode synchronized previne doua apeluri
simultane ale metodei in programele multi-thread.
- static: o metoda statica, cunoscuta ca si metoda clasa, poate fi folosita chiar
daca nici un obiect, instanta al clasei, a fost creat
- native: este un modificator ce se aplica DOAR metodelor si indica faptul ca
metoda este implementata in cod platform-independent; metodele native sunt
non-portabile
- final: metodele declarate final nu pot fi suprascrise (prin mostenire)
- abstract: metodele abstract nu ofera nici un fel de impementare. O clasa
care are cel putin o metoda abstracta trebuie ea insasi sa fie declarata abstracta
si apoi sa fie extinsa.
Observatie: O metoda abstract se incheie cu ; in loc de {}.
Subclasele claselor abstracte trebuie sa ofere implementare pentru metodele
abstracte. Prima subclasa non-abstracta trebuie neaparat sa implementeze toate
metodele abstracte ale superclasei sau ale superclaselor din lantul din care face
parte, metode care nu au fost vreodata implementate in lant. Cuvantul cheie
abstract nu poate fi folosit cu: final, private, static.

Pe langa modificatorii optionali, header-ul unei metode mai contine:


- tipReturnat: Metodele pot returna:
o o referinta: variabilele referinta refera un obiect sau un sir
o un tip primitiv (Tipurile primitive sunt: char, boolean, byte, short,
int, long, double, float)
o void
- nume: numele metodei care trebuie sa fie un identificator valid si sa fie unic in
cadrul clasei (exceptie fac metodele supradefinite)
- listaParametri: declara numele si tipul argumentelor trimise unei metode de
catre apelant. Numele parametrului este folosit pentru a-l accesa in body-ul
metodei. Numele poate fi identic cu numele unei variabile din cadrul clasei,
dar nu poate fi duplicat cu al altei variabile din cadrul metodei. Lista de
parametri poate fi goala.
- throws: declara tipurile de exceptii pe care metoda le poate arunca si care sunt
trimise inapoi la apelant.

Cand se apeleaza o metoda care are parametru, in Java, se trimite intotdeauna valoarea
parametrului si este creata o copie a variabilei argument. Daca argumentul este de tip
primitiv, metoda nu poate schimba valoarea variabilei originale pentru ca ea
acceseaza o copie a variabilei. Daca argumentul este de tip referinta, metoda primeste
o copie a referintei catre obiect. Metoda poate apoi folosi aceasta referinta copie
pentru a accesa continutul obiectului original si schimba datele.

Trimiterea de varargs unei metode


Putem folosi feature-rul varargs pentru a trimite unei metode un numar variabil de
argumente. Var-args sunt folositoare in situatii in care nu se cunoaste numarul
parametrilor care vor fi trimisi metodei. Spre exemplu, cand o metoda este folosita
pentru a deschide o conexiune la Internet si are nevoie de argumente precum
username, parola, numele fisierului, protocol, samd. Daca vreunul dintre argumentele
anterioare nu sunt cunoscute se vor transmite valorile predefinite. Sintaxa in acest caz
este data de ....
methodName(type t … arguments)
In exemplul urmator vom folosi o metoda pentru a trimite un numar variabil de
argumente.

static void testMethod ( int ... tVals ){


System.out.print( "Numar de argumente: " + tVals.length +
" Contine: " );

for ( int a : tVals )


System.out.print ( a + " " );

System.out.println();
}

public static void main ( String args [] ){


testMethod ( 5 ); // 1 argument
testMethod ( 0, 0, 7 ); // 3 argumente
testMethod ( 3, 2, 1 ); // 3 argumente
}

O metoda accepta si parametri normali impreuna cu cei varargs. Trebuie insa ca cei
variabili sa fie trecuti ultimii in lista. De asemenea, o metoda nu poate declara mai
mult de un paramatru varargs.

Sintaxa in cazul amintit va fi:


methodName ( type t1, type t2, type t … arguments )

In Java, metodele cu parametri varargs pot fi supraincarcate in doua feluri:


- se pot declara metode cu diferite tipuri pentru parametrii varargs, Java facand
diferenta tipurilor in momentul apelarii metodelor.
- prin adaugarea unui parametru normal in lista de parametri. Java foloseste
ambele indicii: numarul si tipul parametrilor pentru a decide ce metoda sa
apeleze.

Dam, in continuare, un exemplu de supraincarcare:

static void testMethod ( int ... testArray ){


System.out.print( "folosind int: " +
"numar de argumente: "+testArray.length + " contine: " );

for ( int a : testArray )


System.out.print ( a + " " );
System.out.println();
}

static void testMethod ( boolean ... testArray ){


System.out.print( "folosind boolean: " +
"numar de argumente: "+testArray.length + " contine: " );

for ( boolean a : testArray )


System.out.print ( a + " " );
System.out.println();
}

static void testMethod ( String msg, int ... testArray ){


System.out.print( "folosind String si int: " +
msg + testArray.length + " contine: " );
for ( int a : testArray )
System.out.print ( a + " " );
System.out.println();
}

public static void main ( String args [] ){


testMethod ( 5 ); // 1 argument
testMethod ( "Intregi ", 0, 7 ); // 3 argumente
testMethod ( false, true, false ); // 3 argumente
}

Sa consideram exemplul anterior, care ilustreaza ambele modalitati prin metoda


testMethod(), care este supraincarcata de trei ori.

Mai intai, o metoda cu varargs se poate supraincarca declarand diferite tipuri pentru
parametrul varargs, cum se intampla in primele doua cazuri. Java face diferenta
tipurilor si stie astfel ce metoda sa apeleze.

De asemenea, se poate supraincarca si adaugand un parametru normal la lista


parametrilor. Java foloseste numarul de argumente si tipul lor pentru a sti ce metoda
sa apeleze.

Uneori pot rezulta erori atunci cand supraincarcam o metoda. Aceste erori sunt
rezultatul unui apel ambiguu catre o metoda varargs. Spre exemplu, daca apelam
metoda print(“testing”), din exemplul urmator, vom face un apel ambiguu.

Compilatorul nu este capabil sa resolve apelul metodei chiar daca lista parametrilor
celor doua metode difera.

public static void main(String[] args) throws Exception {


print("testing");
}

public static void print(String ... arguments) {


for (short i=0; i < arguments.length; i++) {
System.out.println(arguments[i]);
}
}

public static void print(String str, String ... arguments) {


for (short i=0; i < arguments.length; i++) {
System.out.println(arguments[i]);
}
}

Domeniul (scope) unei variabile


Domeniul unei variabile este spatiul in care variabila poate fi adresata corect, folosind
numele sau. Domeniul determina unde poate fi accesata, cand, si cum poate fi creata
si distrusa.

Domeniul depinde de locul unde este declarata variabila si, pentru variabile membri,
accesibilitatea lor.
Observatie: Domeniul nu este acelasi lucru cu vizibilitatea variabilei, care determina
daca variabilele membre pot fi folosite de metode ce apartin unor clase diferite.
Vizibilitatea se aplica doar variabilelor membri, pe cand domeniul se aplica
variabilelor locale, parametrilor metodei si parametrilor de tratare a erorilor, precum si
variabilelor membri.

Cele patru tipuri de domenii de variabile sunt:


- variabile membri: o variabila membru poate fi declarata oriunde in interiorul
clasei, dar in exteriorul metodelor. Este accesibila de oriunde in clasa, dar in
afara clasei doar daca are modificatorii de acces corespunzatori
- variabile locale: sunt declarate in metode sau intr-un bloc de cod dintr-o
metoda. Domeniul unei variabile se intinde de la declararea ei pana la sfarsitul
blocului de cod care contine declararea.
- parametri metoda: domeniul lor este intreaga metoda
- parametri tratare exceptii: sunt argumente catre un handler de exceptii. Sunt
accesibili in interiorul blocului catch in care sunt declarati (apelati).

Este mult mai eficient sa declaram variabile doar atunci cand avem nevoie de ele.
Declarand variabilele cu nume, valoare initiala si tip, ne putem concentra pe toate
proprietatile unei variabile locale deodata, deoarece sunt intr-un singur loc.

O variabila este considerata locala daca este declarata intr-un bloc de cod, cum ar fi o
metoda. O variabila locala este accesibila doar de codul continut de acel bloc, ce
previne accesul neautorizat al codului exterior blocului in care este variabila.

Lucrul cu obiecte in Java


Un obiect in Java este obtinut dintr-o clasa, care se este de fapt un template ce
defineste proprietatile obiectelor sale. Declararea unei clase creaza template-ul si nu
obiectul actual.

Fiecare obiect este o instanta a clasei, ce contine valori unice de date definite de clasa.
Cand cream un obiect, mai intai declaram variabila referinta a obiectului ca fiind de
un anumit tip particular de clasa.

Observatie: Obiectele pot fi declarate si de tip interfata, dar pentru a putea fi folosite
trebuie sa fie atribuite unei instante de clasa.

Pentru a crea obiectul in sine trebuie sa-l instantiem, folosind operatorul new si
numele clasei de care obiectul va apartine. De asemenea, pot fi specificati si
argumente, care initiaza un apel la constructorul clasei, cu parametrii potriviti. Daca
nu se specifica argumente, atunci constructorul fara argumente este apelat.

Variabilele referinta ale unui obiect creat in Java nu contin obiectul in sine, ci doar
adresa de memorie a obiectului. Java creeaza obiecte in memorie intr-un spatiu numit
heap. O variabila referinta a unui obiect contie informatii cu care JVM trebuie sa
localizeze obiectul din memoria heap.

Se pot creea multiple referinte ale unui obiect.


Utilizarea unui obiect
Daca accesam o variabila instanta a unui obiect, accesam de fapt variabilele specifice
ale obiectului, si nu copii ale acestora.

Pentru a accesa o variabila instanta a unui obiect folosim numele obiectului si


operatorul dot, ., sau putem folosi o expresie care evalueaza o referinta catre acel
obiect. Cand accesam o variabila instanta prin intermediul unei referinte obiect,
referim acea variabila particulara a obiectului.

Daca putem accesa variabilele unui obiect, le putem inspecta si schimba.

Pentru a apela metodele unui obiect folosim tot operatorul dot. Daca metoda intoarce
o valoare, putem atribui rezultatul unei variabile, o putem incorpora intr-o structura
decizionala sau folosi intr-un ciclu.

Tipul ENUM
O enumerare este o lista finita de valori constante. Aceasta multime de valori trebuie
sa fie bine definita si specifica unui anumit tip de data.

Java detinea cuvantul rezervat enum cu mult timp inainte de a-l fi implementat. In
J2SE 5.0 este inclus tipul enumerare, datele de tip enum extinzand clasa
java.lang.Enum. Spre deosebire de alte limbaje, enumerarile in Java sunt instante ale
unei clase.

Cand declaram enumerari, putem include orice metoda, constructori si variabile


necesare. Pentru ca enumerarile Java se bucura de toate beneficiile de a fi clase „fully-
fledged”(cu drepturi depline), compilatorul executa verificari de tip cand foloseste
instante de enumerari. Totusi, compilatorul nu permite definirea claselor care extind
in mod explicit java.lang.Enum.

Enum-urile suntmai eficiente si mai fiabile ca si pattern-ul int enum pentru ca:
- sunt mai sigure ca tip (type safe): fiecare membru al enumerarii este de tip
obiect. Aceasta insemna ca, compilatorul nu va permite folosirea unui obiect
incorect cand un anumit tip de enumerare este asteptat. Aceasta contrazice
pattern-ul int enum, in care o valoare intreaga incorecta putea fi trimisa unei
metode
- pot contine membri arbitrari
- au valori public static final: deci nu pot fi schimbate. Putem compara
aceste valori folosind operatorul == sau metoda equals()
- implementeaza interfetele Comparable si Serializable: deci putem folosi
metoda compareTo() pentru a compara si sorta valorile enumerarii.
- pot suprascrie metoda toString(): folosita pentru a returna numele valorilor
din enumerare. Putem suprascrie metoda pentru a personaliza output-ul
- ofera o metoda valueOf(): Enumerarile includ o metoda values() si o
metoda statica valueOf(), metoda care ne permite sa iteram printre valorile
enumerarii. Metoda valueOf() este complementul metodei toString(),
asadar trebuie sa suprascriem ambele metode daca dorim sa suprascriem
metoda valueOf()
- definesc o metoda finala numita ordinal(): returneaza fiecare pozitie a
valorilor enumerarii, in ordinea in care ele sunt definite in enum.

Declararea unui tip de date enum se face folosind cuvantul cheie enum urmat de
numele tipului si setul de valori.

Putem, de asemenea, sa specificam orice interfata pe care enum o implementeaza,


precum si orice alti membri. Declararea unui enum reprezinta un alt mod de declarare
a unei clase. Dupa definirea unei enumerari, putem folosi instante ale sale.

Cuvantul cheie enum face usoara definirea unei multimi fixe de valori constante
pentru tipul enumerare. Spre exemplu, pentru a reprezenta zilele unei saptamani,
declaram o clasa enum Day, si in definitia clasei specificam valorile care sunt valide
pentru enumerare. Valorilor enumerarii Day le sunt automat atribuite valorile de 0 la
6.

O data declarat tipul enumerat, putem folosi ciclu for pentru a itera in el.

public static enum Day { SUNDAY, MONDAY, TUESDAY, WEDNESDAY,


THURSDAY, FRIDAY, SATURDAY }

public static void main(String[] args) {


for (Day d : EnumSet.range(Day.MONDAY, Day.WEDNESDAY))
System.out.println(d) ;

In exemplul anterior am folosit clasa EnumSet din pachetul java.util care


gestioneaza submultimile de valori ale enumerarii.

Putem declara un enum intr-un fisier sursa separat sau intr-o clasa inclusa.

O declaratie complexa a unei enumerari poate include diferiti membri, constructori,


metode si variabile. Toti constructorii sunt implicit private. Nu avem voie sa folosim
modificatorul public in declararea constructorilor. Putem adauga metode in acelasi
mod in care adaugam metode intr-o clasa Java.

public enum ComplexEnum {


FIRST(1), THIRD(3), TENTH(10) ;

private int index = 0 ;

private ComplexEnum(int index) {


this.index = index ;
}

public int getIndex() {


return index ;
}

public void print() {


System.out.println("indexul este setat in
ComplexEnumeration la " + index) ;
}
}
public static void main(String[] args) {
ComplexEnum complex = ComplexEnum.TENTH ;
complex.print();

for (ComplexEnum c : ComplexEnum.values()) {


switch (c) {
case FIRST:
case THIRD:
System.out.println("ComplexEnum poate fi 1 sau
3") ;
break ;
default:
System.out.println("ComplexEnum este 10") ;
}
}

System.out.println("pozitia lui THIRD este " +


ComplexEnum.valueOf("THIRD").getIndex()) ;
}

Am adaugat metodele getIndex() si print() in acelasi fel ca pentru orice clasa


Java. In plus am modificat si indexul predefinit, folosind constructorul clasei.
Putem, de asemenea, sa suprascriem valorile ordinale atribuite default (0,1 si 2)
specificand valorile dorite intre paranteze.

Metoda values() intoarce un sir cu toate constantele enumerarii, in care fiecare


valoare este o instanta a tipului enum. Predefinit, metoda values() intoarce aceste
valori in ordinea in care sunt declarate. Putem folosi apoi sirul returnat ca parte dintr-
un ciclul for sau in alte contexte.

Metoda valueOf() determina care constanta din enumerare este identificata printr-un
anumit string. Metoda primeste ca parametru un string pentru cautare si intoarce un
tip enumerat daca identificatorul a fost gasit.

Metoda valueOf() in conjunctie cu metoda getIndex(), este folosita pentru a afisa


valori care corespund unui anumit string, in cazul exemplului THIRD.

Observatii:
– Un enum NU este un String sau un int; tipul constantelor enum este tipul enum
– Un enum poate fi declarat afara sau inauntrul unei clase, dar NU in interiorul
unei metode
– Un enum declarat in afara clasei NU trebuie marcat static, final, abstract,
protected sau private.
– Constructorii enum pot avea argumente si pot fi suprascrisi. De asemenea, ei NU
trebuie niciodata invocati direct in cod. Sunt intotdeauna apelati automat atunci
cand un enum este initializat

Declararea si importarea pachetelor Java


Clasele Java sunt organizate in pachete, fiecare clasa fiind membra a unui pachet.
Pachetele contin in mod obisnuit grupuri de clase aflate in legatura si permit gasirea
acestor clase foarte usor.
Modul in care structuram pachetele poate determina accesul la clase, metode si
variabile.

Pachetele faciliteaza management-ul namespace-ului, asigurandu-ne astfel ca numele


claselor din aplicatie sunt unice. Creand clase in pachete, oferim fiecarei clase un
nume si un identificator de pachet, evitand astfel coliziuni de namespace.

Daca nu plasam clasele intr-un anumit pachet, ele vor face parte dintr-un pachet
default, fara nume. Pachetul default corespunde directorului de lucru default folosit
pentru a salva fisiere .java si .class.

Pentru a adauga o clasa intr-un pachet folosim cuvantul cheie package urmat de
numele intreg al pachetului.

Daca includem package in cod, linia care-l contine trebuie sa fie prima din fisierul
sursa. Orice clasa declarata in acest fisier va apartine pachetului specificat.

Numele intreg al clasei se compune din numele clasei plus numele pachetului.
Putem plasa pachete intr-o ierarhie de subpachete. Pentru aceasta folosim operatorul
dot (Exemplu: package company.people).

Fisierele class ale claselor dintr-un pachet trebuie sa fie stocate intr-o structura de
directoare corepunzatoare ierarhiei de pachete.

Indiferent de pachetul din care face parte o clasa, fiecare clasa publica este stocata
intr-un fisier separat. Numele fisierului este format din numele clasei plus extensia
.class.

Un fisier sursa poate contine mutiple definitii de clase, dar o singura clasa publica.
Fisierul sursa trebuie trebuie sa aiba extensia .java corespunzatoare singurei clase
publice.

Toate clasele care fac parte din Java API (Application Programming Interface) au
pachetul java sau javax in varful ierarhiei de pachete.

Clasele din pachetul java sunt parte din Java API core, si sunt disponibile pe orice
platforma care suporta Java.

Pachetul javax indica un pachet care initial a fost o extensie a platformei core. Totusi,
mai multe pachete javax au fost adaugate apoi la core-ul Java API, pastrandu-si insa
numele javax.

Instructiunea import
Instructiunea import nu citeste sau incarca pachetele sau clasele referite. Permite doar
sa referim clasele direct in cod, fara a le preceda numele cu numele pachetului.
Nota: Instructiunea import in Java difera de cea din C/C++, directiva include, care si
incarca fisierele sau clasele referite.

Intr-un fisier sursa, instructiunile import apar dupa package, si inainte de orice
altceva. Putem include un numar nelimitat de instructiuni import intr-un fisier sursa.
Exista doua forme ale instructiuni import:
- Importam doar clasa: pentru a va referi direct la un anumit nume de clasa pe
parcursul codului, fara a preceda numele cu cel al pachetului, folosim
instructiunea in forma: import package.InterfaceClass;
- Importam toate clasele din pachet: pentru a ne referi direct la toate clasele din
pachet folosim forma: import package.*;

Observatie: Orice program Java importa pachetul java.lang in mod. Asadar, cand
vrem sa referim clasa java.lang.Object, putem sa va referim direct Object, fara a
folosi instructiunea import.

Instructiunea static import


Java furnizeaza o facilitate numita static import. Aceasta extinde capabilitatile
cuvantului cheie import si ne permite sa importam membrii statici ai unei interfete
sau clase.

Aceasta facilitate ne ajuta sa referim un membru static fara a folosi numele clasei,
fapt ce reduce sintaxa pentru utilizarea membrilor statici.

Putem folosi static import nu numai pentru a importa membrii clasei sau
interfetele definite de Java API, dar si pentru a importa clasele si interfetele definite
de utilizator.

Puteti folosi static import pentru a importa:


- un singur membru static. Pentru a-l referi direct, fara a-l preceda de numele
pachetului sau clasei, trebuie sa specificam:
o intregul nume de pachet
o interfata sau clasa care contine membrul dorit
o numele membrului de importat
- toti membrii statici: putem sa ne referim direct la toti membrii din clasa sau
interfata. Aceasta ne permite sa aducem la vedere toate metodele statice dintr-
o clasa sau interfata, fara a le specifica individual. Sintaxa este: import
static package.type.*

O problema potetiala in folosirea importurilor statice sunt coliziunile namespace-ului.


Cand importam toti membrii unei clase sau interfete, trebuie sa fim atenti la
ambiguitatile din numele membrilor.

Daca doua nume de membrii sunt la fel, compilatorul intotdeauna va favoriza numele
care a fost explicit referit.

import static java.lang.Math.max;


import static java.lang.System.out;
public class ForLoop {
public static void main(String[] args) {
double A, B;
double C;

A = 400.0;
B = 600.0;
C = max(A,B);
out.println("maximul este " + C);
}
}

Sau un exemplu de definire a unui pachet:

package car;

public interface CarValues {


public static final short AUTO = 1;
public static final short MANUAL = 2;
public static final short DRIVE = 3;
public static final double PI = 3.1;
}

import static car.CarValues.*;


import static java.lang.Math.PI;

public class StaticImports {


public static void main(String[] args) throws Exception {
int r = 5;
double c = 2*PI*r;

System.out.println("Circumferinta este " + c);


System.out.println("valoarea lui DRIVE este " + DRIVE);
System.out.println("valoarea lui PI este " + car.CarValues.PI);
System.out.println("valoarea lui PI este " + PI);
}
}

Accesul public si private


Limbajele OO, precum, Java ne permit sa distingem intre o interfata a unui obiect,
publica sau privata, permitand obiectului sa ascunda anumite informatii fata de alte
obiecte.

Aceasta inseamna ca putem schimba codul intern al unui obiect fara a afecte alte
obiecte care il folosesc, si sa ne asiguram ca aplicatiile nu pot folosi cod intr-un mod
neadecvat, spre exemplu accesand informatii confidentiale.

In Java putem folosi mai multe cuvinte cheie ca modificatori de acces, pentru a
controla accesul la clase, metode si variabile membrii.

Nu putem folosi modificatori de acces cu variabile locale, pentru ca aceste variabile


pot fi folosite doar in interiorul metodei in care sunt declarate.

Observatie: Variabilele membre ale unui obiect sunt variabile non-locale. Variabilele
membre si metodele sunt cunoscute si ca membrii clasei.

Modificatorii de acces folositi pentru a specifica accesul la clase, metode si variabilele


membre sunt:
- public: pentru o clasa, metoda, sau variabila pe care dorim ca orice clasa sa o
poata folosi
o in cazul unei clase indica faptul ca orice alta clasa o poate folosi, fara
nici o restrictie
o in cazul unei variabile a clasei, orice clasa din orice pachet poate
accesa acea variabila, atat timp cat clasa ce contine variabila este si ea
accesibila
o in cazul unei metode, orice clasa din orice pachet o poate invoca.
- private: o variabila, metoda, inner class poate fi private. Acestea vor putea
fi accesate doar de o instanta a clasei in care au fost definite.
o O metoda sau variabila declarata private pot fi accesate doar de o
instanta a clasei in care au fost definite. Aceasta ne permite sa ne
asiguram ca alte obiecte nu au acces direct la starea obiectelor sau
codul de implemetare privat. Pentru a permite altor clase acces indirect
la variabile private, trebuie sa cream o metoda publica care opereaza
cu acele variabile.
o Nici subclasele nu au acces la metodele si variabilele private. Daca
dorim ca acestea sa aiba accses, trebuie sa schimbam accesul in
protected.

Este recomandat sa marcam toate variabilele membre private, si sa oferim acces,


prin intermediul unor metode publice, la un anumit subset de date. Aceasta impune
incapsularea datelor si mentine integritatea lor prin prevenirea manipularii externe
ilegale sau accidentale. Variabilele locale sunt intotdeauna private, ele sunt vizibile
doar in cadrul metodei, sau blocului de cod in care sunt definite si inaccesibile codului
din afara.

Modificatorul private poate fi folosit si cu clase nested sau inner pentru a crea
obiecte sigure in cadrul obiectelor parinte.

Acces default si protected


Daca nu folosim un modificator atunci cand cream o clasa, variabila sau metoda,
acesta va avea accesul default, care este cunoscut sub numele de package access. Doar
clasele din acelasi pachet il pot accesa.

Daca cream o subclasa a clasei si o punem intr-un pachet diferit, aceasta nu va putea
accesa nici o variabila sau metoda din superclasa, care are accesul default.

Accesul la o metoda sau variabila protected este mai putin restictiv ca accesul la un
membru default. Membrii protected pot fi accesati de orice clasa din cadrul aceluiasi
pachet, exact ca la accesul default. Totusi, cele protected pot fi accesate si de
subclase, chiar daca acestea sunt in pachete diferite.

Prevenirea instantierii
Putem folosi modificatori de acces pentru a controla accesul la constructori. Prin
aceasta putem preveni instantierea unei clase intr-un cod din exteriorul clasei sau
pachetului din care face parte aceasta.

Spre exemplu, cream o clasa Config, care urmareste starea curenta a aplicatiei in timp
ce ruleaza. Din cauza functionalitatii, dorim ca o singura instanta a clasei Config sa
existe. Pentru a obtine aceasta avem nevoie de :
- prevenirea crearii unui constructor fara argumente
- toti constructorii casei Config sa fie private pentru a preveni folosirea lor de
catre cod extern.
- sa oferim acces la instanta clasei Config prin intermediul unei metode factory,
statica. Aceasta metoda ofera un acces coordonat la o singura instanta. Aceasta
este folosita in locul unui constructor. Cand primul utilizator acceseaza metoda
factory, o singura instanta a clasei este creata. Cand utilizatori ulteriori vor
accesa metoda, ei vor primi o referinta catre deja existenta instanta. Metoda
este declarata static pentru ca ea sa poate fi apelata inainte de a exista o
instanta Config.
- sa cream o variabila membru private static pentru a stoca o referinta la
instanta Config.

class Config {

// Single instance of Config stored here


private static Config config;
public String creationString = "";

// Internal constructor
private Config () {
creationString += "Created";
}

// Factory method: must be static!


public static Config getConfigInstance () {
if (config!=null)
return config;
else
return (config = new Config() );
}
}

Mostenire
Programarea OO ne permite sa definim noi clase bazate pe clasele existente, proces
cunoscut sub numele de mostenire. Clasa existenta este adesea numita superclasa si
clasa noua subclasa.

In implementarea mostenirii, POO modeleaza natura obiectelor din viata reala. Sa


luam ca exemplu angajatii unei companii. Toti angajatii detin caracteristici comune
precum numele, numarul angajatului, departamentul si titlul postului. Un portar
(janitor) are caracteristici comune cu ceilalti angajati. De asemenea, el are si propriile
caracteristici, sarcini, precum schimbul turelor, etc.

Sa presupunem ca dorim sa cream o clasa Employee care contine variabile si metode


impartasite de toate obiectele Employee precum nume, numar angajat, departament si
titlul postului. Daca dorim sa cream o clasa Portar, nu trebuie sa o scriem de la zero,
pentru ca un portar este un angajat in companie. Putem spune ca clasa Portar
(Janitor) extinde clasa Employee.

Noua clasa Portar include automat toate variabilele si metodele incluse in clasa
Employee. Clasa Portar se spune ca mosteneste clasa Employee si este o subclasa a
acesteia. Clasa Employee este superclasa clasei Portar.

In POO se determina ce clase sunt superclase sau subclase examinindu-le legaturile


dintre ele folosind exprimarea is A (este O/Un). Tipul obiectului unei subclase este de
tipul superclasei, si relatiile dintre ele subclasa si superclasa trebuie intotdeauna sa
aiba sens, asa precum o masina este Un autovehicul, o para este Un fruct si un portar
este Un angajat.

Odata creata o clasa prin extinderea unei superclase, putem scrie metode si variabile
aditionale pentru noua clasa, care nu sunt in superclasa. Acestea vor defini
caracteristicile specifice ale subclasei.

Putem sa modificam comportamentul metodelor mostenite. Spre exemplu, putem scrie


o metoda specifica portarului, metoda ce difera de aceeasi metoda din superclasa
Employee. Aceasta se numeste suprascrierea unei metode. Suprascrierea unei metode
necesita acelasi numar si tip de parametri trimisi ca metoda care este suprascrisa. Pe
de alta parte, nu putem suprascrie o metoda definita final.

In plus, putem scrie versiuni de variabile mostenite specifice subclasei. Aceasta se


numeste ascunderea variabilelor superclasei.

Daca este necesar, putem scrie subclase ale subclasei. Spre exempu, putem scrie o
clasa PortarSubsol care extinde clasa Portar si mosteneste toate variabiele si
metodele sale.

Putem sa ne cream arborele de mostenire numit si ierarhia claselor. Desi nu exista o


limita a numarului de nivele pe care il putem avea in ierarhia mostenirii, este
recomandabil ca acesta sa fie cat mai mic cu putinta. Ierarhiile complexe sunt greu de
mentinut.

Este posibil sa cream superclase care detin doar anumite functionalitati sau chiar nici
o functionalitate. Aceste superclase contin variabile si metode, unele sau chiar toate
goale, si se numesc clase abstracte. Clasele abstracte sunt proiectate pentru a fi
mostenite astfel incat functionalitatile lipsa sa poata fi furnizate in mod specific. Nu
putem crea un obiect din acest tip de superclasa. Totusi, putem crea subclase ale
claselor abstracte.

Pentru a examina cum cream o superclasa in detaliu, sa presupunem ca am fost


contractat sa scriem o aplicatie care gestioneaza rezervarile sau participantii la o
conferinta.

Vor exista doua tipuri de participanti la conferinta: Delegat si Speaker. Participantii


au anumite caracteristici, dintre care unele in comun.

Fiecare Delegat:
- are un nume si un numar de contact
- poate participa la evenimente specifice pe durata conferintei
- poate solicita mancaruri vegetariene
- trebuie sa plateasca pentru a participa la conferinta

Fiecare Speaker:
- are un nume si un numar de contact
- poate participa la evenimente specifice pe durata conferintei
- poate solicita mancaruri vegetariene
- vorbeste la un anumit eveniment

Pentru ca participantii impartasesc cateva caracteristici, cel mai eficient mod de a crea
clasele necesare este de a crea o superclasa Attendee, care include caractericticile
comune. Apoi putem crea clasele Delegate si Speaker.

Polimorfismul
Mostenirea si suprascrierea dintr-o clasa de baza reprezinta un concept important in
OOP numit polimorfism. Acesta inseamna ca putem implementa variante specifice
subclaselor ale metodelor superclaselor. O metoda dintr-o superclasa poate adopta
diferite forme dependente de subclasa careia ii apartine.

Utilizand mostenirea si polimorfismul putem:


 crea subclase extinzand superclasele
 ascunde variabilele superclasei
 suprascrie metodele superclasei adaugand functionalitate specifica

Datorita polimorfismului tipul unui obiect nu poate fi determinat la compilare ci doar


in faza de executie. Aceasta se numeste legare tarzie (late binding) a metodelor de
instanta. Legarea tarzie permite Javei sa alega versiunea corecta a metodei apelate la
executie, dependent de tipul obiectului ce este creat.

Pe linga legarea tarzie exista si legarea timpurie (early binding), in care variabilele si
metodele sunt cunoscute inca din faza de compilare.

Implementarea mostenirii in Java


Toate clasele in Java mostenesc clasa Object (din pachetul java.lang) si de aceea au
anumite caracteristici comune. Pentru a crea o subclasa folosim cuvntul rezervat
extends, conform sintaxei: public class A extends B{}. In acest caz A este
superclasa a lui B.

Cand cream o subclasa nu putem mosteni acele metode sau variabile ce au restrictii de
acces, adica sunt declarate private sau friendly si clasa derivata nu se afla in acelasi
pachet cu superclasa. Implementarea mostenirii in Java implica:
 ascunderea variabilelor: adica in subclasa putem crea o noua variabila cu
acelasi nume ca al variabilei mostenite. Subclasa va folosi noua variabila in
locul celei mostenite. Java face o copie a fiecarei variabile mostenite din
superclasa disponibila fiecarui obiect al superclasei, chiar daca variabila este
ascunsa. Pentru a accesa un membru specific dintr-o ierarhie de clase trebuie
uneori sa facem cast explicit. In exemplul urmator, variabila canFly.

class Bird {
String name;
String color;
boolean canFly;
public void move() {
System.out.println ("I'm on the wing");
}
}
class Penguin extends Bird {
static String canFly = "Penguins can't fly";
public void move() {
System.out.println (canFly);
}
}
public class Bird2 {
public static void main (String args[]) {
Penguin thePenguin = new Penguin();
thePenguin.move();
}
}
 suprascrierea unei metode: inseamna modificarea metodei din superclasa.
Metodele suprascrise au aceeasi semnatura cu cele din superclasa. Pentru a
facilita polimorfismul nu putem furniza o metoda cu un modificator de acces
mai restrictiv decat cel al superclasei. In exemplul anterior metoda move().
 Supraincarcarea unei metode: putem crea metode cu acelasi nume dar cu
semnaturi diferite. Cand Java apeleaza metoda supraincarcata a unui obiect,
versiunea de metoda aleasa depinde de numar, tip si ordinea argumentelor
chiar daca aceasta este o metoda mostenita de la superclasa. Supraincarcarea
este folosita si in cazul constructorilor.
 Mostenirea constructorilor: constructorii nu sunt neaparat mosteniti. Daca
dorim sa folosim constructorul superclasei trebuie sa-l invocam prin cuvantul
rezervat super, urmat de argumentele potrivite. Acest apel trebuie facut in
prima linie de cod a constructorului subclasei. Obiectele se construiesc in
ordinea in care au fost definite in ierarhia de clase. Daca nu apelam explicit
constructorul unei superclase, java automat va apela constructorul fara
parametri al superclasei daca acesta exista, altfel va fi semnalata eroare. Prin
super putem accesa si membri hidden. Nu putem, insa, accesa membrii super-
superclasei (super.super). Exemplu:

public class Employee extends Staff {


boolean partTime;
public Employee (String name, Department dept) {
super( name, dept);
partTime = false;
}
}

Programare generica

Introducere
Programarea generica a fost introdusa in J2SE 5.0 si modifica semnificativ sintaxa
limbajului facand codul mult mai sigur din punct de vedere al tipului de date (type
safe).

Programarea generica foloseste parametri cu tip atunci cand defineste clase sau
metode. Codul generic este inrudit cu tipurile parametrizate sau template-urile.

Programarea generica inlatura necesitatea conversiei de tip, cu avantaje substantiale


pentru programatori si pentru procesul de debugging. Astfel, multe dintre erorile de
executie sunt detectate inca din faza de compilare.

Codul non-generic este denumit cod legacy.


// Legacy Code
List list = new ArrayList();

//Generic Code
List<Integer> list = new ArrayList<Integer>();

In exemplul anterior ambele linii creaza o lista de obiecte. Prima creza un sir ce
accepta orice tip de obiect. Aceasta inseamna ca anumite metode ale clasei ArrayList
necesita conversie manuala ceea ce poate conduce la aparitia erorilor la executie.

In cea de-a doua linie am folosit cod generic. Tipul elementelor sirului a fost scris
intre semnele <> si determina ca toate elementele sa fie de tip Integer.

Avantajele folosirii tipului generic sunt:


 Colectiile generice stocheaza numai tipuri cunoscute
 Erorile de tip sunt prinse inca in faza de compilare
 In majoritatea cazurilor elimina nevoia de conversie
 Codul este mai usor de inteles
 Timpul de executie este scurtat
 Este usor de trecut de la legacy la generic si invers pentru ca exista backward
compatibilty intre generic si legacy

Dezvantaje:
 Codul generic e greu de dezvoltat
 Codul generic e greu de interpretat

Urmatorul exemplu ilustreaza producerea unei erori la executie din cauza folosirii
defectoase a tipului row (legacy code). Elementele listei se considera de tip Object.
Eroare nu a fost identificata la compilare. In versiuni mai mari de 5, avem atentionari
in ceea ce priveste folosirea tipului row.
import java.util.*;
public class GenericError {
public static void main(String[] args) {
// This list is not type safe, as it can store any type of
object
List list = new ArrayList();

list.add(5);
list.add("42");

for (Iterator i = list.iterator(); i.hasNext();) {


// urmatoarea linie va genera o eroare la rulare relativ
la conversia string-ului "4" in Integer
int value = ((Integer) i.next()) * 10;
System.out.println(value);
}
}
}

Solutia este:
import java.util.*;
public class GenericFix {
public static void main(String[] args) {
// lista va fi declarata de tip Integer
List<Integer> list = new ArrayList<Integer>();

list.add(5);
list.add("4");

for (Iterator<Integer> i = list.iterator(); i.hasNext();) {


int value = (i.next()) * 10;
System.out.println(value);
}
}
}

in care primim eroarea de incompatibilitatea a tipurilor inca din faza de compilare.

Compilatorul foloseste un proces numit erasure sau type erasure pentru a asigura
faptul ca codul generic este compatibil cu codul legacy, de aceea cele doua tipuri de
cod pot fi folosite simultan.
class ABC<T>{
// declara un obiect de tip T
T obj;

// constructorul
ABC (T aObj) {
obj = aObj
}

// o metoda ce returneaza obj


T getObj(){
return obj;
}
}

In aceasta clasa generica procesul de compilare este urmatorul: tipul parametrizat


generic, T, este indepartat si inlocuit de tipul corespunzator prin eventuale conversii.

Daca nu se specifica niciun tip atunci se foloseste tipul Object pentru a asigura
compatibilitatea cu alte clase.

Definirea unui tip generic se face dupa urmatoarea sintaxa:

class nume<lista_parametri_generici>{
}

Sintaxa poate fi aplicata si interfetelor sau oricaror tipuri container din ierarhia
Collection precum: List, Map, etc. Poate fi aplicata, de asemenea, constructorilor
sau metodelor unei clase. Parametrii sunt intotdeauna tipuri clasa. Iata cateva
exemple:

public interface<T>{
void add(T e);
}

List<Account> list = new ArrayList<>();


Map<Integer, String> level = new HashMap<>();
Putem defini metode generice in interiorul claselor generice si putem defini clase
generice ce extind clase generice sau nongenerice.

Urmatorul cod va genera eroare la atribuire din cauza incompatibilitatii tipurilor.


class Gen<Type> {
// diverse metode;
}

class GenTest {
public static void main(String[] args) {
// initializarea instantelor claselor generice Gen
Gen<Integer> a = new Gen<Integer>();
Gen<String> b = new Gen<String>();

a = b; //eroare!
}
}

Putem folosi colectii generice. Acestea pot fi declarate cat si intializate ca generice.

Daca o colectie este initializata ca generica, dar nu are o declaratie generica atunci ea
va accepta obiecte arbitrare. Aceasta este ilustrat in exemplul urmator:

List list =new ArrayList<Integer>();

Apeluri ca:

list.add(5);
list.add("4");

sunt posibile, dar vor genera atentionari la compilare. La executie, insa, urmatoarea
sintaxa va genera eroare:

for (Iterator<Integer> i = list.iterator(); i.hasNext();) {


int value = (i.next()) * 10;
System.out.println(value);
}

O restricie fundamentala in cazul programarii generice este ca un tip generic T nu este


compatibil cu Object. Astfel, nu putem face atribuirea List<T> la un List<Object>.

Motivatia vine din faptul ca un obiect de orice tip ar putea fi inserat in lista.

public class Generic {


public static void main(String[] args) {
List<Account> list = new ArrayList<Account>();

//desi Account extinde Object, o lista Account nu poate


// fi atribuita unei liste Object.
List<Object> objects = list;
}
}

class Account {
}
Tipul List<Object> este practic identic cu codul legacy List.

In anumite situatii trebuie sa facem conversii explicite cand utilizam tipuri generice.
Cazul cel mai frecvent este atunci cand folosim obiecte instante ale unor clase
derivate din clasa declarata ca parametru. Un exemplu este in cele ce urmeaza:

public class Generic {


public static void main(String[] args) {
List<Account> list = new ArrayList<Account>();
// Adaugarea unui element dintr-o clasa derivata din Account
list.add(new Mortgage());
// cand extragem elementul trebuie sa facem un cast
Mortgage mortgage = (Mortgage) list.get(0);
}
}
class Account {
}
class Mortgage extends Account {
}

Putem adauga si liste intregi intre elementele unei liste existente, caz ilustrat mai jos:

import java.util.*;
public class Generic{
public static void main(String[] args) {
List<Mortgage> mortgageList = new ArrayList<Mortgage>();
mortgageList.add(new Mortgage());
List<Deposit> depositList = new ArrayList<Deposit>();
depositList.add(new Deposit());
List<Account> accountList = new ArrayList<Account>();
accountList.addAll(mortgageList);
accountList.addAll(depositList);
}
}
class Account {
}
class Mortgage extends Account {
}
class Deposit extends Account {
}

Daca adaugarea a fost posibila, in cazul extragerii vom avea nevoie de cast. Atribuirea
directa nu este posibila, asa precum am aratat anterior. Urmatorul cod genereaza
eroare la compilare:

accountList=mortgageList;

Solutia pentru atribuirea subtipului unui supertip este folosirea sintaxei: <? extends
Account>. ? poarta denumirea de wildcard. Codul din exemplul anterior se modifica,
pentru atribuire, dupa cum urmeaza:

import java.util.*;
public class Generic {
public static void main(String[] args) {
List<Mortgage> mortgageList = new ArrayList<Mortgage>();
mortgageList.add(new Mortgage());

List<? extends Account> list = mortgageList;


list = mortgageList;
}
}
class Account {
}
class Mortgage extends Account {
}

Codul generic - tipul bounded


Tipurile bounded nu exista in cazul codului legacy. Toate obiectele in cod legacy sunt
implicit marginite superior de Object. Astfel, orice metoda poate accepta orice tip de
obiect, ceea ce determina folosirea conversiei pentru a incerca prevenirea exceptiilor.
In schimb, tipurile generice elimina conversiile, dar aceasta introduce o limitare si
anume tipurile generice nu pot fi specificate.

Presupunem ca dorim sa cream o clasa ce returneaza suma, ca un double, a unui sir


de numere de oricare dintre tipurile: int, double sau float. Aceasta ar insemna
crearea a trei clase cu cod aproape identic.

class Sum<Integer> { // calcularea sumei


}

class Sum<Float> { // calcularea sumei


}

class Sum<Double> { // calcularea sumei


}

In Java 5.0 se introduce tipul bounded pentru a elimina acest neajuns. Sintactic, in
programarea generica, tipul bounded este folosit in sintaxa <T extends
superclasa>, unde T poate fi orice subtip al superclasei sau chiar superclasa
(superclasa este de tip bounded). In cazul presupunerii nostre Integer, Float si
Double sunt toate subclase ale clasei Number, care este clasa de tip bounded.

public class Sum<T extends Number> {


T[] nums; // nums este un sir de tip Number sau subclasa a sa

// constructor
Sum (T[] o){
nums = o;
}

// tipul returnat este double in oricare dintre cazuri


double sumTotal() {
double sum = 0.0;

for (int i=0; i < nums.length; i++)


sum += nums[i].doubleValue();

return sum;
}

public static void main(String[]args){


Integer a[]={1,2,3};
Double d[]={8.0,5.0,6.0};
Sum<Integer> intregi=new Sum<Integer>(a);
System.out.println(intregi.sumTotal());
Sum<Double> reali=new Sum<Double>(d);
System.out.println(reali.sumTotal());
}
}

Clasa Number se numeste bounded si compilatorul stie ca obiectele de tip T pot apela
orice metoda declarata de Number. In plus, compilatorul va preveni crearea oricaror
obiecte Sum fara a fi numerice (instanta a lui Number sau derivate din aceasta).
Presupunem, in continuare, ca dorim sa comparam sumele calculate in doua clase Sum.
Pentru aceasta am definit in clasa Sum metoda String compare( Sum<T>). Aceasta,
insa, ridica o problema deoarece Sum<Integer> nu poate fi comparata cu
Sum<Number>, chiar daca Integer este subtip al lui Number. Pentru rezolvarea acestei
probleme Java face apel la elementul ? (wildcard), ce specifica un tip necunoscut. Cu
acesta semnatura metodei devine: String compare( Sum<?>) si acum accepta orice
tip de obiect Sum. Codul, cu adaugarea acestei metode devine:

public class Sum<T extends Number> {


T[] nums; // nums este un sir de tip Number sau subclasa a sa

// constructor
Sum (T[] o){
nums = o;
}

// tipul returnat este double in oricare dintre cazuri


double sumTotal() {
double sum = 0.0;

for (int i=0; i < nums.length; i++)


sum += nums[i].doubleValue();

return sum;
}

String comparare(Sum<?> ob){


if (this.sumTotal()<ob.sumTotal())
return "mai mic";
else
return "mai mare sau egal";
}

public static void main(String[]args){


Integer a[]={1,2,3};
Double d[]={8.0,5.0,6.0};
Sum<Integer> intregi=new Sum<Integer>(a);
System.out.println(intregi.sumTotal());
Sum<Double> reali=new Sum<Double>(d);
System.out.println(reali.sumTotal());
System.out.println(intregi.comparare(reali));
}
}

Si cu wildcardurile putem folosi tipurile marginite (bounded) intr-o sintaxa de tipul <?
extends superclasa>.
Conventie: putem folosi ce identificatoare dorim, dar urmatoarele sunt deseori foarte
sugestive:
- T: Type
- E: Element
- K: Key
- V: Value
- S, U: utilizati ca al doilea tip, al treilea, etc.

In Java SE 7 nu este nevoie sa repetam tipurile in partea dreapta a unei instructiuni.


Parantezele unghiulare simple indica ca parametrii de tip sunt aceeasi.
Ex: List <Integer> l = new ArrayList<>();

Ca bune practici adaugarea unui element intr-o colectie ar trebui facuta printr-o
metoda cu semnatura:

<T> void addItem(T a, Collection<T> c);

Copierea a doua liste ar trebui facuta folosind metode cu semnatura:

<T> void copy(List<T> dest, List<? extends T> src);

sau intr-un mod fara wild card:

<T, S extends T> void copy(List<T> dest, List<S> src);

Sintaxa ? super T denota un tip necunoscut ce este supertip al lui T si se numeste


lower bound.

Lint warnings
Lint reprezinta o functionalitate a Javei ce ne permite sa identificam cod nesigur. Lint
warnings se genereaza cand incercam sa compilam cod nesigur. Recompilarea unui
cod cu –Xlint:unchecked, va genera avertismentele de cod nesigur indicand locul
in care pot aparea erori la executie.

Declararea unei clase generice


Cand declaram o clasa generica trebuie sa furnizam parametrul tip. Clasa generica se
va executa indiferent de tipurile parametru transmise.

Cand cream o instanta a unui tip generic, argumentul tip transmis parametrului tip
trebuie sa fie tip clasa.

className<lista-arg-tip> varName= new className<lista-arg-tip>(lista-


arg-constr);

Iata un exemplu:
class GenericClass<T> {
private T genericValue;

public GenericClass(T genericValue) {


this.genericValue = genericValue;
}
public T get() {
return genericValue;
}

public void set(T genericValue) {


this.genericValue = genericValue;
}
}

Unde T este parametrul tip ce va fi inlocuit in momentul in care clasa generica va fi


apelata, ca in exemplul urmator.

import java.util.*;

public class GenericExample {


public static void main(String[] args) {
GenericClass<String> gen1 = new GenericClass<String>("Hello");
GenericClass<Integer> gen2 = new GenericClass<Integer>(8);
GenericClass<Double> gen3 = new GenericClass<Double>(2.34);

System.out.println("gen1.genericValue este " + gen1.get());


System.out.println("gen2.genericValue este " + gen2.get());
System.out.println("gen3.genericValue este " + gen3.get());

gen1.set("World");
System.out.println("gen1.genericValue is " + gen1.get());
}}

Codul declara trei obiecte de tip GenericClass, dar de versiuni diferite.

Crearea metodelor generice si a constructorilor


Constructorul poate fi generic chiar daca clasa nu este generica.

Cand declaram o metoda in interiorul clasei generice aceasta este capabila sa


foloseasca parametrul tip al clasei. Putem apela o metoda generica in metoda main()
fara a specifica argumentul tip.

Urmatoarea secventa, bazata pe exemplul anterior, va genera o eroare la compilare:

gen1.set(gen2.get());

deoarece cele doua obiecte nu au aceeasi valoare a parametrului tip.

Subclase generice
Clasele generice pot forma ierarhii de clase.

Cand o clasa generica mosteneste o alta clasa generica tipul parametru al superclasei
trebuie sa fie declarat de subclasa. Aceasta trebuie realizata chiar daca tipul nu este
folosit in subclasa.

class SubClasaGenerica<T,V> extends ClasaGenerica<T>

Cand o clasa generica mosteneste o clasa negenerica nu trebuie sa specificam niciun


argument.
class SubClasaGenerica<T> extends ClasaNegenerica

In continuare vom da un exemplu pentru a ilustra primul caz.

public class Sum {


public static void main(String[] args) {
GenericClass<String> gen1 = new
GenericClass<String>("Hello");
GenericClass<Integer> gen2 = new
GenericClass<Integer>(8);
GenericClass<Double> gen3 = new
GenericClass<Double>(2.34);

System.out.println("gen1.genericValue este " +


gen1.get());
System.out.println("gen2.genericValue este " +
gen2.get());
System.out.println("gen3.genericValue este " +
gen3.get());

gen1.set("World");
System.out.println("gen1.genericValue este " +
gen1.get());
// urmatoarea linie ar cauza eroare la compilare
// gen1.set(gen2.get());

GenericSubclass<String, Integer> subGen = new


GenericSubclass("Andy", 40);

boolean b1 = subGen instanceof GenericClass;


boolean b2 = subGen instanceof GenericSubclass;
// urmatoarea linie ar cauza eroare la compilare
// boolean b3 = subGen instanceof GenericClass<String>;

System.out.println("este subGen instanta a GenericClass:


" + b1);
System.out.println("este subGen instanta a
GenericSubclass: " + b2);

GenericClass<Integer> genInt1 = null;


GenericClass<String> genInt2 = null;

if (b1) {
// urmatoarea linie ar cauza eroare la compilare
// GenericClass<Integer> genInt1 =
(GenericClass<String>) subGen;

// urmatoarea linie NU genereaza eroare la


compilare
genInt2 = (GenericClass<String>) subGen;
}

}
}

class GenericClass<T> {
private T genericValue;

public GenericClass(T genericValue) {


this.genericValue = genericValue;
}

public T get() {
return genericValue;
}

public void set(T genericValue) {


this.genericValue = genericValue;
}
}

class GenericSubclass<T, V> extends GenericClass<T> {


private V subType;

public GenericSubclass(T genericValue, V subType) {


super(genericValue);

this.subType = subType;
}

public V getSubtype() {
return subType;
}
}

Operatorul instanceof ne permite sa determinam daca un obiect este o instanta a


unei clase. Acelasi lucru poate fi facut si pentru clasele generice.
In codul anterior subGen este o instanta a lui GenericClass.

Linia ce-l declara pe b3 va genera o eroare la compilare. Tipul


GenericClass<String> nu exista la rulare pentru ca informatiile despre tipul
obiectelor sunt indepartate la rulare printr-un proces numit type erasure.
Putem converti o instanta a unei clase generice daca sursa si destinatia au argumente
de tip identice sau compatibile.

Clase abstracte si interfete

Clase Abstracte
Putem crea clase care nu pot fi instantiate. Acestea sunt clasele abstracte. Clasele
abstracte sunt folosite pentru a defini metodele si variabilele subclaselor. Se folosesc
atunci cand dorim sa cream un grup de clase cu o baza comuna dar implementari
diferite pentru fiecare clasa.

O subclasa a unei clase abstracte nu este obligata sa implementeze vreuna dintre


metodele mostenite de la superclasa.

O clasa abstracta poate include zero sau mai multe metode abstracte. O metoda
abstracta consta doar din semnatura, fara niciun detaliu de implementare.
Daca o metoda este abstracta intr-o clasa, intreaga clasa este abstracta si acest lucru
trebuie obligatoriu declarat.

O clasa ce mosteneste o clasa abstracta ori defineste toate metodele abstracte ori
devine abstracta si acest lucru trebuie obligatoriu declarat.
Folosirea claselor abstracte pentru a specifica comportamentul comun este limitata
deoarece acesta necesita ca subclasa sa fie parte a superclasei. In plus, Java nu suporta
mostenirea multipla.

package conference;

public abstract class Attendee {

//member variables
String name;
String company;
long phoneNumber;
boolean vegetarian;
int eventsAttending;

//methods
public abstract int attendingEvents();

public boolean bookEvent(ConferenceEvent evt){

if (evt.isBookedOut())
return false;
else {
evt.bookAttendee(this);
eventsAttending++;
return true;
}
}
}

Interfete
Interfata defineste protocoale de comportament fara limitari de implementare sau
mostenire. Interfetele nu pot fi instantiate. Toate metodele dintr-o interfata trebuie
obligatoriu implementate.

Interfetele se declara prin cuvantul rezervat interface. Toate metodele din interfata
sunt predefinit abstract si public. Interfetele pot fi declarate private sau
protected doar daca sunt continute intr-o clasa.

Toate variabilele dintr-o interfata trebuie sa fie initializate si sunt considerate static
si final.
public interface MathApplication{

int MAX_VOLUME = 10;


double FACTOR = 5.5;

public void calculateVolume();


}

Interfetele pot fi extinse.

public interface interfaceB extends interfaceA

Pentru a implementa interfete folosim cuvantul rezervat implements. Daca clasa nu


implementeaza toate metodele interfetei devine abstracta. O clasa poate implementa
oricate interfete. O clasa care extinde o superclasa si implementeaza interfete mai intai
extinde.

public class classB extends SuperClassA implements TheInterface,


SecondInterface {

// implementation code

Declarari de clase si variabile instanta in Java


O clasa Java poate avea doua tipuri de membri variabila: instanta si clasa. Noi copii
ale variabilelor instanta sunt create de fiecare data un obiect al clasei este instantiat. O
variabila clasa reprezinta un membru asociat clasei nu unei instante particulare de
clasa.

Unul dintre avantajele variabilelor clasa este ca ele salveaza memorie pentru ca doar o
copie exista pentru toate obiectele. Un alt avantaj il constituie faptul ca variabilele
clasa sunt folosite pentru ca o informatie sa fie folosita la comun de obiectele clasei.

Variabilele clasa sunt formate din:


 Constante
 Contuare comune
 Variabile globale

Variabilele clasa sunt declarate static si sunt accesate fara neaparat o instanta a
clasei.

Metodele instanta sunt metode membre asociate unui obiect. Toate instantele clasei au
aceeasi implementare a metodei si pot accesa variabilele clasei (inclusiv prin this).

O metoda clasa este o metoda ce poate fi invocata fara referentierea la un obiect. Se


declara statice.

Metodele clasa pot accesa doar variabile clasa, pentru ca nu sunt asociate unei instante
a clasei. Se pot accesa prin numele clasei sau al unui obiect.

Initializarea intr-o singura linie este cel mai uzual mod de a initializa variabilele unei
clase. Totusi, au dezavantajul ca initializarea nu poate arunca exceptii. O varianta ar fi
initializarea in constructor. Initializarea varibilelor clasa poate fi facuta si intr-un bloc
static{}, ceea ce conduce la salvarea memoriei. Intr-un bloc static putem arunca
exceptii. Exemplu:

public class Exemplu{


public static int s;
static{
s=7;
System.out.println("salut "+s);
}
public static void main(String args[]) {
}
}
Intr-o clasa putem include oricate contexte statice. Interpretorul va apela blocurile in
ordinea in care au aparut in cod. Blocurile statice se executa inaintea functiei main().

Modificatorul final
Modificatorul final permite crearea unei clase sau membru ce nu poate fi modificat.
Un membru variabila final nu poate fi modificat dupa initializare. Metoda finala nu
poate fi suprascrisa. O clasa finala nu poate fi extinsa. Si parametrii unei metode pot fi
declarati finali, ei fiind practic constante in interiorul metodei. Aceasta nu afecteaza
suprascrierea metodei. Parametrii final nu afecteza argumentele transmise metodei
singurul efect este ca ele nu pot fi modificate nici macar in interiorul metodei.

public void getNumber(final int x, final int y){


// ...
}

De obicei variabilele finale sunt statice. Daca initilizarea lor nu s-a facut in momentul
declararii in mod sigur ele trebuie initializate in context static. Aceste variabile sunt
cunoscute ca variabile blank.

Nu declaram variabilele finale drept statice atunci cand dorim sa aiba valori diferite
pentru fiecare instanta. Ele sunt initializate in constructor.

O variabila finala ce refera un obiect intotdeauna refera acelasi obiect. Valorile din
interiorul obiectului pot fi modificate. In mod asemantor si pentru array-uri.

Metodele finale nu pot fi suprascrise in subclase. Metodele declarate private sunt


intotdeauna finale. Metodele finale imbunatatesc:
 performanta: cand compilam codul fiecare metoda ce apeleaza o metoda
finala poate substitui apelul cu codul metodei apelate. Aceasta este cunoscut
ca inlining. Aceasta poate mari viteza de executie
 securitatea: nu poate fi alterata de o alta persoana

O clasa finala nu mai poate fi clasa de baza. Toate metodele dintr-o clasa finala devin
implicit finale. Declaram o clasa finala pentru:
 consideratii de design
 optimizarea compilarii
 securitate

Garbage collection si finalization


In Java sistemele de rulare gestioneaza interactiunile task-urilor cu memoria printr-o
facilitate numita garbage collection. Garbage collection este un fir daemon (ce ruleaza
in beneficiul altor fire) ce automat elibereaza memoria alocata ce nu mai este folosita.
Un fir de executie este un proces in interiorul unui program. Un program poate
contine mai multe fire ce ruleaza concurent. Garbage collector ruleaza ca un fir de
prioritate scazuta, asteptand dupa firele de prioritate ridicata sa elibereze procesorul.
In Java 1.4 se folosesc diferiti algoritmi, printre care copying collector algorithm, care
opresc toate firele aplicatiei pina cand garbage collector este incheiat. Exista si
algoritmi care folosesc fire paralele pentru copying collector.
In 1.5 sunt introduse mai multe modificari fata de 1.4. Garbage Collector porneste un
parallel collector in locul precedentului serial collector.

Daca dorim sa ne asiguram ca un obiect este colectat de garbage collector folosim


codul:

myObject theObject = new myObject();


theObject = null;

Obiectul poate fi colectat cand garbage collector va rula. Garbage collectorul ruleaza
sincron la intervale regulate sau asincron la intervale neregulate dependent de sistemul
pe care ruleaza Java. Putem invoca garbage collector-ul oricand prin invocarea
metodei: System.gc().

Fiecare clasa Java poate avea o metoda finalize() ce ajuta returnarea resurselor
sistemului. In mod efectiv, finalizarea inseamna ca inainte ca un obiect sa fie colectat
de catre Java Runtime System i se ofera ocazia sa se curete sigur. Java apeleza
finalize() din cand in cand dupa ce sistemul determina ca un obiect este candidat
pentru returnare si inainte ca obiectul sa fie colectat. Aceasta metoda poate fi utilizata
pentru a elibera orice resursa non-memory utilizata de obiect, spre exemplu fisierele
deschise.

Orice metoda finalize() creata de utilizator trebuie sa suprascrie metoda goala


finalize() din clasa Object. Semnatura este urmatoarea:

public void finalize() throws Throwable {


firstName = null ;
lastName = null ;
super.finalize() ;
}

O clasa poate avea doar o singura metoda finalize(). Suprascrierea metodei nu este
permisa. Putem forta finalizarea prin apelul metodei runFinalization() din clasa
System. Aceasta metoda elibereaza resursele sistem prin apelul metodei finalize()
pentru toate obiectele afectate de garbage collector.

Inner clases
Clasele definite in interiorul altor clase se numesc clase interioare (inner) sau
incuibate (nested). Clasele incuibate sau interioare implementeaza relatia de agregare
intre obiecte. Agregarea numita si compunere determina faptul ca ciclul de viata al
unui obiect este intr-un totul asociat obiectului gazda. Spunem ca obiectul gazda are
un/o (has a) relatie cu obiectul interior. Clasele interioare permit claselor exterioare sa
le foloseasca fara a modifica ierarhia de mostenire.

Clasele interioare se declara la fel ca clasele obisnuite doar ca definirea lor se face
intre acoladele clasei exterioare. Clasele interioare pot mosteni orice superclasa sau
implementa orice interfata.

Clasele incuibate sunt clase statice, in timp ce clasele interioare sunt non-statice.
Clasele interioare nu pot avea membri statici sau initializatori statici.
public class Exterioara {

private int i = 100;


Interioara in;

public Exterioara(int i) {
this.i = i;
in = new Interioara(++i);
}

class Interioara {
int y;

public Interioara(int startVal) {


System.out.println(i);
y = startVal;
}
}
}

Trebuie sa avem o instanta a clasei exterioare inainte ca sa putem instantia clasa


interioara. De obicei instantierea clasei interioare se face in constructorul clasei
exterioare. Dupa ce am creat un obiect interior el refera implicit instanta clasei
exterioare, de care apartine. Toti membrii clasei exterioare sunt direct si automat
accesibili clasei interioare, inclusiv cei privati.

Cand instantiem o clasa interioara dintr-o clasa externa trebuie mai intai instantiata
clasa exterioara corespunzatoare. Aceasta se poate face intr-o singura linie:

Exterioara.Interioara in = new Exterioara().new Interioara();

sau in doua linii:

Exterioara out = new Exterioara();


Exterioara.Interioara in = out.new Interioara();

Apelul unui membru din clasa interioara se face:

in.metoda();

Calificarea completa a tipului clasei interioare este Exterioara.Interioara.


Aceasta calificare completa se foloseste doar din exteriorul clasei esterioare. Din
interior tipul este Interioara.

Dupa compilare se va crea un fisier cu numele: Exterioara$Interioara.class.


Obiectele statice incuibate opereaza in strinsa asociere cu clasele exterioare, dar nu
sunt legate de nicio instanta a acestora.

La fel ca metodele si variabilele statice, clasele incuibate pot fi folosite fara o instanta
a clasei exterioare. Asadar, clasele incuibate pot fi create fara a crea o instanta a clasei
exterioare.

In afara clasei exterioare crearea clasei incuibate se face astfel:


Exterioara.Interioara obj=new Exterioara.Interioara();

Putem crea oricate instante a unei clase incuibate. Aceasta caracteristica a claselor
incuibate le deosebeste de variabilele statice prin aceea ca o clasa are o singura copie
a fiecarei variabile statice.

Urmatorul cod are eroare de compilare:

public class Exterioara {


static int j;
int k;
static class Interioara {
void aMethod() {
int l = j;
int m = k;
}
}
}

A doua atribuire este ilegala pentru ca intr-un context static folosim o variabila
nestatica, k.

O clasa locala interioara este o clasa definita in interiorul unei metode.


public class Exterioara {

public void aMethod(final int i) {


class Interioara {
private int y;

public Interioara(int startVal) {


System.out.println(i);
y = startVal;
}
}

Interioara in = new Interioara(20);


System.out.println(in.y);
}

public static void main(String[] args){


new Exterioara().aMethod(10);

}
}

Trebuie sa declaram variabilele locale si parametrii metodei in care facem definitia


drept final daca dorim sa ii accesam din clasa locala interioara. Valorile variabilelor
si parametrilor finali vor fi copiate fiecarei instante a clasei locale.

Unul dintre motivele pentru care declaram o clasa interiora ca locala este acela ca
devine complet ascunsa codului exterior.

Pentru clasele locale nu se declara modificatori de acces expliciti. Domeniul clasei


locale este limitat la blocul in care a fost declarata.
O clasa interioara anonima este o clasa interioara ce este declarata la apelul unei
metode si nu are nume. Instantele claselor anonime sunt intotdeauna create si utilizate
in acelasi loc in care clasa a fost definita, asa incat nu avem nevoie sa folosim numele
clasei pentru a declara o referinta sau a crea o instanta. Clasele anonime nu au
constructor. O clasa anonima poate fi subclasa sau poate implementa o interfata, dar
nu poate fi o subclasa ce implementeaza interfete. Modalitatea tipica de utilizare a
claselor anonime este la manipularea evenimentelor, ele sunt folosite pe post de
receptori. Sintaxa generala este:

metoda( new Clasa(){


//corp clasa
});

In cod Clasa este de fapt o superclasa. Clasa anonima extinde de fapt Clasa. Clasa
anonima a fost instantiata (o data si numai o data) in momentul declararii si apelului
metodei metoda(), prin cuvantul rezervat new.

S-ar putea să vă placă și