Essayer de coder ← Retour aux sujets

try / catch / finally

exceptions

Une exception est un événement qui interrompt le déroulement normal du programme. On l'attrape avec try / catch pour réagir, au lieu de laisser tout s'arrêter.

try { … } catch (Exception e) { … }
Exécute le bloc ; en cas d'exception, saute au catch
catch (NumberFormatException e)
Attrape seulement ce type d'exception
finally { … }
Toujours exécuté, exception ou non (nettoyage)
throw new IllegalArgumentException("msg");
Lève une exception
e.getMessage()
Le message de l'exception
void lire() throws IOException
La méthode peut lever cette exception vérifiée
Conversion.java
public class Conversion {
    public static void main(String[] args) {
        String saisie = "abc";

        try {
            int n = Integer.parseInt(saisie);
            System.out.println("n = " + n);
        } catch (NumberFormatException e) {
            System.out.println("Ce n'est pas un entier : " + saisie);
        } finally {
            System.out.println("fin du test");
        }
    }
}
Affichage
Ce n'est pas un entier : abc
fin du test

Lever ses propres exceptions

throw

Une méthode qui détecte une situation anormale lève une exception avec throw. On peut aussi créer ses propres types d'exceptions, en héritant de Exception ou de RuntimeException.

throw new IllegalArgumentException("age negatif");
Signale un argument invalide
class SoldeInsuffisant extends Exception { … }
Exception vérifiée personnalisée
class MonErreur extends RuntimeException { … }
Exception non vérifiée
try (Scanner sc = new Scanner(…)) { … }
try-with-resources : ferme la ressource automatiquement
Age.java
public class Age {
    static void verifier(int age) {
        if (age < 0) {
            throw new IllegalArgumentException("age negatif : " + age);
        }
        System.out.println("age valide : " + age);
    }

    public static void main(String[] args) {
        verifier(17);
        try {
            verifier(-5);
        } catch (IllegalArgumentException e) {
            System.out.println("Erreur : " + e.getMessage());
        }
    }
}
Affichage
age valide : 17
Erreur : age negatif : -5

⚠️ Le cours : gérer les erreurs

Quand quelque chose se passe mal (fichier introuvable, division par zéro, texte non numérique…), Java lève une exception. Si personne ne l'attrape, le programme s'arrête et affiche la trace de la pile (*stack trace*). Gérer les exceptions, c'est décider quoi faire à la place : redemander la saisie, utiliser une valeur par défaut, prévenir l'utilisateur.

Lire un message d'erreur

Exception in thread "main" java.lang.ArithmeticException: / by zero
        at Division.main(Division.java:5)
  • Le type (ArithmeticException) dit la nature du problème.
  • Le message (/ by zero) précise la cause.
  • La ligne (Division.java:5) dit où ça s'est produit : va-y d'abord.

try, catch et le programme qui continue

Le code à risque est placé dans try. Dès qu'une exception est levée, le reste du bloc est abandonné et l'exécution reprend dans le catch correspondant, puis après le bloc.

Division.java
public class Division {
    static int diviser(int a, int b) {
        return a / b;
    }

    public static void main(String[] args) {
        try {
            System.out.println(diviser(10, 2));
            System.out.println(diviser(1, 0));
            System.out.println("jamais affiche");
        } catch (ArithmeticException e) {
            System.out.println("division par zero : " + e.getMessage());
        }
        System.out.println("le programme continue");
    }
}
Affichage
5
division par zero : / by zero
le programme continue

La famille des exceptions

Throwable
├── Error                    problèmes graves de la JVM (OutOfMemoryError…) : on ne les attrape pas
└── Exception
    ├── RuntimeException     NON vérifiées : bugs de programmation
    │     (NullPointerException, ArrayIndexOutOfBoundsException,
    │      ArithmeticException, IllegalArgumentException, NumberFormatException)
    └── IOException, …       VÉRIFIÉES : à déclarer (throws) ou à attraper
ExceptionCause typique
NumberFormatExceptionInteger.parseInt("abc")
ArithmeticExceptiondivision entière par zéro
ArrayIndexOutOfBoundsExceptionindice hors du tableau
NullPointerExceptionappel d'une méthode sur une référence null
InputMismatchExceptionnextInt() alors que l'utilisateur tape du texte
FileNotFoundExceptionfichier introuvable (vérifiée)

Plusieurs catch et finally

On peut enchaîner plusieurs catch pour réagir différemment selon le type. Le premier qui correspond est choisi : place donc les types les plus précis en premier. Le bloc finally s'exécute dans tous les cas, même après un return : c'est l'endroit pour fermer un fichier ou une connexion. Ici, une valeur invalide est ignorée sans arrêter le calcul.

