Administrativ
Laboratoare
Tema
Teste
Resurse utile
Alte resurse
Arhiva Teme
Administrativ
Laboratoare
Tema
Teste
Resurse utile
Alte resurse
Arhiva Teme
Ce este o excepţie? În esenţǎ, o excepţie este un eveniment care se produce în timpul execuţiei unui program şi care perturbǎ fluxul normal al instrucţiunilor acestuia. De exemplu, în cadrul unui program care copiazǎ un fişier, astfel de evenimente excepţionale pot fi:
O abordare foarte des intâlnitǎ, ce precedǎ apariţia conceptului de excepţie, este întoarcerea unor valori speciale din funcţii, care sǎ desemneze situaţia apǎrutǎ. De exemplu, în C, funcţia fopen întoarce NULL dacǎ deschiderea fişierului a eşuat. Aceastǎ abordare are douǎ dezavantaje principale:
int openResult = open(); if (openResult == FILE_NOT_FOUND) { // handle error } else if (openResult == INUFFICIENT_PERMISSIONS) { // handle error } else { // SUCCESS int readResult = read(); if (readResult == DISK_ERROR) { // handle error } else { // SUCCESS ... } }
Mecanismul bazat pe excepţii înlaturǎ ambele neajunsuri menţionate mai sus. Codul ar arǎta aşa:
try { open(); read(); ... } catch (FILE_NOT_FOUND) { // handle error } catch (INUFFICIENT_PERMISSIONS) { // handle error } catch (DISK_ERROR) { // handle error }
Se observǎ includerea instrucţiunilor ce aparţin fluxului normal de execuţie într-un bloc try, şi precizarea condiţiilor excepţionale posibile la sfârşit, în câte un bloc catch. Logica este urmǎtoarea: se executǎ instrucţiune cu instrucţiune secvenţa din blocul try şi, la apariţia unei situaţii excepţionale, semnalate de o instrucţiune, se abandoneazǎ restul instrucţiunilor rǎmase neexecutate şi se sare direct la blocul catch corespunzǎtor.
Când o eroare se produce într-o funcţie, aceasta creeazǎ un obiect excepţie şi il paseazǎ cǎtre runtime system
. Un astfel de obiect conţine informaţii despre situaţia apǎrutǎ:
Pasarea menţionatǎ mai sus poartǎ numele de aruncarea (throwing) unei excepţii.
Exemplu de aruncare a unei excepţii:
List<String> l = getArrayListObject(); if (l == null) throw new Exception("The list is empty");
În acest exemplu, încercǎm sǎ obţinem un obiect de tip ArrayList
; dacǎ funcţia getArrayListObject
întoarce null, aruncǎm o excepţie. Pe exemplul de mai sus putem face urmǎtoarele observaţii:
new
)
În realitate, clasa Exception
este pǎrintele majoritǎţii claselor excepţie din Java. Enumerǎm câteva excepţii standard:
null
).Iterator
care nu mai conţine un element urmǎtor.În momentul în care se instanţiazǎ un obiect excepţie, în acesta se reţine întregul lanţ de apeluri de funcţie, prin care s-a ajuns la instrucţiunea curentǎ. Aceastǎ succesiune se numeşte stack trace, şi se poate afişa prin apelul e.printStackTrace() unde este obiectul excepţie.
Când o excepţie a fost aruncatǎ, runtime system
încearcǎ sǎ o trateze (prindǎ - catch
). Tratarea (sau prinderea) unei excepţii este fǎcutǎ de o porţiune de cod specialǎ.
Sǎ observǎm urmǎtorul exemplu:
public void f() throws Exception { List<String> l = null; if (l == null) throw new Exception(); } public void catchFunction() { try { f(); } catch (Exception e) { System.out.println("S-a generat o exceptie"); } }
Se observǎ cǎ, dacǎ o funcţie aruncǎ o excepţie şi nu o prinde, trebuie, în general, sǎ adauge clauza throws în antet. Funcţia f
va arunca întotdeauna o excepţie (din cauza cǎ l
este mereu null
). Observaţi cu atenţie funcţia catchFunction
:
try
, în interiorul cǎruia se apeleazǎ f
. În general, pentru a prinde o excepţie, trebuie sǎ specificǎm o zonǎ în care aşteptǎm ca excepţia sǎ se producǎ (guarded region
). Aceastǎ zonǎ este introdusǎ prin try
.catch
(Exception e
). La producerea excepţiei, blocul catch
corespunzǎtor va fi executat. În cazul nostru, se va afişa mesajul “S-a generat o excepţie”
.Observaţi urmǎtorul exemplu:
public void f() throws MyException, WeirdException { List<String> l = null; if (l == null) throw new MyException(); throw new WeirdException("This exception never gets thrown"); } public void catchFunction() { try { f(); } catch (MyException e) { System.out.println("S-a generat o exceptie Null Pointer"); } catch (WeirdException e) { System.out.println("S-a generat o exceptie ciudata"); } }
În acest exemplu, funcţia f
a fost modificatǎ astfel încât sǎ arunce MyException
. Observaţi faptul cǎ, în catchFunction
avem doua blocuri catch
. Cum excepţia aruncatǎ de f
este de tip MyException
, numai primul bloc catch
se va executa. Prin urmare:
try
si catch
;funcţie de tipul acestora.
Nivelul la care o excepţie este tratatǎ depinde de logica aplicaţiei. Acesta nu trebuie sǎ fie neaparat nivelul imediat urmǎtor, ce invocǎ secţiunea generatoare de excepţii. Desigur, propagarea de-a lungul mai multor nivele (metode) presupune utilizarea clauzei throws
. Dacǎ o excepţie nu este tratatǎ nici în main
, aceasta va conduce la încheierea execuţiei programului!
În general, vom dispune în acelaşi bloc try-catch
instrucţiunile care pot fi privite ca înfǎptuind un acelaşi scop. Astfel, dacǎ o operaţie din secvenţa esueazǎ, se renunţǎ la instrucţiunile rǎmase şi se sare la un bloc catch
. Putem specifica operaţii opţionale, al cǎror eşec sǎ nu influenţeze întreaga secvenţǎ. Pentru aceasta folosim blocuri try-catch
imbricate:
try { op1(); try { op2(); op3(); } catch (Exception e) { ... } op4(); op5(); } catch (Exception e) { ... }
Dacǎ apelul op2
eşueazǎ, se renunţǎ la apelul op3
, se executǎ blocul catch
interior, dupǎ care se continuǎ cu apelul op4
.
Presupunem cǎ, în secvenţa de mai sus, care deschide şi citeşte un fişier, avem nevoie sǎ închidem fişierul deschis, atât în cazul normal, cât şi în eventualitatea apariţiei unei erori. În aceste condiţii, se poate ataşa un bloc finally
dupǎ ultimul bloc catch
, care se va executa în ambele cazuri menţionate. Secvenţa de cod urmǎtoare conţine o structurǎ try-catch-finally
:
try { open(); read(); ... } catch (FILE_NOT_FOUND) { // handle error } catch (INUFFICIENT_PERMISSIONS) { // handle error } catch (DISK_ERROR) { // handle error } finally { // close file }
Blocul finally
se dovedeşte foarte util când în blocurile try-catch
se gǎsesc instrucţiuni return. El se va executa şi în acest caz, exact înainte de execuţia instrucţiunii return, aceasta fiind executatǎ ulterior.
Nu toate excepţiile trebuie prinse cu try-catch
. Pentru a înțelege de ce, sǎ analizǎm clasificarea excepţiilor din figure 1:
FileNotFoundException
. Un program bine scris va prinde aceastǎ excepţie, va afişa utilizatorului un mesaj de eroare, şi îi va permite acestuia (eventual) sǎ reintroducǎ un nou nume de fişier.IOError
. Aplicaţia poate încerca sǎ prindǎ aceastǎ excepţie, pentru a anunţa utilizatorul despre problema apǎrutǎ; dupǎ aceastǎ însǎ, programul va eşua (afişând eventual stack trace
).null
va produce NullPointerException
. Fireşte, putem prinde excepţia. Mai natural însǎ ar fi sǎ eliminǎm din program un astfel de bug care produce excepţia.try-catch
. Toate excepţiile sunt checked cu excepţia celor de tip Error, RuntimeException şi subclasele acestora.
Excepţiile error nu trebuie (în mod obligatoriu) prinse folosind try-catch
. Opţional, programatorul poate alege sǎ le prindǎ.
Excepţiile runtime nu trebuie (în mod obligatoriu) prinse folosind try-catch
. Ele sunt de tip RuntimeException. Aţi întâlnit deja exemple de excepţii runtime, în urma diferitelor neatenţii de programare: NullPointerException
, ArrayIndexOutOfBoundsException
etc. Putem arunca o RuntimeException
fǎrǎ sǎ o menţionǎm în clauza throws din antet:
public void f(Object o) { if (o == null) throw new NullPointerException("o is null"); }
Când aveţi o situaţie în care alegerea unei excepţii (de aruncat) nu este evidentǎ, puteţi opta pentru a scrie propria voastrǎ excepţie, care sǎ extindǎ Exception, RuntimeException sau Error. Exemplu:
class TemperatureException extends Exception {} class TooColdException extends TemperatureException {} class TooHotException extends TemperatureException {}
În aceste condiţii, trebuie acordatǎ atenţie ordinii în care se vor defini blocurile catch. Acestea trebuie precizate de la clasa excepţie cea mai particularǎ, pânǎ la cea mai generalǎ (în sensul moştenirii). De exemplu, pentru a întrebuinţa excepţiile de mai sus, blocul try-catch
ar trebui sǎ arate ca mai jos:
try { ... } catch (TooColdException e) { ... } catch (TemperatureException e) { ... } catch (Exception e) { ... }
Afirmaţia de mai sus este motivatǎ de faptul cǎ întotdeauna se alege primul bloc catch care se potriveşte. Un bloc catch referitor la o clasǎ excepţie pǎrinte, ca TemperatureException
prinde şi excepţii de tipul claselor copil, ca TooColdException
. Poziţionarea unui bloc mai general înaintea unuia mai particular ar conduce la ignorarea blocului particular.
Metodele suprascrise (overriden) pot arunca numai excepţiile specificate de metoda din clasa de bazǎ sau excepţii derivate din acestea.
1. (2p)Modificaţi rezolvarea exerciţiului 1 din laboratorul de Colecții, astfel încât fiecare semnalare de eroare sǎ fie înlocuitǎ cu aruncarea unei excepţii.
2. (2p) Scrieţi o metodǎ (scurtǎ) care sǎ genereze OutOfMemoryError şi o alta care sǎ genereze StackOverflowError. Verificaţi posibilitatea de a continua rularea dupǎ interceptarea acestei erori. Comparaţi rǎspunsul cu posibilitatea de a realiza acelaşi lucru într-un limbaj compilat, ce ruleazǎ direct pe platforma gazdǎ (ca C).
3. (4p) Definiţi o clasǎ care sǎ implementeze operaţii pe numere întregi. Operaţiile vor arunca excepţii.
Calculator
, ce conţine douǎ metode:add
: primeşte doi întregi şi întoarce un întregdivide
: primeşte doi întregi şi întoarce un întregaverage
: primeşte o colecţie ce conţine obiecte Integer
, şi întoarce media acestora. Pentru calculul mediei, sunt folosite operaţiile add
şi divide
.add
:OverflowException
: este aruncatǎ dacǎ suma celor doua numere depǎşeşte Integer.MAX_VALUE
UnderflowException
: este aruncatǎ dacǎ suma celor doua numere este mai micǎ decat Integer.MIN_VALUE
Calculator
.
4. (2p) Demonstraţi într-un program execuţia blocului finally
chiar şi în cazul unui return
din metoda.