Exception Handling

De la WikiLabs
Jump to navigationJump to search

In Java, exceptions represent an interruption in the normal execution of the algorithm. They are used to manage the issues that appear during the execution and to take action in order to recover and resume the program when possible. Exceptions, like everything else in Java, are objects. The base class for any type of exception is java.lang.Throwable.

From class Throwable, two other classes are derived:

  • java.lang.Error - a type of exception describing a serious problem in program execution because of which the application can no longer continue; these types of exceptions usually can't be managed or recovered.
  • java.lang.Exception - a class of exceptions that the application can manage and after which execution can resume correctly.

Generating (Throwing) Exceptions

An exception is generated by using keyword throw:

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

    age = _age;
}

An exception once thrown, the normal execution of the program is interrupted and it is resumed in a catch block associated to that type of exception (see Managing Exceptions). For example, in the case above, after the exception has been thrown, the assignment age = _age; is not executed anymore.

Managing Exceptions

In certain situations, exceptions interrupt the program for good. These exceptions are not managed (those extended from class Error). There are however, exceptions that can be managed, those extended from class Exception. There are two types of these:

Rule: Checked exceptions which are not caught (managed) in the current method, must appear in the list of thrown exceptions of the method. Unchecked exceptions that are not caught (managed) in the current method don't need to appear in the list of thrown exceptions of the method. Error type exceptions are unchecked.

The list of checked exceptions generated by a method is placed after the list of arguments, but before the body of the method, using the word throws

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

    age = _age;
}

Since class Exception is checked, it needs to appear in the list of exceptions generated by the method. However, RuntimeException can be missing:

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

    age = _age;
}

Stack Propagation

The execution stack of a program represents the sequence of methods that called one another.

public class Person{

    private String fullName;
    private int age;
    private boolean gender;

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.
      */
     age = _age;
}

}

In the situation when the caller method does not catch the exception either, then it's considered that it throws the exception itself, in a sort of recursive manner:

public class Person{

    private String fullName;
    private int age;
    private boolean gender;

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;
    }

    age = _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 fullName;
    private int age;
    private boolean gender;

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;
    }

    age = _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.