Vos recrutements informatiques

700 000 développeurs, chefs de projets, ingénieurs, informaticiens...

Contactez notre équipe spécialiste en recrutement

FAQ Langage JavaConsultez toutes les FAQ

Nombre d'auteurs : 41, nombre de questions : 294, dernière mise à jour : 21 mars 2016  Ajouter une question

 

Cette FAQ a été réalisée à partir des questions fréquemment posées sur le forum Java de http://java.developpez.com ainsi que l'expérience personnelle des auteurs.

Nous tenons à souligner que cette FAQ ne garantit en aucun cas que les informations qu'elle propose sont correctes. Les auteurs font leur maximum, mais l'erreur est humaine. Cette FAQ ne prétend pas non plus être complète. Si vous trouvez une erreur, ou que vous souhaitez nous aider en devenant rédacteur, lisez ceci.

Sur ce, nous vous souhaitons une bonne lecture.


SommaireBases du langageExceptions (10)
précédent sommaire suivant
 

En Java, il existe trois grandes catégories d'exceptions et d'erreurs.

Checked exception
Ce genre d'exception décrit une erreur que votre programme doit savoir traiter et gérer ; elles sont déclarées sur les méthodes qui peuvent les générer lorsqu'elles doivent faire remonter des erreurs. Par exemple, si vous demandez l'ouverture en lecture d'un fichier qui n'existe pas, une checked exception de type java.io.FileNotFoundException sera générée lorsque votre code s’exécutera. En attrapant cette erreur, vous pouvez en informer l'utilisateur de votre programme. Lors de la compilation, le compilateur va vérifier que les exceptions de ce genre sont bien déclarées dans la signature des méthodes qui sont susceptibles de les générer et vous suggérer soit de les traiter dans le corps des méthodes appelantes, soit de les déclarer dans leur signature pour les faire remonter à un niveau plus élevé. Ces exceptions héritent de la classe java.lang.Exception et ce sont celles que vous serez le plus souvent amené à manipuler et également à implémenter.

Quelques exemples :

  • java.io.IOException - la classe parente de toutes les exceptions liées aux manipulations en lecture/écriture de fichiers et de flux ;
    • java.io.FileNotFoundException - le fichier cible n'existe pas ;
  • java.lang.InterruptedException - le thread courant a vu son sommeil être interrompu.


Runtime exception
Ces exceptions peuvent être générées au cours de l’exécution (runtime) de votre code en suivant des conditions qui ne sont pas forcement prévisibles. Il peut s'agir, par exemple, d'un paramètre non valide dans une méthode, d'une erreur sur une opération mathématique, d'un dépassement d'indice de tableau ou de liste ou encore d'un dépassement de taille de tampon ou encore l'invocation d'une méthode sur une référence qui est à la valeur null. Le compilateur ne force pas la déclaration de ces exceptions dans les signatures de méthodes (il reste cependant possible de les déclarer dans les signatures de méthodes) et ne va pas vérifier leur génération possible lors de la compilation ; vous devez donc vérifier votre code et vos algorithmes pour éviter qu'elles ne soient générées. Ces exceptions héritent toutes de la classe java.lang.RuntimeException et sont principalement à usage de la JVM ; vous ne serez donc pas amené à en implémenter, mais vous serez amené à les manipuler pour traiter ce genre d'erreurs.

Quelques exemples :

  • java.lang.NullPointerException - la référence sur laquelle vous avez invoqué une méthode ou un membre est à la valeur null ;
  • java.lang.IllegalArgumentException - le paramètre de la méthode n'est pas valide ;
  • java.lang.IndexOutOfBoundsException - l'index du tableau ou de la collection n'est pas valide ;
    • java.lang.ArrayIndexOutOfBoundsException - généralement générée par les accès sur des tableaux ; permet de conserver la valeur d'index non valide ;
  • java.lang.NumberFormatException - une erreur est survenue lors de la tentative de parser un nombre à partir d'une chaîne de caractères.


Error
Ces erreurs sont générées dans le cas de situations anormales pour signaler un problème sérieux qui ne devrait jamais survenir, par exemple, lorsque la JVM a consommé toute la mémoire qui est à sa disposition et qu'elle ne peut plus en allouer, une erreur de type java.lang.OutOfMemoryError est lancée. Cette erreur peut survenir sur n'importe quelle instruction de votre code et il est donc préférable de ne pas essayer de la capturer, mais plutôt d'essayer de résoudre en amont le problème qui a provoqué ce lancement pour éviter qu'il ne se reproduise par la suite. Ces erreurs héritent de la classe java.lang.Error. Le compilateur ne force pas la déclaration de ces erreurs dans les signatures de méthodes et ne va pas vérifier leur lancement possible lors de la compilation.

