ICS3U

Accueil > Programmer avec Java >

Gérer les exceptions avec try et catch

Survol et attentes

Définitions
Entrée invalide
une entrée qui ne correspond pas à une gamme de valeurs acceptables.
Exception
un événement imprévu qui cause le plantage d’un programme s’il n’est pas géré. Java inclut une classe Exception qui permet de gérer ces événements en fournissant des informations sur l’erreur qui s’est produite.
try et catch
blocs de code utilisés pour gérer les exceptions en Java. Le bloc try contient le code qui pourrait générer une exception, et le bloc catch contient le code qui gère l’exception si elle se produit.
throws
un mot-clé utilisé dans la signature d’une méthode pour indiquer qu’elle utilise du code qui peut générer une exception et ne le gère pas. La méthode passe ainsi la responsabilité de gérer l’exception à la méthode appelante.
Responsabilité unique
principe de conception de logiciel qui stipule qu’une classe ou une méthode devrait être responsable d’une seule tâche.

Objectifs d’apprentissage

À la fin de cette leçon vous devrez être en mesure de :

Critères de succès

Responsabilité unique

En génie logicielle, il y a un principe qui s’appelle la responsabilité unique. Ce principe nous dit que chaque méthode doit être responsable d’une seule chose. C’est une des raisons pourquoi nous brisons nos algorithmes en méthodes plus petites : cela limite la responsabilité de main à l’organisation globale des opérations et permet à chaque méthode d’avoir un objectif clair.

Si on utilise du code qui peut générer une exception, on a deux choix :

  • ajouter une déclaration throws Exception à la signature de la méthode pour passer la gestion de l’exception à la méthode appelante, ou
  • utiliser un bloc try et catch pour gérer l’exception dans la méthode directement.

La deuxième option est préférable selon le principe de responsabilité unique, car la méthode gère les problèmes potentiels avec le code qu’il utilise pour accomplir sa tâche, et les autres méthodes n’ont pas besoin de s’en soucier : elles peuvent se concentrer sur leur propre tâche.

Bloc try et catch pour des entrées invalides

Le bloc try contient le code qui peut lancer une exception, et le bloc catch contient le code à faire si une exception est lancée. Voici la structure de base d’un bloc try et catch :

1
2
3
4
5
try {
    // code qui pourrait lancer une exception
} catch (Exception e) {
    // code à exécuter si une exception est lancée
}

Voici un exemple de code1 qui lance une exception si l’utilisateur entre un texte qui ne peut pas être converti en un nombre entier. Dans cet exemple, on ne laisse pas le programme planter, mais on demande à l’utilisateur de réessayer :

Fichier: ExceptionExample.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.util.*;

Scanner input = new Scanner(System.in);

int getInt(String prompt) {
    // tentatives infinies jusqu'à ce que tout dans le try fonctionne
    while (true) { 
        try {
            System.out.print(prompt);
            int number = input.nextInt(); // peut planter
            return number; // quitte la méthode si tout va bien
        } catch (Exception e) { // si ça plante
            System.out.println("   Entrée invalide");
            input.next(); // vide l'entrée invalide du Scanner
        }
    }
}

void main() {
    int age = getInt("Entrez votre âge : "); // pas besoin de savoir que ça peut planter
    System.out.println("Vous avez soumis un âge de : " + age);
}

Notez que dans main on veut simplement obtenir un nombre entier et l’utiliser. Cela est possible parce que la méthode getInt gère les exceptions qui pourraient survenir lors de la conversion de l’entrée utilisateur en un nombre entier. Ainsi, chaque méthode s’occupe de sa propre responsabilité et n’a pas besoin de connaître les détails internes des autres méthodes. C’est un bon exemple de la responsabilité unique.

Voici une autre version de main 1 pour ce même problème, mais dans ce cas-ci, main gère une autre sorte d’entrée invalide : une entrée qui n’est pas un âge valide. Ce type d’erreur ne cause pas d’exception, mais pourrait causer des erreurs de logique dans le programme.

