Rechercher
 

Améliorations / Corrections

Vous avez des améliorations (ou des corrections) à proposer pour ce document : je vous remerçie par avance de m'en faire part, cela m'aide à améliorer le site.

Emplacement :

Description des améliorations :

Mise en oeuvre d'une classe d'exception

Introduction à la gestion des exceptions Comparaison try/finally VS try-with-resources



Accès rapide :
La vidéo
Définir ses propres classes d'exceptions.
Comment utiliser nos classes d'exceptions.
Capturer nos propres exceptions.

La vidéo

Cette vidéo vous montre comment coder une classe d'exception en Java. La différence entre « checked exceptions » et « unchecked exceptions » y est aussi présentée.


Mise en oeuvre d'une classe d'exception

Définir ses propres classes d'exceptions.

Dans le chapitre précédent, nous avons vu qu'il existe, de base, un grand nombre de classes d'exception en Java. Chaque classe représente un type d'erreurs particulier : par exemple java.lang.NullPointerException, java.io.IOException ou encore java.sql.SQLException.

Mais peut-être vous êtes vous posé la question de comment définir nos propres types d'erreurs ?
La réponse est simple, en dérivant une nouvelle classe d'une autre classe d'exception (à minima, de java.lang.Throwable).

Pour tester cette possibilité, nous allons reprendre l'exemple développé durant le chapitre portant sur l'encapsulation : il s'agissait d'une classe de manipulation de nombres rationnels (de fractions). Pour rappel, si vous cherchez à produire une fraction avec la valeur 0 comme dénominateur, cela doit déclencher une exception. Dans l'exemple initial, nous avions utilisé le type java.lang.RuntimeException pour ne pas avoir à gérer la remontée de l'exception sur les différentes signatures de nos méthodes.

Nous allons remplacer cette partie de code pour maintenant déclencher une exception de type fr.koor.poo.RationalException : cette classe dérivera directement de java.lang.Exception. Voici le code de cette classe d'exception.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
package fr.koor.poo;

public class RationalException extends Exception {

    private static final long serialVersionUID = 6677487610288558193L;

    
    public RationalException() {
        this( "Denominator cannot be zero" );
    }

    public RationalException( String message ) {
        super( message );
    }
    
    public RationalException( String message, Throwable cause ) {
        super( message, cause );
    }
    
    // Autres méthodes si nécessaire.
    
}
Une classe d'exception représentant une incohérence lors d'une manipulation d'un nombre rationnel

Comme vous avez pu vous en apercevoir, trois constructeurs sont redéfinis afin de faciliter la production d'instances de nos exceptions (en fonction des besoins). En fait, ces trois possibilités correspondent à des constructeurs déjà existants au niveau de la classe de base utilisée (la classe Exception). Mais pour rappel, n'oubliez pas que les constructeurs ne s'héritent pas : il est donc nécessaire de les redéfinir.

Pour les deux premières signatures de constructeurs, je pense qu'elles parlent d'elles-mêmes. Par contre, la troisième est un peu plus subtile. Imaginons que vous codiez une nouvelle librairie. Parfois, ces codes déclenchent, en interne, des exceptions de bas niveau. Or fréquement, vous souhaitez indiquer à vos utilisateurs qu'une erreur vient de survenir via une classe d'exception faisant partie de votre librairie. Dans ce cas, interceptez les exceptions de bas niveau dans le code de votre librairie via des blocs try / catch puis redéclenchez, dans vos blocs catch, des exceptions basées sur vos propres types en les construisant par-dessus l'exception de bas niveau et donc en utilisant ce troisième constructeur. Ainsi vos utilisateurs pourront utiliser vos classes d'exceptions pour intercepter les problèmes et malgré tout avoir accès à l'exception de bas niveau ayant produit le problème. Dans la « stack trace » vous retrouverez ces exceptions de bas niveau via les marqueurs Caused by:. Voici un petit exemple de code permettant de produire ce type de combinaison d'exceptions.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
package fr.koor.exceptions;

