Aller au contenu principal

Exceptions en Java

En Java, les exceptions sont un mécanisme puissant pour gérer les erreurs qui peuvent survenir lors de l'exécution d'un programme. Elles permettent de séparer le code de gestion des erreurs du code principal, ce qui rend le code plus propre et plus facile à maintenir.

Déclaration des exceptions

La documentation Java est une ressource essentielle pour les développeurs. Elle fournit des informations détaillées sur les classes, les interfaces et les méthodes de l'API Java, y compris les exceptions qu'elles peuvent lever.

Pour savoir si une méthode peut lever une exception, il faut consulter sa déclaration. Le mot-clé throws indique qu'une méthode peut potentiellement lever une ou plusieurs exceptions. Comment déterminer si une méthode a le mot-clé throws? En validant ses informations sur le site d'Oracle:

https://docs.oracle.com/en/java/javase/23/docs/api/java.base/java/io/FileWriter.html

Par exemple, soit le constructeur (concept que l'on reviendra plus tard dans le cours), on note à la fin le mot-clé throws. Ceci indique le type d'exception que peut lever FileWriter(String) et quand elle va le faire.

throw vs throws

  • throw : déclenche une exception à l'exécution (dans le code). L'exception est envoyée immédiatement et le code après le throw ne s'exécutera pas.
  • throws : déclare qu'une méthode peut lever une exception (dans la signature).

Exemple avec throw

public static void validerAge(int age) {
if (age < 0) {
throw new IllegalArgumentException("L'âge ne peut pas être négatif");
// Cette ligne ne sera JAMAIS exécutée si l'exception est lancée
}
System.out.println("Âge valide: " + age);
}

Exemple avec throws

Quand une méthode déclare throws, elle ne gère pas l'exception elle-même. Elle indique que l'exception peut se produire et que la méthode appelante doit la gérer avec un try-catch.

// Cette méthode déclare qu'elle peut lever une IllegalArgumentException
public static void validerAge(int age) throws IllegalArgumentException {
if (age < 0 || age > 150) {
throw new IllegalArgumentException("L'âge doit être entre 0 et 150");
}
System.out.println("Âge valide: " + age);
}

// La méthode appelante DOIT gérer l'exception
public static void main(String[] args) {
try {
validerAge(200); // On DOIT utiliser try-catch ici
} catch (IllegalArgumentException e) {
System.err.println("Erreur: " + e.getMessage());
}
}

Autre exemple combiné

// Cette méthode peut lever une exception (throws)
// et la lance elle-même avec throw
public static void traiterDonnees(String donnees) throws IllegalArgumentException {
if (donnees == null || donnees.isEmpty()) {
throw new IllegalArgumentException("Les données ne peuvent pas être vides");
}
// Traitement des données...
}

// La méthode appelante doit gérer l'exception
public static void main(String[] args) {
try {
traiterDonnees(""); // Cette méthode peut lever une exception
} catch (IllegalArgumentException e) {
System.err.println("Erreur: " + e.getMessage());
}
}

Bloc try-catch-finally

Pour gérer les exceptions, on utilise le bloc try-catch. Le code susceptible de lever une exception est placé dans un bloc try, et le code qui gère l'exception est placé dans le bloc catch. Le bloc finally (optionnel) s'exécute toujours, qu'il y ait exception ou non.

FileWriter writer = null;
try {
writer = new FileWriter("monfichier.txt");
writer.write("Bonjour le monde !");
} catch (IOException e) {
System.err.println("Erreur lors de l'écriture du fichier : " + e.getMessage());
} finally {
// S'exécute toujours, qu'il y ait exception ou non
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
System.err.println("Erreur lors de la fermeture du fichier : " + e.getMessage());
}
}
}

Alternative moderne : Try-with-resources

Java propose une syntaxe plus propre qui ferme automatiquement les ressources (close() automatique) :

try (FileWriter writer = new FileWriter("monfichier.txt")) {
writer.write("Bonjour le monde !");
} catch (IOException e) {
System.err.println("Erreur lors de l'écriture du fichier : " + e.getMessage());
}
// FileWriter est fermé automatiquement

