• 10 heures
  • Facile

Ce cours est visible gratuitement en ligne.

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 29/08/2024

Gérez les exceptions

L’équipe se renforce avec l’arrivée de Juan. C’est un testeur, son objectif est de vérifier que le code implémenté correspond bien à ce qui est attendu.

Mauvaise nouvelle, après quelques tests Juan s’est rendu compte que la contrainte de la valeur minimale pour la hauteur, la largeur et la longueur ne sont pas prises en compte. On peut par exemple créer un bloc avec une longueur à -1, ce qui n’a pas de sens !

Vous allez devoir régler ce problème et comme d’habitude Marc vous donne le bon conseil : “Il faudrait utiliser le mécanisme des exceptions en Java.”
Vous obtiendrez le résultat suivant :

Diagramme montrant des classes, une interface et une énumération avec leurs relations
Modélisation du résultat à obtenir à l'issue du chapitre

Découvrez les exceptions

Java possède un mécanisme pour gérer les erreurs. Une erreur est un problème que le code rencontre, c’est que nous appelons une exception.

Par exemple, vous déclarez une variable typée par la classe Bloc mais vous ne l’initialisez pas. Puis vous appelez le getter de l’attribut longueur. Que se passe-t-il ?

Bien qu’il n’y ait aucune faute de syntaxe empêchant la compilation, à l’exécution ce code ne va pas fonctionner.

En effet, il lui sera impossible d’exécuter une méthode sur une variable qui vaut null.

Ainsi s’il est vrai que l’étape de compilation nous protège de nombreux problèmes, c’est bien le mécanisme de gestion des exceptions qui va rendre stable notre application à l’exécution. L’objectif est d’anticiper ces potentiels problèmes et de prévoir un traitement adapté. L’expérience utilisateur sera ainsi préservée. 

Et comment ça se passe du coup ?

Rappelons que Java est un langage objet. Du coup, les exceptions seront aussi des objets qui représentent donc les erreurs. Il existe une classe générique pour représenter une erreur qui est tout simplement java.lang.Exception

Cette classe pourra être héritée pour créer des exceptions particulières. Sachez qu’il en existe déjà de nombreuses ! Si on reprend la situation précédente, nous aurions obtenu une java.lang.NullPointerException

Gérez une exception

Savoir que les exceptions existent et qu’elles sont des objets est un bon point de départ. Dans la démonstration suivante, je vous montre comment les gérer :

Que pouvez-vous retenir ?

  • Le code qui peut générer une erreur (et donc une exception) doit être imbriqué dans un bloc try { }

  • Le code à exécuter si jamais l’exception est rencontrée doit être écrit dans un bloc catch(...) { }

  • Lorsqu’une exception est déclenchée, l’exécution des instructions du bloc try s’arrêtent et le code du bloc catch est exécuté. 

Le code vu ensemble est le suivant, à noter que la classe Kit a été altérée pour que nous soyons en mesure de simuler un problème :

Kit.java

package ej;

import java.util.LinkedHashSet;
import java.util.Set;

public class Kit {

    private Set<String> motsCles;
    private Set<IBloc> blocs = new LinkedHashSet<Bloc>();

    public Kit() {
        blocs.add(new Mur(3, 2, 2, true));
        blocs.add(new Mur(3, 2, 2, true));
        blocs.add(new Mur(2, 1, 2, false));
        blocs.add(new Mur(2, 1, 2, false));
        blocs.add(new Porte(1, 2, 2, true));
    }

    public void afficherKit() {
        System.out.println("Nombre de bloc dans le kit : " + this.blocs.size());
        System.out.print("Liste des mots clés du kit : ");
        
        for(String motCle : motsCles) {
            System.out.print(motCle);
        }
    }
    
    public Set<String> getMotsCles() {
        return motsCles;
    }
    
    public Set<IBloc> getBlocs() {
        return blocs;
    }
}

Main.java

package ej;

public class Main {

    public static void main(String[] args) {
        Kit kitDeDemarrage = new Kit();
        try {
            int resultat = kitDeDemarrage.getMotsCles().size();
            System.out.println("Nombre de mots clés : " + resultat);
        } catch (NullPointerException exception) {
            System.out.println("Liste de mots clés indisponible");
        }
    }
}