Quelques exemples :

  • java.lang.AssertionError - le test d'une assertion a échoué ;
  • java.lang.OutOfMemoryError - la JVM ne peut plus allouer de mémoire.

Mis à jour le 7 juillet 2015 bouye

Pour lancer ou déclencher une exception, il vous faut tout d'abord un objet de type java.lang.Throwable, généralement une exception de type checked ou runtime fournie par l'API Java ou la bibliothèque que vous utilisez, ou d'un type que vous avez créé de toute pièce. Lorsque l'initialisation de cette exception est finalisée, vous pouvez la lancer grâce au mot-clé throw.

Par exemple :

Code Java : Sélectionner tout
1
2
MonException monException = new MonException("Il y a eu une erreur"); 
throw monException;

Toutes les instructions situées après le throw sont inaccessibles ; le compilateur vous générera une erreur indiquant que ces instructions ne sont pas atteignables (unreachable statement).

Si votre exception est de type runtime, vous n’êtes pas forcé de la déclarer dans la signature de la méthode dans laquelle elle est lancée. Cependant, cela reste une bonne pratique que de l'y inclure et de la mentionner également dans la javadoc de cette méthode.

Si votre exception est de type checked, vous devez :
  • soit la traiter dans le corps de la méthode pour éviter qu'elle ne remonte à un niveau supérieur ;
  • soit la déclarer dans la signature de la méthode pour qu'une méthode appelante puisse la traiter.


La déclaration d'une exception dans une signature de méthode se fait à l'aide du mot-clé throws (avec un s à la fin) placé en fin de signature et suivi de la liste de tous les types d'exceptions qui peuvent être lancées par la méthode séparés par des virgules. Il est également fortement recommandé de la décrire dans la javadoc de la méthode via le tag @exception ou @throws.

Par exemple :

Code Java : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/** 
 * Ma méthode à moi. 
 * @param parametre1 Le paramètre de la méthode. 
 * @throws NullPointerException Si {@code parametre1} est {@code null}. 
 * @throws MonException En cas d'erreur. 
 */ 
public void maMethode(String parametre1) throws NullPointerException, MonException  { 
    Objects.requireNonNull(parametre1); // Peut signaler une NullPointerException 
    [...] 
    if (testEchoue) {  // Si le test échoue, on signale une MonException.   
        MonException monException = new MonException("Il y a eu une erreur"); 
        throw monException; 
    } 
}

Mis à jour le 14 avril 2015 bouye Mickael Baron

Pour capturer ou attraper une exception, vous devez utiliser un bloc try-catch ou try-catch-finally. Vous placerez votre traitement de l'exception dans le bloc catch de cette structure.

Par exemple :

Code Java : Sélectionner tout
1
2
3
4
5
6
7
8
try { 
    // Instructions précédentes. 
    maMethode("toto");// Méthode pouvant signaler une MonException. 
    // Instructions suivantes. 
} catch (MonException e) {    
    // Traitement à effectuer sur l'exception. 
    [...] 
}

Ici, si une exception est signalée dans maMethode(), toutes les instructions qui se trouvent après son invocation seront ignorées et ce jusqu'au bloc catch qui permet de capturer l'erreur. Si aucune exception n'est signalée, les instructions qui se trouvent après l'invocation de la méthode seront exécutées normalement et le contenu du bloc catch sera ignoré.

Il est tout à fait possible de régénérer une exception depuis un bloc catch après avoir effectué des traitements (fermetures de connexion, enregistrement dans un journal, etc.) de manière à la faire passer dans un niveau plus élevé de l'application (ex. : interface graphique pour afficher son message d'erreur) en invoquant le mot-clé throw.

Code Java : Sélectionner tout
1
2
3
4
5
6
7
8
9
try { 
    // Instructions précédentes. 
    maMethode("toto");// Méthode pouvant générer une MonException. 
    // Instructions suivantes. 
} catch (MonException e) { 
    // Traitement à effectuer sur l'exception. 
    [...] 
    throw e; 
}

Dans ce cas, les règles concernant la signature de la méthode s'appliquent de manière identique à celles vues précédemment.

Mis à jour le 14 avril 2015 bouye Mickael Baron

Il existe plusieurs manières de capturer plusieurs exceptions :

Plusieurs blocs catch
Il est possible d'enchaîner plusieurs blocs catch de manière à traiter toutes les exceptions susceptibles d’être signalées par une méthode ou un bloc de code. Dans le cas où vous désirez capturer une exception spécifique, vous devez traiter celle-ci avant le type plus général.

