Version sans JavaScript, en lecture seule : les ateliers, les quiz et l’exécution du code n’y sont pas. Version complète · Catalogue · Se connecter

Support de cours · chapitre 9

Les exceptions

Ce que devient l'exécution quand une opération échoue, la différence entre exceptions vérifiées et non vérifiées, et les façons courantes de masquer une erreur sans le vouloir.

Ce chapitre prolonge les séquences 7, 8 et 9.

Codes de retour et exceptions

int prix = canard.getPrix();
if (prix == -1) { /* erreur ? ou canard gratuit ? */ }

Un code d'erreur peut se confondre avec une valeur valide, on peut l'ignorer sans que rien ne le signale, et il faut le transmettre à la main d'un appelant à l'autre. Une exception interrompt le déroulement normal du programme, transporte un message, et ne peut pas être ignorée par simple oubli.

public void setPrix(int prix) {
    if (prix < 0) throw new IllegalArgumentException("prix >= 0");
    this.prix = prix;
}

throw interrompt immédiatement la méthode : les instructions suivantes ne sont pas exécutées.

try, catch, finally

try {
    CanardEnPlastique c = new CanardEnPlastique("Jean", -50);
    System.out.println("jamais atteint");
} catch (IllegalArgumentException e) {
    System.out.println("Erreur : " + e.getMessage());
} finally {
    System.out.println("toujours exécuté : on vide le bassin");
}
  • try contient le traitement normal ;
  • catch indique quoi faire en cas d'échec, selon le type d'exception ;
  • finally contient le nettoyage, exécuté dans tous les cas : succès, échec, et même après un return.

return dans un finally

Avant de lancer le programme, essayez de prévoir ce que renvoie lireProfondeur() et ce que devient l'exception.

Finally.java

class Bassin {

    /** Une mesure qui échoue. */
    static int mesurer() {
        throw new IllegalStateException("sonde débranchée");
    }

    /** Version fautive : un return dans le finally. */
    static int lireProfondeur() {
        try {
            return mesurer();
        } finally {
            return -1;                  // ← avale tout
        }
    }

    /** Version fautive n° 2 : le catch qui ne dit rien. */
    static int lireTemperature() {
        try {
            return mesurer();
        } catch (Exception e) {
            return 0;                   // l'erreur n'a laissé aucune trace
        }
    }

    /** Version correcte : on nettoie, on ne décide pas à la place de l'appelant. */
    static int lireCorrectement() {
        try {
            return mesurer();
        } finally {
            System.out.println("   (nettoyage : sonde remise en place)");
        }
    }

    public static void main(String[] args) {

        System.out.println("1. lireProfondeur()  = " + lireProfondeur());
        System.out.println("2. lireTemperature() = " + lireTemperature());

        System.out.println("3. lireCorrectement() :");
        try {
            System.out.println("   valeur = " + lireCorrectement());
        } catch (IllegalStateException e) {
            System.out.println("   l'exception est bien remontée : " + e.getMessage());
        }
    }
}

Ici, un atelier : on y écrit et on y exécute du code. Il demande JavaScript.

Vérifiée ou non vérifiée

classDiagram
    direction TB
    class Throwable
    class Error["Error (problèmes JVM)"]
    class Exception
    class RuntimeException["RuntimeException (non vérifiée)"]
    class IOException["IOException (vérifiée)"]
    class NullPointerException
    class IllegalArgumentException

    Throwable <|-- Error
    Throwable <|-- Exception
    Exception <|-- RuntimeException
    Exception <|-- IOException
    RuntimeException <|-- NullPointerException
    RuntimeException <|-- IllegalArgumentException

Pour une exception vérifiée, le compilateur exige qu'elle soit déclarée (throws) ou traitée (catch). Elle signale un événement extérieur qu'un programme correct doit prévoir, comme un fichier absent ou une coupure réseau.

Pour une exception non vérifiée, le compilateur n'exige rien. Elle signale qu'un invariant n'est pas respecté, c'est-à-dire un bug : un argument invalide, une référence nulle.

Définir sa propre exception

public class PrixCanardException extends Exception {
    public PrixCanardException(String message) { super(message); }
}

public void setPrix(int prix) throws PrixCanardException {
    if (prix < 0) throw new PrixCanardException("Prix négatif interdit !");
    this.prix = prix;
}

Avec extends Exception, l'exception est vérifiée et l'appelant devra la traiter ; avec extends RuntimeException, elle est non vérifiée. C'est la classe parente qui détermine la catégorie : il n'existe pas de mot-clé pour cela.

Une exception métier nommée est préférable à une IllegalArgumentException générique, car on peut la rattraper séparément et son nom indique ce qui s'est passé.

Masquer une erreur sans le vouloir

Bonnes pratiques

  • Attrapez le type d'exception précis, et non Exception par commodité.
  • Écrivez un message utile, qui indique ce qui était attendu et ce qui a été reçu. Un message comme "Erreur" n'aide pas à trouver le problème.
  • Conservez la cause en la passant au constructeur : throw new TraitementException("import du bassin", e), avec le e.
  • N'utilisez pas les exceptions pour contrôler le déroulement du programme : sortir d'une boucle par un throw, par exemple, est un mauvais usage.
Question d'épreuve

Quelle est la différence entre une exception vérifiée et une non vérifiée ?

Une exception vérifiée doit être déclarée ou traitée, sans quoi le compilateur refuse le code ; elle signale un événement extérieur prévisible. Une exception non vérifiée (RuntimeException et ses sous-classes) n'impose rien et signale une erreur de programmation. La catégorie dépend de la classe parente.