Tratarea excepțiilor: Diferență între versiuni

De la WikiLabs
(Pagină nouă: În Java, excepțiile reprezintă o întrerupere în execuția normală a algoritmului. Acestea sunt folosite pentru a administra problemele apărute în timpul execuției și a lua m...)
 
(Administrarea excepțiilor)
 
(Nu s-au afișat 20 de versiuni intermediare efectuate de același utilizator)
Linia 9: Linia 9:
 
O excepție este generată (aruncată) folosind cuvântul cheie '''throw''':
 
O excepție este generată (aruncată) folosind cuvântul cheie '''throw''':
 
<syntaxhighlight lang="java">
 
<syntaxhighlight lang="java">
public void setAge(int _age){
+
public void setAge(int age){
     if(_age < 0 || _age > 150){
+
     if(age < 0 || age > 150){
         Error _e = new Error("Invalid age " + _age);
+
         Error e = new Error("Invalid age: " + age);
         throw _e;
+
         throw e;
 
     }
 
     }
  
     age = _age;
+
     mAge = age;
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
O excepție odată generată, execuția normală a programului se întrerupe și se reia doar într-un bloc '''catch''' asociat acelui tip de excepție (vezi [[#tratarea excepțiilor]]). Spre exemplu, în situația de mai sus, după ce eroarea a fost generată folosind cuvântul cheie '''throw''', asignarea ''age = _age;'' nu se mai execută.
+
O excepție odată generată, execuția normală a programului se întrerupe și se reia doar într-un bloc '''catch''' asociat acelui tip de excepție (vezi [[#Administrarea excepțiilor]]). Spre exemplu, în situația de mai sus, după ce eroarea a fost generată folosind cuvântul cheie '''throw''', asignarea ''age = _age;'' nu se mai execută.
  
 
== Tratarea excepțiilor ==
 
== Tratarea excepțiilor ==
  
 
În anumite situații, excepțiile întrerup definitiv execuția programului. Aceste tipuri de excepții nu se administrează (cele extinse din clasa '''Error'''). Există totuși excepții care pot fi tratate, cele extinse din clasa '''Exception'''. Acestea sunt de două tipuri:
 
În anumite situații, excepțiile întrerup definitiv execuția programului. Aceste tipuri de excepții nu se administrează (cele extinse din clasa '''Error'''). Există totuși excepții care pot fi tratate, cele extinse din clasa '''Exception'''. Acestea sunt de două tipuri:
*
+
* verificate la compile-time (cele extinse direct din '''Exception''', EXCLUSIV [http://docs.oracle.com/javase/7/docs/api/java/lang/RuntimeException.html java.lang.RuntimeException]), numite ''checked exceptions'';
 +
* verificate la runtime (cele extinse din [http://docs.oracle.com/javase/7/docs/api/java/lang/RuntimeException.html java.lang.RuntimeException]), numite ''unchecked exceptions''.
 +
 
 +
<div class="regula"><font color="#ff0000">Regulă:</font> Excepțiile ''checked'' care nu sunt administrate în metoda curentă trebuie obligatoriu să apară în lista de excepții generate ale metodei. Excepțiile ''unchecked'' care nu sunt administrate în metoda curentă NU trebuie obligatoriu să apară în lista de excepții generate ale metodei. Excepțiile de tip '''Error''' sunt de tip ''unchecked''.</div>
 +
 
 +
Lista de excepții ''checked'' generate de o metodă se scrie după lista de argumente, dar înainte de corpul metodei, utilizând cuvântul cheie '''throws''':
 +
<syntaxhighlight lang="java">
 +
public void setAge(int age) throws Exception{
 +
    if(age < 0 || age > 150){
 +
        Exception e = new Exception("Invalid age: " + age);
 +
        throw e;
 +
    }
 +
 
 +
    mAge = age;
 +
}
 +
</syntaxhighlight>
 +
 
 +
Cum clasa '''Exception''' este de tip ''checked'', ea trebuie să apară în lista de excepții generată de metodă. În schimb '''RuntimeException''' poate să lipsească:
 +
<syntaxhighlight lang="java">
 +
public void setAge(int age){
 +
    if(age < 0 || age > 150){
 +
        RuntimeException e = new RuntimeException("Invalid age: " + age);
 +
        throw e;
 +
    }
 +
 
 +
    mAge = age;
 +
}
 +
</syntaxhighlight>
 +
 
 +
=== Propagarea pe stivă ===
 +
 
 +
Stiva de execuție a unui program reprezintă secvența de metode apelate una de cealaltă.
 +
 
 +
<syntaxhighlight lang="java">
 +
public class Person{
 +
 
 +
    private String mFullName;
 +
    private int mAge;
 +
    private boolean mGender;
 +
 
 +
public Person(String fullName, int age, boolean gender){
 +
    setAge(age);
 +
}
 +
 
 +
public static void main(String[] args){
 +
    Person p = new Person("Ghita Vasile", 20, true);
 +
}
 +
 
 +
public void setAge(int age){
 +
    /* in this point, the execution stack is as follows:
 +
      *
 +
      *  /---------------------------------\    ^
 +
      *  |        void setAge(int)        |    |
 +
      *  +---------------------------------+    |
 +
      *  |  Person(String, int, boolean)  |    |
 +
      *  +---------------------------------+    |
 +
      *  |    static void main(String[])  |    |
 +
      *  \---------------------------------/    |
 +
      *
 +
      *  where method main(String[]) is at the
 +
      *  bottom of the stack (first one called)
 +
      *  and method setAge(int) is on the top
 +
      *  of the stack (last one called), and it's
 +
      *  the method that's currently in execution.
 +
      */
 +
    mAge = age;
 +
}
 +
 
 +
}
 +
</syntaxhighlight>
 +
 
 +
 
 +
În situația în care nici metoda apelantă nu administrează excepția generată, atunci se consideră că și ea la rândul ei generează același tip de excepție, într-un mod recursiv:
 +
<syntaxhighlight lang="java">
 +
public class Person{
 +
 
 +
    private String mFullName;
 +
    private int mAge;
 +
    private boolean mGender;
 +
 
 +
public Person(String fullName, int age, boolean gender) throws Exception{
 +
    //...
 +
    /* setAge() throws a checked exception of type Exception
 +
    * which isn't caught in this method, so this method will
 +
    * throw the exception further up the stack, thus the 'throws Exception'
 +
    * in the method declaration
 +
    */ 
 +
    setAge(age);
 +
    //..
 +
    System.out.println("If setAge() throws an exception, this line will not be executed!");
 +
}
 +
 
 +
public void setAge(int age) throws Exception{
 +
    if(age < 0 || age > 150){
 +
        Exception e = new Exception("Invalid age " + age);
 +
        throw e;
 +
    }
 +
 
 +
    mAge = age;
 +
}
 +
 
 +
}
 +
</syntaxhighlight>
 +
 
 +
=== Administrarea excepțiilor ===
 +
 
 +
Administrarea unei excepții de face într-un bloc '''try-catch'''. Instrucțiunile care pot genera excepții (apeluri de metode sau instrucțiuni '''throw''') se plasează într-un bloc '''try''' după care apar unul sau mai multe blocuri '''catch''' care administrează efectiv fiecare tip de excepție care poate fi generată:
 +
 
 +
<syntaxhighlight lang="java">
 +
public class Person{
 +
 
 +
    private String mFullName;
 +
    private int mAge;
 +
    private boolean mGender;
 +
 
 +
public Person(String fullName, int age, boolean gender) throws Exception{
 +
    //...
 +
    /* setAge() throws a checked exception of type Exception
 +
    * which isn't caught in this method, so this method will
 +
    * throw the exception further up the stack, thus the 'throws Exception'
 +
    * in the method declaration
 +
    */ 
 +
    setAge(age);
 +
    //..
 +
    System.out.println("If setAge() throws an exception, this line will not be executed!");
 +
}
 +
 
 +
public static void main(String[] args){
 +
    try{
 +
        Person p = new Person("Ghita Vasile", -30, true);
 +
    }catch(Exception e){
 +
        /* if an exception is thrown by the constructor, the execution
 +
        * is continued with this catch block. The reference _e is to
 +
        * the object that has been thrown. In this particular case,
 +
        * the instantiation of the object is not complete, therefor
 +
        * p is still null.
 +
        */
 +
        System.out.println("Can't create Person object: " + e.getMessage());
 +
    }
 +
 
 +
    /* after the catch block completes, or in case no exception is thrown,
 +
    * the execution continues from here.
 +
    */
 +
    System.out.println("Program done!");
 +
}
 +
 
 +
public void setAge(int age) throws Exception{
 +
    if(age < 0 || age > 150){
 +
        Exception e = new Exception("Invalid age " + age);
 +
        throw e;
 +
    }
 +
 
 +
    mAge = age;
 +
}
 +
 
 +
}
 +
</syntaxhighlight>
 +
 
 +
O metodă poate arunca mai multe excepții. Acestea pot fi, fiecare în parte, administrate sau propagate mai departe pe stivă. Atenție, principiul polimorfismului se aplică și aici:
 +
<syntaxhighlight lang="java">
 +
import java.io.*;
 +
 
 +
public class Person{
 +
 
 +
public static void main(String[] _args){
 +
    try{
 +
        Person _p = new Person();
 +
        int _age = _p.readAgeFromFile("ageFile.bin");
 +
    }catch(IOException _e){
 +
        System.out.println("Unable to open file for reading: " + _e.getMessage());
 +
    }catch(Exception _e){
 +
        System.out.println("The age is invalid: " + _e.getMessage());
 +
    }
 +
 
 +
    System.out.println("Program done!");
 +
}
 +
 
 +
public int readAgeFromFile(String _fileName) throws IOException, Exception{
 +
    FileInputStream _fileInputStream = new FileInputStream(_fileName);
 +
    int _age = _fileInputStream.read();
 +
    _fileInputStream.close();
 +
 
 +
    if(_age < 0 || _age > 150){
 +
        throw new Exception("Invalid age read from file: " + _age);
 +
    }
 +
 
 +
    return _age;
 +
}
 +
 
 +
}
 +
</syntaxhighlight>
 +
 
 +
Cum [http://docs.oracle.com/javase/7/docs/api/java/io/IOException.html java.io.IOException] este extinsă din '''java.lang.Exception''', atunci ar fi fost suficient ca metoda '''readAgeFromFile()''' să declare că aruncă doar '''java.lang.Exception''', dar atunci cele două excepții nu ar fi putut fi tratate separat în două blocuri '''catch'''. Din același motiv, dacă blocul '''catch''' care administrează excepția de tip '''java.lang.Exception''' ar fi fost prima după blocul '''try''', atunci acest bloc '''catch''' ar fi prins și excepțiile de tip '''java.io IOException''', deci în acest caz, ordinea blocurilor '''catch''' contează.
 +
 
 +
=== Blocul finally și try-with-resources ===
 +
 
 +
Există situații în care se dorește execuția unei bucăți de program indiferent dacă o excepție a fost generată sau dacă blocul '''try''' a fost executat cu succes. În acest caz se folosește un bloc '''finally''' după ultimul '''catch'''. De cele mai multe ori, motivul pentru utilizarea unui bloc '''finally''' este de a elibera resursele care au rămas potențial alocate:
 +
 
 +
<syntaxhighlight lang="java">
 +
public void writeAgeToFile(int _age, String _filename){
 +
    FileOutputStream _file = null;
 +
 
 +
    try{
 +
        _file = new FileOutputStream(_filename);
 +
        _file.write(100 / _age);       
 +
    }catch(ArithmeticException e){
 +
        System.err.println("Caught ArithmeticException: " +  e.getMessage());
 +
    }catch(IOException e){
 +
        System.err.println("Caught IOException: " +  e.getMessage());
 +
    }finally{
 +
        if(_file != null){
 +
            _file.close();
 +
        }
 +
    }
 +
 +
</syntaxhighlight>
 +
 
 +
Începând cu versiunea Java 7.0, a fost introdus un nou tip de bloc '''try''': ''try-with-resources''. Acesta primește ca argumente o serie de expresii de instanțiere a unor obiecte. Aceste obiecte trebuie obligatoriu să implementeze interfața [http://docs.oracle.com/javase/7/docs/api/java/lang/AutoCloseable.html java.lang.AutoClosable]. Avantajul este că la ieșirea din blocul ''try-with-resources'', aceste resurse sunt eliberate automat:
 +
 
 +
<syntaxhighlight lang="java">
 +
static String readFirstLineFromFile(String _path) throws IOException{
 +
    try(BufferedReader _reader = new BufferedReader(new FileReader(_path))){
 +
        return _reader.readLine();
 +
    }
 +
}
 +
</syntaxhighlight>
 +
 
 +
este echivalent cu:
 +
 
 +
<syntaxhighlight lang="java">
 +
static String readFirstLineFromFileWithFinallyBlock(String _path) throws IOException{
 +
    BufferedReader _reader = new BufferedReader(new FileReader(_path));
 +
    try{
 +
        return _reader.readLine();
 +
    }finally{
 +
        if (_reader != null){
 +
            _reader.close();
 +
        }
 +
    }
 +
}
 +
</syntaxhighlight>
 +
 
 +
Mai multe despre excepții pe [http://docs.oracle.com/javase/tutorial/essential/exceptions/index.html site-ul oficial Oracle].

Versiunea curentă din 14 octombrie 2016 09:40

În Java, excepțiile reprezintă o întrerupere în execuția normală a algoritmului. Acestea sunt folosite pentru a administra problemele apărute în timpul execuției și a lua măsurile de rigoare pentru a relua programul din locul în care este posibil. Excepțiile, ca orice altceva în Java, sunt obiecte. Clasa de bază pentru orice tip de excepție este java.lang.Throwable.

Din clasa Throwable se extind două alte clase:

  • java.lang.Error - un tip de excepție care descrie o problemă gravă în execuția programului și din cauza căreia aplicația nu mai poate continua; aceste tipuri de excepții nu pot fi administrate.
  • java.lang.Exception - o clasă care descrie excepții pe care o aplicație le poate administra și după care execuția se poate relua în mod corect;

Generarea excepțiilor

O excepție este generată (aruncată) folosind cuvântul cheie throw:

public void setAge(int age){
    if(age < 0 || age > 150){
        Error e = new Error("Invalid age: " + age);
        throw e;
    }

    mAge = age;
}

O excepție odată generată, execuția normală a programului se întrerupe și se reia doar într-un bloc catch asociat acelui tip de excepție (vezi #Administrarea excepțiilor). Spre exemplu, în situația de mai sus, după ce eroarea a fost generată folosind cuvântul cheie throw, asignarea age = _age; nu se mai execută.

Tratarea excepțiilor

În anumite situații, excepțiile întrerup definitiv execuția programului. Aceste tipuri de excepții nu se administrează (cele extinse din clasa Error). Există totuși excepții care pot fi tratate, cele extinse din clasa Exception. Acestea sunt de două tipuri:

Regulă: Excepțiile checked care nu sunt administrate în metoda curentă trebuie obligatoriu să apară în lista de excepții generate ale metodei. Excepțiile unchecked care nu sunt administrate în metoda curentă NU trebuie obligatoriu să apară în lista de excepții generate ale metodei. Excepțiile de tip Error sunt de tip unchecked.

Lista de excepții checked generate de o metodă se scrie după lista de argumente, dar înainte de corpul metodei, utilizând cuvântul cheie throws:

public void setAge(int age) throws Exception{
    if(age < 0 || age > 150){
        Exception e = new Exception("Invalid age: " + age);
        throw e;
    }

    mAge = age;
}

Cum clasa Exception este de tip checked, ea trebuie să apară în lista de excepții generată de metodă. În schimb RuntimeException poate să lipsească:

public void setAge(int age){
    if(age < 0 || age > 150){
        RuntimeException e = new RuntimeException("Invalid age: " + age);
        throw e;
    }

    mAge = age;
}

Propagarea pe stivă

Stiva de execuție a unui program reprezintă secvența de metode apelate una de cealaltă.

public class Person{

    private String mFullName;
    private int mAge;
    private boolean mGender;

public Person(String fullName, int age, boolean gender){
    setAge(age);
} 

public static void main(String[] args){
    Person p = new Person("Ghita Vasile", 20, true);
}

public void setAge(int age){
     /* in this point, the execution stack is as follows:
      * 
      *  /---------------------------------\    ^
      *  |        void setAge(int)         |    |
      *  +---------------------------------+    |
      *  |  Person(String, int, boolean)   |    |
      *  +---------------------------------+    |
      *  |    static void main(String[])   |    |
      *  \---------------------------------/    |
      *
      *  where method main(String[]) is at the 
      *  bottom of the stack (first one called)
      *  and method setAge(int) is on the top
      *  of the stack (last one called), and it's
      *  the method that's currently in execution.
      */
     mAge = age;
}

}


În situația în care nici metoda apelantă nu administrează excepția generată, atunci se consideră că și ea la rândul ei generează același tip de excepție, într-un mod recursiv:

public class Person{

    private String mFullName;
    private int mAge;
    private boolean mGender;

public Person(String fullName, int age, boolean gender) throws Exception{
    //...
    /* setAge() throws a checked exception of type Exception
     * which isn't caught in this method, so this method will
     * throw the exception further up the stack, thus the 'throws Exception'
     * in the method declaration
     */  
    setAge(age);
    //..
    System.out.println("If setAge() throws an exception, this line will not be executed!");
} 

public void setAge(int age) throws Exception{
    if(age < 0 || age > 150){
        Exception e = new Exception("Invalid age " + age);
        throw e;
    }

    mAge = age;
}

}

Administrarea excepțiilor

Administrarea unei excepții de face într-un bloc try-catch. Instrucțiunile care pot genera excepții (apeluri de metode sau instrucțiuni throw) se plasează într-un bloc try după care apar unul sau mai multe blocuri catch care administrează efectiv fiecare tip de excepție care poate fi generată:

public class Person{

    private String mFullName;
    private int mAge;
    private boolean mGender;

public Person(String fullName, int age, boolean gender) throws Exception{
    //...
    /* setAge() throws a checked exception of type Exception
     * which isn't caught in this method, so this method will
     * throw the exception further up the stack, thus the 'throws Exception'
     * in the method declaration
     */  
    setAge(age);
    //..
    System.out.println("If setAge() throws an exception, this line will not be executed!");
} 

public static void main(String[] args){
    try{
        Person p = new Person("Ghita Vasile", -30, true);
    }catch(Exception e){
        /* if an exception is thrown by the constructor, the execution
         * is continued with this catch block. The reference _e is to 
         * the object that has been thrown. In this particular case, 
         * the instantiation of the object is not complete, therefor
         * p is still null.
         */
         System.out.println("Can't create Person object: " + e.getMessage());
    }

    /* after the catch block completes, or in case no exception is thrown,
     * the execution continues from here.
     */
    System.out.println("Program done!");
}

public void setAge(int age) throws Exception{
    if(age < 0 || age > 150){
        Exception e = new Exception("Invalid age " + age);
        throw e;
    }

    mAge = age;
}

}

O metodă poate arunca mai multe excepții. Acestea pot fi, fiecare în parte, administrate sau propagate mai departe pe stivă. Atenție, principiul polimorfismului se aplică și aici:

import java.io.*;

public class Person{

public static void main(String[] _args){
    try{
        Person _p = new Person();
        int _age = _p.readAgeFromFile("ageFile.bin");
    }catch(IOException _e){
         System.out.println("Unable to open file for reading: " + _e.getMessage());
    }catch(Exception _e){
         System.out.println("The age is invalid: " + _e.getMessage());
    }

    System.out.println("Program done!");
}

public int readAgeFromFile(String _fileName) throws IOException, Exception{
    FileInputStream _fileInputStream = new FileInputStream(_fileName);
    int _age = _fileInputStream.read();
    _fileInputStream.close();

    if(_age < 0 || _age > 150){
        throw new Exception("Invalid age read from file: " + _age);
    }

    return _age;
}

}

Cum java.io.IOException este extinsă din java.lang.Exception, atunci ar fi fost suficient ca metoda readAgeFromFile() să declare că aruncă doar java.lang.Exception, dar atunci cele două excepții nu ar fi putut fi tratate separat în două blocuri catch. Din același motiv, dacă blocul catch care administrează excepția de tip java.lang.Exception ar fi fost prima după blocul try, atunci acest bloc catch ar fi prins și excepțiile de tip java.io IOException, deci în acest caz, ordinea blocurilor catch contează.

Blocul finally și try-with-resources

Există situații în care se dorește execuția unei bucăți de program indiferent dacă o excepție a fost generată sau dacă blocul try a fost executat cu succes. În acest caz se folosește un bloc finally după ultimul catch. De cele mai multe ori, motivul pentru utilizarea unui bloc finally este de a elibera resursele care au rămas potențial alocate:

public void writeAgeToFile(int _age, String _filename){
    FileOutputStream _file = null;

    try{
        _file = new FileOutputStream(_filename);
        _file.write(100 / _age);        
    }catch(ArithmeticException e){
        System.err.println("Caught ArithmeticException: " +  e.getMessage());
    }catch(IOException e){
        System.err.println("Caught IOException: " +  e.getMessage());
    }finally{
        if(_file != null){
             _file.close();
        }
    }
}

Începând cu versiunea Java 7.0, a fost introdus un nou tip de bloc try: try-with-resources. Acesta primește ca argumente o serie de expresii de instanțiere a unor obiecte. Aceste obiecte trebuie obligatoriu să implementeze interfața java.lang.AutoClosable. Avantajul este că la ieșirea din blocul try-with-resources, aceste resurse sunt eliberate automat:

static String readFirstLineFromFile(String _path) throws IOException{
    try(BufferedReader _reader = new BufferedReader(new FileReader(_path))){
        return _reader.readLine();
    }
}

este echivalent cu:

static String readFirstLineFromFileWithFinallyBlock(String _path) throws IOException{
    BufferedReader _reader = new BufferedReader(new FileReader(_path));
    try{
        return _reader.readLine();
    }finally{
        if (_reader != null){
            _reader.close();
        }
    }
}

Mai multe despre excepții pe site-ul oficial Oracle.