Par exemple :

Code Java : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
try { 
    // Action signalant une exception. 
} catch (FileNotFoundException e1) { 
    // Traitement spécifique pour l'absence de fichier. 
} catch (IOException e2) { 
    // Traitement général pour les erreurs d’entrée/sortie. 
} catch (NullPointerException e3) { 
    // Traitement pour un problème de référence nulle. 
} catch (Throwable t) { 
    // Traitement générique pour des exceptions de type runtime non planifiées. 
}

Super type
Il est possible de réduire le nombre de blocs catch en utilisant un type parent commun entre les exceptions. Ainsi lorsque les traitements à effectuer sont identiques, il devient plus rapide de capturer une IOException plutôt que de répéter exactement le même bloc de traitement pour une FileNotFoundException, ZipException ou ClosedChannelException. On peut donc remplacer :

Code Java : Sélectionner tout
1
2
3
4
5
6
7
8
9
try { 
    // Action signalant une exception. 
} catch (FileNotFoundException e1) { 
    // Traitement. 
} catch (ZipException e2) { 
    // Traitement identique au précédent. 
} catch (ClosedChannelException e3) { 
    // Traitement identique au précédent. 
}

Par :

Code Java : Sélectionner tout
1
2
3
4
5
try { 
    // Action signalant une exception. 
} catch (IOException e) { 
    // Traitement. 
}

Note : avant le JDK 7, le compilateur ne peut pas déterminer le type correct de l'exception si elle est régénérée dans le bloc catch. Une telle méthode serait donc obligée de se déclarer comme signalant le type commun utilisé : IOException. À partir du JDK 7, le compilateur est capable d’inférer correctement les types des exceptions générées par les méthodes incluses dans le bloc try, il est donc possible de spécifier que la méthode peut lancer le trio FileNotFoundException, ZipException ou ClosedChannelException.

Bloc multi-catch
Depuis le JDK 8, il est possible de fusionner des blocs catch en un bloc multi-catch de manière à éviter la duplication de code pour les traitements. Ainsi, précédemment on écrivait :

Code Java : Sélectionner tout
1
2
3
4
5
6
7
try { 
    // Action signalant une exception. 
} catch (IOException e1) { 
    // Traitement. 
} catch (SQLException e2) { 
    // Traitement. 
}

Désormais, il est possible de fusionner toutes les exceptions dans une seule déclaration en séparant chacun des types par une barre verticale :

Code Java : Sélectionner tout
1
2
3
4
5
try { 
    // Action signalant une exception. 
} catch (IOException | SQLException ex) { 
    // Traitement. 
}

Mis à jour le 14 avril 2015 bouye Mickael Baron

Dans le cas d'une utilisation d'un bloc finally, le contenu de ce bloc est tout le temps exécuté après le bloc try et, s'il est présent, le bloc catch même quand aucune exception n'est signalée.

Code Java : Sélectionner tout
1
2
3
4
5
6
7
8
9
try { 
    maMethode("toto"); // Méthode pouvant signaler une MonException.  
} catch (MonException e) { 
    // Traitement à effectuer sur l'exception. 
    [...] 
} finally { 
    // Le contenu de ce bloc est tout le temps exécuté que l'exception soit signalée ou pas. 
    [...] 
}

Le bloc finally permet de faire du nettoyage à la fin d'un bloc de code, par exemple fermer les flux ou les fichiers, les connexions vers des bases de données, etc. tant quand le code s’exécute correctement que quand il échoue à cause d'une erreur. Vous devez cependant prendre garde à ce que les instructions contenues dans ce bloc ne génèrent pas elles-mêmes de nouvelles exceptions lorsqu'elles s’exécutent sous peine de ne pas pouvoir finaliser vos nettoyages.

Note : les blocs try-finally permettent d'effectuer des traitements en cas d'exception ou d'erreur, mais ils n’empêchent pas la remontée de cette dernière au niveau supérieur. Dans ce cas, l'exception doit être déclarée dans la signature de la méthode appelante.

Par exemple :

Code Java : Sélectionner tout
1
2
3
4
5
6
7
8
public void uneAutreMethode() throws MonException { 
    try { 
         maMethode("toto"); // Méthode pouvant signaler une MonException. 
    } finally { 
        // Le contenu de ce bloc est tout le temps exécuté que l'exception soit signalée ou pas. 
        [...] 
    } 
}

Mis à jour le 14 avril 2015 bouye Mickael Baron

