Skip to content

19. Exceptions et Gestion des Erreurs

Table des matières


Les Exceptions constituent le mécanisme structuré de Java pour gérer les conditions anormales à runtime. Elles permettent de séparer le flux normal d'exécution de la logique de gestion des erreurs, améliorant la robustesse, la lisibilité et l'exactitude du programme.

19.1 Hiérarchie et types dexceptions

Toutes les exceptions dérivent de Throwable. La hiérarchie définit quelles conditions sont récupérables, lesquelles doivent être déclarées, et lesquelles représentent des défaillances système fatales.

java.lang.Object
└── java.lang.Throwable
    ├── java.lang.Error
    └── java.lang.Exception
        └── java.lang.RuntimeException

19.1.1 Throwable

  • Classe de base pour toutes les erreurs et exceptions
  • Fournit message, cause et stack trace
  • Seuls Throwable et ses sous-classes peuvent être lancés ou capturés

19.1.2 Error (unchecked)

  • Représente des problèmes graves de la JVM ou du système
  • Non destiné à être capturé ou géré
  • Exemples: OutOfMemoryError, StackOverflowError

Note

Les erreurs indiquent des conditions dont l'application ne peut généralement pas se remettre.

19.1.3 Exceptions Checked (Exception)

  • Sous-classes de Exception à lexclusion de RuntimeException
  • Représentent des conditions que l'application peut vouloir gérer
  • Doivent être soit capturées soit déclarées
  • Exemples: IOException, SQLException

19.1.4 Exceptions Unchecked (RuntimeException)

  • Sous-classes de RuntimeException
  • Ne doivent pas obligatoirement être déclarées ou capturées
  • Représentent généralement des erreurs de programmation
  • Exemples: NullPointerException, IllegalArgumentException

19.2 Déclarer et lancer des exceptions

19.2.1 Déclarer des exceptions avec throws

Une méthode déclare les exceptions checked avec la clause throws. Cela fait partie du contrat API de la méthode.

void readFile(Path p) throws IOException {
    Files.readString(p);
}

Note

  • Seules les exceptions checked doivent être déclarées.
  • Les exceptions unchecked peuvent l'être, mais sont généralement omises.

19.2.2 Lancer des exceptions

Les exceptions sont créées avec new et lancées explicitement avec throw.

if (value < 0) {
    throw new IllegalArgumentException("value must be >= 0");
}
  • throw lance exactement une instance dexception
  • throws déclare les exceptions possibles dans la signature de la méthode

19.3 Redéfinition de méthodes et règles sur les exceptions

Lors de la redéfinition dune méthode, les règles sur les exceptions sont strictement appliquées :

  • Une méthode redéfinie peut lancer moins d'exceptions checked ou des exceptions plus spécifiques
  • Elle peut lancer n'importe quelles exceptions unchecked
  • Elle ne peut lancer aucune nouvelle exception checked plus large
class Parent {
    void work() throws IOException {}
}

class Child extends Parent {
    @Override
    void work() throws FileNotFoundException {} // OK
}

Note

Modifier uniquement les exceptions unchecked ne viole jamais le contrat de redéfinition.

Important

Rappel : les constructeurs suivent une règle différente.

Un Constructeur doit déclarer toutes les exceptions checked déclarées dans le constructeur de base (ou les superclasses de ces exceptions checked).

Il peut également déclarer des exceptions checked supplémentaires. Ce comportement est l’opposé de celui de l’overriding de méthodes.

Une méthode qui override ne peut pas lancer d’exception checked autre que celles déclarées par la méthode overridée. Elle ne peut lancer que des sous-classes de ces exceptions.

19.4 Gestion des exceptions: try, catch, finally

19.4.1 Syntaxe de base try-catch

try {
    riskyOperation();
} catch (IOException e) {
    handle(e);
}
  • Un bloc try doit être suivi d'au moins un catch ou d'un finally
  • Les catch sont évalués de haut en bas

19.4.2 Plusieurs blocs catch

try {
    process();
} catch (FileNotFoundException e) {
    recover();
} catch (IOException e) {
    log();
}

Note