public class TestException {


    public static void main(String[] args) throws Exception {

        try {
            int den = 0;
            int res = 1 / den;
        } catch( ArithmeticException baseException ) {
            throw new Exception( "Root exception", baseException );
        }
        
    }

}
Exceptions combinées

Et voici la « stack trace » produite et un fameux Caused by:

Exception in thread "main" java.lang.Exception: Root exception
    at fr.koor.exceptions.TestException.main(TestException.java:12)
Caused by: java.lang.ArithmeticException: / by zero
    at fr.koor.exceptions.TestException.main(TestException.java:10)

Comment utiliser nos classes d'exceptions.

Revenons à l'utilisation de notre classe RationalException. Nous souhaitons maintenant modifier le code de notre classe Rational pour y déclencher des instances de type RationalException en cas de problème. Il ne suffit pas de changer le type d'exception. Effectivement, notre nouvelle classe n'est pas une « unchecked exception » : si vous souhaitez que votre programme compile, il est donc nécessaire d'indiquer que certaines de vos méthodes déclenchent potentiellement des exceptions de type RationalException, via le mot clé throws.

Pour le déclenchement d'une exception, il faudra, bien entendu, utiliser le mot clé throw. Naturellement, ce déclenchement d'exception sera conditionné à l'utilisation de la valeur 0 comme dénominateur. Voici le code de la méthode setDenominator alignés sur l'utilisation de la classe RationalException.

 1 
 2 
 3 
 4 
 5 
 6 
public void setDenominator( int denominator ) throws RationalException {
    if ( denominator == 0 ) {
        throw new RationalException();
    }
    this.denominator = denominator;
}
La méthode setDenominator pouvant produire une RationalException

Notez bien que un throws peut se propager à des multiples endroits. Par exemple, le constructeur à deux paramètres de la classe Rational utilise la méthode setDenominator. Comme cette dernière peut déclencher une exception, il est nécessaire de le dire aussi au niveau du constructeur.

 1 
 2 
 3 
 4 
 5 
public Rational( int numerator, int denominator ) throws RationalException {
    this.setNumerator( numerator );
    this.setDenominator( denominator );
    this.simplify();
}
Propagation du throws sur un constructeur

Il y a un endroit dans la classe Rational ou les choses se compliquent : c'est la méthode d'ajout de deux rationnels. Mathématiquement parlant, si nos deux rationnels sont valident (ce que notre classe doit normalement garantir), le produit des deux dénominateurs, forcément différents de 0, doit produire un résultat non nul. Il serait donc vraiment dommage d'indiquer une possible remontée d'exception alors que cela n'arrivera jamais. Or, notre implémentation de l'addition utilise le constructeur vu ci-dessus. Il est donc nécessaire de ne pas passer par la méthode setDenominator et d'accéder directement à l'attribut : cela est possible car nous sommes bien à l'intérieur de la classe Rational.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
public Rational add( Rational r2 ) {
    /* Avant changement de la classe d'exception :
    int numerator = this.numerator * r2.denominator + this.denominator * r2.numerator;
    int denominator = this.denominator * r2.denominator;
    return new Rational( numerator, denominator );*/
    
    /* Après */
    Rational result = new Rational();
    result.numerator = this.numerator * r2.denominator + this.denominator * r2.numerator;
    result.denominator = this.denominator * r2.denominator;
    return result;
}

Voici le code complet de la classe Rational alignés sur l'utilisation de la classe RationalException.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
 37 
 38 
 39 
 40 
 41 
 42 
 43 
 44 
 45 
 46 
 47 
 48 
 49 
 50 
 51 
 52 
 53 
 54 
 55 
 56 
 57 
 58 
 59 
 60 
 61 
 62 
 63 
 64 
 65 
 66 
 67 
 68 
 69 
 70 
 71 
 72 
 73 
 74 
 75 
 76 
 77 
 78 
 79 
 80 
 81 
 82 
 83 
 84 
 85 
 86 
 87 
 88 
 89 
 90 
 91 
 92 
 93 
 94 
 95 
 96 
 97 
 98 
 99 
 100 
 101 
 102 
 103 