Depuis le JDK 7, il est possible de s'affranchir de la nécessité d'utiliser des blocs finally pour fermer manuellement des ressources telles que des flux ou des connexions. En effet, la syntaxe try-with-resources permet d'avoir une fermeture automatique de tout objet qui étend l'interface java.lang.AutoCloseable.

Par exemple lors de la lecture de flux, il fallait précédemment faire quelque chose du genre :

Code Java : Sélectionner tout
1
2
3
4
5
6
7
8
9
InputStream input = null; 
try { 
    input = new FileInputStrean(fichier); // Peut signaler une IOException.    
    [...]    // Opérations qui peuvent signaler une IOException.    
} finally { 
    if (input!= null) { 
        input.close(); // Peut signaler une IOException. 
    } 
}

Ce qui devenait vite assez compliqué à lire lorsqu'on manipulait plusieurs flux d’entrée/sortie.

Depuis le JDK 7, il est possible de simplifier la chose en faisant :

Code Java : Sélectionner tout
1
2
3
try (InputStream input = new FileInputStrean(fichier)) { // Peut signaler une IOException. 
    [...]  // Opérations qui peuvent signaler une IOException.    
}

Ici, si l'initialisation du flux a été couronnée de succès, ce dernier sera automatiquement fermé à la fin du du bloc try, même en cas de génération d'exception.

Il est également possible de déclarer plusieurs variables dans un tel bloc :

Code Java : Sélectionner tout
1
2
3
try (InputStream input = new FileInputStrean(fichier) ;  InputStreamReader reader = new InputStreamReader(input)) { // Peuvent signaler une IOException. 
    [...]  // Opérations qui peuvent générer une IOException.    
}