Les exceptions plus spécifiques doivent précéder les plus générales, sinon la compilation échoue. Si un catch pour une superclasse précède celui d'une sous-classe, ce dernier devient inatteignable.

19.4.3 Multi-catch Java-7

try {
    process();
} catch (IOException | SQLException e) {
    log(e);
}
  • Les types d'exception doivent être non liés (pas parent/enfant)
  • La variable capturée est implicitement final

19.4.4 Bloc finally

Le bloc finally s'exécute qu'il y ait exception ou non, sauf en cas d'arrêt extrême de la JVM.

try {
    open();
} finally {
    close();
}
  • Utilisé pour la logique de nettoyage
  • S'exécute même si return est utilisé dans try ou catch

Note

Un bloc finally peut écraser une valeur de retour ou avaler une exception. Cela est déconseillé car cela complique le flux de contrôle.

Important

Lorsquun bloc catch et un bloc finally lancent tous deux une exception, lexception lancée dans le bloc finally est celle qui est propagée par la méthode.

Lexception lancée dans le bloc catch est perdue et nest pas ajoutée à la liste des exceptions supprimées.

try {
    throw new RuntimeException("try");
} catch (RuntimeException e) {
    throw new RuntimeException("catch");
} finally {
    throw new RuntimeException("finally");
}

Dans ce cas, seule lexception "finally" est lancée.

19.5 Gestion automatique des ressources try-with-resources

Le try-with-resources permet la fermeture automatique des ressources implémentant AutoCloseable. Il élimine le besoin d'un bloc finally explicite dans la plupart des cas.

19.5.1 Syntaxe de base

try (BufferedReader br = Files.newBufferedReader(path)) {
    return br.readLine();
}
  • Les ressources sont fermées automatiquement
  • La fermeture a lieu même en cas d'exception
  • Les ressources sont fermées avant l’exécution de tout bloc catch ou finally.
try (Resource a = new Resource()) {
    a.read();
} finally {
    a.close();  // ❌ Compile-time error: a est hors de portée ici
}

19.5.2 Déclarer plusieurs ressources

try (InputStream in = Files.newInputStream(p);
        OutputStream out = Files.newOutputStream(q)) {
    in.transferTo(out);
}
  • Les ressources sont fermées en ordre inverse de déclaration

19.5.3 Portée des ressources

  • Les ressources sont visibles uniquement dans le bloc try
  • Elles sont implicitement final
  • Depuis Java 9, on peut déclarer les ressources avant le try-with-resources si elles sont final ou effectivement finales
final var firstWriter = Files.newBufferedWriter(filePath);

try (firstWriter; var secondWriter = Files.newBufferedWriter(filePath)) {
    // CODE
}

Note

Tenter de réaffecter une variable ressource provoque une erreur de compilation.

Resource a = new Resource();
try(a){ // since Java 9
  ...
}finally{
   a.close(); // ce code compile, mais la ressource référencée par la référence `a` a déjà été fermée.
}

19.6 Exceptions supprimées

Lorsque le bloc try et la méthode close() d'une ressource lancent tous deux une exception, Java conserve l'exception principale et supprime les autres.

try (BadResource r = new BadResource()) {
    throw new RuntimeException("main");
}

Si close() lance aussi une exception, elle devient supprimée.

catch (Exception e) {
    for (Throwable t : e.getSuppressed()) {
        System.out.println(t);
    }
}
  • L'exeption principale est lancée
  • Les exceptions secondaires sont accessibles via getSuppressed()

Important

Les exceptions supprimées sont générées uniquement par le bloc finally implicite créé par le try-with-resources.

En revanche, les exceptions lancées dans un bloc finally explicite ne sont pas supprimées : elles remplacent toute exception précédente et deviennent la seule exception propagée.

19.7 Résumé des exceptions

  • Les exceptions checked doivent être capturées ou déclarées
  • Les méthodes redéfinies ne peuvent pas élargir les exceptions checked
  • Utiliser multi-catch pour une logique de gestion commune
  • Préférer try-with-resources au nettoyage via finally
  • Les ressources se ferment en ordre inverse
  • Les exceptions supprimées préservent le contexte complet de défaillance