Variabile
- Detalii
- Categorie: Programare Java
- Accesări: 7,756
Declaratii de variabile
O variabila în limbajul Java este o locatie de memorie care poate pastra o valoare de un anumit tip. În ciuda denumirii, exista variabile care îşi pot modifica valoarea şi variabile care nu şi-o pot modifica, numite în Java variabile finale. Orice variabila trebuie sa fie declarata pentru a putea fi folosita. Aceasta declaratie trebuie sa contina un tip de valori care pot fi memorate în locatia rezervata variabilei şi un nume pentru variabila declarata. În functie de locul în sursa programului în care a fost declarata variabila, aceasta primeşte o clasa de memorare locala sau statica. Aceasta clasa de memorare defineşte intervalul de existenta al variabilei în timpul executiei.
În forma cea mai simpla, declaratia unei variabile arata în felul urmator:
Tip NumeVariabila [, NumeVariabila];
Tipul unei variabile
Tipul unei variabile poate fi fie unul dintre tipurile primitive definite de limbajul Java fie o referinta. Creatorii limbajului Java au avut grija sa defineasca foarte exact care sunt caracteristicile fiecarui tip primitiv în parte şi care este setul de valori care se poate memora în variabilele care au tipuri primitive. În plus, a fost exact definita şi modalitatea de reprezentare a acestor tipuri primitive în memorie. În acest fel, variabilele Java devin independente de platforma hardware şi software pe care lucreaza.
În acelaşi spirit, Java defineşte o valoare implicita pentru fiecare tip de data, în cazul în care aceasta nu a primit nici o valoare de la utilizator. În acest fel, ştim întotdeauna care este valoarea cu care o variabila intra în calcul. Este o practica buna însa aceea ca programele sa nu depinda niciodata de aceste initializari implicite.
Numele variabilelor
Numele variabilei poate fi orice identificator Java. Conventia nescrisa de formare a numelor variabilelor este aceea ca orice variabila care nu este finala are un nume care începe cu litera minuscula în timp ce variabilele finale au nume care contin numai majuscule. Daca numele unei variabile care nu este finala contine mai multe cuvinte, cuvintele începînd cu cel de-al doilea se scriu cu litere minuscule dar cu prima litera majuscula. Exemple de nume de variabile care nu sunt finale ar putea fi:
culoarea numarulDePaşi urmatorulElement
Variabilele finale ar putea avea nume precum:
PORTOCALIUVERDEALBASTRUDESCHIS
Initializarea variabilelor
Limbajul Java permite initializarea valorilor variabilelor chiar în momentul declararii acestora. Sintaxa este urmatoarea:
Tip NumeVariabila = ValoareInitiala;
Desigur, valoarea initiala trebuie sa fie de acelaşi tip cu tipul variabilei sau sa poata fi convertita într-o valoare de acest tip.
Deşi limbajul Java ne asigura ca toate variabilele au o valoare initiala bine precizata, este preferabil sa executam aceasta initializare în mod explicit pentru fiecare declaratie. În acest fel marim claritatea propriului cod.
Regula ar fi deci urmatoarea: nici o declaratie fara initializare.
Tipuri primitive
Tipul boolean
Tipul boolean este folosit pentru memorarea unei valori de adevar. Pentru acest scop, sunt suficiente doar doua valori: adevarat şi fals. În Java aceste doua valori le vom nota prin literalii true şi respectiv false. Aceste valori pot fi reprezentate în memorie folosindu-ne de o singura cifra binara, adica pe un bit.
Valorile booleene sunt foarte importante în limbajul Java pentru ca ele sunt valorile care se folosesc în conditiile care controleaza instructiunile repetitive sau cele conditionale. Pentru a exprima o conditie este suficient sa scriem o expresie al carui rezultat este o valoare booleana, adevarat sau fals.
Valorile de tip boolean nu se pot transforma în valori de alt tip în mod nativ. La fel, nu exista transformare nativa dinspre celelalte valori înspre tipul boolean. Cu alte cuvinte, avînd o variabila de tip boolean nu putem memora în interiorul acesteia o valoare întreaga pentru ca limbajul Java nu face pentru noi nici un fel de presupunere legata de ce înseamna o anumita valoare întreaga din punctul de vedere al valorii de adevar. La fel, daca avem o variabila întreaga, nu îi putem atribui o valoare de tip boolean.
Orice variabila booleana nou creata primeşte automat valoarea implicita false. Putem modifica aceasta comportare specificînd în mod explicit o valoare initiala true.
Pentru a declara o variabila de tip boolean, în Java vom folosi cuvîntul rezervat boolean ca în exemplele de mai jos:
- boolean terminat;
- boolean areDreptate;
Rîndurile de mai sus reprezinta declaratia a doua variabile de tip boolean numite terminat respectiv areDreptate. Cele doua variabile au, dupa declaratie, valoarea false.
Tipul caracter char
Orice limbaj de programare ne ofera într-un fel sau altul posibilitatea de a lucra cu caractere grafice care sa reprezinte litere, cifre, semne de punctuatie, etc. În cazul limbajului Java acest lucru se poate face folosind tipul primitiv numit tip caracter.
O variabila de tip caracter poate avea ca valoare coduri Unicode reprezentate pe 16 biti, adica doi octeti. Codurile reprezentabile astfel sunt foarte multe, putînd acoperi caracterele de baza din toate limbile scrise existente.
În Java putem combina mai multe caractere pentru a forma cuvinte sau şiruri de caractere mai lungi. Totuşi, trebuie sa precizam ca aceste şiruri de caractere nu trebuiesc confundate cu tablourile de caractere pentru ca ele contin în plus informatii legate de lungimea şirului.
Codul nu este altceva decît o corespondenta între numere şi caractere fapt care permite conversii între variabile întregi şi caractere în ambele sensuri. O parte din aceste transformari pot sa altereze valoarea originala din cauza dimensiunilor diferite ale zonelor în care sunt memorate cele doua tipuri de valori. Convertirea caracterelor în numere şi invers poate sa fie utila la prelucrarea în bloc a caracterelor, cum ar fi trecerea tuturor literelor minuscule în majuscule şi invers.
Atunci cînd declaram un caracter fara sa specificam o valoare initiala, el va primi automat ca valoare implicita caracterul null al codului Unicode, \u0000.
Pentru a declara o variabila de tip caracter folosim cuvîntul rezervat char ca în exemplele urmatoare:
- char primaLitera;
- char prima, ultima;
În cele doua linii de cod am declarat trei variabile de tip caracter care au fost automat initializate cu caracterul null.
Tipuri întregi
Tipul octet byte
Între tipurile întregi, acest tip ocupa un singur octet de memorie, adica opt cifre binare. Într-o variabila de tip octet sunt reprezentate întotdeauna valori cu semn, ca de altfel în toate variabilele de tip întreg definite în limbajul Java. Aceasta conventie simplifica schema de tipuri primitive care, în cazul altor limbaje include separat tipuri întregi cu semn şi fara.
Fiind vorba de numere cu semn, este nevoie de o conventie de reprezentare a semnului. Conventia folosita de Java este reprezentarea în complement fata de doi. Aceasta reprezentare este de altfel folosita de majoritatea limbajelor actuale şi permite memorarea, pe 8 biti a 256 de numere începînd de la -128 pîna la 127. Daca aveti nevoie de numere mai mari în valoare absoluta, apelati la alte tipuri întregi.
Valoarea implicita pentru o variabila neinitializata de tip octet este valoarea 0 reprezentata pe un octet.
Iata şi cîteva exemple de declaratii care folosesc cuvîntul Java rezervat byte:
- byte octet;
- byte eleviPeClasa;
Tipul întreg scurt short
Tipul întreg scurt este similar cu tipul octet dar valorile sunt reprezentate pe doi octeti, adica 16 biti. La fel ca şi la tipul octet, valorile sunt întotdeauna cu semn şi se foloseşte reprezentarea în complement fata de doi. Valorile de întregi scurti reprezentabile sunt de la -32768 la 32767 iar valoarea implicita este 0 reprezentat pe doi octeti.
Pentru declararea variabilelor de tip întreg scurt în Java se foloseşte cuvîntul rezervat short, ca în exemplele urmatoare:
- short i, j;
- short valoareNuPreaMare;
Tipul întreg integer
Singura diferenta dintre tipul întreg şi tipurile precedente este faptul ca valorile sunt reprezentate pe patru octeti adica 32 biti. Valorile reprezentabile sunt de la -2147483648 la 2147483647 valoarea implicita fiind 0. Cuvîntul rezervat este int ca în:
- int salariu;
Tipul întreg lung long
În fine, pentru cei care vor sa reprezinte numerele întregi cu semn pe 8 octeti, 64 de biti, exista tipul întreg lung. Valorile reprezentabile sunt de la -9223372036854775808 la 9223372036854775807 iar valoarea implicita este 0L.
Pentru cei care nu au calculatoare care lucreaza pe 64 de biti este bine de precizat faptul ca folosirea acestui tip duce la operatii lente pentru ca nu exista operatii native ale procesorului care sa lucreze cu numere aşa de mari.
Declaratia se face cu cuvîntul rezervat long.
Tipuri flotante
Acest tip este folosit pentru reprezentarea numerelor reale sub forma de exponent şi cifre semnificative. Reprezentarea se face pe patru octeti, 32 biti, aşa cum specifica standardul IEEE
Tipul flotant float
Valorile finite reprezentabile într-o variabila de tip flotant sunt de forma:
- sm2e unde s este semnul +1 sau -1, m este partea care specifica cifrele reprezentative ale numarului, numita şi mantisa, un întreg pozitiv mai mic decît 224 iar e este un exponent întreg între -149 şi 104.
Valoarea implicita pentru variabilele flotante este 0.0f. Pentru declararea unui numar flotant, Java defineşte cuvîntul rezervat float. Declaratiile se fac ca în exemplele urmatoare:
- float procent;
- float x,y;
Tipul flotant dublu double
Daca valorile reprezentabile în variabile flotante nu sunt destul de precise sau destul de mari, puteti folosi tipul flotant dublu care foloseşte opt octeti pentru reprezentare, urmînd acelaşi standard IEEE
Valorile finite reprezentabile cu flotanti dubli sunt de forma:
sm2e unde s este semnul +1 sau -1, m este mantisa, un întreg pozitiv mai mic decît 253 iar e este un exponent întreg între -1045 şi 1000. Valoarea implicita în acest caz este 0.0d.
Pentru a declara flotanti dubli, Java defineşte cuvîntul rezervat double ca în: double distantaPînaLaLuna;
În afara de valorile definite pîna acum, standardul IEEE defineşte cîteva valori speciale reprezentabile pe un flotant sau un flotant dublu.
Reali speciali definiti de IEEE
Prima dintre acestea este NaN (Not a Number), valoare care se obtine atunci cînd efectuam o operatie a carei rezultat nu este definit, de exemplu 0.0 / 0.0.
În plus, standardul IEEE defineşte doua valori pe care le putem folosi pe post de infinit pozitiv şi negativ. Şi aceste valori pot rezulta în urma unor calcule.
Aceste valori sunt definite sub forma de constante şi în ierarhia standard Java, mai precis în clasa java.lang.Float şi respectiv în java.lang.Double. Numele constantelor este POSITIVE_INFINITY, NEGATIVE_INFINITY, NaN.
În plus, pentru tipurile întregi şi întregi lungi şi pentru tipurile flotante exista definite clase în ierarhia standard Java care se numesc respectiv java.lang.Integer, java.lang.Long, java.lang.Float şi java.lang.Double. În fiecare dintre aceste clase numerice sunt definite doua constante care reprezinta valorile minime şi maxime care se pot reprezenta în tipurile respective. Aceste doua constante se numesc în mod uniform MIN_VALUE şi MAX_VALUE.
Tipuri referinta
Tipurile referinta sunt folosite pentru a referi un obiect din interiorul unui alt obiect. În acest mod putem înlantui informatiile aflate în memorie.
Tipurile referinta au, la fel ca şi toate celelalte tipuri o valoare implicita care este atribuita automat oricarei variabile de tip referinta care nu a fost initializata. Aceasta valoare implicita este definita de catre limbajul Java prin cuvîntul rezervat null.
Puteti întelege semnificatia referintei nule ca o referinta care nu trimite nicaieri, a carei destinatie nu a fost înca fixata.
Simpla declaratie a unei referinte nu duce automat la rezervarea spatiului de memorie pentru obiectul referit. Singura rezervare care se face este aceea a spatiului necesar memorarii referintei în sine. Rezervarea obiectului trebuie facuta explicit în program printr-o expresie de alocare care foloseşte cuvîntul rezervat new.
O variabila de tip referinta nu trebuie sa trimita pe tot timpul existentei sale catre acelaşi obiect în memorie. Cu alte cuvinte, variabila îşi poate schimba locatia referita în timpul executiei.
Tipul referinta catre o clasa
Tipul referinta catre o clasa este un tip referinta care trimite catre o instanta a unei clase de obiecte. Clasa instantei referite poate fi oricare clasa valida definita de limbaj sau de utilizator.
Clasa de obiecte care pot fi referite de o anumita variabila de tip referinta la clasa trebuie declarata explicit. De exemplu, pentru a declara o referinta catre o instanta a clasei Minge, trebuie sa folosim urmatoarea sintaxa:
- Minge mingeaMea;
Din acest moment, variabila referinta de clasa numita mingeaMea va putea pastra doar referinte catre obiecte de tip Minge sau catre obiecte apartinînd unor clase derivate din clasa Minge. De exemplu, daca avem o alta clasa, derivata din Minge, numita MingeDeBaschet, putem memora în referinta mingeaMea şi o trimitere catre o instanta a clasei MingeDeBaschet.
În mod general însa, nu se pot pastra în variabila mingeaMea referinte catre alte clase de obiecte. Daca se încerca acest lucru, eroarea va fi semnalata chiar în momentul compilarii, atunci cînd sursa programului este examinata pentru a fi transformata în instructiuni ale maşinii virtuale Java.
Tipul referinta catre o interfata
Tipul referinta catre o interfata permite pastrarea unor referinte catre obiecte care respecta o anumita interfata. Clasa obiectelor referite poate fi oricare, atîta timp cît clasa respectiva implementeaza interfata ceruta.
Declaratia se face cu urmatoarea sintaxa:
ObiectSpatioTemporal mingeaLuiVasile;
în care tipul este chiar numele interfetei cerute. Daca clasa de obiecte Minge declara ca implementeaza aceasta interfata, atunci variabila referinta mingeaLuiVasile poate lua ca valoare referinta catre o instanta a clasei Minge sau a clasei MingeDeBaschet.
Prin intermediul unei variabile referinta catre o interfata nu se poate apela decît la functionalitatea ceruta în interfata respectiva, chiar daca obiectele reale ofera şi alte facilitati, ele apartinînd unor clase mai bogate în metode.
Tipul referinta catre un tablou
Tipul referinta catre un tablou este un tip referinta care poate pastra o trimitere catre locatia din memorie a unui tablou de elemente. Prin intermediul acestei referinte putem accesa elementele tabloului furnizînd indexul elementului dorit.
Tablourile de elemente nu exista în general ci ele sunt tablouri formate din elemente de un tip bine precizat. Din aceasta cauza, atunci cînd declaram o referinta catre un tablou, trebuie sa precizam şi de ce tip sunt elementele din tabloul respectiv.
La declaratia referintei catre tablou nu trebuie sa precizam şi numarul de elemente din tablou.
Iata cum se declara o referinta catre un tablou de întregi lungi:
- long numere[ ];
Numele variabilei este numere. Un alt exemplu de declaratie de referinta catre un tablou:
- Minge echipament[ ];
Declaratia de mai sus construieşte o referinta catre un tablou care pastreaza elemente de tip referinta catre o instanta a clasei Minge. Numele variabilei referinta este echipament. Parantezele drepte de dupa numele variabilei specifica faptul ca este vorba despre un tablou.
Clasa de memorare
Fiecare variabila trebuie sa aiba o anumita clasa de memorare. Aceasta clasa ne permite sa aflam care este intervalul de existenta şi vizibilitatea unei variabile în contextul executiei unui program.
Este important sa întelegem exact aceasta notiune pentru ca altfel vom încerca sa referim variabile înainte ca acestea sa fi fost create sau dupa ce au fost distruse sau sa referim variabile care nu sunt vizibile din zona de program în care le apelam.
Variabile locale
Aceste variabile nu au importanta prea mare în contextul întregii aplicatii, ele servind la rezolvarea unor probleme locale. Variabilele locale sunt declarate, rezervate în memorie şi utilizate doar în interiorul unor blocuri de instructiuni, fiind distruse automat la ieşirea din aceste blocuri. Aceste variabile sunt vizibile doar în interiorul blocului în care au fost create şi în subblocurile acestuia.
Variabile statice
Variabilele statice sunt în general legate de functionalitatea anumitor clase de obiecte ale caror instante folosesc în comun aceste variabile. Variabilele statice sunt create atunci cînd codul specific clasei în care au fost declarate este încarcat în memorie şi nu sunt distruse decît atunci cînd acest cod este eliminat din memorie.
Valorile memorate în variabile statice au importanta mult mai mare în aplicatie decît cele locale, ele pastrînd informatii care nu trebuie sa se piarda la disparitia unei instante a clasei. De exemplu, variabila în care este memorat numarul de picioare al obiectelor din clasa Om nu trebuie sa fie distrusa la disparitia unei instante din aceasta clasa. Aceasta din cauza ca şi celelalte instante ale clasei folosesc aceeaşi valoare. Şi chiar daca la un moment dat nu mai exista nici o instanta a acestei clase, numarul de picioare ale unui Om trebuie sa fie accesibil în continuare pentru interogare de catre celelalte clase.
Variabilele statice nu se pot declara decît ca variabile ale unor clase şi contin în declaratie cuvîntul rezervat static. Din cauza faptului ca ele apartin clasei şi nu unei anumite instante a clasei, variabilele statice se mai numesc uneori şi variabile de clasa.
Variabile dinamice
Un alt tip de variabile sunt variabilele a caror perioada de existenta este stabilita de catre programator. Aceste variabile pot fi alocate la cerere, dinamic, în orice moment al executiei programului. Ele vor fi distruse doar atunci cînd nu mai sunt referite de nicaieri.
La alocarea unei variabile dinamice, este obligatoriu sa pastram o referinta catre ea într-o variabila de tip referinta. Altfel, nu vom putea accesa în viitor variabila dinamica. În momentul în care nici o referinta nu mai trimite catre variabila dinamica, de exemplu pentru ca referinta a fost o variabila locala şi blocul în care a fost declarata şi-a terminat executia, variabila dinamica este distrusa automat de catre sistem printr-un mecanism numit colector de gunoaie.
Colectorul de gunoaie poate porni din initiativa sistemului sau din initiativa programatorului la momente bine precizate ale executiei.
Pentru a rezerva spatiu pentru o variabila dinamica este nevoie sa apelam la o expresie de alocare care foloseşte cuvîntul rezervat new. Aceasta expresie aloca spatiul necesar pentru un anumit tip de valoare. De exemplu, pentru a rezerva spatiul necesar unui obiect de tip Minge, putem apela la sintaxa:
Minge mingeaMea = new Minge( );
iar pentru a rezerva spatiul necesar unui tablou de referinte catre obiecte de tip Minge putem folosi declaratia:
Minge echipament[ ] = new Minge[ 5 ];
Am alocat astfel spatiu pentru un tablou care contine 5 referinte catre obiecte de tip Minge. Pentru alocarea tablourilor continînd tipuri primitive se foloseşte aceeaşi sintaxa. De exemplu, urmatoarea linie de program aloca spatiul necesar unui tablou cu 10 întregi, creînd în acelaşi timp şi o variabila referinta spre acest tablou, numita numere:
int numere[ ] = new int[ 10 ];
Tablouri de variabile
Tablourile servesc, dupa cum s-a vazut, la memorarea secventelor de elemente de acelaşi tip. Tablourile unidimensionale au semnificatia vectorilor de elemente. Se poate întîmpla sa lucram şi cu tablouri de referinte catre tablouri, în acest caz modelul fiind acela al unei matrici bidimensionale, sau putem extinde definitia şi pentru mai mult de doua dimensiuni.
Declaratia variabilelor de tip tablou
Pentru a declara variabile de tip tablou, trebuie sa specificam tipul elementelor care vor umple tabloul şi un nume pentru variabila referinta care va pastra trimiterea catre zona de memorie în care sunt memorate elementele tabloului.
Deşi putem declara variabile referinta catre tablou şi separat, de obicei declaratia este facuta în acelaşi timp cu alocarea spatiului ca în exemplele din paragraful anterior.
Sintaxa Java permite plasarea parantezelor drepte care specifica tipul tablou înainte sau dupa numele variabilei. Astfel, urmatoarele doua declaratii sunt echivalente:
int[ ] numere;
int numere[ ];
Daca doriti sa folositi tablouri cu doua dimensiuni ca matricile, puteti sa declarati un tablou de referinte catre tablouri cu una dintre urmatoarele trei sintaxe echivalente:
float [ ][ ] matrice;
float [ ] matrice[ ];
float matrice[ ][ ];
De precizat ca şi în cazul dimensiunilor multiple, declaratiile de mai sus nu fac nimic altceva decît sa rezerve loc pentru o referinta şi sa precizeze numarul de dimensiuni. Alocarea spatiului pentru elementele tabloului trebuie facuta explicit.
Pentru tablourile cu mai multe dimensiuni, rezervarea spatiului se poate face cu urmatoarea sintaxa:
byte [ ][ ]octeti = new byte[23][5];
În expresia de alocare sunt specificate în clar numarul elementelor pentru fiecare dimensiune a tabloului.
Initializarea tablourilor.
Limbajul Java permite şi o sintaxa pentru initializarea elementelor unui tablou. Într-un astfel de caz este rezervat automat şi spatiul de memorie necesar memorarii valorilor initiale. Sintaxa folosita în astfel de cazuri este urmatoarea:
char [ ]caractere = { ’a’, ’b ’, ’c ’, ’d ’ };
Acest prim exemplu aloca spatiu pentru patru elemente de tip caracter şi initializeaza aceste elemente cu valorile dintre acolade. Dupa aceea, creeaza variabila de tip referinta numita caractere şi o initializeaza cu referinta la zona de memorie care pastreaza cele patru valori.
Initializarea functioneaza şi la tablouri cu mai multe dimensiuni ca în exemplele urmatoare:
int [ ][ ]numere = {
{ 1, 3, 4, 5 },
{ 2, 4, 5 },
{ 1, 2, 3, 4, 5 }
};
double [ ][ ][ ]reali = {
{ { 0.0, -1.0 }, { 4.5 } },
{ { 2.5, 3.0 } }
};
Dupa cum observati numarul initializatorilor nu trebuie sa fie acelaşi pentru fiecare element.
Lungimea tablourilor
Tablourile Java sunt alocate dinamic, ceea ce înseamna ca ele îşi pot schimba dimensiunile pe parcursul executiei. Pentru a afla numarul de elemente dintr-un tablou, putem apela la urmatoarea sintaxa:
float [ ]tablou = new float[25];
int dimensiune = tablou.length; // dimensiune primeşte valoarea 25
sau
float [ ][ ]multiTablou = new float[3][4];
int dimensiune1 = multiTablou[2].length; // dimensiune1 primeşte valoarea 4
int dimensiune2 = multiTablou.length; // dimensiune2 primeşte valoarea 3
Referirea elementelor din tablou
Elementele unui tablou se pot referi prin numele referintei tabloului şi indexul elementului pe care dorim sa-l referim. În Java, primul element din tablou este elementul cu numarul 0, al doilea este elementul numarul 1 şi aşa mai departe.
Sintaxa de referire foloseşte parantezele patrate [ şi ]. Între ele trebuie specificat indexul elementului pe care dorim sa-l referim. Indexul nu trebuie sa fie constant, el putînd fi o expresie de complexitate oarecare.
Iata cîteva exemple:
int [ ]tablou = new int[10];
tablou[3] = 1;
// al patrulea element primeşte valoarea 1
float [ ] []reali = new float[3][4];
reali[2][3] = 1.0f;
// al patrulea element din al treilea tablou
// primeşte valoarea 1
În cazul tablourilor cu mai multe dimensiuni, avem în realitate tablouri de referinte la tablouri. Asta înseamna ca daca consideram urmatoarea declaratie:
char [][]caractere = new char [5][];
Variabila referinta numita caractere contine deocamdata un tablou de 5 referinte la tablouri de caractere. Cele cinci referinte sunt initializate cu null. Putem initializa aceste tablouri prin atribuiri de expresii de alocare:
- caractere[0] = new char [3];
- caractere[4] = new char [5];
La fel, putem scrie:
char [ ]tablouDeCaractere = caractere[0];
Variabila tablouDeCaractere trimite catre acelaşi tablou de caractere ca şi cel referit de primul element al tabloului referit de variabila caractere.
Sa mai precizam ca referirea unui element de tablou printr-un index mai mare sau egal cu lungimea tabloului duce la oprirea executiei programului cu un mesaj de eroare de executie corespunzator.
Alocarea şi eliberarea tablourilor
În cazul în care nu avem initializatori, variabilele sunt initializate cu valorile implicite definite de limbaj pentru tipul corespunzator. Aceasta înseamna ca, pentru tablourile cu mai multe dimensiuni, referintele sunt initializate cu null.
Pentru eliberarea memoriei ocupate de un tablou, este suficient sa taiem toate referintele catre tablou. Sistemul va sesiza automat ca tabloul nu mai este referit şi mecanismul colector de gunoaie va elibera zona. Pentru a taia o referinta catre un tablou dam o alta valoare variabilei care refera tabloul. Valoarea poate fi null sau o referinta catre un alt tablou.
De exemplu:
float [ ]reali = new float[10];
reali = null; // eliberarea tabloului
sau
reali = new float[15]; // eliberarea în alt fel
sau
{
float [ ]reali = new float[10];
Conversii
Operatiile definite în limbajul Java au un tip bine precizat de argumente. Din pacate, exista situatii în care nu putem transmite la apelul acestora exact tipul pe care compilatorul Java îl aşteapta. În asemenea situatii, compilatorul are doua alternative: fie respinge orice operatie cu argumente greşite, fie încearca sa converteasca argumentele catre tipurile necesare. Desigur, în cazul în care conversia nu este posibila, singura alternativa ramîne prima.
În multe situatii însa, conversia este posibila. Sa luam de exemplu tipurile întregi. Putem sa convertim întotdeauna un întreg scurt la un întreg. Valoarea rezultata va fi exact aceeaşi. Conversia inversa însa, poate pune probleme daca valoarea memorata în întreg depaşeşte capacitatea de memorare a unui întreg scurt.
În afara de conversiile implicite, pe care compilatorul le hotaraşte, exista şi conversii explicite, pe care programatorul le poate forta la nevoie. Aceste conversii efectueaza de obicei operatii în care exista pericolul sa se piarda o parte din informatii. Compilatorul nu poate hotarî de unul singur în aceste situatii.
Conversiile implicite pot fi un pericol pentru stabilitatea aplicatiei daca pot sa duca la pierderi de informatii fara avertizarea programatorului. Aceste erori sunt de obicei extrem de greu de depistat.
În fiecare limbaj care lucreaza cu tipuri fixe pentru datele sale exista conversii imposibile, conversii periculoase şi conversii sigure. Conversiile imposibile sunt conversiile pe care limbajul nu le permite pentru ca nu ştie cum sa le execute sau pentru ca operatia este prea periculoasa. De exemplu, Java refuza sa converteasca un tip primitiv catre un tip referinta. Deşi s-ar putea imagina o astfel de conversie bazata pe faptul ca o adresa este în cele din urma un numar natural, acest tip de conversii sunt extrem de periculoase, chiar şi atunci cînd programatorul cere explicit aceasta conversie.
Conversii de extindere a valorii
În aceste conversii valoarea se reprezinta într-o zona mai mare fara sa se piarda nici un fel de informatii. Iata conversiile de extindere pe tipuri primitive:
- byte la short, int, long, float sau double
- short la int, long, float sau double
- char la int, long, float sau double
- int la long, float sau double
- long la float sau double
- float la double
Sa mai precizam totuşi ca, într-o parte din aceste cazuri, putem pierde din precizie. Aceasta situatie apare de exemplu la conversia unui long într-un float, caz în care se pierd o parte din cifrele semnificative pastrîndu-se însa ordinul de marime. De altfel aceasta observatie este evidenta daca tinem cont de faptul ca un long este reprezentat pe 64 de biti în timp ce un float este reprezentat doar pe 32 de biti.
Precizia se pierde chiar şi în cazul conversiei long la double sau int la float pentru ca, deşi dimensiunea zonei alocata pentru cele doua tipuri este aceeaşi, numerele flotante au nevoie de o parte din aceasta zona pentru a reprezenta exponentul.
În aceste situatii, se va produce o rotunjire a numerelor reprezentate.
Conversii de trunchiere a valorii
Conventiile de trunchiere a valorii pot produce pierderi de informatie pentru ca ele convertesc tipuri mai bogate în informatii catre tipuri mai sarace. Conversiile de trunchiere pe tipurile elementare sunt urmatoarele:
- byte la char
- short la byte sau char
- char la byte sau short
- int la byte, short sau char
- long la byte, short char, sau int
- float la byte, short, char, int sau long
- double la byte, short, char, int, long sau float.
În cazul conversiilor de trunchiere la numerele cu semn, este posibil sa se schimbe semnul pentru ca, în timpul conversiei, se îndeparteaza pur şi simplu octetii care nu mai încap şi poate ramîne primul bit diferit de vechiul prim bit. Copierea se face începînd cu octetii mai putin semnificativi iar trunchierea se face la octetii cei mai semnificativi.
Conversii pe tipuri referinta
Conversiile tipurilor referinta nu pun probleme pentru modul în care trebuie executata operatia din cauza ca, referinta fiind o adresa, în timpul conversiei nu trebuie afectata în nici un fel aceasta adresa. În schimb, se pun probleme legate de corectitudinea logica a conversiei. De exemplu, daca avem o referinta la un obiect care nu este tablou, este absurd sa încercam sa convertim aceasta referinta la o referinta de tablou.
Limbajul Java defineşte extrem de strict conversiile posibile în cazul tipurilor referinta pentru a salva programatorul de eventualele necazuri care pot apare în timpul executiei. Iata conversiile posibile:
- O referinta catre un obiect apartinînd unei clase C poate fi convertit la o referinta catre un obiect apartinînd clasei S doar în cazul în care C este chiar S sau C este derivata direct sau indirect din S.
- O referinta catre un obiect apartinînd unei clase C poate fi convertit catre o referinta de interfata I numai daca clasa C implementeaza interfata I.
- O referinta catre un tablou poate fi convertita la o referinta catre o clasa numai daca clasa respectiva este clasa Object.
- O referinta catre un tablou de elemente ale carui elemente sunt de tipul T1 poate fi convertita la o referinta catre un tablou de elemente de tip T2 numai daca T1 şi T2 reprezinta acelaşi tip primitiv sau T2 este un tip referinta şi T1 poate fi convertit catre T2.
Conversii la operatia de atribuire
Conversiile pe care limbajul Java le executa implicit la atribuire sunt foarte putine. Mai exact, sunt executate doar acele conversii care nu necesita validare în timpul executiei şi care nu pot pierde informatii în cazul tipurilor primitive.
În cazul valorilor apartinînd tipurilor primitive, urmatorul tabel arata conversiile posibile. Pe coloane avem tipul de valoare care se atribuie iar pe linii avem tipurile de variabile la care se atribuie:
boolean | char | byte | short | int | long | float | double | |
boolean | Da | Nu | Nu | Nu | Nu | Nu | Nu | Nu |
char | Nu | Da | Da | Da | Nu | Nu | Nu | Nu |
byte | Nu | Da | Da | Nu | Nu | Nu | Nu | Nu |
short | Nu | Da | Da | Da | Nu | Nu | Nu | Nu |
int | Nu | Da | Da | Da | Da | Nu | Nu | Nu |
long | Nu | Da | Da | Da | Da | Da | Nu | Nu |
float | Nu | Da | Da | Da | Da | Da | Da | Nu |
double | Nu | Da | Da | Da | Da | Da | Da | Da |
Conversiile posibile într-o operatie de atribuire cu tipuri primitive. Coloanele reprezinta tipurile care se atribuie iar liniile reprezinta tipul de variabila catre care se face atribuirea.
Dupa cum se observa, tipul boolean nu poate fi atribuit la o variabila de alt tip.
Valorile de tip primitiv nu pot fi atribuite variabilelor de tip referinta. La fel, valorile de tip referinta nu pot fi memorate în variabile de tip primitiv. În ceea ce priveşte tipurile referinta între ele, urmatorul tabel defineşte situatiile în care conversiile sunt posibile la atribuirea unei valori de tipul T la o variabila de tipul S: S=T
T este o clasa care nu este finala | T este o clasa care este finala | T este o interfata | T = B[] este un tablou cu elemente de tipul B | |
S este o clasa care nu este finala | T trebuie sa fie subclasa a lui S | T trebuie sa fie o subclasa a lui S | eroare la compilare | S trebuie sa fie Object |
S este o clasa care este finala | T trebuie sa fie aceeaşi clasa ca şi S | T trebuie sa fie aceeaşi clasa ca şi S | eroare la compilare | eroare la compilare |
S este o interfata | T trebuie sa implementeze interfata S | T trebuie sa implementeze interfata S | T trebuie sa fie o subinterfata a lui S | eroare la compilare |
S = A[] este un tablou cu elemente de tipul A | eroare la compilare | eroare la compilare | eroare la compilare | A sau B sunt acelaşi tip primitiv sau A este un tip referinta şi B poate fi atribuit lui A |
Conversiile posibile la atribuirea unei valori de tipul T la o variabila de tipul S.
Conversii explicite
Conversiile de tip cast, sau casturile, sunt apelate de catre programator în mod explicit. Sintaxa pentru constructia unui cast este scrierea tipului catre care dorim sa convertim în paranteze în fata valorii pe care dorim sa o convertim. Forma generala este:
( Tip ) Valoare
Conversiile posibile în acest caz sunt mai multe decît conversiile implicite la atribuire pentru ca în acest caz programatorul este prevenit de eventuale pierderi de date el trebuind sa apeleze conversia explicit. Dar, continua sa existe conversii care nu se pot apela nici macar în mod explicit.
În cazul conversiilor de tip cast, orice valoare numerica poate fi convertita la orice valoare numerica.
În continuare, valorile de tip boolean nu pot fi convertite la nici un alt tip.
Nu exista conversii între valorile de tip referinta şi valorile de tip primitiv.
În cazul conversiilor dintr-un tip referinta într-altul putem separa doua cazuri. Daca compilatorul poate decide în timpul compilarii daca conversia este corecta sau nu, o va decide. În cazul în care compilatorul nu poate decide pe loc, se va efectua o verificare a conversiei în timpul executiei. Daca conversia se dovedeşte greşita, va apare o eroare de executie şi programul va fi întrerupt.
Iata un exemplu de situatie în care compilatorul nu poate decide daca conversia este posibila sau nu:
Minge mingeaMea;
....
MingeDeBaschet mingeaMeaDeBaschet; // MingeDeBaschet este o clasa derivata din clasa Minge
mingeaMeaDeBaschet=(MingeDeBaschet)mingeaMea;
În acest caz, compilatorul nu poate fi sigur daca referinta memorata în variabila mingeaMea este de tip MingeDeBaschet sau nu pentru ca variabilei de tip Minge i se pot atribui şi referinte catre instante de tip Minge în general, care nu respecta întru totul definitia clasei MingeDeBaschet sau chiar referinta catre alte tipuri de minge derivate din clasa Minge, de exemplu MingeDePolo care implementeaza proprietati şi operatii diferite fata de clasa MingeDeBaschet.
Iata şi un exemplu de conversie care poate fi decisa în timpul compilarii:
Minge mingeaMea;
MingeDeBaschet mingeaMeaDeBaschet;
....
mingeaMea = ( Minge ) mingeaMeaDeBaschet;
În urmatorul exemplu însa, se poate decide în timpul compilarii imposibilitatea conversiei:
MingeDeBaschet mingeaMeaDeBaschet;
MingeDePolo mingeaMeaDePolo;
....
mingeaMeaDePolo = ( MingeDePolo ) mingeaMeaDeBaschet;
În fine, tabelul urmator arata conversiile de tip cast a caror corectitudine poate fi stabilita în timpul compilarii. Conversia încearca sa transforme printr-un cast o referinta de tip T într-o referinta de tip S.
T este o clasa care nu este finala | T este o clasa care este finala | T este o interfata | T = B[] este un tablou cu elemente de tipul B | |
S este o clasa care nu este finala | T trebuie sa fie subclasa a lui S | T trebuie sa fie o subclasa a lui S | Totdeauna corecta la compilare | S trebuie sa fie Object |
S este o clasa care este finala | S trebuie sa fie subclasa a lui T | T trebuie sa fie aceeaşi clasa ca şi S | S trebuie sa implementeze interfata T | eroare la compilare |
S este o interfata | Totdeauna corecta la compilare | T trebuie sa implementeze interfata S | Totdeauna corecta la compilare | eroare la compilare |
S = A[] este un tablou cu elemente de tipul A | T trebuie sa fie Object | eroare la compilare | eroare la compilare | A sau B sunt acelaşi tip primitiv sau A este un tip referinta şi B poate fi convertit cu un cast la A |
Cazurile posibile la convertirea unei referinte de tip T într-o referinta de tip S.
Conversii de promovare aritmetica
Promovarea aritmetica se aplica în cazul unor formule în care operanzii pe care se aplica un operator sunt de tipuri diferite. În aceste cazuri, compilatorul încearca sa promoveze unul sau chiar amîndoi operanzii la acelaşi tip pentru a putea fi executata operatia.
Exista doua tipuri de promovare, promovare aritmetica unara şi binara.
În cazul promovarii aritmetice unare, exista un singur operand care în cazul ca este byte sau short este transformat la int altfel ramîne nemodificat.
La promovarea aritmetica binara se aplica urmatorul algoritm:
- Daca un operand este double, celalalt este convertit la double.
- Altfel, daca un operand este de tip float, celalalt operand este convertit la float.
- Altfel, daca un operand este de tip long, celalalt este convertit la long
- Altfel, amîndoi operanzii sunt convertiti la int.
De exemplu, în urmatoarea operatie amîndoi operanzii vor fi convertiti la double prin promovare aritmetica binara:
- float f;
- double i = f + 3;
Dupa efectuarea operatiei, valoarea obtinuta va fi convertita implicit la double.
În urmatorul exemplu, se produce o promovare unara la int de la short.
short s, r;
...
int min = ( r < -s ) ? r : s;
În expresia conditionala, operandul -s se traduce de fapt prin aplicarea operatorului unar - la variabila s care este de tip short. În acest caz, se va produce automat promovarea aritmetica unara de la short la int, apoi se va continua evaluarea expresiei.