Tipuri de date. Clase si obiecte |
Tipuri primitive |
In limbajul Java avem la dispozitie urmatoarele tipuri primitive de date:
In ceea ce priveste tipurile numerice (intregi si reale), cu
ajutorul exemplului de mai jos puteti afla care sunt limitele maxime pentru
fiecare. Pentru a putea intelege pe deplin exemplul, va
trebui sa aveti putintica rabdare, pana ajungem la capitolul cu clasele
infasuratoare. Deocamdata tot ce aveti de facut este
sa compilati si sa executati programul:
MaxVariablesDemo.java public class
MaxVariablesDemo |
Daca vreti sa aflati si limitele minime pentru tipurile numerice trebuie sa inlocuiti in exemplul de mai sus simbolul MAX_VALUE cu MIN_VALUE si apoi sa rulati programul.
Asa dupa cum stim, tipurile sunt folosite pentru a putea declara variabile. Indiferent ca e vorba de tipuri primitive sau nu, intr-un program Java putem avea declaratii de variabile in urmatoarele locuri:
Despre functiile dintr-un program Java vom vorbi mai pe larg in modulul urmator, iar despre variabile care nu sunt de tipuri primitive vom vorbi in capitolul despre Crearea obiectelor.
In afara de variabile, intr-un program Java putem intalni si constante literale. In cele ce urmeaza, prin cateva exemple vom arata cum se scriu constantele pentru fiecare tip primitiv:
constantele de tip boolean: true si
false.
Tipuri definite de utilizator: clase |
Asa cum am aratat in primul modul, unul din principalele avantaje aduse de limbajele de programare OO il constituie posibilitatea utilizatorului de a-si defini propriile tipuri. Aceste tipuri sunt de fapt clasele.
O clasa reprezinta descrierea unei multimi de obiecte care au aceeasi structura si acelasi comportament.
Ca urmare,
intr-o clasa vom gasi definitiile datelor si ale operatiilor (functiilor) ce caracterizeaza obiectele clasei respective.
Datele
definite intr-o clasa se mai numesc date-membru, variabile-membru,
atribute sau campuri, iar operatiile se mai numesc metode
sau functii-membru.
Constructia sintactica
folosita pentru a defini o clasa in Java este:
class nume_clasa
Pentru a
prezenta modul in care definim membrii dintr-o clasa, vom utiliza urmatorul
exemplu concret:
class Punct public void move(int dx, int dy) public int getX( ) public int getY( ) |
Clasa Punct modeleaza un punct de pe ecranul unui monitor si are ca date-membru 2 variabile de tip int: x si y reprezentand coordonatele punctului, iar ca metode:
Se observa ca definitiile datelor si ale functiilor din interiorul clasei sunt prefixate de anumite cuvinte cheie, ca public si private, care se numesc modificatori de acces. Vom vedea putin mai jos care este rolul acestora.
In limbajele de POO clasele
pot fi considerate ca mecanisme prin care programatorul isi construieste
propriile sale tipuri de date, pe care apoi le va folosi aproape la fel cum
foloseste tipurile predefinite.
Cu alte cuvinte:
Clasa
este un tip de date si ca atare, unul dintre rolurile
ei fundamentale este acela de a servi la declararea variabilelor.
Valorile unui tip clasa
se numesc obiecte sau instante ale clasei respective.
Pentru a avea o imagine mai clara a diferentei dintre o clasa si un obiect vom considera urmatorul exemplu luat din matematica: daca dorim sa descriem multimea formata din numerele naturale mai mari decat 5 vom scrie:
M5 =
Valoarea 10 este un element particular al multimii M5.
In cazul unui program OO,
clasa este similara lui M5, adica este o
descriere a unei multimi, in timp ce obiectul este un element particular al
multimii respective. Generalizand, vom spune ca intr-un program:
Tipurile sunt multimi, iar constantele si valorile variabilelor sunt elemente ale respectivelor multimi.
Daca luam exemplul clasei Punct, putem spune ca ea descrie multimea tuturor obiectelor caracterizate prin 2 coordonate intregi si prin posibilitatea de a executa operatiile definite in clasa Punct. Un element particular al acestei multimi se obtine dand valori concrete coordonatelor x si y.
Observatie: si tipurile primitive ale unui limbaj de programare reprezinta tot multimi, dar care nu au o descriere explicita, ci una subinteleasa, derivata din modul de reprezentare interna a valorilor tipurilor respective. De exemplu, in Java tipul short este multimea numerelor intregi reprezentabile pe 2 octeti.
Crearea obiectelor |
In cele ce
urmeaza vom considera un mic program Java care foloseste clasa Punct
prezentata mai sus, pentru a vedea cum se creaza obiecte ale acestei
clase:
class Punct public void move(int dx, int dy) public int getX( ) public int getY( ) class ClientPunct |
Variabilele p1
si p2 sunt referinte la obiecte de tip Punct.
In Java orice variabila al
carei tip este o clasa constituie o referinta, adica o
variabila ale carei valori sunt adrese de memorie. La adresele respective se
vor afla obiecte ale clasei care reprezinta tipul referintei. Spunem ca referinta
indica spre un obiect.
Obiectul
indicat de o referinta nu apare automat, prin simpla declarare a referintei, ci
el trebuie creat.
De fapt lucrurile se intampla
asa: intai se creaza obiectul si apoi adresa de memorie unde el a fost pus este atribuita referintei.
Nu este suficient sa declaram:
Punct p1;
deoarece acest lucru este echivalent cu a
declara o variabila pe care nu o initializam cu nici o valoare.
In programul de mai sus se
poate observa modul in care are loc crearea unui obiect, si anume, prin aplicarea operatorului new, care se mai
numeste operator de alocare:
p1 = new Punct( );
Efectul acestui operator este urmatorul: in memorie se aloca un spatiu necesar pentru
stocarea unui obiect al clasei Punct, iar adresa zonei alocate se
returneaza ca valoare.
Vom vedea ceva mai incolo (la
capitolul despre constructori) ce semnificatie are constructia Punct( ) care apare in contextul
operatorului new.
Dupa initializare, referintele
vor putea fi folosite pentru a apela metodele
obiectelor create. Notatia prin care se realizeaza un
apel de metoda este:
nume_referinta . nume_metoda ( lista_parametri_actuali )
In figura de mai jos este reprezentata in mod intuitiv imaginea in memorie a celor 2 obiecte create in program:
Se observa ca in interiorul
obiectelor am figurat atat datele cat si functiile.
Putem sa ne imaginam ca acele date si functii exista
in atatea exemplare, cate obiecte sunt create in program la un moment
dat.
Poate va
intrebati de ce in figura apar cuvintele "stiva" si "heap".
Aici trebuie spus ca variabilele locale ale metodelor sunt alocate in mod static,
iar zona de memorie utilizata in acest scop este
exploatata in regim de stiva.
In ceea ce
priveste obiectele, ele sunt create in mod dinamic, iar zona de memorie
utilizata pentru alocari dinamice se mai numeste heap (gramada).
In cazul unui apel de forma p1.init(. . .) se mai spune ca se apeleaza metoda init pentru obiectul referit de p1, sau ca obiectul referit de p1 receptioneaza cererea (mesajul) de a executa operatia init. De aceea, cand vom prezenta anumite metode, vom utiliza frecvent denumirea de obiect receptor ca sa desemnam obiectul care detine metodele respective.
Nu intamplator am numit
clasa principala din programul nostru ClientPunct: aceasta clasa
(prin intermediul metodei main) solicita unor obiecte ale clasei Punct
sa execute anumite functii sau, altfel spus, solicita
prestarea unor servicii.
Spunem in acest caz ca
obiectele clasei Punct sunt obiecte server, iar ClientPunct
este clientul lor.
Practic, interactiunile dintre
obiecte se bazeaza pe conceptul de client-server: obiectul care apeleaza
o metoda este clientul, iar obiectul care contine
metoda respectiva este serverul.
Reguli de vizibilitate |
Am vazut ca in definitiile membrilor
unei clase se utilizeaza niste cuvinte speciale, numite modificatori de
acces. Acestia stabilesc drepturile de acces ale
clientilor la membrii unei clase.
Cand discutam despre
drepturile de acces la membrii unei clase trebuie sa
abordam acest subiect din 2 perspective:
1. Interiorul clasei sau, mai concret, metodele clasei.In cadrul metodelor unei clase exista acces nerestrictiv la toti membrii, date sau functii. De exemplu, in metodele clasei Punct se face referire la campurile x si y. In mod asemanator s-ar fi putut referi (apela) si metodele. De exemplu, am fi putut defini functia move pe baza functiei init astfel:
class
Punct |
Se
observa ca in interiorul clasei nu se foloseste notatia cu punct pentru a
referi membrii, acestia fiind pur si simplu accesati prin numele lor. Cand o metoda face referire la alti
membri ai clasei, de fapt sunt accesati membrii corespunzatori ai obiectului
receptor, indiferent care ar fi el. De exemplu, cand se apeleaza metoda init
a obiectului referit de p1, are loc initializarea membrilor x
si y ai acelui obiect.
In legatura cu accesul din
interiorul unei clase, trebuie spus ca absenta restrictiilor se aplica si daca este vorba despre membrii altui obiect din ACEEASI clasa,
dar diferit de cel receptor. De exemplu, daca in clasa Punct am
avea cate o metoda de calcul al distantei pe vericala/orizontala dintre 2
puncte, unul fiind obiectul receptor, iar celalalt un obiect dat ca parametru,
atunci am putea scrie:
class Punct public
int distH(Punct p) |
Se observa ca din interiorul metodelor distV / distH putem accesa liber membrii privati ai obiectului p dat ca parametru. La fel ar sta lucrurile si daca p ar fi o variabila locala a unei metode din clasa Punct.
Observatie: in fraza de mai sus am utilizat expresia "obiectul p";
de fapt, corect este "obiectul indicat de p".
Din motive de simplificare a exprimarii insa, de acum
inainte, acolo unde nu exista riscul unei confuzii, vom "imprumuta"
obiectului numele referintei.
p1.x
compilatorul ar raporta o eroare.
O observatie importanta pe care o putem desprinde din exemplul clasei Punct este aceea ca structura unei clase, sau modul ei de reprezentare, care este dat de variabilele membru, de regula se ascunde fata de clienti. Daca este necesar ca acestia sa poata consulta valorile datelor membru, se va opta pentru definirea unor metode de genulgetValoare, iar nu pentru declararea ca publice a datelor respective.
Intr-o
clasa Java putem declara membri care sa nu fie
precedati de nici un modificator de acces. In acest caz, membrii respectivi se
spune ca sunt accesibili la nivel de pachet ("package"), adica
sunt vizibili de catre toti clientii aflati in acelasi pachet cu clasa in
cauza.
De asemenea, mai exista un modificator de acces, protected, utilizat
in contextul ierarhiilor de clase. Despre el vom vorbi cand
vom invata despre mostenire.
Initializarea campurilor unui obiect. Constructori |
Am vazut in exemplul cu ClientPunct
ca dupa crearea obiectelor p1 si p2, pentru ele s-a apelat metoda init care avea drept scop
initializarea campurilor x si y respectiv ale celor
2 obiecte. Practic, metoda init poate constitui un exemplu de mod de initializare a campurilor unui obiect,
dar nu este cel mai bun. De exemplu, un repros care i
se poate aduce este acela ca programatorul ar putea omite apelul metodei, caz
in care campurile vor avea alte valori (vom vedea care) decat cele
dorite.
La definirea unei clase in
Java, programatorul poate prevedea una sau mai multe metode speciale, numite constructori,
al caror scop este tocmai initializarea datelor unui
obiect imediat dupa ce el a fost creat. Vom ilustra, tot cu ajutorul clasei Punct,
modul de specificare a constructorilor:
class Punct public
Punct(Punct p) |
Cele 2 metode declarate in secventa de mai sus se numesc constructori. Iata care sunt caracteristicile unui constructor:
Astfel, in
loc sa folosim metoda init, in functia main
din clasa ClientPunct am fi putut scrie:
class ClientPunct |
Deci, dand parametrii doriti, la crearea celor 2 instante automat vor intra in executie constructorii corespunzatori, astfel incat este eliminat riscul omiterii initializarii.
Am vazut ca intr-o clasa putem avea mai multi constructori. De fapt, acest lucru este un efect al facilitatii de supraincarcare a functiilor existenta in limbajul Java.
Prin supraincarcare se intelege posibilitatea de a defini in acelasi domeniu de vizibilitate mai multe functii cu acelasi nume, dar cu parametri diferiti ca tip si/sau numar
NU se permite ca functiile
supraincarcate sa difere DOAR prin tipul
returnat.
In exemplul nostru am definit
2 constructori: unul cu 2 parametri de tip int, iar celalalt cu un parametru de tip Punct. Am putea atunci sa mai cream o instanta a clasei Punct, sub
forma:
Punct p3 = new Punct(p1);
caz in care obiectul referit de p3 ar avea aceleasi valori ale coordonatelor ca si obiectul referit de p1.
Intrebare: avand in vedere ca p3 si p1 sunt referinte, ce s-ar fi intamplat daca am fi scris:
Punct p3 = p1;
in loc de fraza de mai sus?
Atunci cand un constructor nu are parametri el se numeste constructor no-arg.
Daca programatorul nu prevede intr-o clasa NICI UN constructor, atunci compilatorul va insera automat in clasa respectiva un constructor implicit de tip no-arg si al carui corp de instructiuni este vid.
Asa au stat lucrurile in cazul primei definitii a clasei Punct. Astfel se explica de ce la crearea unui obiect Punct am folosit notatia:
p1 = new Punct ( );
Practic, aici am apelat constructorul implicit generat automat de compilator.
Daca programatorul include intr-o clasa CEL PUTIN un constructor, indiferent de care, compilatorul NU va mai genera constructorul implicit pentru acea clasa.
Astfel, in definitia clasei Punct care include cei 2 constructori descrisi mai inainte nu avem constructor implicit, dar nici constructor no-arg. Ca urmare, daca incercam sa scriem:
p1 = new Punct ( );
compilatorul va semnala eroare, in sensul ca nu exista vreo definitie de constructor
care sa nu aiba parametri.
In legatura cu constructorii
trebuie spus ca, exceptand anumite cazuri deosebite, ei vor trebui sa aiba modificatorul de acces public. Altfel, nu vom putea crea obiecte ale claselor respective.
Alte metode de initializare a campurilor unei clase
Pe langa constructori, limbajul Java mai ofera si alte posibilitati de a initializa campurile unei clase:
Initializatori la declarare, care pot fi impliciti sau expliciti:
class Punct
|
Initializatorii impliciti depind de tipul campului respectiv, si anume: sunt 0 pentru tipurile numerice, false pentru tipul boolean si null pentru referinte la obiecte.
Atentie! Initializarile implicite nu se aplica si in cazul variabilelor locale ale metodelor. Acestea trebuie initializate explicit de catre programator.
Blocuri de initializare: sunt secvente de cod cuprinse intre acolade, plasate
imediat dupa declaratia campului pe care il initializeaza, si care pot fi
asimilate cu niste constructori no-arg. Ele se folosesc atunci cand valoarea de
initializare a unui camp nu este o constanta simpla, ci trebuie obtinuta prin
calcule.
Ca exemplu vom presupune ca
variabila x din clasa Punct trebuie initializata cu
valoarea sumei primelor 6 numere Fibonacci:
class Punct |
Pentru o clasa in care apar mai multe mecanisme de initializare, ordinea lor de aplicare este urmatoarea:
Deosebirea esentiala intre
constructori si celelalte tipuri de initializari este
aceea ca, in cazul constructorilor putem initializa fiecare instanta cu valori
diferite pe care le dam ca parametri. Celelalte mecanisme de
initializare presupun ca toate instantele clasei vor fi initializate cu
aceleasi valori in campurile respective.
Membrii statici ai claselor |
Pana acum, in clasa Punct
am definit doar membri non-statici. Acestia se mai numesc si membri
ai instantelor, deoarece, asa cum am vazut si in figura prezentata mai la
inceputul acestui material, fiecare instanta isi are propriile exemplare ale
membrilor respectivi.
Limbajul
Java permite definirea unei categorii speciale de membri, numiti statici
sau membri de clasa. Acesti membri vor exista in exemplare unice pentru fiecare
clasa, fiind accesati in comun de toate instantele clasei respective. Mai mult,
membrii statici pot fi referiti chiar si fara a
instantia clasa, ei nedepinzand de obiecte.
Pentru a defini un membru static se utilizeaza cuvantul cheie static,
plasat dupa modificatorul de acces:
modificator_acces static tip_membru nume_membru ;
Referirea unui membru static NU se va face prin intermediul numelui obiectului, ci prin intermediul numelui clasei:
nume_clasa . nume_membru_static
Pentru exemplificare vom
modifica clasa Punct astfel incat sa
putem calcula numarul de apeluri ale metodei move pentru TOATE
obiectele create intr-un program. Pentru aceasta vom defini o variabila statica
contor si o metoda statica getContor:
class Punct public void move(int dx, int dy) public static int getContor()
//metoda main din
clasa radacina p1.move(8,-2); p2.move(6,7);
|
In figura de mai jos este reprezentata in mod intuitiv imaginea in memorie a celor 2 obiecte create in programul de mai sus:
In legatura cu membrii statici ai unei clase trebuie facuta urmatoarea observatie:
Intr-o metoda statica NU este permisa referirea simpla a unui membru non-static. Acest lucru se explica prin aceea ca un membru non-static exista doar in interiorul unui obiect, pe cand membrii statici exista independent de obiecte.
Daca in exemplul nostru am fi declarat variabila contor ca non-statica, atunci ea ar fi existat in atatea exemplare, cate obiecte s-ar fi creat si ar fi contorizat pentru fiecare obiect in parte numarul de apeluri ale metodei move.
In legatura cu drepturile de acces la membrii statici ai unei clase, regulile sunt aceleasi ca si pentru membrii non-statici.
Dintre clasele predefinite
ale mediului Java, un exemplu tipic de clasa in care
majoritatea membrilor sunt statici este clasa Math din pachetul java.lang.
Aceasta contine metodele necesare aplicatiilor stiintifico-ingineresti: functii
transcendente, functii de rotunjire numerica, constantele pi si e
(care sunt campuri statice ale clasei). Cu toate aceste
metode putem lucra fara a crea instante ale clasei Math.
De asemenea, obiectul out
folosit in operatiile de afisare pe ecran este un
membru static al clasei System.
In fine, metoda main
care apare in clasa radacina am vazut inca de la
inceput ca este o metoda statica. Ea este apelata
inainte ca in program sa se creeze vreun obiect.
Alte informatii |
Tema |
Rulati aplicatiile din modul. Vizitati resursele propuse la Alte informatii.
Observatii, comentarii sunt binevenite in Conferinta Software Consulting.
Se cere sa se defineasca o clasa Complex care sa modeleze lucrul cu numere complexe. Membrii acestei clase vor fi:
parte_reala + i*parte_imaginara
Pe langa clasa Complex se va defini o clasa radacina ClientComplex care va contine exemple de utilizare a metodelor clasei Complex.
Se cere sa se defineasca o clasa Data care sa modeleze data calendaristica. Membrii acestei clase vor fi:
zi.luna.an
Pe langa clasa Data se va defini o clasa ClientData care va testa toate metode clasei Data.
Observatie: Pentru ambele aplicatii, cele 2 clase pot fi definite in acelasi fisier .java, caz in care doar clasa client este publica sau in fisiere separate, cand ambele clase se declara publice.
|