Du nouveau dans la gestion des exceptions avec java 7 !

java7Java SE 7 est maintenant disponible depuis quelques temps… En attendant la révolution Java 8 qui vous a été présentée ici, je vous propose un petit rappel sur quelques nouveautés apportées par cette release.

Dans cet article, on se focalisera sur la gestion des exceptions, avec 3 améliorations qui vont vous simplifier la vie.

Nous verrons donc :
1. try-with-resources : gestion automatisée des ressources
2. Multicatch : traitement de plusieurs exceptions dans le même bloc catch
3. Rethrow : propagation d’exceptions avec vérification des sous-types

La page officielle java SE 7 présentant les nouvelles features : ici

L’intégralité des sources est disponible via Github.

1. try-with-resources : gestion automatisée des ressources

Jusqu’à présent, la gestion des ressources, et notamment leur libération, reste à la charge du développeur. Ainsi, lorsqu’on gère une ressource de type connexion à une base de données ou un fichier, on doit s’assurer que, même en cas d’exception, la ressource sera libérée. Cette tâche peut rapidement devenir très complexe et fastidieuse.

Prenez par exemple le code suivant, cas classique de gestion d’un fichier I/O :

class FileIO{
    int readValue;
    FileInputStream fileInputStream;
    public static void main(String args[]){
        FileIO fileIO = new FileIO();
        fileIO.readFile();
    }

    private void readFile(){
        try{
            fileInputStream = new FileInputStream("FileIO.java");
            while((readValue = fileInputStream.read()) != -1)
                logger.log(String.valueOf(readValue));
        }catch(IOException ioexception){
            logger.log("IOException occurred: "+ioexception.getMessage());
            try{
                if (fileInputStream != null)
                    fileInputStream.close();
            }catch(IOException ioe){}
        }
    }
}

L’instance de FileInputStream est fermée dans le bloc catch, ce try/catch étant lui-même encapsulé dans un bloc catch. Malheureusement, il se peut que votre ressource ne soit pas libérée si une exception autre que IOException est générée dans le premier try.

Avec le try-with-resource, vous êtes assuré que vos ressources seront libérées à la fin du bloc, peu importe les exceptions générées et leurs traitements. Par ressource, on entend ici tout objet qui doit être fermé lorsque le bloc dans lequel il est utilisé se termine. Ainsi, tout objet implémentant java.lang.AutoCloseable peut être utilisé comme ressource.

Exemple d’utilisation :

try (   FileInputStream fis = new FileInputStream(filePath);
        DataInputStream in = new DataInputStream(fis);
        BufferedReader br = new BufferedReader(new InputStreamReader(in))){
   //code qui peut générer une exception
}catch(Exception ex){
   logger.log(ex);
   throw ex;
}