Plusieurs.java
public class Plusieurs {
    public static void main(String[] args) {
        String[] valeurs = {"12", "x", "7"};
        int somme = 0;

        for (String texte : valeurs) {
            try {
                somme += Integer.parseInt(texte);
            } catch (NumberFormatException e) {
                System.out.println("ignore : " + texte);
            }
        }
        System.out.println("somme = " + somme);
    }
}
Affichage
ignore : x
somme = 19

Pour traiter plusieurs types de la même façon : catch (NumberFormatException | ArrayIndexOutOfBoundsException e) { … }.

Lever une exception : throw

Une méthode qui reçoit un argument inacceptable ne doit pas continuer avec une valeur absurde : elle lève une exception, avec un message qui explique le problème (voir l'exemple de la deuxième carte). L'appelant décide ensuite de l'attraper ou non.

Exceptions vérifiées et personnalisées

Une exception vérifiée (qui hérite de Exception sans être une RuntimeException) oblige le compilateur à contrôler que tu la traites : soit par un catch, soit en la déclarant avec throws dans la signature de la méthode. On crée ses propres exceptions pour exprimer des erreurs du domaine (ici, un solde insuffisant).

RetraitBanque.java
class SoldeInsuffisant extends Exception {
    SoldeInsuffisant(String message) {
        super(message);
    }
}

class Compte {
    private double solde;

    Compte(double solde) {
        this.solde = solde;
    }

    void retirer(double montant) throws SoldeInsuffisant {
        if (montant > solde) {
            throw new SoldeInsuffisant("manque " + (montant - solde) + " euros");
        }
        solde -= montant;
    }
}

public class RetraitBanque {
    public static void main(String[] args) {
        Compte c = new Compte(50);

        try {
            c.retirer(20);
            c.retirer(100);
        } catch (SoldeInsuffisant e) {
            System.out.println("refuse : " + e.getMessage());
        }
    }
}
Affichage
refuse : manque 70.0 euros

try-with-resources : fermer automatiquement

Une ressource (fichier, Scanner, connexion) doit être fermée même en cas d'erreur. Déclarée entre les parenthèses du try, elle est fermée automatiquement à la sortie du bloc, sans finally.

Ressource.java
import java.util.Scanner;

public class Ressource {
    public static void main(String[] args) {
        try (Scanner sc = new Scanner("12 7 5")) {
            int somme = 0;
            while (sc.hasNextInt()) {
                somme += sc.nextInt();
            }
            System.out.println("somme = " + somme);
        }                          // sc est fermé automatiquement
    }
}
Affichage
somme = 24

Bonnes pratiques

  • Attrape le type d'exception le plus précis possible, pas systématiquement Exception.
  • N'écris jamais un catch vide : au minimum, affiche le message ou journalise-le.
  • Préfère prévenir l'erreur quand c'est simple (if (b != 0), vérifier une saisie) ; réserve les exceptions aux situations vraiment exceptionnelles.
  • Donne un message utile quand tu lèves une exception : la valeur fautive, ce qui était attendu.

⚠️ Erreurs fréquentes

Un catch vide qui avale l'erreur

À éviter
Faux.java
try {
    traiter();
} catch (Exception e) {
    // on ignore : le bug devient invisible
}
Correct
Correct.java
try {
    traiter();
} catch (Exception e) {
    System.out.println("Erreur : " + e.getMessage());
}

Mettre la classe parente avant la classe fille

Dans catch (Exception e) suivi de catch (NumberFormatException e), le second ne serait jamais atteint : le compilateur le refuse (« exception has already been caught »). Écris toujours du plus précis au plus général.

Oublier de déclarer ou d'attraper une exception vérifiée

Appeler une méthode qui déclare throws SoldeInsuffisant sans try / catch ni throws donne l'erreur « unreported exception SoldeInsuffisant; must be caught or declared to be thrown ».

Attraper une NullPointerException au lieu de la corriger

Une NullPointerException indique qu'une variable vaut null alors qu'elle ne devrait pas. Cherche pourquoi (objet jamais créé, valeur de retour null non testée) plutôt que de l'étouffer dans un catch. La trace de la pile donne la ligne exacte.

Utiliser les exceptions comme un if

Lever et attraper une exception coûte cher et rend le code difficile à suivre. if (indice >= 0 && indice < t.length) est plus clair et plus rapide qu'un catch (ArrayIndexOutOfBoundsException e).

Un return dans finally

Un return placé dans un bloc finally écrase silencieusement le résultat du try et même une exception en cours. Évite-le.

📋 Aide-mémoire

  • try { … } catch (Type e) { … } finally { … } : le finally s'exécute toujours.
  • Le premier catch qui correspond gagne : du plus précis au plus général ; A | B pour grouper.
  • throw new Type("message") lève ; throws Type dans la signature déclare une exception vérifiée.
  • RuntimeException et ses filles : non vérifiées (bugs) ; Exception seule : vérifiées (à traiter).
  • try (Ressource r = …) { … } ferme r tout seul.
  • Lire la trace : type, message, ligne ; jamais de catch vide.