Documente Academic
Documente Profesional
Documente Cultură
Aplicatiile intalnesc erori la executie. Aplicatiile robuste trebuie sa manipuleze erorile cat mai
elegant cu putinta. Cateva motive de aparitie a erorilor:
- Aplicatia nu are comportamentul asteptat
- rezultat al unui bug
- factori din spatele aplicatiei: conexiune la baza de date intrerupta, functionarea driver-
elor, etc
In Java, atunci cand folosim resurse externe, compilatorul cere sa manipulam exceptiile ce pot
aparea (spre ex: java.nio, java.sql). A manipula o exceptie inseamna sa adaugam in cod
un bloc de manipulare a erorii (try-catch). A declara o exceptie inseamna ca declaram ca o
metoda poate esua in executie sa (clauza throws).
O exceptie reprezinta, asadar, un eveniment ce intrerupe executia normala a unui program.
Cand se intalneste o exceptie la rulare, metoda in care a aparut exceptia creaza un obiect
exceptie. Metoda poate arunca exceptia inapoi catre metoda apelanta, care probabil o va trata.
Obiectul exceptie include informatii despre exceptie, cum ar fi metoda in care exceptia a
aparut si cauza exceptiei. Metoda apelanta poate trimite exceptia, mai departe, unei alte
metode, care la randul sau a apelat-o. Daca nicio metoda nu manipuleaza exceptia, programul
se termina cand obiectul exceptie atinge metoda main().
Cand apare o exceptie o putem preveni trimitand-o in stiva de apel sau tratand-o, astfel
aplicatia continundu-si executia pe o alta cale.
Blocul try cuprinde codul ce poate cauza o exceptie. Acest bloc este numit si cod protejat
(protected code). Un bloc try este urmat de zero sau mai multe blocuri catch ce specifica
tipurile de exceptie ce pot fi prinse.
catch ar trebui sa fie folosit pentru:
- a reincerca executia unei operatii
- incercarea de a executa o alta operatie
- o iesire eleganta sau apelul lui return
Scrierea unui bloc catch vid este o bad practice.
Blocurile catch se executa in ordinea in care au fost scrise, de sus in jos, pina la intilnirea
primei exceptii ce se potriveste ca tip cu cel al tipului aruncat. Cel mult un bloc catch se
executa. De aceea, vom plasa blocul catch, cel mai general, pe ultima pozitie in blocurile din
sirul blocurilor catch. Daca nu se arunca nicio exceptie atunci nici un bloc catch se executa.
Blocul finally este optional, daca try este urmat de macar un catch. Blocul finally se
executa indiferent daca avem sau nu aruncata exceptie, cu o singura exceptie, atunci cand
executam System.exit.
// sintaxa de baza a lui try
try {
// cod ce poate arunca o exceptie
}
catch (TipExceptie1 Identificator1) {
// cod de manipulare a exceptiei
}
catch (TipExceptie2 Identificator2) {
// cod de manipulare a exceptiei
}
finally {
// 0 sau 1 clauze finally
}
Cand deschidem resurse precum fisiere sau conexiuni la baza de date, trebuie intotdeauna sa
le inchidem. Incercarea de a le inchide in try poate fi problematica deoarece putem incheia
blocul sarind operatia. De aceea este recomandata inchiderea acestora in blocul finally.
Uneori operatia pe care o executam in blocul finally poate genera o exceptie. In acest caz va
trebui sa scrie un bloc try-catch in interiorul lui finally. Putem avea try-catch si in
interiorul lui try sau al lui catch.
public class FinallyExampleMain {
public static void main(String[] args) {
InputStream in = null;
try {
System.out.println("About to open a file");
in = new FileInputStream("missingfile.txt");
System.out.println("File open");
int data = in.read();
} catch (FileNotFoundException e) {
System.out.println(e.getMessage());
} catch (IOException e) {
System.out.println(e.getMessage());
} finally {
try {
if (in != null) {
in.close();
}
} catch (IOException e) {
System.out.println("Failed to close file");
}
}
}
}
Toate exceptiile si erorile Java sunt subclase ale clasei Throwable din pachetul java.lang.
Putem loga exceptiile ce apar pe parcursul aplicatiei folosind Apache Log4j si framework-uri
predefinite din java.util.
In Java SE7 avem la dispozitie un try cu resurse, ce va inchide automat resursele. Astfel
eliminam folosirea lui finally. Orice clasa ce implementeaza java.lang.AutoCloseable
poate fi utilizata ca resursa. Pentru ca o resursa sa fie automat inchisa, referinta sa trebuie
declarata in interiorul parantezelor rotunde ale lui try.
public class FinallyExampleMain {
public static void main(String[] args) {
System.out.println("About to open a file");
try(InputStream in = new FileInputStream("missingfile.txt")) {
System.out.println("File open");
int data = in.read();
} catch (FileNotFoundException e) {
System.out.println(e.getMessage());
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
}
Putem deschide mai multe resurse daca ele sunt separate prin punct si virgula. Inchiderea lor
se va face in ordine inversa deschiderii.
Daca o exceptie apare in momentul crearii unei resurse AutoCloseable, controlul va fi
preluat imediat de blocul catch.
Daca o exceptie apare in blocul try, toate resursele vor fi inchise inainte de a rula catch.
Daca o exceptie va fi generata in timpul inchiderii resursei, ea va fi suprimata. Controlul va fi
preluat de un bloc catch. Exceptiile ar putea fi afisate astfel:
public class FinallyExampleMain {
public static void main(String[] args) {
System.out.println("About to open a file");
try (InputStream in = new FileInputStream("missingfile.txt")) {
System.out.println("File open");
int data = in.read();
} catch (Exception e) {
System.out.println(e.getMessage());
for (Throwable t : e.getSuppressed())
System.out.println(t.getMessage());
}
}
}
Resursele din try cu resurse trebuie sa implementeze unul dintre:
- java.lang.AutoCloseable, care poate arunca un Exception
- java.io.Closeable, care extinde AutoCloseable si care poate arunca o
IOException
Interfata AutoCloseable are o singura metoda void close() throws Exception;
Metoda close() din AutoCloseable, spre deosebire de cea din Closeable, nu este
idempotenta. Aceasta inseamna ca apelul sau mai mult decat o data poate avea efecte
secundare. Cei care implementeaza metoda din AutoCloseable vor trebui sa o faca
idempotenta
Java SE7 furnizeaza o clauza multi-catch. Avantajele deriva din faptul ca uneori dorim sa
facem anumite actiuni independent de exceptia ce este generata. Multi-catch reduce cantitatea
de cod scris, prin eliminarea necesitatii scrieri mai multor clauze catch cu acelasi
comportament. Un alt avantaj este acela ca face mai putin probabila incercarea de a prinde o
exceptie generica. Alternativele de tip, separate prin bara verticala, nu trebuie sa aiba o relatie
de mostenire.
public class FinallyExampleMain {
public static void main(String[] args) {
System.out.println("About to open a file");
try (InputStream in = new FileInputStream("missingfile.tx");
Scanner s = new Scanner(in);) {
System.out.println("File open");
int data = s.nextInt();
} catch (NoSuchElementException | IOException e) {
System.out.println(e.getClass().getName());
System.out.println("Quiting");
}
}
}
Numai un obiect Throwable poate fi aruncat, si acesta include exceptii sau erori de sistem.
Metodele din Throwable sunt:
getMessage(), ce returneaza un string cuprinzand mesajul de eroare al obiectului
Throwable
toString(), returneaza o descriere a obiectului exceptie incluzand si tipul exceptiei
initCause(), seteaza cauza exceptiei care este intotdeauna un alt obiect Throwable,
ceea ce permite inlantuirea exceptiilor
printStackTrace(), permite identificarea metodei ce a aruncat o anumita exceptie
Ierarhia Throwable este urmatoarea:
Clasa Throwable are doua subclase directe:
Error, si clasele derivate din ea sunt folosite pentru erori de sistem sau erori la
compilare ce nu pot fi tratate de aplicatie, ca spre ex.
ExceptionInInitializerError, StackOverflowError si NoClassDefFoundError
Exception, si clasele derivate din ea sunt utilizate pentru implementarea exceptiilor
pe care o aplicatie se asteapta sa le intilneasca. Fiecare subclasa a lui Exception
reprezinta un tip particular de exceptie
Clasa RuntimeException si subclasele sale descriu obiectele exceptie ce sunt aruncate
automat de JVM, la rulare. Exceptiile la rulare sunt generate, in general, de bug-uri in codul
sursa. Printre subclasele acesteia amintim:
ArithmeticException, pentru exceptiile ce cuprind incalcarea regulilor matematice,
cum ar fi impartirea la zero
IndexOutOfBoundsException, cand indexul unui string sau array nu este in domeniul
acceptat
NegativeArraySizeException, cand incercam sa cream un sir cu numar negativ de
elemente
Pentru a prinde exceptiile la rulare putem folosi blocurile try-catch, dar acest lucru nu este
obligatoriu. Daca try-catch nu este prezent, toate exceptiile la rulare vor fi considerate
exceptii neverificate (unchecked exceptions). Aceasta inseamna ca compilatorul nu verifica
daca ele au fost declarate sau manipulate.
Toate exceptiile ce mostenesc Exception dar nu mostenesc RuntimeException sunt
cunoscute ca clase exceptie verificate. Acestea trebuie declarate intr-o metoda folosind clauza
throws sau tratate explicit in corpul metodei. Altfel se va genera eroare la compilare. Din
categoria acestor exceptii amintim:
ClassNotFoundException, este aruncata atunci cand un program incearca sa incarce
o clasa, folosind numele ei, dar definirea clasei cu numele specificat nu este gasita.
Aceste exceptii apar atunci cand un program utilizeaza metoda forName() din Class
sau findSystemClass() sau loadClass() din ClassLoader
InterruptedException, aruncata atunci cand un fir a fost inactiv pentru mult timp si
un alt fir ce foloseste metoda interrupt() din Thread incearca sa-l intrerupa
IllegalAccessException, aruncata atunci cand executia metodei curente nu are
acces la definitia unui camp pe care doreste sa-l acceseze sau modifice sau la definitia
unei metode pe care doreste sa o invoce
FileNotFoundException, apartine pachetului java.io si este aruncata atunci cand
aplicatia nu poate deschide un fisier specificat (fisier inaccesibil sau inexistent).
Exceptiile pot fi tratate in interiorul metodei sau in metoda sa apelanta. Cand o exceptie a fost
aruncata trebuie sa implementam interfata ExceptionListener ce receptioneaza doar
exceptii si apeleza metoda exceptionThrown().
Pentru a ne asigura ca o metoda poate arunca exceptii utilizam cuvantul rezervat throws cand
declaram metoda. In clauza throws specificam tipurile de exceptii ce pot fi aruncate, tipuri
separate prin virgula.
In metoda apelanta exceptia poate fi tratata sau trimisa metodei apelante a acesteia adaugand
clauzei throws exceptiile netratate.
Daca o metoda suprascrie o metoda din clasa de baza ea poate arunca numai aceleasi exceptii
ca si metoda din clasa de baza, o submultime a lor sau subclase ale exceptiilor. Metoda
suprascrisa nu poate arunca exceptii ce sunt superclase ale claselor exceptie ale metodei din
clasa de baza, sau exceptii ce apartin unei alte ierarhii. Aceste reguli se aplica numai
exceptiilor verificate.
Fie urmatorul exemplu:
public class ThrowsMain {
public static void main(String[] args) {
try {
int data = readByteFromFile();
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
public DAOException() {
super();
}
}
package exceptions;
}
import daos.EntityDAO;
import exceptions.DAOException;
try {
ent.findById(5);
} catch (DAOException e) {
Throwable t = e.getCause();
System.out.println(e.getMessage() + t.getMessage());
}
}
O implementare DAO poate utiliza o exceptie wrapper pentru a pastra abstractia si pentru a
evita netratarea exceptiilor. Iata explicatia: majoritatea metodelor din DAO trebuie sa trateze
exceptii (IOExceptions sau SQLExceptions). Daca am folosi semnaturi fara throws am
determina clientul sa trateze exceptiile, la implementare. Am pierde astfel abstractia. Prin
folosirea exceptiilor wrapper ii dam clientului posibilitatea de a stii care sunt problemele
aparute la rulare.
In versiunile anterioare ale Javei daca aparea o eroare intr-un fir atunci se arunca o eroare
exceptie si firul se termina, exceptia navigand spre radacina ThreadGroup. Numele firului,
numele exceptiei, mesajul exceptiei si stack trace-ul erau afisate. In 1.4 si versiunile
anterioare inseram cod propriu in ThreadGroup pentru a manipula aceste exceptii. In 1.6
manipularea exceptiilor neprinse este simplificata pentru ca putem defini manipulatorul
exceptiilor neprinse, la nivelul firului de executie.
Metoda setUncaughtExceptionHandler(), introdusa in 1.5, ne permite sa preluam controlul
deplin asupra raspunsului unui fir, in cazul exceptiilor neprinse, prin setarea unui manipulator
de exceptii neprinse. Pentru a seta manipulatorul in toate firele vom trimite ca parametru al
acestei metode o implementare a interfetei Thread.UncaughtExceptionHandler. Metoda
acestei interfete este uncaughtException(). Daca niciun manipulator nu este setat obiectul
ThreadGroup va functiona ca un manipulator de exceptie.
Metoda getUncaughtExceptionHandler() returneaza handler-ul invocat atunci cand un fir
se termina din cauza unei exceptii neprinse. Pentru a determina cum sa manipuleze exceptia
neprinsa, JVM mai intai cauta un manipulator de fire pe care sa-l invoce. Daca exista il
invoca. Apoi JVM trimite exceptia in ierarhia ThreadGroup pina ce atinge radacina acesteia,
ceea ce inseamna ca ThreadGroup nu a suprascris uncaughtException(). In cele din urma
JVM apeleza Thread.getDefaultExceptionHandler() pentru a invoca manipulatorul
predefinit.
In exemplul urmator vom crea un manipulator de exceptie, HandlerPropriu, pe care il vom
seta intr-un fir, ExceptieNeprinsa. Metoda run() a acestui fir nu prinde nicio exceptie, desi
face o impartire la zero. Exceptia este preluata automat de catre manipulatorul definit.
class HandlerPropriu implements Thread.UncaughtExceptionHandler {
public void uncaughtException(Thread t, Throwable e) {
System.err.printf("%s: %s la linia %d in fisierul %s%n",
t.getName(), e.toString(), e.getStackTrace()[0].getLineNumber(),
e.getStackTrace()[0].getFileName());
}
}
Asertiuni (assertions)
Asertiunea ne permite sa testam presupunerea despre cum ruleaza un program. Asertiunea
contine o expresie booleana, avand valoarea predefinita true. Asertiunea este diferita de toate
celelalte teste pentru ca verifica conditii ce nu pot fi violate. Daca expresia booleana are
valoarea false atunci sistemul arunca o AssertionError. Mecanismul de asertiuni este
valoros deoarece ofera debugging mult mai eficient, ajuta la intretinerea codului prin
documentarea lucrului intern al programului, incurajeaza forma de programare contractuala
(contract programming).
Programarea contractuala este o tehnica utilizata pentru a incuraja un inalt nivel al calitatii
softului, reutilizarii si increderii. Furnizorul si consumatorul unui apel de metoda sunt
impreuna de acord sa adere la un contract bine definit, ce este fortat de folosirea asertiunilor.
Asertiunile sunt in mod predefinit dezactivate. Putem sa le activam sau dezactivam folosind
comenzi la rulare. Asertiunile asigura ca o regula stabilita in cod nu poate fi niciodata
modificata. Putem folosi asertiunile ca pe o masura de siguranta, pentru a confirma
presupunerile despre comportamentul unui program.
Exista doua forme de asertiuni:
simpla, ce poate fi exprimata ca: assert Expresie; unde Expresie este o expresie
logica. Cand asertiunile sunt activate, sistemul ruleaza asertiunile si evalueaza
expresia din ele. Daca aceasta are valoarea false atunci se va arunca o
AssertionError, fara niciun mesaj
complexa, ce poate fi exprimata ca: assert Expresie1:Expresie2;. Aceasta se
foloseste pentru a furniza un mesaj complex in caz de eroare. Expresie1 este
booleana si va fi evaluata. In caz de eroare valoarea lui Expresie2 va fi trimisa
constructorului lui AssertionError si un mesaj de eroare detaliat va fi afisat.
AssertionError este o eroare ce opreste executia programului si reprezinta o
subclasa a java.lang.Error. Aceasta eroare nu poate fi prinsa.
Nu vom folosi asertiunile pentru:
verificarea argumentelor metodelor: metodele publice au un contract clar definit cu
utilizatorii, care nu depinde de faptul ca asertiunile sunt activate. In locul asertiunile
pentru argumente ilegale se poate folosi IllegalArgumentException.
efectuarea functiilor critice cerute de aplicatie. Aceasta problema apare atunci cand
asertiunile sunt dezactivate
producerea efectelor secundare. Acesta problema apare atunci cand asertiunile sunt
dezactivate
Urmatorul exemplu ilustreaza de ce nu putem folosi asertiunile pentru executarea operatiilor
critice
assert carNames.remove(null);
In cazul in care asertiunile sunt dezactivate metoda remove() nu se va executa, ceea ce ar
produce rezultate inconsitente. Solutia de rezolvare consistenta a situatiei este urmatoarea:
boolean nullsRemoved = carNames.remove( null );
assert nullsRemoved;
Asertiunile pot fi folosite pentru testarea:
invariantilor interni: un invariant intern este o conditie ce trebuie sa fie true tot
timpul. Asertiunile se folosesc ori de cate ori facem o presupunem asupra unui
invariant. O folosim in if-uri multiple, pe ultima ramura else sau in switch pe
ramura default. Spre exemplu:
if (j % 5 == 0) {
...
} else if (j % 5 == 1) {
...
} else {
assert j % 5 == 2 : "presupunerea este incorecta";
....
}
invariantilor de control ai fluxului: pentru aceasta plasam asertiunea intr-o locatie de
cod in care presupunem ca nu putem ajunge. Spre exemplu:
void metoda() {
for (...) {
if (...)
return;
}
assert false; // acest punct al executiei nu ar trebui atins
}
De obicei se foloseste assert false;
Preconditiilor, postconditiilor si invariantilor clasei. O preconditie este o conditie pe
care o presupunem adevarata la inceputul metodei. Putem testa presupunerea intr-o
metoda nepublica, la inceputul acesteia
private void metoda(int param) {
assert ((param >= 50) && (param <= 500)) : param;
...
}
Putem folosi asertiunile pentru a testa postconditiile atat in metode publice cat si
nepublice. Putem testa o post conditie chiar inainte de instructiunea return. Pentru
aceasta insa trebuie sa ne asiguram ca exista o singura iesire din metoda. Un invariant
de clasa este o conditie ce trebuie sa fie adevarata pentru fiecare instanta a clasei.
Presupunem ca avem o metoda booleana privata numita IsCorrect() care trebuie sa
returneze true inainte si dupa ce orice metoda s-a incheiat. In cod adaugam o
asertiune inainte de instructiunea return pentru fiecare metoda publica si constructor.
In acest moment testul devine un invariant.
Folosim comenzi in linia de comanda pentru a activa asertiunile. Putem activa asertiunile
pentru o aplicatie, un pachet sau o clasa. Sintaxa generala este:
java [-enableassertions | -ea] [:package name"..." | :class name]
Comanda urmatoare activeaza asertiunile aplicatiei Application:
java –ea Application
Pentru o clasa folosim comanda:
java –ea:com.vehicle.motorcar.YourClass
iar pentru un pachet:
java –ea:com.vehicle.motorcar…
cele trei puncte din finalul comenzii precizeaza ca asertiunile sunt active pentru pachetul
motorcar si pentru toate subpachetele sale.
Unele clase nu sunt incarcate de class loader ci de JVM. De aceea folosim in completarea
comenzii de activare a asertiunilor clauza -eas sau –enablesystemassertions. Spre
exemplu:
java –esa –ea:com.vehicle.motorcar.YourClass
Dezactivarea asertiunilor se face folosind clauza –da sau –disableassertions.
Cuvantul rezervat assertion nu poate fi folosit cu versiunile mai vechi ale Javei. Compilarea
cu versiunea 1.3 va genera o eroare sau un avertisment. In rest compilarea se face cu: javac
–source 1.x fisier, unde x este 4, 5 sau 6.
Vom exemplifica utilizarea asertiunilor folosind frameworkul JUnit 4 din Eclipse. Vom crea
mai intai o clasa a carei metoda dorim sa o testam.
public class Asertiuni {
import org.junit.Test;
@Test
public final void testMain() {
Asertiuni a=new Asertiuni();
a.numeleZilei(8);
}
}
Observatie: daca folosim asertiuni (ca in cazul exemplului nostru) din
Window>Preferences>Java>JUnit selectam checkbox-ul Add ‘-ea’….
Rulam apoi aplicatia ca pe un JunitTest. Un API complet gasim in org.junit.Assert.
JUnit uses a lot of static methods and Eclipse cannot automatically import static imports. You
can make the JUnit test methods available via the content assists.
Open the Preferences via Window -> Preferences and select Java > Editor > Content Assist >
Favorites. Add then via "New Member" the methods you need. For example this makes the
assertTrue, assertFalse and assertEquals method available.
You can now use Content Assist (Ctrl+Space) to add the method and the import.
org.junit.Assert.assertTrue
org.junit.Assert.assertFalse
org.junit.Assert.assertEquals
org.junit.Assert.fail
Annotations
Table 1. Annotations
Annotation Description
Annotation @Test identifies that this
@Test public void method()
method is a test method.
Will perform the method() before each test.
This method can prepare the test
@Before public void method()
environment, e.g. read input data, initialize
the class)
@After public void method() Test method must start with test
Will perform the method before the start of
all tests. This can be used to perform time
@BeforeClass public void method()
intensive activities for example be used to
connect to a database
Will perform the method after all tests
have finished. This can be used to perform
@AfterClass public void method()
clean-up activities for example be used to
disconnect to a database
Will ignore the test method, e.g. useful if
the underlying code has been changed and
@Ignore the test has not yet been adapted or if the
runtime of this test is just to long to be
included.
Tests if the method throws the named
@Test(expected=IllegalArgumentException.class)
exception
Fails if the method takes longer then 100
@Test(timeout=100)
milliseconds
Assert statements
Table 2. Test methods
Statement Description
Let the method fail, might be usable to check that a
fail(String)
certain part of the code is not reached.
assertTrue(true); True
Statement Description
assertsEquals([String message], Test if the values are the same. Note: for arrays the
expected, actual) reference is checked not the content of the arrays
assertsEquals([String message], Usage for float and double; the tolerance are the
expected, actual, tolerance) number of decimals which must be the same
assertNull([message], object) Checks if the object is null
assertNotNull([message], object) Check if the object is not null
assertSame([String], expected, actual) Check if both variables refer to the same object
Check that both variables refer not to the same
assertNotSame([String], expected, actual)
object
assertTrue([message], boolean condition) Check if the boolean condition is true.