Aller au contenu principal

Gestion des exceptions

Notions théoriques

Qu'est-ce qu'une exception ?

Une exception est un événement anormal qui se produit pendant l'exécution d'un programme et qui interrompt le flux normal. Par exemple : division par zéro, fichier introuvable, connexion réseau coupée, saisie invalide.

Sans gestion des exceptions, le programme s'arrête brutalement avec un message d'erreur peu compréhensible pour l'utilisateur. La gestion des exceptions permet de réagir proprement à ces situations.

Structure try / catch / finally

try {
// Code qui peut lever une exception
int resultat = 10 / 0;
} catch (ArithmeticException e) {
// Code exécuté si une ArithmeticException est levée
System.out.println("Erreur : " + e.getMessage());
} finally {
// Code TOUJOURS exécuté (avec ou sans exception)
System.out.println("Fin du traitement.");
}
  • try : le bloc de code "à risque"
  • catch : attrape une exception d'un type précis
  • finally : s'exécute toujours, même si une exception est levée (utile pour fermer des ressources)

Hiérarchie des exceptions

Throwable
├── Error ← erreurs graves de la JVM (OutOfMemoryError...) - ne pas attraper
└── Exception
├── RuntimeException ← exceptions NON vérifiées (non-checked)
│ ├── NullPointerException
│ ├── ArrayIndexOutOfBoundsException
│ ├── IllegalArgumentException
│ └── NumberFormatException
└── IOException ← exceptions VÉRIFIÉES (checked)
└── FileNotFoundException

Exceptions vérifiées vs non vérifiées

  • Exceptions vérifiées (checked) : héritent d'Exception (mais pas de RuntimeException). Le compilateur oblige à les gérer avec try/catch ou à les déclarer avec throws.
    • Exemples : IOException, SQLException
  • Exceptions non vérifiées (unchecked) : héritent de RuntimeException. Pas obligatoire de les attraper.
    • Exemples : NullPointerException, NumberFormatException, IllegalArgumentException

Déclarer une exception avec throws

Si une méthode peut lever une exception vérifiée, vous pouvez soit la gérer dans la méthode, soit la propager à l'appelant avec throws :

public static String lireFichier(String chemin) throws IOException {
return Files.readString(Path.of(chemin));
// L'appelant devra gérer l'IOException
}

Lever une exception avec throw

Vous pouvez lever une exception vous-même pour signaler une situation invalide :

public static void setAge(int age) {
if (age < 0 || age > 150) {
throw new IllegalArgumentException("L'âge doit être entre 0 et 150, reçu : " + age);
}
this.age = age;
}

Créer sa propre exception

Hérite d'Exception (vérifiée) ou de RuntimeException (non vérifiée) :

// Exception personnalisée vérifiée
public class NoteInvalideException extends Exception {
public NoteInvalideException(String message) {
super(message);
}
}

// Utilisation
public static void setNote(double note) throws NoteInvalideException {
if (note < 0 || note > 20) {
throw new NoteInvalideException("Note invalide : " + note + " (doit être entre 0 et 20)");
}
}
Héritage en Java

En Java, une classe ne peut hériter que d'une seule classe (extends). En revanche, elle peut implémenter plusieurs interfaces (implements I1, I2, ...). Ce choix évite les conflits d'implémentation qui surviendraient si deux classes parentes définissaient la même méthode différemment.

Attraper plusieurs types d'exceptions

try {
// code à risque
} catch (IOException e) {
System.out.println("Erreur fichier : " + e.getMessage());
} catch (NumberFormatException e) {
System.out.println("Nombre invalide : " + e.getMessage());
} catch (Exception e) {
// Attrape tout le reste (mettre en dernier !)
System.out.println("Erreur inattendue : " + e.getMessage());
}
Ne pas attraper Exception générique en premier

Mets toujours les types les plus précis avant les types généraux. catch (Exception e) attrape TOUT : si vous le mettez en premier, les blocs catch suivants ne seront jamais atteints.

Exemple pratique

public class GestionExceptions {

// Exception personnalisée
public static class AgeInvalideException extends Exception {
public AgeInvalideException(String message) {
super(message);
}
}

// Méthode qui valide l'âge
public static void validerAge(int age) throws AgeInvalideException {
if (age < 0) {
throw new AgeInvalideException("L'âge ne peut pas être négatif : " + age);
}
if (age > 120) {
throw new AgeInvalideException("L'âge semble irréaliste : " + age);
}
}

// Méthode qui parse et valide
public static int lireAge(String texte) throws AgeInvalideException {
try {
int age = Integer.parseInt(texte);
validerAge(age);
return age;
} catch (NumberFormatException e) {
throw new AgeInvalideException("'" + texte + "' n'est pas un nombre valide");
}
}

public static void main(String[] args) {
String[] tests = {"25", "-5", "abc", "200"};
for (String test : tests) {
try {
int age = lireAge(test);
System.out.println("Âge valide : " + age);
} catch (AgeInvalideException e) {
System.out.println("Erreur : " + e.getMessage());
}
}
}
}
astuce

e.getMessage() retourne le message passé au constructeur de l'exception. e.getClass().getSimpleName() retourne le nom de la classe d'exception. Les deux sont utiles pour des messages d'erreur clairs.

Test de mémorisation/compréhension


Quel bloc de code est TOUJOURS exécuté, qu'il y ait une exception ou non ?


Quelle est la différence entre une exception vérifiée (checked) et non vérifiée (unchecked) ?


Quel mot-clé lève une exception dans le code ?


Pour créer une exception personnalisée non vérifiée (unchecked), quelle classe faut-il hériter ?


Quelle exception Java est levée quand on appelle une méthode sur un objet null ?


Si plusieurs catch sont présents, dans quel ordre faut-il les placer ?


TP pour réfléchir et résoudre des problèmes

Vous allez créer un système de validation des inscriptions d'élèves avec des exceptions personnalisées.

Étape 1 — Créer l'exception personnalisée

Crée une exception InscriptionException vérifiée avec un message d'erreur.


Bonne pratique - Nommer les exceptions avec le suffixe Exception

Par convention, toutes les classes d'exception se terminent par Exception : InscriptionException, NoteInvalideException, ConnexionException. Cela les identifie immédiatement à la lecture du code.

Étape 2 — Méthode de validation avec throw

Crée une méthode qui valide un nom d'élève et lève InscriptionException si le nom est vide.


Bonne pratique - Messages d'erreur explicites

Le message passé à l'exception doit être suffisamment explicite pour permettre de comprendre et corriger le problème sans chercher dans le code. Inclus la valeur invalide dans le message quand c'est pertinent : "Note invalide : " + note + " (doit être entre 0 et 20)".

Étape 3 — Gérer les exceptions dans main

Dans main, appelle validerNom dans un bloc try/catch et affiche un message d'erreur si la validation échoue.


Bonne pratique - Ne jamais laisser un catch vide

Un bloc catch vide {} ou qui ne fait que e.printStackTrace() masque les problèmes. Affiche toujours au minimum le message de l'exception, et dans une application réelle, log-le (voir séance suivante sur SLF4J).

📌 Une solution