Cette approche est préférable car elle garantit que la ressource est fermée même en cas d'exception, sans avoir besoin de finally.

Comment gérer plusieurs exceptions?

Soit l'objet suivant:

Quelle exception peut-il créer?

https://docs.oracle.com/en/java/javase/23/docs/api/java.base/java/util/Scanner.html#nextInt()

Trois exceptions sont possibles:

  • InputMismatchException - if the next token does not match the Integer regular expression, or is out of range
  • NoSuchElementException - if input is exhausted
  • IllegalStateException - if this scanner is closed
try {
// Faire des trucs dangereux, par exemple:

Scanner inputFromCommandLine = new Scanner(System.in);
int age = inputFromCommandLine.nextInt();
System.out.println("Bonjour");
}
catch (InputMismatchException e) {
System.out.println("Erreur: veuillez entrer un nombre");
}
catch (NoSuchElementException e) {
System.out.println("Erreur: entrée manquante");
}
catch (IllegalStateException e) {
System.out.println("Erreur: scanner fermé");
}
catch (Exception e) {
// Bloc de secours très large, à utiliser avec parcimonie
System.out.println("Erreur inconnue");
}

Pourquoi "dangereux"? Ce code est considéré dangereux car il appelle nextInt() sans validation préalable. Si l'utilisateur entre autre chose qu'un nombre (ex: "abc"), le programme lève une exception. Il aurait été préférable de vérifier d'abord avec hasNextInt() pour éviter ces situations d'exception.

Alternative : Multi-catch - On peut aussi regrouper plusieurs exceptions dans un même bloc catch avec l'opérateur | :

try {
Scanner input = new Scanner(System.in);
int age = input.nextInt();
System.out.println("Age saisi: " + age);
}
catch (InputMismatchException | NoSuchElementException e) {
System.out.println("Entrée invalide ou manquante");
}
catch (IllegalStateException e) {
System.out.println("Scanner fermé");
}

Un if plutôt qu'une exception

Avant de lire une entrée avec Scanner.nextInt(), il est TOUJOURS préférable de vérifier si l'entrée est un entier à l'aide de la méthode hasNextInt(). Cela permet d'éviter de lever une InputMismatchException.

Scanner input = new Scanner(System.in);

// Mauvais : lève facilement InputMismatchException
// int age = input.nextInt();

// Bon : on vérifie avant
if (input.hasNextInt()) {
int age = input.nextInt();
System.out.println("Age saisi: " + age);
} else {
System.out.println("Veuillez entrer un entier.");
}

Règles des exceptions

  • Un bloc try doit être suivi d'au moins un bloc catch ou un bloc finally.
  • Il ne peut pas y avoir de code entre un bloc try et un bloc catch.
  • Les exceptions ne doivent pas être utilisées pour le traitement normal du programme. Elles doivent être réservées aux situations exceptionnelles.

Comment générer une exception?

Pour créer et lancer votre propre exception dans votre code, utilisez le mot-clé throw suivi d'une instance d'exception.

Exemple :

public static void uneMethode(int a, int b) {
if (a == b) {
throw new IllegalArgumentException("a et b sont égaux");
}
}

public static void main(String[] args) {
try {
uneMethode(3, 3);
} catch (IllegalArgumentException e) {
System.out.println("On doit bien gérer l'exception qqpart");
}
}

Enchaînement d'exceptions

L'enchaînement d'exceptions permet d'ajouter du contexte à une exception tout en préservant l'erreur originale. C'est utile quand on veut transformer une exception technique en un message plus spécifique au domaine de l'application.

public static void chargerProfilUtilisateur(String nomUtilisateur) throws Exception {
try {
FileReader fichier = new FileReader(nomUtilisateur + ".txt");
// ... lecture du profil
} catch (FileNotFoundException e) {
// Transformer l'exception technique en message métier avec la cause originale
throw new Exception("Impossible de charger le profil pour: " + nomUtilisateur, e);
}
}

public static void main(String[] args) {
try {
chargerProfilUtilisateur("john_doe");
} catch (Exception e) {
System.out.println("Erreur: " + e.getMessage());
System.out.println("Cause originale: " + e.getCause());
// Affiche la trace complète de l'erreur
e.printStackTrace();
}
}