attention Notez ici qu’on déclare trois ressources (fis, in et br), chaque déclaration étant séparée par un point-virgule.
attention Notez également que l’appel aux méthodes close() de nos objets ressources est fait dans l’ordre opposé de leur création. C’est pour cela que fis est déclaré séparément de in et br, sans quoi la méthode close de fis ne serait pas appelée. (i.e : BufferedReader br = new BufferedReader(new InputStreamReader(new DataInputStream(new FileInputStream(“file”))))

2. Multicatch : traitement de plusieurs exceptions dans le même bloc catch

Il vous arrive de faire le même traitement sur plusieurs blocs catch successifs ? Avec java 7, votre code sera simplifié grâce au multicatch !

Prenez le code suivant :

try{
...
}catch(NullPointerException ex){
logger.log(ex);
throw ex;
}catch(ArithmeticException ex){
logger.log(ex);
throw ex;
}

On voit bien qu’il y a ici du code redondant. Mais maintenant, on a enfin un moyen de mutualiser le traitement d’exceptions lorsqu’on souhaite les gérer de la même façon.
En java 7, ce même code deviendra :

try{
...
}catch(NullPointerException | ArithmeticException ex){
logger.log(ex);
throw ex;
}

attention Notez qu’on sépare les types d’exceptions gérés par la clause catch avec une barre verticale (|)

attention Dans le cas où on gère plus d’un type d’exception dans le bloc catch, le paramètre du bloc (ici ex) devient final.
On ne peut donc plus modifier son contenu dans le corps du catch.

3. Rethrow : propagation d’exceptions avec vérification des sous-types

On a tous déjà fait un rethrow après avoir traité dans notre bloc catch l’exception. Par exemple :


public void maMethode() throws IOException {
try {
...
} catch (Exception ex) {
logger.log(ex);
throw ex;
}
}

Problème : dans les versions précédentes de java, on aurait eu une erreur du type “unreported exception Exception; must be caught or declared to be thrown“, car on ne peut générer une exception “supertype” de celle attendue. On pourrait donc modifier la déclaration de la méthode en généralisant le type d’exception :


public void maMethode() throws Exception

Malheureusement, on perd de la précision sur le type d’exception remonté par notre méthode.

Avec java 7, lorsqu’on déclare les types d’exceptions dans la clause throws de notre méthode, par exemple IOException, le compilateur sait que même si le paramètre du catch est de type Exception, il sera une instance de IOException. Ceci est rendu possible par une analyse plus fine des types d’exceptions déclarées dans la signature de la méthode.

attention Cette analyse du compilateur n’est plus possible si vous modifiez la valeur de ex dans le bloc catch. Vous perdez ainsi l’avantage du nouveau rethrow et retrouvez le comportement
des compilateurs 1.6 et précédents.
attention Comme pour le multi-catch, notre Exception ex est implicitement déclarée final.
attention Le paramètre du catch doit être un “supertype” d’un des types défini dans le throws.

En espérant vous avoir convaincu de passer à java 7… avant la sortie de java 8 (septembre 2013), dont les fonctionnalités sont à découvrir  iciici et ici !

logger.log(ex);
throw ex;

Nombre de vue : 136

COMMENTAIRES 3 commentaires

  1. raphael.jeannin dit :

    Bonjour,

    Dans le code en exemple :

    try ( FileInputStream fis = new FileInputStream(filePath);
    DataInputStream in = new DataInputStream(fis);
    BufferedReader br = new BufferedReader(new InputStreamReader(in))){
    }catch(Exception ex){}

    Je ne comprends pas l’objectif de faire un try/catch sur un bloc vide : {}

    Ne devrait il pas y avoir au moins une instruction ?

    De plus pourquoi le bloc catch est vide : ne faut il pas a tout prix éviter de masquer une exception : Always throw and never catch


    try (
    FileInputStream fis = new FileInputStream(filePath);
    DataInputStream in = new DataInputStream(fis);
    BufferedReader br = new BufferedReader(new InputStreamReader(in))
    )
    {
    /*code ajouté*/
    this.doSomeThatCouldCauseException();
    }
    catch(Exception ex)
    {
    /*code ajouté*/
    this.throwExceptionOrLog();

    }

  2. Brice Hugon dit :

    Bonjour,

    Effectivement sur cet exemple le block try/catch ne contient ni code pouvant produire un exception, ni code traitant l’exception. Le but était de mettre uniquement l’architecture du try-with-resources.

    Mais faire un rappel sur les bonnes pratiques ne peut pas faire de mal, je mettrai à jour le billet du blog avec ta proposition de code.

    Merci de ta participation.

  3. patrick.allain dit :

    Salut Brice,

    Dans ton premier exemple, à la méthode “old-school” d’avant java 7, normalement, tu devrais fermer ton stream dans un bloc finaly et non pas juste dans le catch de l’exception.

    En effet, si aucune exception n’est lancée, le flux ne sera jamais fermé.

    Sinon, ton article est très intéressant ;-).

AJOUTER UN COMMENTAIRE