1
2
3
4
5
6
7
8
void main() {
    int age = getInt("Entrez votre âge : ");
    if (age < 0) { // gérer une valeur logiquement invalide
        System.out.println("Âge invalide");
    } else {
        System.out.println("Vous avez soumis un âge de : " + age);
    }
}

Bloc try et catch pour des ressources comme des fichiers

Nous étudions les fichiers en plus de détail dans la dernière section de cette unité

Si le programme génère une exception lors de l’utilisation d’une ressource comme un fichier, il est important de fermer la ressource avant de quitter le programme. Pour cela, on utilise une déclaration try avec une ressource. Dans cette version de la structure, la ressource est déclarée dans les parenthèses après le mot-clé try. La ressource est automatiquement fermée par Java à la fin du bloc try, même si une exception est lancée.

Voici la structure d’une déclaration try-catch quand le plantage est lié à la ressource utilisée :

1
2
3
4
5
try ( déclaration de la ressource qui peut planter ) {
    // code à exécuter avec la ressource si ça ne plante pas
} catch (Exception e) {
    // code à exécuter si ça plante
}

Voici un exemple où la ressource qui peut planter est un FileWriter pour écrire dans un fichier.

1
2
3
4
5
6
7
void writeMessageToFile(String message) {
    try (FileWriter output = new FileWriter("./data/output.txt")) {
        output.write(message);
    } catch (Exception e) {
        System.out.println("Chemin de fichier invalide");
    }
}

Voici un deuxième exemple où la ressource qui peut planter est l’objet File qu’on passe au Scanner pour extraire son contenu.

1
2
3
4
5
6
7
8
9
10
String getFileContents(Locale decimalFormat) {
    try (Scanner fileReader = new Scanner(new File("./data/input.txt"))) {
        fileReader.useLocale(decimalFormat);
        fileReader.useDelimiter("\\Z"); // next() s'arrête au caractère de fin de fichier
        return fileReader.next();
    } catch (Exception e) {
        System.out.println("Fichier introuvable");
        return null; // quittez la méthode avec une référence `null` pour le String
    }
}

Notez que dans ces deux exemples, on doit déclarer et initialiser la ressource plantable entre des parenthèses après le mot-clé try.

Exceptions spécifiques

Pour ce cours, c’est tout à fait normal d’utiliser Exception - la plus générale de toutes les exceptions - comme type d’exception dans le bloc catch. Mais en situation de production logicielle, c’est important de déclarer l’exception spécifique qui peut survenir dans le bloc catch.

Par exemple :

  • Dans le cas d’entrée invalide, le type d’exception qui est lancée est InputMismatchException (avec les méthodes de Scanner nextInt et nextDouble) ou NumberFormatException (si on utilise plutôt Integer.parseInt(input.next()) ou Double.parseDouble(input.next())). Ce sont des exceptions pour les erreurs de conversion de chaînes de caractères en nombres.
  • Dans le cas de la lecture ou l’écriture de fichiers, l’exception est IOException.

Utiliser une exception spécifique permet de gérer les exceptions de manière plus précise. En fait, cette structure nous permet de déclarer un bloc catch pour chaque type d’exception qui peut survenir. Le premier bloc catch qui correspond à l’exception lancée est celui qui est exécuté, comme une cascade if-else if, alors c’est important de les mettre dans l’ordre du plus spécifique (p. ex. IOException) au plus général (Exception).

Exercices

📚 Tester la compréhension

aucun quiz de vérification des concepts ici encore

🛠️ Pratique

Travaillez dans le répertoire GitHub partagé par votre enseignant pour la pratique et les exercices

  1. Créez une copie de ExceptionExample.java et le tester.
  2. Ajoutez une méthode double getDouble(String prompt) qui fonctionne de la même manière que getInt, mais pour les nombres à virgule flottante et ajouter une utilisation de cette méthode dans main.
  3. Remplacez System.out.println("Entrée invalide"); dans les blocs catch avec e.printStackTrace(); pour voir le message d’erreur complet sans toutefois faire planter le programme. Notez le type d’exception qui est lancée (celui qui s’affiche en premier dans le message d’erreur).
  1. Exemple applicant les classes implicites et le main d’instance de JEP 463 (pleinement intégré aux outils Java22+)  2