Exceptions

Lorsqu'une méthode ou une opération ne peut pas produire le résultat attendu, elle lève une exception. Une telle exception doit être capturée pour être traitée de façon spécifique, ou le programme s'arrête.

Exception in thread "main" java.lang.NumberFormatException: For input string: "douze"
        at java.lang.NumberFormatException.forInputString(Unknown Source)
        at java.lang.Integer.parseInt(Unknown Source)
        at java.lang.Integer.parseInt(Unknown Source)
        at Test.main(Test.java:4)

Le message obtenu en cas d'arrêt violent du programme met en avant les cirsconstances du problème :

  • le processus responsable (ici, le processus principal),
  • le type d'exception rencontré (ici, NumberFormatException),
  • le message associé,
  • la trace de la pile des appels (qui ici nous montre qu'on s'est arrêté à la ligne 4).

On peut anticiper une exception en indiquant au programme comment réagir sans tout arrêter à l'aide d'une nouvelle structure de contrôle :

try {
  int n = Integer.parseInt(args[0]);
  System.out.println("En hexadécimal : " + Integer.toHexString(n));
} catch(NumberFormatException e1) {
  System.err.println('\"' + args[0] + "\" n'est pas un entier !");
} catch(ArrayIndexOutOfBoundsException e2) {
  System.err.println("Pas d'argument à convertir !");
}

Le bloc try doit contenir le code susceptible d'échouer ainsi que tout le code qui dépend de son succès. Les blocs catch précisent quelle sorte d'exception est capturée et quoi faire dans ce cas. Si une exception pourrait être attrapée par plusieurs blocs catch, seul le premier qualifié (dans l'ordre du texte) s'active.

Remarque Ces blocs peuvent être suivis d'un bloc finally qui s'active dans tous les cas, mais il est rare que ce dernier bloc soit nécessaire.

Une fonction qui contient du code pouvant lever une exception n'est pas obligée de la capturer : dans ce cas, tout appel à cette fonction devient une source potentielle d'exception. Il est donc possible de placer le bloc qui capture une exception à n'importe quel niveau de la pile d'appel.

Toutes les exceptions sont des objets de diverses classes qui héritent de Throwable. On peut les créer comme n'importe quel autre objet, avec un constructeur. Pour lever une exception, il faut utiliser l'instruction throw, qui marche exactement comme return mais n'accepte que les références de type Throwable.

if (pasContent) {
  IllegalArgumentException ex = new IllegalArgumentException("Pas content !");
  throw ex;
}

Le compilateur n'exige pas que l'on attrape toutes les exceptions. Dans certains cas, c'est parce que les exceptions sont trop fréquentes : presque une ligne de code sur deux est susceptible de lever une NullPointerException. Dans d'autres cas, l'exception est facile à éviter (l'accès a une case d'un tableau ne lève jamais de ArrayIndexOutOfBoundsException quand on est rigoureux).

On dit que ces exceptions sont non-vérifiées. Les classes de ces exceptions héritent toutes de RuntimeException.

Certaines exceptions sont trop graves pour permettre au programme de continuer ; on ne devrait donc jamais les capturer. Ces exceptions non-vérifiées appartiennent aux classes qui héritent de Error.

Toutes les exceptions qui ne tombent pas dans ces catégories sont des exceptions vérifiées : leur capture est obligatoire (sous peine de ne pas compiler). Puisque la capture peut se faire à différents niveaux, les fonctions qui choisissent de laisser s'échapper une exception vérifiée doivent le déclarer dans leur signature :

public void pause() throws InterruptedException {
  Thread.sleep(1000000L);
}

Remarque Il est important de ne pas confondre throw (à l'impératif puisqu'il est dans une instruction) et throws (troisième personne du singulier du présent puisqu'il est dans une déclaration).

  1. Plantages. Écrivez cinq applications qui lèvent chacune une exception sans l'attraper. La classe de l'exception sera suivant les cas :

    Remarque Aucune méthode ou opération connue ne lève cette dernière sorte d'exception...

  2. Capture. Reprenez l'application de l'exercice précédent qui lève une ArithmeticException. Mettez l'instruction responsable de l'exception dans une autre méthode que main, et invoquez cette méthode dans le main. Qu'est-ce qui change lors de l'exécution ?

    Ajoutez dans la méthode secondaire un bloc try...catch pour capturer l'exception et afficher un message d'erreur de votre choix.

    Enlevez ensuite le bloc try...catch de la méthode secondaire, et placez-le dans le main de façon à tout de même capturer l'exception. Qu'est-ce que ça change ?

  3. Incomplet. Le programme suivant est bien écrit, cependant à la compilation il reste une erreur. Sans toucher aux lignes existantes, ajoutez ce qui manque pour que l'application compile et s'exécute.

    public class Incomplet {
    	public static void main(String[] args) {
    		byte[] txt = {0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x0D, 0x0A};
    		System.out.write(txt);
    	}
    }

  4. Apparences. Reprenez le premier exercice du premier sujet sur les évènements et ajoutez au début de la méthode principale l'instruction suivante :

    UIManager.setLookAndFeel("javax.swing.plaf.nimbus.NimbusLookAndFeel");

    Que faut-il ajouter pour que ça compile ? Que constatez-vous lorsque vous exécutez cette application ?

    Remplacez la chaîne de caractère par "javax.swing.JFrame". Que fait alors l'application ? Et pour la chaîne "toto" ? Si ce n'est pas déjà le cas, faites en sorte d'afficher un message d'erreur différent pour ces deux situations.

  5. Degrés. Écrivez une application qui ouvre une fenêtre contenant deux champs de texte. En tapant une température en degrés Celsius dans le premier champ de texte, on fait apparaître la conversion en degrés Farenheit dans le second, et vice-versa. Si le texte tapé n'est pas un réel, le résultat sera ???.

retour à la page d'accueil

retour au sommet