Ici, les deux entités seront automatiquement fermées (dans l'ordre inverse de leur déclaration) à la fin du bloc try.

Mis à jour le 14 avril 2015 bouye Mickael Baron

Une exception peut également contenir une autre exception qui est sa cause directe. Cette cause est accessible via la méthode getCause().

Par exemple, imaginons que vous soyez en train de lire le contenu d'un fichier texte : votre code échoue à parser un entier, car les données dans le fichier ne sont pas dans un format correct. Ceci génère en général une exception de type NumberFormatException. Mais pour les besoins de votre application, vous ne gérez que des exceptions de type IOException en cas d’échec de la lecture de votre fichier. Il est possible de créer une nouvelle exception de type IOException et d'indiquer que l'exception de type NumberFormatException en est la cause.

Code Java : Sélectionner tout
1
2
3
4
5
6
try { 
    // Lecture du contenu du fichier. 
} catch (NumberFormatException e1) { 
    IOException e2 = new IOException("Erreur de lecture du fichier", e1); 
    throw e2; 
}

Pour les types d'exception qui ne supportent pas le chaînage de la cause dans leur constructeur, il est possible d'invoquer la méthode initCause() pour chaîner une exception dans une autre :

Code Java : Sélectionner tout
1
2
3
4
5
6
7
try { 
    // Lecture du contenu du fichier. 
} catch (NumberFormatException e1) { 
    IOException e2 = new IOException("Erreur de lecture du fichier"); 
    e2.initCause(e1); 
    throw e2; 
}

L’analyse de la trace de cette exception d’entrée/sortie montrera bien qu'elle a été générée à cause d'une exception signalée lors de l’interprétation du contenu du fichier.

Mis à jour le 8 juillet 2015 bouye

À partir du JDK 7, une exception ou une erreur peut contenir plusieurs autres exceptions dites supprimées (suppressed) et accessibles dans un tableau retourné via la méthode getSuppressed(). Cette manière de chaîner des exceptions est utilisée dans des domaines qui doivent être plus tolérants aux fautes et erreurs d’exécution.

Par exemple, dans le cas d'une utilisation d'un bloc try-with-resources contenant de nombreux flux, une exception peut être générée lors de la fermeture de chacun des flux. Si votre code signale une exception, la JVM va « supprimer » les divers signalements d'exception lors des fermetures de toutes les ressources et ce de manière à les gérer correctement. À la fin du traitement, elle va toutes les empaqueter en tant qu'exceptions supprimées dans l'exception signalée par votre code.

Note : le tableau retourné par la méthode getSuppressed() est vide lorsque l'exception ne contient pas d'exception supprimée.

Mis à jour le 8 juillet 2015 bouye

Pour ajouter une ou plusieurs exceptions supprimées dans une exception, vous pouvez invoquer sa méthode addSuppressed().

Par exemple :

Code Java : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
List<Throwable> suppressed = new LinkedList<>; 
// Faire une ou plusieurs actions susceptibles de signaler des exceptions non critiques. 
// Comme ces exceptions ne sont pas critiques, elles n'interrompent pas le traitement, mais elles doivent être conservées. 
try { 
    [...] 
} catch (UneExceptionNonCritique erreur) { 
    suppressed.add(erreur); 
} 
// Fin du traitement. 
// Si des exceptions ont été supprimées, on génère une exception les contenant. 
if (!suppressed.isEmpty()) { 
   ExceptionMajeure ex = new ExceptionMajeure("Il y a eu des erreurs durant le traitement !"); 
   for (Throwable t : suppressed) { 
       ex.addSuppressed(t); 
   } 
   throw ex; 
}

Mis à jour le 15 septembre 2015 bouye

Les articles concernant null ou les NullPointerException et comment les éviter sont nombreux. Ils présentent des arguments intéressants, mais manquent souvent les aspects faciles, sûrs et élégants de la classe java.util.Optional proposée depuis le JDK 8.

En partant de ce code :

Code Java : Sélectionner tout
1
2
String unsafeTypeDirName = project.getApplicationType().getTypeDirName(); 
System.out.println(unsafeTypeDirName);

Avant le JDK 8
Il est évident que si une des valeurs utilisées ou retournées dans la première ligne est null, une exception de type NullPointerException sera signalée.

Voici une manière typique de résoudre ce problème :

Code Java : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
// Sûr, mais pas très élégant, et un oubli est vite arrivé 
if (project != null) { 
    ApplicationType applicationType = project.getApplicationType(); 
    if (applicationType != null) { 
        String typeDirName = applicationType.getTypeDirName(); 
        if (typeDirName != null) { 
            System.out.println(typeDirName); 
        } 
    } 
}

Si ce code résout bien le problème, il n’est pas très joli.

JDK 8

Essayons avec la classe Optional de Java 8.

Code Java : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Supposons que c’est ce que le modèle retournera dans le futur ; en attendant... 
Optional<Project> optionalProject = Optional.ofNullable(project); 
  
// Sûr, mais toujours peu séduisant et sujet à omissions. 
if (optionalProject.isPresent()) { 
    ApplicationType applicationType = optionalProject.get().getApplicationType(); 
    Optional<ApplicationType> optionalApplicationType = Optional.ofNullable(applicationType); 
    if (optionalApplicationType.isPresent()) { 
        String typeDirName = optionalApplicationType.get().getTypeDirName(); 
        Optional<String> optionalTypeDirName = Optional.ofNullable(typeDirName); 
        if (optionalTypeDirName.isPresent()) { 
            System.out.println(optionalTypeDirName); 
        } 
    } 
}

Comme observé par plusieurs, cette version n’est pas réellement différente des comparaisons à null. Certains y voient une clarification de l’intention. Je n’y vois pas de gain significatif, l’exemple précédent montrant que l’intention est déjà assez claire pour une situation de ce type.

Essayons donc avec une approche plus fonctionnelle, qui permet d’utiliser plus de puissance de la classe Optional.

Code Java : Sélectionner tout
1
2
3
4
5
// Sûr, plus élégant. 
optionalProject 
    .map(project -> project.getApplicationType()) 
    .map(applicationType -> applicationType.getTypeDirName()) 
    .ifPresent(typeDirName -> System.out.println(typeDirName));

map() retournera toujours une instance de Optional, les null étant dès lors impossibles. Il n’est dès lors plus nécessaire de convertir les valeurs de et vers Optional.

ifPresent() exécutera le code seulement si une valeur est spécifiée. Pas besoin d’une valeur par défaut.

Pour exprimer la même chose plus succinctement, passons à des références de méthodes :

Code Java : Sélectionner tout
1
2
3
4
5
// Sûr, plus gracieux. 
optionalProject 
    .map(Project::getApplicationType) 
    .map(ApplicationType::getTypeDirName) 
    .ifPresent(System.out::println);

En utilisant Optional et en ne travaillant jamais avec null, il est possible d’éviter complètement les NullPointerException. Comme cet artifice n’est plus utilisé, on évite d’oublier une comparaison à null, source d’erreurs. Bien entendu, il faut pour cela traiter au plus vite les valeurs retournées par du code ancien pouvant retourner null (List, Set, Map…). Les convertir au plus vite en Optional permettra d’éviter tout souci.

Mis à jour le 7 octobre 2015 ymajoros

Proposer une nouvelle réponse sur la FAQ

Ce n'est pas l'endroit pour poser des questions, allez plutôt sur le forum de la rubrique pour ça


Réponse à la question

Liens sous la question
précédent sommaire suivant
 

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2016 Developpez Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

 
Responsables bénévoles de la rubrique Java : Mickael Baron - Robin56 -