package fr.koor.poo;

public class Rational {

    // --- Nos deux attributs ---
    private int numerator;
    private int denominator;
    
    // --- Quatre constructeurs ---
    public Rational() {
        this.numerator = 0;
        this.denominator = 1;
    }
    
    public Rational( int numerator ) {
        this.numerator = numerator;
        this.denominator = 1;
    }
    
    public Rational( int numerator, int denominator ) throws RationalException {
        this.setNumerator( numerator );
        this.setDenominator( denominator );
        this.simplify();
    }
    
    public Rational( double value ) {
        this.denominator = 1;
        // On teste s'il y a des chiffres après la virgule
        while( value != (int) value ) {
            // on multiplie chacune des deux parties par 10
            value *= 10;
            this.denominator *= 10;
        }
        // value devient notre numérateur
        this.numerator = (int) value;
        this.simplify();
    }
    
    // --- Nos deux propriétés (getters/setters) ---
    public int getNumerator() {
        return numerator;
    }
    
    public void setNumerator( int numerator ) {
        this.numerator = numerator;
    }
    
    
    public int getDenominator() {
        return denominator;
    }
    
    public void setDenominator( int denominator ) throws RationalException {
        if ( denominator == 0 ) {
            throw new RationalException();
        }
        this.denominator = denominator;
    }
    
    // Quelques méthodes de la classe
    
    public Rational add( Rational r2 ) {
        Rational result = new Rational();
        result.numerator = this.numerator * r2.denominator + this.denominator * r2.numerator;
        result.denominator = this.denominator * r2.denominator;
        return result;
    }
    
    public boolean eq( Rational r2 ) {
        return this.numerator * r2.denominator == this.denominator * r2.numerator;
    }
    
    // Pour les détails sur l'algorithme d'Euclide pour le calcul du PGCD
    // https://fr.wikipedia.org/wiki/Algorithme_d%27Euclide#Description_de_l'algorithme
    public void simplify() {
        int a;
        int b;
        
        if ( this.numerator > this.denominator ) {
            a = this.numerator;
            b = this.denominator;
        } else {
            a = this.denominator;
            b = this.numerator;
        }
        
        int rest;
        while( (rest = a % b) != 0 ) {
            a = b;
            b = rest;
        }
        
        this.numerator /= b;
        this.denominator /= b;
    }    
    
    
    @Override 
    public String toString() {
        return String.format( "[%d/%d]", this.numerator, this.denominator );
    }
    
}
La classe Rational utilisant notre nouveau type d'exception (RationalException)

Capturer nos propres exceptions.

Comme vous vous en doutez, vous pouvez capturer vos propres exceptions. Cela se fait de la même manière que pour les exceptions standards Java. Voici un exemple d'interception de notre type d'exception.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
package fr.koor.poo;

import java.util.Scanner;

public class RationalStart {

    public static void main( String[] args ) {

        try ( Scanner scanner = new Scanner( System.in ) ) {

            System.out.print( "Veuillez saisir le numerateur : " );
            int num = Integer.parseInt( scanner.nextLine() );
            System.out.print( "Veuillez saisir le dénominateur : " );
            int den = Integer.parseInt( scanner.nextLine() );
            
            Rational r1 = new Rational( num, den );
            System.out.println( r1 );
            
        } catch( NumberFormatException exception ) {
        
            System.out.println( "Seules des valeurs numériques entières sont autorisée !!!" );
            
        } catch ( RationalException exception ) {

            System.out.println( "Svp, pas 0 pour le dénominateur !!!" );

        }
    }

}
Exemple d'interception de notre type d'exception


Introduction à la gestion des exceptions Comparaison try/finally VS try-with-resources