Documente Academic
Documente Profesional
Documente Cultură
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.
Incapsulare
Obiectele din viata reala au o stare, informatia asociata obiectului, si un
comportament.
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.
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.
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.
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.
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.
System.out.println();
}
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.
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.
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.
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.
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.
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.
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.
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.
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.
Putem declara un enum intr-un fisier sursa separat sau intr-o clasa inclusa.
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.
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
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.
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.
Daca doua nume de membrii sunt la fel, compilatorul intotdeauna va favoriza numele
care a fost explicit referit.
A = 400.0;
B = 600.0;
C = max(A,B);
out.println("maximul este " + C);
}
}
package car;
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.
Observatie: Variabilele membre ale unui obiect sunt variabile non-locale. Variabilele
membre si metodele sunt cunoscute si ca membrii clasei.
Modificatorul private poate fi folosit si cu clase nested sau inner pentru a crea
obiecte sigure in cadrul obiectelor parinte.
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 {
// Internal constructor
private Config () {
creationString += "Created";
}
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.
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.
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.
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.
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.
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.
Pe linga legarea tarzie exista si legarea timpurie (early binding), in care variabilele si
metodele sunt cunoscute inca din faza de compilare.
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:
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.
//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.
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");
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");
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
}
Daca nu se specifica niciun tip atunci se foloseste tipul Object pentru a asigura
compatibilitatea cu alte clase.
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);
}
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:
Apeluri ca:
list.add(5);
list.add("4");
sunt posibile, dar vor genera atentionari la compilare. La executie, insa, urmatoarea
sintaxa va genera eroare:
Motivatia vine din faptul ca un obiect de orice tip ar putea fi inserat in lista.
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:
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());
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.
// constructor
Sum (T[] o){
nums = o;
}
return sum;
}
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:
// constructor
Sum (T[] o){
nums = o;
}
return sum;
}
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.
Ca bune practici adaugarea unui element intr-o colectie ar trebui facuta printr-o
metoda cu semnatura:
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.
Cand cream o instanta a unui tip generic, argumentul tip transmis parametrului tip
trebuie sa fie tip clasa.
Iata un exemplu:
class GenericClass<T> {
private T genericValue;
import java.util.*;
gen1.set("World");
System.out.println("gen1.genericValue is " + gen1.get());
}}
gen1.set(gen2.get());
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.
gen1.set("World");
System.out.println("gen1.genericValue este " +
gen1.get());
// urmatoarea linie ar cauza eroare la compilare
// gen1.set(gen2.get());
if (b1) {
// urmatoarea linie ar cauza eroare la compilare
// GenericClass<Integer> genInt1 =
(GenericClass<String>) subGen;
}
}
class GenericClass<T> {
private T genericValue;
public T get() {
return genericValue;
}
this.subType = subType;
}
public V getSubtype() {
return subType;
}
}
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 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;
//member variables
String name;
String company;
long phoneNumber;
boolean vegetarian;
int eventsAttending;
//methods
public abstract int attendingEvents();
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{
// implementation code
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 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).
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:
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.
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.
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
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.
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 {
public Exterioara(int i) {
this.i = i;
in = new Interioara(++i);
}
class Interioara {
int y;
Cand instantiem o clasa interioara dintr-o clasa externa trebuie mai intai instantiata
clasa exterioara corespunzatoare. Aceasta se poate face intr-o singura linie:
in.metoda();
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.
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.
A doua atribuire este ilegala pentru ca intr-un context static folosim o variabila
nestatica, k.
}
}
Unul dintre motivele pentru care declaram o clasa interiora ca locala este acela ca
devine complet ascunsa codului exterior.
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.