Un program trebuie sa tina cont de posibilitatea aparitiei, la executie, a unor situatii limita neobisnuite. Spre exemplu, daca avem un program care incearca sa deschida un fisier, care nu exista pe disc, pentru a citi din el. Ideal, toate situatiile neobisnuite, ce pot sa apara pe parcursul executiei unui program, trebuie detectate si tratate corespunzator de acesta.
Dupa cum am vazut deja pana acum, in limbajul Java, aproape orice este vazut ca un obiect. Prin urmare, este destul de intuitiv faptul ca o exceptie nu este altceva decat un obiect care se defineste aproape la fel ca orice alt obiect. Cu toate acestea, exista si o conditie suplimentara ce trebuie satisfacuta: clasa care defineste obiectul trebuie sa mosteneasca, direct sau indirect, clasa predefinita [http://docs.oracle.com/javase/7/docs/api/java/lang/Throwable.html Throwable].
Pentru a intelege mai bine notiunea de exceptie, vom considera un exemplu ipotetic simplu. Sa presupunem ca avem un vector intrinsec cu elemente de tip int. Daca ii alocam o dimensiune fixa, in timpul instantierii, nu vom mai putea introduce elemente in acest vector atunci cand atingem dimensiunea maxima. De asemenea, nu putem incerca sa eliminam un element din vector daca acesta este gol. In continuare, se vor prezenta doua implementari ce se pot uzita intr-un astfel de scenariu.
class ExceptieSirPlin extends Exception { public ExceptieSirPlin() { super("Sirul a atins capacitatea maxima!"); } } class ExceptieSirVid extends Exception { public ExceptieSirVid(String text) { super(text); } }
Inca din primele laboratoare am spus ca obiecte interactioneaza prin apeluri de metoda. Din perspectiva unui obiect care apeleaza o metoda a unui alt obiect, la revenirea din respectivul apel putem fi in doua situatii: metoda s-a executat in mod obisnuit sau metoda s-a terminat in mod neobisnuit, datorita aparitiei unei exceptii. Clauza throws apare in antetul unui metode si ne spune ce tipuri de exceptii pot conduce la terminarea neobisnuita a respectivei metode. Mai jos, vom prezenta modul in care specificam metodele clasei SirNumere care pot sa termine executia datorita unei situatii neobisnuite.
class SirNumere { Vector vector; //Implementarea propriu-zisa nu ne intereseaza (momentan) public void adauga(int x) throws ExceptieSirPlin { } //Implementarea propriu-zisa nu ne intereseaza (momentan) public int sterge(int x) throws ExceptieSirVid { //returneaza pozitia elementului sters } }
Clauza throws poate fi vazuta ca o specificare suplimentara a tipului returnat de o metoda. De exemplu, metoda sterge returneaza, in mod obisnuit, o valoare de tip int, reprezentand pozitia elementului sters. Clauza throws spune ca, in situatii exceptionale, metoda poate returna o referinta la un obiect de tip ExceptieSirVid sau o referinta la un obiect al carui tip reprezinta un subtip al acestei clase.
Pana acum am vorbit despre modul de definire a unei exceptii si de modul in care specificam faptul ca o metoda poate sa se termine cu una sau mai multe exceptii. In continuare, ne vom ocupa de raspunsul urmatoarei intrebari: ,,Cum poate afla un obiect, folosit pentru apelul unei metode ce se poate termina cu una sau mai multe exceptii, daca apelul s-a terminat normal sau nu?''. Raspunsul este unui simplu, deoarece, evident, cei care au proiectat Java s-au gandit la o rezolvare pentru o astfel de situatie. Acest lucru se rezolva uzitand un bloc try-catch-finaly, structura generala pentru un astfel de bloc fiind prezentata mai jos.
try { //Secventa de instructiuni in care ar putea sa //apara exceptii } chatch(TipExceptie1 e) { //Secventa de instructiuni ce se executa cand, in //sectiunea try apare o exceptie, avand tipul sau //subtipul TipExceptie1 (parametrul fiind o referinta //la exceptia prinsa) } catch(TipExceptie2 e) { //Acelasi lucru doar ca este vorba de TipExceptie2 } //Sectiunea catch poate lipsi din acest bloc //.................................................... catch (TipExceptiN e) { //Acelasi lucru doar ca este vorba de TipExceptien } finaly { //Secventa de instructiuni ce se executa in orice //conditii la terminarea executiei celorlalte sectiuni //Aceasta este obtionala (poate exista maxim una) } //Alte instructiuni ale metodei
In aceasta sectiune vom vedea cum anume se anunta explicit aparitia unei situatii neobisnuite. Mai exact, vom vedea cum se emite explicit o exceptie. Acest lucru se va realiza uzitand instructiunea throw.
In continuare, vom prezenta implementarea integrala a clasei SirNumere, pentru o intelegere mai buna a conceptelor explicate in aceasta sectiune.
class SirNumere { int vector[], dim, poz = 0; public SirNumere(int dim) { vector = new int[dim]; this.dim = dim; } public void adauga(int x) throws ExceptieSirPlin { if(vector.length == dim) { throw new ExceptieSirPlin(); } else { vector[this.poz++] = x; } } public int sterge(int x) throws Exception { if(this.poz == 0) { throw new ExceptieSirVid("Sirul este vid"); } else { int pozitie = -1; for(int i = 0; i < this.poz && pozitie==-1; i++) { if(vector[i] == x) { pozitie = i; } } if(pozitie != -1) { for(int i = pozitie; i < this.poz - 1; i++) vector[i] = vector[i+1]; return pozitie; } else { throw new Exception("Valoarea nu exista!"); } } } public static void main(String args[]) { SirNumere sir = new SirNumere(10); try { for(int i = 0; i < 10; i++) { int nr = sir.sterge(sir.vector[i]); } while(true) { sir.adauga((int) Math.random() * 100); } } catch(ExceptieSirPlin e) { e.printStackTrace(); } catch(ExceptieSirVid e) { e.printStackTrace(); } catch(Exception e) { e.printStackTrace(); } finally { System.out.println("Am terminat"); } } }
In Java, exista instructiuni care pot emite exceptii intr-o maniera implicita, in sensul ca nu se utilizeaza instructiunea throw pentru emiterea lor. Aceste exceptii sunt emise de masina virtuala Java, in momentul detectiei unei situatii anormale la executie.
Spre exemplu, vom considera un vector intrinsec, avand elemente de tip double si o capacitate de 10 elemente. In momentul in care vom incerca sa accesam un element folosind un index mai mare ca 9 sau mai mic ca 0, masina virtuala Java va emite o exceptie de tip IndexOutOfBoundsException.
class Test { double v[]; public Test(int dim) { v = new double[dim]; } public static void main(String args[]) { Test t = new Test(10); for(int i = 0; i < 9; i++) { t.v[i] = (double) Math.random()*100; } System.out.println(t.v[10]); } }
Pentru a face prelucrari si pentru a obtine rezultate este nevoie de date. Si aceste valori de intrare sunt de obicei stocate in variabile statice, variabilele locale (definite in metode) sau variabile de instanta (variabile nonstatice definite in clase). Pentru a initializa o variabila o poti face la definitia sau mai tarziu intr-o metoda (constructor sau nu). In ciuda acestor doua solutii comune, exista o alta cale folosind blocuri de initializare.
In continuare, se va prezenta un prim exemplu minimal de uzitare a blocurilor de initializare.
class Test { //bloc static de initializare static { System.out.println("Bloc static de initializare!"); } //bloc de initializare { System.out.println("Bloc de initializare!"); } public static void main(String[] args) { System.out.println("Aceasta este metoda main()!"); } }
Dupa cum am vazut in exemplul anterior, exista doua tipuri de blocuri de initializare cu comportamente diferite.
Bocurile statice de initializare pot accesa DOAR atributele statice ale clasei in care sunt definite. NU se pot folosi variabile de instanta in blocuri de initializare statice (daca incercati sa faceti acest lucru, veti primi o eroare de compilare: non-static variable value cannot be referenced from a static context).
Blocurile instanta de initializare pot accesa atribute statice si variabile de instanta (variabilele de instanta reprezinta atributele instantei construite) deoarece acestea sunt executate chiar inainte de construirea instantei.