Ce code affichera donc dans la console “Liste de mots-clés indisponible” car dans la classe Kit la liste n’a pas été initialisée. L’appel de la méthode size se fait donc sur une valeur NULL et nous obtenons une NullPointerException.

A noter que sans le bloc try { } catch() { } le message suivant aurait été affiché dans la console :

Exception in thread "main" java.lang.NullPointerException: Cannot invoke "java.util.Set.size()" because the return value of "ej.Kit.getMotsCles()" is null

at ej.Main.main(Main.java:8)

L’application se serait arrêtée sur cette erreur. Cela aurait été beaucoup moins sympa pour l’utilisateur !

Java ne se limite pas à nous laisser gérer une exception, nous pouvons également les déclencher !

Levez une exception

Un développeur Java va se servir du mécanisme des exceptions pour gérer les erreurs associées à la logique de son application. Il s’agira dans ce cas de comportement possible et anticipé, qu’il faudra gérer pour que l’application reste stable. Reprenons le problème détecté par Juan concernant les valeurs minimales non respectées.

Reprenons le code de la classe Bloc :

Bloc.java

package ej;

public abstract class Bloc implements IBloc {

        protected int longueur;
        protected int largeur;
        protected int hauteur;

        Bloc(final int longueur, final int largeur, final int hauteur) {
            this.longueur = longueur;
            this.largeur = largeur;
            this.hauteur = hauteur;
        }
        
}

La classe Bloc gère une longueur, une largeur et une hauteur mais en effet aucun contrôle n’est fait concernant la valeur minimale.

Par contre ces dernières sont bien définies dans l’interface IBloc  :

IBloc.java

package ej;

public interface IBloc {

    int MIN_LONGUEUR = 1;
    int MIN_LARGEUR = 1;
    int MIN_HAUTEUR = 1;
    
    public void afficherDescription();
    
}

Comment faire pour empêcher la création d’un bloc si l’une des trois caractéristiques ne respecte pas la valeur minimale ?

Une idée simple serait de mettre une structure conditionnelle dans le constructeur, par exemple :

Bloc(final int longueur, final int largeur, final int hauteur) {
    if(longueur < MIN_LONGUEUR || largeur < MIN_LARGEUR || hauteur < MIN_HAUTEUR) {
        return;
    }
    this.longueur = longueur;
    this.largeur = largeur;
    this.hauteur = hauteur;
}

Certes nous avons empêché ces valeurs d’être assignées dans les attributs mais nous ne pouvons nous satisfaire de cette solution car :

  1. Le code qui va instancier un Bloc ne se rendra pas compte du problème.

  2. La valeur de longueur, largeur et hauteur sera celle par défaut pour une variable de type int en Java c’est-à-dire 0, ce qui n’est donc pas valide.

Je vous propose d’utiliser le mécanisme des exceptions pour implémenter une solution adaptée à notre problématique :

Bloc(final int longueur, final int largeur, final int hauteur) {
    if(longueur < MIN_LONGUEUR || largeur < MIN_LARGEUR || hauteur < MIN_HAUTEUR) {
        throw new IllegalArgumentException();
    }
    
    this.longueur = longueur;
    this.largeur = largeur;
    this.hauteur = hauteur;
}

Le mot-clé throw suivi de l’instanciation de la classe java.lang.IllegalArgumentException permet de lever l’exception. 

On peut donc imaginer le code suivant désormais :

Main.java

package ej;

public class Main {
    
    public static void main(String[] args) {
        try {
            Bloc b = new Mur(1, 1, 1, false);
            b.afficherDescription();
        } catch (IllegalArgumentException e) {
            System.out.println("Valeurs pour construire le bloc invalides");
        }
    }
    
}

Nous avons donc désormais un programme qui a sa propre gestion des erreurs.

Mais ce n’est pas tout, vous pouvez créer votre exception, par exemple :

IllegalBlocException.java

public class IllegalBlocException extends Exception {
}

Et le code sera alors :

Bloc.java

