Cours BUT Info

Ch. 4 — Exceptions et manipulation du système

La hiérarchie Throwable de bout en bout : checked et unchecked, ordre des catch, propagation, exceptions personnalisées, try-with-resources, multi-catch — puis la classe File pour dialoguer avec le système.

Un mécanisme intégré au langage

La gestion des erreurs Java repose sur des objets Exception et Error, et sur des instructions pour les détecter, capturer et traiter (try, catch, finally) comme pour les lever ou les propager (throw, throws). Quand une erreur survient, un objet spécifique est créé — on dit que l'exception est levée — puis propagé à travers la pile d'exécution jusqu'à ce qu'il soit traité. Objectif : renforcer la sécurité et la robustesse du code.

int i = 3;
int j = 0;
System.out.println("Result = " + (i / j));
// → java.lang.ArithmeticException: / by zero, avec la trace des appels

La hiérarchie Throwable

Throwable, fille directe d'Object, est la classe mère de toutes les exceptions et erreurs : seules ses instances — ou celles de ses classes dérivées — peuvent être levées et capturées. Elle se scinde en deux branches :

  • Error : erreur grave dans la machine virtuelle — l'application s'arrête instantanément, ces erreurs n'ont pas vocation à être traitées (VirtualMachineError, OutOfMemoryError, StackOverflowError, LinkageError…). 12 classes en dérivent directement, car non traitables ;
  • Exception : les erreurs moins graves, que l'application peut traiter — obligatoirement pour les checked (IOException, ClassNotFoundException…), optionnellement pour les unchecked, c'est-à-dire les RuntimeException (NullPointerException, ArithmeticException, ClassCastException…). 75 classes en dérivent directement, dont 31 sous IOException et 53 sous RuntimeException.

try, catch, finally

try surveille un bloc susceptible de lever une exception ; catch la capture et la traite. Les blocs catch sont parcourus en séquence, et le premier compatible — par polymorphisme d'héritage — traite l'exception. Le bloc finally, s'il existe, est toujours exécuté : après toutes les instructions du try, ou après la dernière instruction qui a traité une exception levée. Dès qu'une exception survient, le reste du bloc est abandonné : si riskyMethod1() lève, riskyMethod2() n'est pas exécutée.

Le traitement peut être informatif — afficher ou exporter un message facilitant l'identification de l'erreur — ou curatif — corriger : valeur par défaut, paramètre modifié, méthode différente. Un catch vide ignore l'exception : mauvaise pratique, à éviter.

L'ordre des catch

S'il y a plusieurs types à intercepter, il faut autant de blocs catch — et leur ordre n'est pas décoratif : du spécifique au générique, sous peine d'erreur de compilation :

try {
    System.out.println("résultat = " + (i / j));
} catch (Exception e) {             // capture déjà tout…
} catch (ArithmeticException ae) {  // …donc inatteignable : ne compile pas
}

Lever et propager

throw lève manuellement une exception : throw new MyException();. La clause throws figure dans la signature : elle déclare qu'une méthode peut lever une exception sans la traiter. Toute méthode appelante doit alors choisir : traiter (bloc try/catch) ou propager à son tour. Les Error et RuntimeException n'ont à être ni déclarées ni propagées.

Règle du cours : une méthode qui lève une exception ne traite pas celles levées par les méthodes qu'elle appelle — elle liste les siennes via throws, quitte à concentrer le traitement dans une méthode qui en gère plusieurs. But : éviter la dilution du traitement des cas problématiques dans le code.

Throwable et exceptions personnalisées

Throwable offre des constructeurs — sans message, avec String message, avec message et cause — et des méthodes à connaître : getMessage(), toString(), printStackTrace(), getCause(), initCause(). Pour la division par zéro : getMessage() renvoie / by zero, toString() renvoie java.lang.ArithmeticException: / by zero, printStackTrace() ajoute la trace des appels.

Créer sa propre exception se fait par héritage — de Exception (checked) ou de RuntimeException (unchecked), jamais d'Error — avec, par convention, le mot « Exception » dans le nom :

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

Dérivée d'une unchecked, c'est plus facile — rien à déclarer — mais risqué : on peut oublier de la traiter, ou ne pas savoir qu'une méthode peut la lever. Le cours recommande le checked.

try-with-resources et multi-catch

Le try-with-resources est lié à l'interface AutoCloseable, qui garantit une méthode close() de fermeture de ressource — l'utiliser sur un type qui ne l'implémente pas ne compile pas. Les ressources se placent entre parenthèses juste après try (séparées par des ; si plusieurs) ; le bloc peut exister sans catch ni finally ; depuis Java SE 9, la variable peut être déclarée hors du bloc. La version classique du cours — BufferedReader créé hors du try — souffre de deux maux : ressources non fermées en cas d'exception, return final vide de sens. Le try-with-resources règle les deux :

static String readingOneLineWithResources(String chemin) throws IOException {
    try (BufferedReader br = new BufferedReader(new FileReader(chemin))) {
        return br.readLine();
    }   // close() invoquée automatiquement
}

Le multi-catch attrape plusieurs types avec un seul bloc — plus précis que la capture par polymorphisme : catch (InterestingException | SpecificException e). Contrepartie : sur e, seuls les membres du premier ancêtre commun aux types attrapés sont utilisables (Exception la plupart du temps).

Dialoguer avec le système : la classe File

Un programme échange des informations hétérogènes (texte, image, son) avec des sources et destinations variées — fichiers, sockets réseau, autres programmes. Deux aspects : les traitements du système (créer/supprimer des fichiers, explorer l'arborescence → la classe File) et les flux d'informations (lire un fichier, écrire sur une socket → les classes Reader et InputStream, qui encapsulent l'envoi et la réception).

Portabilité d'abord : les chemins diffèrent d'un OS à l'autre. File.separator construit un chemin (tmp/debug.log sous Unix), System.getProperty("user.dir") donne le répertoire courant. Les méthodes de File se regroupent par intention :

  • tester : isFile(), isDirectory(), exists(), canRead(), canWrite(), canExecute(), isHidden(), length(), lastModified()/setLastModified() ;
  • droits : setReadable(), setWritable(), setExecutable() ;
  • créer/supprimer : createNewFile() (sinon false, ou IOException en cas d'erreur), delete() (false si impossible), mkdir()/mkdirs() (répertoires imbriqués), deleteOnExit() (effacement à la fin de l'application) ;
  • chemins : getName(), getParent(), getPath(), getAbsolutePath(), getCanonicalPath() (chemin direct, résout .. et liens symboliques — peut lever IOException) ;
  • explorer : list()/listFiles() retournent le contenu d'un répertoire — en String[] ou File[], ordre imprédictible — filtrables par FileFilter/ FilenameFilter ; la méthode statique listRoots() retourne les racines du système de fichiers.

A retenir

  • Throwable → Error (JVM, non traitable) et Exception (traitable, sauf RuntimeException facultative) ;
  • catch du spécifique au générique ; finally toujours exécuté ; jamais de catch vide ;
  • throw lève, throws déclare ; propager plutôt que diluer le traitement ;
  • exceptions personnalisées : extends Exception, nom en « …Exception » ;
  • AutoCloseable + try-with-resources ; File pour le système, Reader/InputStream pour les flux.


coursQU_04.pdf

R2.03 · Cours 4 — Gestion des exceptions / manipulation du système · 38 diapositives

Consultation seule — téléchargement désactivé

Chargement du document…

Dernière mise à jour le

Sur cette page