Avantages :

  • Le message d'erreur est plus clair pour l'utilisateur ("Impossible de charger le profil pour john_doe")
  • L'erreur technique originale est préservée (getCause() permet d'y accéder)
  • La pile d'exécution complète est disponible pour le débogage

Types d'exception

Il y a deux types d'exception:

TypeExemplesQuand?Obligation
CheckedIOException, SQLExceptionErreurs liées à l'IO, au réseau, aux ressources externesDoit être catch ou déclaré throws
Unchecked (Runtime)NullPointerException, IllegalArgumentException, IndexOutOfBoundsExceptionBugs de logique, mauvais arguments, états invalidesPas d'obligation de catch; corriger la logique

Checked Exception

Les exceptions checked (vérifiées) doivent être gérées obligatoirement. Le compilateur refuse de compiler votre code si vous ne les gérez pas avec un try-catch ou si vous ne les déclarez pas avec throws.

Exemple qui ne compile pas :

import java.io.File;
import java.io.FileReader;
import java.io.IOException;

public class ExempleChecked {
public static void lireFichier(String nomFichier) {
// ERREUR DE COMPILATION : IOException non gérée
FileReader fr = new FileReader(nomFichier); // Erreur ici!
}
}

Message d'erreur du compilateur :

java: unreported exception java.io.FileNotFoundException; must be caught or declared to be thrown

Solution 1 : Gérer avec try-catch dans la méthode

// La méthode gère elle-même l'exception
public static void lireFichier(String nomFichier) {
try {
File file = new File(nomFichier);
FileReader fr = new FileReader(file);
// ... utiliser le fichier
fr.close();
} catch (IOException e) {
System.err.println("Erreur de lecture: " + e.getMessage());
}
}

public static void main(String[] args) {
lireFichier("donnees.txt"); // Pas besoin de try-catch ici
}

Solution 2 : Propager avec throws

// La méthode déclare throws et ne gère pas l'exception
public static void lireFichier(String nomFichier) throws IOException {
File file = new File(nomFichier);
FileReader fr = new FileReader(file);
// ... utiliser le fichier
fr.close();
}

// Le main doit alors gérer l'exception
public static void main(String[] args) {
try {
lireFichier("donnees.txt");
} catch (IOException e) {
System.err.println("Erreur de lecture: " + e.getMessage());
}
}

Unchecked Exception (Runtime)

Les exceptions unchecked (non vérifiées) sont des exceptions qui se produisent à l'exécution et qui indiquent généralement un bug de programmation. Elles n'ont pas besoin d'être déclarées ou catchées.

Ces exceptions signalent que votre code a un problème de logique qui doit être corrigé, pas géré avec try-catch.

Exemple : IndexOutOfBoundsException

public static void main(String[] args) {
int[] nombres = new int[5]; // Array de taille 5 (indices 0 à 4)
nombres[5] = 10; // ERREUR : indice 5 n'existe pas!
}

Sortie :

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 5

Mauvaise pratique : catcher l'exception

// MAUVAIS : on cache le bug au lieu de le corriger
try {
int[] nombres = new int[5];
nombres[5] = 10;
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Oups!");
}

Bonne pratique : corriger la logique

// BON : corriger le bug
int[] nombres = new int[5];
nombres[4] = 10; // Utiliser un indice valide (0 à 4)

// Ou mieux : valider avant d'accéder
int index = 5;
if (index >= 0 && index < nombres.length) {
nombres[index] = 10;
} else {
System.out.println("Index invalide");
}

Autres exemples d'Unchecked Exceptions :

  • NullPointerException : vous essayez d'utiliser un objet qui est null
  • IllegalArgumentException : vous passez un argument invalide à une méthode
  • ArithmeticException : division par zéro

Règle générale : Si c'est une Unchecked Exception, c'est probablement un bug dans votre code - corrigez-le plutôt que de le catcher.

Attendre après le cours 6.

Création de ses propres Exception

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

Et pour la faire apparaître:

if (condition) {
throw new MonExceptionPersonnalisee("Erreur spécifique à mon application.");
}