Bloc(final int longueur, final int largeur, final int hauteur) throws IllegalBlocException {
    if(longueur < MIN_LONGUEUR || largeur < MIN_LARGEUR || hauteur < MIN_HAUTEUR) {
        throw new IllegalBlocException();
    }
    
    this.longueur = longueur;
    this.largeur = largeur;
    this.hauteur = hauteur;
}

Main.java

public static void main(String[] args) {
    try {
        Bloc b = new Mur(0, 1, 1, false);
        b.afficherDescription();
    } catch (IllegalBlocException e) {
        System.out.println("Valeurs pour construire le bloc invalides");
    }
}

Hey mais d’où ça sort ce throws IllegalBlocException dans la signature du constructeur de la classe Bloc ? 

Je vois que vous avez l'œil ! Les deux exceptions qu’on a utilisé jusqu’à alors ( java.lang.NullPointerException et java.lang.IllegalArgumentException) sont des classes filles java.lang.RuntimeException qui est elle-même une classe fille de java.lang.Exception.

RuntimeException et ses classes filles ont le droit à un traitement particulier en ce sens que nous n'avons pas besoin de spécifier dans la signature de la méthode qui lève l’exception que l’exception peut potentiellement être déclenchée.

Par contre, IllegalBlocException étend directement java.lang.Exception et ne profite donc pas de cette souplesse. Ainsi Java nous contraint à ajouter à la signature de la méthode l’information : throws IllegalBlocException . Cela signifie que le constructeur de Bloc redirige cette exception vers le code qui l’appelle.  

De plus, le code de la classe Main sera obligé :

  • soit de mettre un try catch autour de l’appel du constructeur.

  • soit de mettre un throws IllegalBlocException à la méthode main pour qu’elle redirige à son tour cette exception.

Avec ce qu’on vient de voir vous êtes en mesure de gérer mais aussi de lever des exceptions pour obtenir une application stable !

A vous de jouer

Contexte

Juan est désormais satisfait de la prise en compte des valeurs minimales pour la longueur, la largeur et la hauteur d’un bloc.

Une nouvelle tâche vous est assignée :

ID de la tâche : 8

Nom de la tâche : Action d’ouverture d’une porte

Description de la tâche : 

Nous souhaitons faire un premier essai pour l’implémentation d’actions associées aux blocs.

L’objectif est d’ajouter une action qui permet de verrouiller une porte. Le comportement est le suivant, si la porte n’est pas verrouillée alors cette action verrouille la porte. Mais elle est déjà verrouillée alors cette action doit échouer.

Bien évidemment, Marc s’attend à ce que l’échec du verrouillage déclenche une exception. Il n’est pas question de faire sans le mécanisme de gestion des exceptions.

Consignes

Pour réaliser cette tâche, suivez les étapes suivantes :

  1. Créer une nouvelle classe PorteVerrouilleException

  2. Ajouter une nouvelle méthode de visibilité public, de nom verrouiller, sans paramètre dans la classe Porte et qui ne renvoie rien.

  3. Le comportement de la méthod est le suivant : 

    • si la porte est déverrouillée, passez l’attribut verrouille à true.

    • si la porte est verrouillée, levez une exception PorteVerrouilleException.

  4. Dans la classe Main, testez le comportement en essayant de verrouiller une porte qui l’est déjà. Le code devrait être traité dans un bloc try / catch.

En résumé

  • Pour traiter les erreurs, Java utilise un mécanisme de gestion des exceptions.

  • Une exception peut être traité grâce aux instructions try { } catch(...) { }

  • Une exception peut être levée grâce au mot clé throw 

  • Une exception peut être redirigée en ajoutant throws puis le nom de l’exception à la signature de la méthode.

  • Vous pouvez créer votre propre exception en héritant des classes existantes. 

Félicitations pour avoir atteint la fin de cette deuxième partie du cours. En ayant bien assimilé ces nombreuses notions vous avez une solide base pour le développement Java. Avant de vous lancer dans la dernière partie du cours, évaluez vos connaissances dans le quiz juste après.

Exemple de certificat de réussite
Exemple de certificat de réussite