Java Language
Exceptions et gestion des exceptions
Recherche…
Introduction
Throwable
et ses sous-types peuvent être envoyés à la pile avec le mot-clé throw
et capturés avec try…catch
instructions.
Syntaxe
void someMethod () lève SomeException {} // déclaration de méthode, force les appelants de méthode à intercepter si SomeException est un type d'exception vérifié
essayez {
someMethod(); //code that might throw an exception
}
catch (SomeException e) {
System.out.println("SomeException was thrown!"); //code that will run if certain exception (SomeException) is thrown
}
enfin {
//code that will always run, whether try block finishes or not
}
Prendre une exception avec try-catch
Une exception peut être interceptée et gérée à l'aide de l'instruction try...catch
. (En fait, les instructions try
prennent d'autres formes, comme décrit dans d'autres exemples sur try...catch...finally
et try-with-resources
.)
Attrapez avec un bloc catch
La forme la plus simple ressemble à ceci:
try {
doSomething();
} catch (SomeException e) {
handle(e);
}
// next statement
Le comportement d'une simple try...catch
est le suivant:
- Les instructions du bloc
try
sont exécutées. - Si aucune exception n'est levée par les instructions du bloc
try
, le contrôle passe à l'instruction suivante après letry...catch
. - Si une exception est lancée dans le bloc
try
.- L'objet exception est testé pour voir s'il s'agit d'une instance de
SomeException
ou d'un sous-type. - S'il est, le
catch
bloc va attraper l'exception:- La variable
e
est liée à l'objet exception. - Le code dans le bloc
catch
est exécuté. - Si ce code renvoie une exception, l'exception nouvellement renvoyée est propagée à la place de celle d'origine.
- Sinon, le contrôle passe à l'instruction suivante après le
try...catch
.
- La variable
- Si ce n'est pas le cas, l'exception d'origine continue à se propager.
- L'objet exception est testé pour voir s'il s'agit d'une instance de
Prise d'essai avec plusieurs prises
Un try...catch
peut aussi avoir plusieurs blocs de catch
. Par exemple:
try {
doSomething();
} catch (SomeException e) {
handleOneWay(e)
} catch (SomeOtherException e) {
handleAnotherWay(e);
}
// next statement
S'il y a plusieurs blocs catch
, ils sont essayés un à la fois en commençant par le premier, jusqu'à ce qu'une correspondance soit trouvée pour l'exception. Le gestionnaire correspondant est exécuté (comme ci-dessus), puis le contrôle est transmis à l'instruction suivante après l'instruction try...catch
. Les blocs catch
après celui qui correspond sont toujours ignorés, même si le code du gestionnaire renvoie une exception .
La stratégie d'appariement «descendante» a des conséquences dans les cas où les exceptions dans les blocs de catch
ne sont pas disjointes. Par exemple:
try {
throw new RuntimeException("test");
} catch (Exception e) {
System.out.println("Exception");
} catch (RuntimeException e) {
System.out.println("RuntimeException");
}
Cet extrait de code affichera "Exception" plutôt que "RuntimeException". Puisque RuntimeException
est un sous-type d' Exception
, le premier catch
(plus général) sera mis en correspondance. La seconde catch
(plus spécifique) ne sera jamais exécutée.
La leçon à en tirer est que les blocs de catch
les plus spécifiques (en termes de types d'exception) devraient apparaître en premier et les plus généraux en dernier. (Certains compilateurs Java vous avertiront si un catch
ne peut jamais être exécuté, mais ce n’est pas une erreur de compilation.)
Blocs d'interception multi-exception
À partir de Java SE 7, un bloc catch
unique peut gérer une liste d'exceptions non associées. Le type d'exception est répertorié, séparé par un symbole de barre verticale ( |
). Par exemple:
try {
doSomething();
} catch (SomeException | SomeOtherException e) {
handleSomeException(e);
}
Le comportement d'une capture multi-exception est une simple extension pour le cas à exception unique. Le catch
correspond si l'exception levée correspond (au moins) à l'une des exceptions répertoriées.
Il y a une subtilité supplémentaire dans la spécification. Le type de e
est une union synthétique des types d'exception de la liste. Lorsque la valeur de e
est utilisée, son type statique est le supertype le moins commun de la union de type. Toutefois, si e
est renvoyé dans le bloc catch
, les types d'exception levés sont les types de l'union. Par exemple:
public void method() throws IOException, SQLException
try {
doSomething();
} catch (IOException | SQLException e) {
report(e);
throw e;
}
Dans ce qui précède, IOException
et SQLException
sont des exceptions vérifiées dont le supertype le moins commun est Exception
. Cela signifie que la méthode de report
doit correspondre au report(Exception)
. Cependant, le compilateur sait que le throw
ne peut lancer qu’une SQLException
IOException
ou une SQLException
. Ainsi, la method
peut être déclarée en tant que throws IOException, SQLException
plutôt que throws Exception
. (Ce qui est une bonne chose: voir Pitfall - Throwable, Exception, Error ou RuntimeException .)
Lancer une exception
L'exemple suivant montre les bases de la création d'une exception:
public void checkNumber(int number) throws IllegalArgumentException {
if (number < 0) {
throw new IllegalArgumentException("Number must be positive: " + number);
}
}
L'exception est lancée sur la 3ème ligne. Cette déclaration peut être divisée en deux parties:
new IllegalArgumentException(...)
crée une instance de la classeIllegalArgumentException
, avec un message décrivant l'erreurIllegalArgumentException
par une exception.throw ...
lance ensuite l'objet exception.
Lorsque l'exception est levée, les instructions englobantes se terminent anormalement jusqu'à ce que l'exception soit gérée . Ceci est décrit dans d'autres exemples.
Il est recommandé de créer et de lancer l’objet exception dans une seule instruction, comme indiqué ci-dessus. Il est également recommandé d'inclure un message d'erreur significatif dans l'exception pour aider le programmeur à comprendre la cause du problème. Cependant, ce n'est pas nécessairement le message que vous devez montrer à l'utilisateur final. (Pour commencer, Java ne prend pas en charge directement l’internationalisation des messages d’exception.)
Il y a encore quelques points à faire:
Nous avons déclaré le
checkNumber
commethrows IllegalArgumentException
. Ce n'était pas strictement nécessaire, carIllegalArgumentException
est une exception vérifiée; voir Hiérarchie des exceptions Java - Exceptions non vérifiées et vérifiées . Toutefois, il est conseillé de le faire et d'inclure les exceptions renvoyant les commentaires javadoc d'une méthode.Code immédiatement après une instruction de
throw
est inaccessible . D'où si nous avons écrit ceci:throw new IllegalArgumentException("it is bad"); return;
le compilateur signalerait une erreur de compilation pour l'instruction
return
.
Chaîne d'exception
De nombreuses exceptions standard ont un constructeur avec un deuxième argument de cause
en plus de l'argument de message
conventionnel. La cause
vous permet d'enchaîner les exceptions. Voici un exemple.
Tout d'abord, nous définissons une exception non vérifiée que notre application va lancer lorsqu'elle rencontre une erreur non récupérable. Notez que nous avons inclus un constructeur qui accepte un argument de cause
.
public class AppErrorException extends RuntimeException {
public AppErrorException() {
super();
}
public AppErrorException(String message) {
super(message);
}
public AppErrorException(String message, Throwable cause) {
super(message, cause);
}
}
Ensuite, voici un code qui illustre le chaînage des exceptions.
public String readFirstLine(String file) throws AppErrorException {
try (Reader r = new BufferedReader(new FileReader(file))) {
String line = r.readLine();
if (line != null) {
return line;
} else {
throw new AppErrorException("File is empty: " + file);
}
} catch (IOException ex) {
throw new AppErrorException("Cannot read file: " + file, ex);
}
}
Le throw
dans le bloc try
détecte un problème et le signale via une exception avec un message simple. En revanche, le throw
dans le bloc catch
gère l'exception IOException
l' IOException
dans une nouvelle exception (cochée). Cependant, il ne rejette pas l'exception d'origine. En faisant passer l’ IOException
tant que cause
, nous l’enregistrons pour qu’elle puisse être imprimée dans la trace de la pile, comme expliqué dans la section Création et lecture de chaînes de caractères .
Exceptions personnalisées
Dans la plupart des cas, il est plus simple du point de vue de la conception de code d'utiliser des classes d' Exception
génériques existantes lors du lancement d'exceptions. Cela est particulièrement vrai si vous avez uniquement besoin de l'exception pour transmettre un message d'erreur simple. Dans ce cas, RuntimeException est généralement préférable car il ne s'agit pas d'une exception vérifiée. D'autres classes d'exception existent pour les classes d'erreurs courantes:
- UnsupportedOperationException - une opération donnée n'est pas prise en charge
- IllegalArgumentException - une valeur de paramètre non valide a été transmise à une méthode
- IllegalStateException - votre API a atteint en interne une condition qui ne devrait jamais se produire ou qui résulte de l'utilisation de votre API de manière non valide
Les cas où vous ne souhaitez utiliser une classe d'exception personnalisée sont les suivantes:
- Vous écrivez une API ou une bibliothèque à utiliser par d'autres utilisateurs et vous souhaitez autoriser les utilisateurs de votre API à intercepter et gérer spécifiquement les exceptions de votre API et à les différencier des autres exceptions plus génériques .
- Vous lancez des exceptions pour un type d'erreur spécifique dans une partie de votre programme, que vous souhaitez intercepter et gérer dans une autre partie de votre programme, et que vous souhaitez pouvoir différencier ces erreurs des autres erreurs plus génériques.
Vous pouvez créer vos propres exceptions personnalisées en étendant RuntimeException
pour une exception non contrôlée ou en vérifiant l'exception en étendant toute Exception
qui n'est pas également la sous-classe de RuntimeException , car:
Les sous-classes d'exception qui ne sont pas également des sous-classes de RuntimeException sont des exceptions vérifiées
public class StringTooLongException extends RuntimeException {
// Exceptions can have methods and fields like other classes
// those can be useful to communicate information to pieces of code catching
// such an exception
public final String value;
public final int maximumLength;
public StringTooLongException(String value, int maximumLength){
super(String.format("String exceeds maximum Length of %s: %s", maximumLength, value));
this.value = value;
this.maximumLength = maximumLength;
}
}
Ceux-ci peuvent être utilisés comme exceptions prédéfinies:
void validateString(String value){
if (value.length() > 30){
throw new StringTooLongException(value, 30);
}
}
Et les champs peuvent être utilisés lorsque l'exception est interceptée et gérée:
void anotherMethod(String value){
try {
validateString(value);
} catch(StringTooLongException e){
System.out.println("The string '" + e.value +
"' was longer than the max of " + e.maximumLength );
}
}
Gardez à l'esprit que, selon la documentation Java d' Oracle :
[...] Si l'on peut raisonnablement s'attendre à ce qu'un client récupère d'une exception, faites-en une exception cochée. Si un client ne peut rien faire pour récupérer à partir de l'exception, faites-en une exception non contrôlée.
Plus:
La déclaration d'essayer avec les ressources
Comme l'illustre l'exemple de l' instruction try-catch-final , le nettoyage des ressources à l'aide d'une clause finally
nécessite une quantité importante de code "chaud-plat" pour implémenter correctement les bordures. Java 7 fournit un moyen beaucoup plus simple de résoudre ce problème sous la forme de l'instruction try-with-resources .
Qu'est ce qu'une ressource?
Java 7 a introduit l'interface java.lang.AutoCloseable
pour permettre la gestion des classes à l'aide de l'instruction try-with-resources . Les instances de classes qui implémentent AutoCloseable
sont appelées ressources . Celles-ci doivent généralement être éliminées en temps opportun plutôt que de compter sur le ramasse-miettes pour en disposer.
L'interface AutoCloseable
définit une méthode unique:
public void close() throws Exception
Une méthode close()
doit éliminer la ressource de manière appropriée. La spécification indique qu'il est prudent d'appeler la méthode sur une ressource déjà supprimée. De plus, les classes qui implémentent Autocloseable
sont fortement encouragées à déclarer la méthode close()
pour générer une exception plus spécifique que Exception
, voire aucune exception.
Un large éventail d'interfaces et de classes Java standard implémentent AutoCloseable
. Ceux-ci inclus:
-
InputStream
,OutputStream
et leurs sous-classes -
Reader
,Writer
et leurs sous-classes -
Socket
etServerSocket
et leurs sous-classes -
Channel
et ses sous-classes, et - les interfaces JDBC
Connection
,Statement
etResultSet
et leurs sous-classes.
Les classes d'application et tierces peuvent également le faire.
L'énoncé de base de try-with-resource
La syntaxe d'un try-with-resources est basée sur des formes classiques de try-catch , try-finally et try-catch-finally . Voici un exemple de formulaire "de base"; c'est à dire la forme sans catch
ou finally
.
try (PrintStream stream = new PrintStream("hello.txt")) {
stream.println("Hello world!");
}
Les ressources à gérer sont déclarées comme variables dans la section (...)
après la clause try
. Dans l'exemple ci-dessus, nous déclarons un stream
variable de ressource et l'initialisons à un nouveau PrintStream
.
Une fois les variables de ressource initialisées, le bloc try
est exécuté. Lorsque cela est terminé, stream.close()
sera appelé automatiquement pour garantir que la ressource ne fuit pas. Notez que l'appel close()
se produit peu importe la manière dont le bloc se termine.
Les instructions try-with-resource améliorées
L'instruction try-with-resources peut être améliorée avec catch
blocs catch
et finally
, comme avec la syntaxe try-catch-finally pré-Java 7. L'extrait de code suivant ajoute un bloc catch
à notre précédent pour gérer l' FileNotFoundException
que le constructeur PrintStream
peut lancer:
try (PrintStream stream = new PrintStream("hello.txt")) {
stream.println("Hello world!");
} catch (FileNotFoundException ex) {
System.err.println("Cannot open the file");
} finally {
System.err.println("All done");
}
Si l'initialisation de la ressource ou le bloc try lève l'exception, le bloc catch
sera exécuté. Le bloc finally
sera toujours exécuté, comme avec une instruction try-catch-finally classique.
Il y a quelques choses à noter cependant:
- La variable de ressource est hors de portée dans les blocs
catch
etfinally
. - Le nettoyage des ressources se produira avant que l'instruction tente de correspondre au bloc
catch
. - Si le nettoyage automatique des ressources a déclenché une exception, cela peut se produire dans l'un des blocs
catch
.
Gestion de plusieurs ressources
Les extraits de code ci-dessus montrent une seule ressource en cours de gestion. En fait, try-with-resources peut gérer plusieurs ressources dans une seule déclaration. Par exemple:
try (InputStream is = new FileInputStream(file1);
OutputStream os = new FileOutputStream(file2)) {
// Copy 'is' to 'os'
}
Cela se comporte comme prévu. Les deux is
et os
sont fermés automatiquement à la fin de l' try
bloc. Il y a quelques points à noter:
- Les initialisations se produisent dans l'ordre du code, et les initialiseurs de variables de ressource ultérieurs peuvent utiliser les valeurs des précédentes.
- Toutes les variables de ressources initialisées avec succès seront nettoyées.
- Les variables de ressource sont nettoyées dans l'ordre inverse de leurs déclarations.
Ainsi, dans l'exemple ci - dessus, is
est initialisé avant os
et nettoyé après, et is
sera nettoyé s'il y a une exception lors de l' initialisation os
.
Équivalence d'essais avec ressources et d'essais classiques
La spécification de langage Java spécifie le comportement des formulaires try-with-resource en fonction de l'instruction try-catch-finally classique. (Veuillez vous référer au JLS pour plus de détails.)
Par exemple, ce try-with-resource de base :
try (PrintStream stream = new PrintStream("hello.txt")) {
stream.println("Hello world!");
}
est défini pour être équivalent à ce try-catch-finally :
// Note that the constructor is not part of the try-catch statement
PrintStream stream = new PrintStream("hello.txt");
// This variable is used to keep track of the primary exception thrown
// in the try statement. If an exception is thrown in the try block,
// any exception thrown by AutoCloseable.close() will be suppressed.
Throwable primaryException = null;
// The actual try block
try {
stream.println("Hello world!");
} catch (Throwable t) {
// If an exception is thrown, remember it for the finally block
primaryException = t;
throw t;
} finally {
if (primaryException == null) {
// If no exception was thrown so far, exceptions thrown in close() will
// not be caught and therefore be passed on to the enclosing code.
stream.close();
} else {
// If an exception has already been thrown, any exception thrown in
// close() will be suppressed as it is likely to be related to the
// previous exception. The suppressed exception can be retrieved
// using primaryException.getSuppressed().
try {
stream.close();
} catch (Throwable suppressedException) {
primaryException.addSuppressed(suppressedException);
}
}
}
(Le JLS spécifie que les variables t
et primaryException
seront invisibles pour le code Java normal.)
La forme améliorée de try-with-resources est spécifiée comme une équivalence avec la forme de base. Par exemple:
try (PrintStream stream = new PrintStream(fileName)) {
stream.println("Hello world!");
} catch (NullPointerException ex) {
System.err.println("Null filename");
} finally {
System.err.println("All done");
}
est équivalent à:
try {
try (PrintStream stream = new PrintStream(fileName)) {
stream.println("Hello world!");
}
} catch (NullPointerException ex) {
System.err.println("Null filename");
} finally {
System.err.println("All done");
}
Création et lecture de stacktraces
Lorsqu'un objet d'exception est créée ( par exemple lorsque vous new
il), le Throwable
constructeur capture d' informations sur le contexte dans lequel l'exception a été créé. Par la suite, ces informations peuvent être générées sous la forme d'un stacktrace, qui peut être utilisé pour diagnostiquer le problème à l'origine de l'exception.
Impression d'un stacktrace
Imprimer une stacktrace consiste simplement à appeler la méthode printStackTrace()
. Par exemple:
try {
int a = 0;
int b = 0;
int c = a / b;
} catch (ArithmeticException ex) {
// This prints the stacktrace to standard output
ex.printStackTrace();
}
La méthode printStackTrace()
sans arguments imprimera sur la sortie standard de l'application; c'est-à-dire le System.out
actuel. Il existe également des printStackTrace(PrintStream)
et des printStackTrace(PrintWriter)
qui impriment sur un Stream
ou un Writer
spécifié.
Remarques:
Le stacktrace n'inclut pas les détails de l'exception elle-même. Vous pouvez utiliser la
toString()
pour obtenir ces détails. par exemple// Print exception and stacktrace System.out.println(ex); ex.printStackTrace();
L'impression Stacktrace doit être utilisée avec parcimonie. voir Piège - Piles superflues ou inappropriées . Il est souvent préférable d'utiliser une infrastructure de journalisation et de passer l'objet exception à journaliser.
Comprendre un stacktrace
Considérons le programme simple suivant composé de deux classes dans deux fichiers. (Nous avons montré les noms de fichiers et les numéros de ligne ajoutés à des fins d’illustration.)
File: "Main.java"
1 public class Main {
2 public static void main(String[] args) {
3 new Test().foo();
4 }
5 }
File: "Test.java"
1 class Test {
2 public void foo() {
3 bar();
4 }
5
6 public int bar() {
7 int a = 1;
8 int b = 0;
9 return a / b;
10 }
Lorsque ces fichiers sont compilés et exécutés, nous obtiendrons la sortie suivante.
Exception in thread "main" java.lang.ArithmeticException: / by zero
at Test.bar(Test.java:9)
at Test.foo(Test.java:3)
at Main.main(Main.java:3)
Lisons cette ligne à la fois pour comprendre ce qu’elle nous dit.
La ligne n ° 1 nous indique que le thread appelé "main" est terminé en raison d'une exception non interceptée. Le nom complet de l'exception est java.lang.ArithmeticException
et le message d'exception est "/ by zero".
Si nous recherchons les javadocs pour cette exception, cela dit:
Lancé lorsqu'une condition arithmétique exceptionnelle s'est produite. Par exemple, un entier "diviser par zéro" lève une instance de cette classe.
En effet, le message "/ by zero" est un indice fort que la cause de l'exception est qu'un code a tenté de diviser quelque chose par zéro. Mais quoi?
Les 3 lignes restantes sont la trace de la pile. Chaque ligne représente un appel de méthode (ou de constructeur) sur la pile d'appels, et chacune nous indique trois choses:
- le nom de la classe et de la méthode en cours d'exécution,
- le nom de fichier du code source,
- le numéro de ligne du code source de l'instruction en cours d'exécution
Ces lignes d'un stacktrace sont répertoriées avec le cadre de l'appel en cours. Le cadre supérieur de notre exemple ci-dessus se trouve dans la méthode Test.bar
et à la ligne 9 du fichier Test.java. C'est la ligne suivante:
return a / b;
Si nous examinons quelques lignes plus tôt dans le fichier où b
est initialisé, il est évident que b
aura la valeur zéro. Nous pouvons dire sans aucun doute que c'est la cause de l'exception.
Si nous devions aller plus loin, nous pouvons voir à partir du stacktrace que bar()
été appelé depuis foo()
à la ligne 3 de Test.java, et que foo()
été à son tour appelé depuis Main.main()
.
Remarque: Les noms de classe et de méthode dans les cadres de pile sont les noms internes des classes et des méthodes. Vous devrez reconnaître les cas inhabituels suivants:
- Une classe imbriquée ou interne ressemblera à "OuterClass $ InnerClass".
- Une classe interne anonyme ressemblera à "OuterClass $ 1", "OuterClass $ 2", etc.
- Lorsque du code dans un constructeur, un initialiseur de champ d'instance ou un bloc d'initialisation d'instance est en cours d'exécution, le nom de la méthode sera "".
- Lorsque le code d'un initialiseur de champ statique ou d'un bloc d'initialisation statique est en cours d'exécution, le nom de la méthode sera "".
(Dans certaines versions de Java, le code de formatage stacktrace détectera et éliminera les séquences répétées de pile, comme cela peut se produire lorsqu'une application échoue en raison d'une récursivité excessive.)
Chaînage des exceptions et empilements imbriqués
Le chaînage des exceptions se produit lorsqu'un morceau de code intercepte une exception, puis en crée et en lance une nouvelle, en faisant passer la première exception comme cause. Voici un exemple:
File: Test,java
1 public class Test {
2 int foo() {
3 return 0 / 0;
4 }
5
6 public Test() {
7 try {
8 foo();
9 } catch (ArithmeticException ex) {
10 throw new RuntimeException("A bad thing happened", ex);
11 }
12 }
13
14 public static void main(String[] args) {
15 new Test();
16 }
17 }
Lorsque la classe ci-dessus est compilée et exécutée, nous obtenons le stacktrace suivant:
Exception in thread "main" java.lang.RuntimeException: A bad thing happened
at Test.<init>(Test.java:10)
at Test.main(Test.java:15)
Caused by: java.lang.ArithmeticException: / by zero
at Test.foo(Test.java:3)
at Test.<init>(Test.java:8)
... 1 more
Le stacktrace commence par le nom de la classe, la méthode et la pile d'appels correspondant à l'exception qui (dans ce cas) a provoqué le blocage de l'application. Ceci est suivi par une ligne "Caused by:" qui signale l'exception de cause
. Le nom de la classe et le message sont signalés, suivis des cadres de pile de l'exception cause. La trace se termine par un "... N more" qui indique que les N dernières images sont les mêmes que pour l'exception précédente.
Le "Causé par:" est uniquement inclus dans la sortie lorsque la cause
l'exception principale n'est pas null
. Les exceptions peuvent être chaînées indéfiniment et, dans ce cas, le stacktrace peut avoir plusieurs traces "Caused by:".
Remarque: le mécanisme de cause
était uniquement exposé dans l'API Throwable
de Java 1.4.0. Avant cela, le chaînage des exceptions devait être implémenté par l'application en utilisant un champ d'exception personnalisé pour représenter la cause et une méthode printStackTrace
personnalisée.
Capturer une stacktrace en tant que chaîne
Parfois, une application doit être capable de capturer un stacktrace en tant que String
Java, afin de pouvoir l'utiliser à d'autres fins. L'approche générale pour ce faire consiste à créer un OutputStream
ou un Writer
temporaire qui écrit dans un tampon en mémoire et le transmet à printStackTrace(...)
.
Les bibliothèques Apache Commons et Guava fournissent des méthodes utilitaires pour capturer une stacktrace en tant que chaîne:
org.apache.commons.lang.exception.ExceptionUtils.getStackTrace(Throwable)
com.google.common.base.Throwables.getStackTraceAsString(Throwable)
Si vous ne pouvez pas utiliser de bibliothèques tierces dans votre base de code, la méthode suivante effectue la tâche:
/**
* Returns the string representation of the stack trace.
*
* @param throwable the throwable
* @return the string.
*/
public static String stackTraceToString(Throwable throwable) {
StringWriter stringWriter = new StringWriter();
throwable.printStackTrace(new PrintWriter(stringWriter));
return stringWriter.toString();
}
Notez que si vous avez l'intention d'analyser le stacktrace, il est plus simple d'utiliser getStackTrace()
et getCause()
que d'essayer d'analyser un stacktrace.
Traitement des exceptions interrompues
InterruptedException
est une bête déroutante - elle apparaît dans des méthodes apparemment anodines comme Thread.sleep()
, mais sa gestion incorrecte conduit à un code difficile à gérer qui se comporte mal dans des environnements concurrents.
À la base, si une InterruptedException
est interceptée, cela signifie que quelqu'un, quelque part, appelé Thread.interrupt()
sur le thread dans Thread.interrupt()
votre code est actuellement exécuté. Vous pourriez être enclin à dire "C'est mon code! Je ne l'interromprai jamais!" " et donc faire quelque chose comme ça:
// Bad. Don't do this.
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// disregard
}
Mais c'est exactement la mauvaise façon de gérer un événement "impossible". Si vous savez que votre application ne rencontrera jamais une InterruptedException
vous devez traiter un tel événement comme une violation grave des hypothèses de votre programme et quitter le plus rapidement possible.
La manière correcte de traiter une interruption "impossible" est comme suit:
// When nothing will interrupt your code
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new AssertionError(e);
}
Cela fait deux choses; il restaure d'abord le statut d'interruption du thread (comme si l' InterruptedException
n'avait pas été lancée en premier lieu), puis lance une AssertionError
indiquant que les invariants de base de votre application ont été violés. Si vous savez avec certitude que vous n'interromprez jamais le thread, ce code s'exécute en toute sécurité car le bloc catch
ne doit jamais être atteint.
L'utilisation de la classe Uninterruptibles
de Guava permet de simplifier ce modèle; L'appel à Uninterruptibles.sleepUninterruptibly()
interrompt l'état interrompu d'un thread jusqu'à ce que la durée de veille expire (à quel moment il est restauré pour que les appels ultérieurs inspectent et lancent leur propre InterruptedException
). Si vous savez que vous n'interromprez jamais ce code, cela évite d'avoir à envelopper vos appels de veille dans un bloc try-catch.
Plus souvent, cependant, vous ne pouvez pas garantir que votre thread ne sera jamais interrompu. En particulier, si vous écrivez du code qui sera exécuté par un Executor
ou par un autre gestionnaire de threads, il est essentiel que votre code réponde rapidement aux interruptions, sinon votre application sera bloquée, voire bloquée.
Dans de tels cas, la meilleure chose à faire est généralement de permettre à InterruptedException
de propager la pile d'appels, en ajoutant une throws InterruptedException
à chaque méthode. Cela peut sembler compliqué mais c'est en fait une propriété souhaitable - les signatures de votre méthode indiquent maintenant aux appelants qu'ils répondront rapidement aux interruptions.
// Let the caller determine how to handle the interrupt if you're unsure
public void myLongRunningMethod() throws InterruptedException {
...
}
Dans des cas limités (par exemple, lors du remplacement d'une méthode ne throw
aucune exception vérifiée), vous pouvez réinitialiser le statut interrompu sans déclencher une exception, en attendant que le code soit exécuté pour gérer l'interruption. Cela retarde le traitement de l'interruption, mais ne le supprime pas entièrement.
// Suppresses the exception but resets the interrupted state letting later code
// detect the interrupt and handle it properly.
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return ...; // your expectations are still broken at this point - try not to do more work.
}
La hiérarchie des exceptions Java - Exceptions non vérifiées et vérifiées
Toutes les exceptions Java sont des instances de classes dans la hiérarchie de classes Exception. Cela peut être représenté comme suit:
-
java.lang.Throwable
- Il s'agit de la classe de base pour toutes les classes d'exception. Ses méthodes et ses constructeurs implémentent une gamme de fonctionnalités communes à toutes les exceptions.-
java.lang.Exception
- Ceci est la super-classe de toutes les exceptions normales.- diverses classes d'exception standard et personnalisées.
-
java.lang.RuntimeException
- C'est la super-classe de toutes les exceptions normales qui sont des exceptions non vérifiées .- diverses classes d'exceptions d'exécution standard et personnalisées.
-
java.lang.Error
- Ceci est la super-classe de toutes les exceptions "d'erreur fatale".
-
Remarques:
- La distinction entre les exceptions cochées et non vérifiées est décrite ci-dessous.
- La
Throwable
,Exception
etRuntimeException
doit être traitée commeabstract
. voir Pitfall - Throwable, Exception, Error ou RuntimeException . - Les exceptions d'
Error
sont renvoyées par la machine virtuelle Java dans les situations où il serait dangereux ou imprudent qu'une application tente de récupérer. - Il serait imprudent de déclarer des sous-types personnalisés de
Throwable
. Les outils et bibliothèques Java peuvent supposer que lesError
etException
sont les seuls sous-types directs deThrowable
, et se comporter de manière incorrecte si cette hypothèse est incorrecte.
Exceptions vérifiées et non vérifiées
L'une des critiques de la prise en charge des exceptions dans certains langages de programmation est qu'il est difficile de savoir quelles exceptions une méthode ou une procédure donnée peut générer. Étant donné qu'une exception non gérée risque de provoquer le blocage d'un programme, cela peut faire des exceptions une source de fragilité.
Le langage Java résout ce problème avec le mécanisme d'exception vérifié. Tout d'abord, Java classe les exceptions en deux catégories:
Les exceptions vérifiées représentent généralement les événements anticipés qu'une application doit pouvoir gérer. Par exemple,
IOException
et ses sous-types représentent des conditions d'erreur pouvant se produire dans les opérations d'E / S. Les exemples incluent, l'ouverture du fichier échoue car un fichier ou un répertoire n'existe pas, les lectures et écritures réseau échouent car une connexion réseau a été interrompue, etc.Les exceptions non vérifiées représentent généralement des événements imprévus auxquels une application ne peut pas faire face. Ce sont généralement le résultat d'un bogue dans l'application.
(Dans ce qui suit, "renvoyé" fait référence à toute exception envoyée explicitement (par une instruction throw
), ou implicitement (dans un déréférencement ayant échoué, tapez cast, etc.). De même, "propagated" fait référence à une exception lancée dans un appel imbriqué, et non pris en compte dans cet appel. L'exemple de code ci-dessous illustre cela.)
La deuxième partie du mécanisme d'exception vérifié est qu'il existe des restrictions sur les méthodes pour lesquelles une exception vérifiée peut se produire:
- Lorsqu'une exception vérifiée est lancée ou propagée dans une méthode, elle doit soit être interceptée par la méthode, soit répertoriée dans la clause
throws
la méthode. (L'importance de lathrows
clause est décrit dans cet exemple .) - Lorsqu'une exception vérifiée est lancée ou propagée dans un bloc d'initialisation, elle doit être interceptée dans le bloc.
- Une exception vérifiée ne peut pas être propagée par un appel de méthode dans une expression d'initialisation de champ. (Il n'y a aucun moyen d'attraper une telle exception.)
En bref, une exception vérifiée doit être gérée ou déclarée.
Ces restrictions ne s'appliquent pas aux exceptions non vérifiées. Cela inclut tous les cas où une exception est lancée implicitement, car tous ces cas provoquent des exceptions non vérifiées.
Exemples d'exceptions vérifiés
Ces extraits de code sont destinés à illustrer les restrictions d'exception vérifiées. Dans chaque cas, nous affichons une version du code avec une erreur de compilation et une seconde version avec l'erreur corrigée.
// This declares a custom checked exception.
public class MyException extends Exception {
// constructors omitted.
}
// This declares a custom unchecked exception.
public class MyException2 extends RuntimeException {
// constructors omitted.
}
Le premier exemple montre comment les exceptions vérifiées peuvent être déclarées "lancées" si elles ne doivent pas être gérées dans la méthode.
// INCORRECT
public void methodThrowingCheckedException(boolean flag) {
int i = 1 / 0; // Compiles OK, throws ArithmeticException
if (flag) {
throw new MyException(); // Compilation error
} else {
throw new MyException2(); // Compiles OK
}
}
// CORRECTED
public void methodThrowingCheckedException(boolean flag) throws MyException {
int i = 1 / 0; // Compiles OK, throws ArithmeticException
if (flag) {
throw new MyException(); // Compilation error
} else {
throw new MyException2(); // Compiles OK
}
}
Le deuxième exemple montre comment traiter une exception vérifiée propagée.
// INCORRECT
public void methodWithPropagatedCheckedException() {
InputStream is = new FileInputStream("someFile.txt"); // Compilation error
// FileInputStream throws IOException or a subclass if the file cannot
// be opened. IOException is a checked exception.
...
}
// CORRECTED (Version A)
public void methodWithPropagatedCheckedException() throws IOException {
InputStream is = new FileInputStream("someFile.txt");
...
}
// CORRECTED (Version B)
public void methodWithPropagatedCheckedException() {
try {
InputStream is = new FileInputStream("someFile.txt");
...
} catch (IOException ex) {
System.out.println("Cannot open file: " + ex.getMessage());
}
}
L'exemple final montre comment gérer une exception vérifiée dans un initialiseur de champ statique.
// INCORRECT
public class Test {
private static final InputStream is =
new FileInputStream("someFile.txt"); // Compilation error
}
// CORRECTED
public class Test {
private static final InputStream is;
static {
InputStream tmp = null;
try {
tmp = new FileInputStream("someFile.txt");
} catch (IOException ex) {
System.out.println("Cannot open file: " + ex.getMessage());
}
is = tmp;
}
}
Notez que dans ce dernier cas, nous devons aussi faire face aux problèmes qui is
ne peuvent pas être attribués à plus d'une fois, mais doit également être attribué à, même dans le cas d'une exception.
introduction
Les exceptions sont des erreurs qui surviennent lors de l'exécution d'un programme. Considérons le programme Java ci-dessous qui divise deux entiers.
class Division {
public static void main(String[] args) {
int a, b, result;
Scanner input = new Scanner(System.in);
System.out.println("Input two integers");
a = input.nextInt();
b = input.nextInt();
result = a / b;
System.out.println("Result = " + result);
}
}
Maintenant, nous compilons et exécutons le code ci-dessus et voyons la sortie pour une tentative de division par zéro:
Input two integers
7 0
Exception in thread "main" java.lang.ArithmeticException: / by zero
at Division.main(Disivion.java:14)
Division par zéro est une opération invalide qui produirait une valeur qui ne peut être représentée par un entier. Java gère cela en lançant une exception . Dans ce cas, l'exception est une instance de la classe ArithmeticException .
Remarque: L'exemple de création et de lecture de traces de pile explique la signification de la sortie après les deux nombres.
L'utilitaire d'une exception est le contrôle de flux qu'il autorise. Sans exceptions, une solution typique à ce problème peut être de vérifier si b == 0
:
class Division {
public static void main(String[] args) {
int a, b, result;
Scanner input = new Scanner(System.in);
System.out.println("Input two integers");
a = input.nextInt();
b = input.nextInt();
if (b == 0) {
System.out.println("You cannot divide by zero.");
return;
}
result = a / b;
System.out.println("Result = " + result);
}
}
Ceci imprime le message You cannot divide by zero.
à la console et quitte le programme de manière harmonieuse lorsque l'utilisateur essaie de diviser par zéro. Une manière équivalente de traiter ce problème via la gestion des exceptions serait de remplacer le contrôle de flux if
par un bloc try-catch
:
...
a = input.nextInt();
b = input.nextInt();
try {
result = a / b;
}
catch (ArithmeticException e) {
System.out.println("An ArithmeticException occurred. Perhaps you tried to divide by zero.");
return;
}
...
Un bloc try est exécuté comme suit:
- Commencez à exécuter le code dans le bloc
try
. - Si une exception se produit dans le bloc try, abandonnez immédiatement et vérifiez si cette exception est interceptée par le bloc
catch
(dans ce cas, lorsque l'exception est une instance deArithmeticException
). - Si l'exception est interceptée , elle est affectée à la variable
e
et le bloccatch
est exécuté. - Si le bloc
try
oucatch
est terminé (c'est-à-dire qu'aucune exception n'est interceptée lors de l'exécution du code), continuez à exécuter le code sous le bloctry-catch
.
Il est généralement considéré comme une bonne pratique d'utiliser la gestion des exceptions dans le cadre du contrôle de flux normal d'une application où le comportement serait autrement indéfini ou inattendu. Par exemple, au lieu de retourner null
lorsqu'une méthode échoue, il est généralement préférable de lancer une exception pour que l'application utilisant la méthode puisse définir son propre contrôle de flux pour la situation via la gestion des exceptions du type illustré ci-dessus. D'une certaine manière, cela résout le problème de devoir renvoyer un type particulier, étant donné que plusieurs types d' exceptions peuvent être générés pour indiquer le problème spécifique survenu.
Pour plus de conseils sur comment et comment ne pas utiliser les exceptions, reportez-vous à la page sur les pièges Java - Utilisation des exceptions
Retour d'instructions dans le bloc try catch
Bien que ce soit une mauvaise pratique, il est possible d'ajouter plusieurs instructions de retour dans un bloc de gestion des exceptions:
public static int returnTest(int number){
try{
if(number%2 == 0) throw new Exception("Exception thrown");
else return x;
}
catch(Exception e){
return 3;
}
finally{
return 7;
}
}
Cette méthode renverra toujours 7 car le bloc finally associé au bloc try / catch est exécuté avant que tout soit renvoyé. Maintenant, comme a finalement le return 7;
, cette valeur remplace les valeurs de retour try / catch.
Si le bloc catch retourne une valeur primitive et que cette valeur primitive est modifiée ultérieurement dans le bloc finally, la valeur renvoyée dans le bloc catch sera renvoyée et les modifications du bloc finally seront ignorées.
L'exemple ci-dessous affichera "0" et non "1".
public class FinallyExample {
public static void main(String[] args) {
int n = returnTest(4);
System.out.println(n);
}
public static int returnTest(int number) {
int returnNumber = 0;
try {
if (number % 2 == 0)
throw new Exception("Exception thrown");
else
return returnNumber;
} catch (Exception e) {
return returnNumber;
} finally {
returnNumber = 1;
}
}
}
Fonctionnalités avancées des exceptions
Cet exemple couvre certaines fonctionnalités avancées et cas d'utilisation des exceptions.
Examiner le callstack par programmation
La principale utilisation des stacktraces d'exception est de fournir des informations sur une erreur d'application et son contexte, de sorte que le programmeur puisse diagnostiquer et résoudre le problème. Parfois, il peut être utilisé pour d'autres choses. Par exemple, une classe SecurityManager
peut avoir besoin d'examiner la pile d'appels pour décider si le code qui effectue un appel doit être approuvé.
Vous pouvez utiliser des exceptions pour examiner la pile d'appels par programme de la manière suivante:
Exception ex = new Exception(); // this captures the call stack
StackTraceElement[] frames = ex.getStackTrace();
System.out.println("This method is " + frames[0].getMethodName());
System.out.println("Called from method " + frames[1].getMethodName());
Il y a quelques mises en garde importantes à ce sujet:
Les informations disponibles dans un
StackTraceElement
sont limitées. Il n'y a pas plus d'informations disponibles que celles affichées parprintStackTrace
. (Les valeurs des variables locales dans le cadre ne sont pas disponibles.)Javadocs pour
getStackTrace()
indique qu'une machine virtuellegetStackTrace()
est autorisée à laisser de côté les cadres:Certaines machines virtuelles peuvent, dans certaines circonstances, omettre un ou plusieurs cadres de pile de la trace de pile. Dans le cas extrême, une machine virtuelle qui ne dispose d'aucune information de suivi de pile concernant ce produit jetable est autorisée à renvoyer un tableau de longueur nulle à partir de cette méthode.
Optimiser la construction des exceptions
Comme mentionné ailleurs, la construction d'une exception est assez coûteuse car elle implique la capture et l'enregistrement d'informations sur tous les cadres de pile du thread en cours. Parfois, nous savons que cette information ne sera jamais utilisée pour une exception donnée; par exemple, le stacktrace ne sera jamais imprimé. Dans ce cas, il existe un truc d'implémentation que nous pouvons utiliser dans une exception personnalisée pour que les informations ne soient pas capturées.
Les informations sur les trames de pile nécessaires pour les stacktraces sont capturées lorsque les constructeurs Throwable
appellent la méthode Throwable.fillInStackTrace()
. Cette méthode est public
, ce qui signifie qu'une sous-classe peut la remplacer. L'astuce consiste à remplacer la méthode héritée de Throwable
par une méthode qui ne fait rien; par exemple
public class MyException extends Exception {
// constructors
@Override
public void fillInStackTrace() {
// do nothing
}
}
Le problème avec cette approche est qu'une exception qui remplace fillInStackTrace()
ne peut jamais capturer la trace de la pile et est inutile dans les scénarios où vous en avez besoin.
Effacer ou remplacer le stacktrace
Dans certains cas, le stacktrace d'une exception créée de manière normale contient des informations incorrectes ou des informations que le développeur ne souhaite pas révéler à l'utilisateur. Pour ces scénarios, Throwable.setStackTrace
peut être utilisé pour remplacer le tableau d'objets StackTraceElement
les informations.
Par exemple, les éléments suivants peuvent être utilisés pour ignorer les informations de pile d'une exception:
exception.setStackTrace(new StackTraceElement[0]);
Suppression d'exceptions
Java 7 a introduit la construction try-with-resources et le concept associé de suppression des exceptions. Considérez l'extrait suivant:
try (Writer w = new BufferedWriter(new FileWriter(someFilename))) {
// do stuff
int temp = 0 / 0; // throws an ArithmeticException
}
Lorsque l'exception est levée, try
appelle close()
sur le w
qui FileWriter
toute sortie mise en mémoire tampon et fermera ensuite FileWriter
. Mais que se passe-t-il si une IOException
est lancée lors du vidage de la sortie?
Qu'est-ce qui se passe est que toute exception qui est levée lors du nettoyage d'une ressource est supprimée . L'exception est interceptée et ajoutée à la liste des exceptions supprimées de l'exception principale. Ensuite, le try-with-resources continuera avec le nettoyage des autres ressources. Enfin, l’exception primaire sera relancée.
Un modèle similaire se produit si une exception est générée lors de l'initialisation de la ressource ou si le bloc try
termine normalement. La première exception lancée devient la principale exception, et celles qui découlent du nettoyage sont supprimées.
Les exceptions supprimées peuvent être extraites de l'objet exception primaire en appelant getSuppressedExceptions
.
Les instructions try-finally et try-catch-finally
L'instruction try...catch...finally
combine la gestion des exceptions avec le code de nettoyage. Le bloc finally
contient le code qui sera exécuté dans toutes les circonstances. Cela les rend appropriés pour la gestion des ressources et d'autres types de nettoyage.
Essayez-enfin
Voici un exemple de la forme plus simple ( try...finally
):
try {
doSomething();
} finally {
cleanUp();
}
Le comportement du try...finally
se présente comme suit:
- Le code dans le bloc
try
est exécuté. - Si aucune exception n'a été émise dans le bloc
try
:- Le code dans le bloc
finally
est exécuté. - Si le bloc
finally
lève une exception, cette exception est propagée. - Sinon, le contrôle passe à l'instruction suivante après l'
try...finally
.
- Le code dans le bloc
- Si une exception a été émise dans le bloc try:
- Le code dans le bloc
finally
est exécuté. - Si le bloc
finally
lève une exception, cette exception est propagée. - Sinon, l'exception d'origine continue à se propager.
- Le code dans le bloc
Le code dans finally
bloc sera toujours exécuté. (Les seules exceptions sont si System.exit(int)
est appelé ou si la JVM panique.) Ainsi, un bloc finally
est le code de lieu correct qui doit toujours être exécuté; fermeture de fichiers et autres ressources ou libération de verrous.
essayer-enfin
Notre deuxième exemple montre comment catch
et finally
peuvent être utilisés ensemble. Cela montre également que le nettoyage des ressources n'est pas simple.
// This code snippet writes the first line of a file to a string
String result = null;
Reader reader = null;
try {
reader = new BufferedReader(new FileReader(fileName));
result = reader.readLine();
} catch (IOException ex) {
Logger.getLogger.warn("Unexpected IO error", ex); // logging the exception
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException ex) {
// ignore / discard this exception
}
}
}
L'ensemble complet des comportements (hypothétiques) de try...catch...finally
dans cet exemple est trop compliqué à décrire ici. La version simple est que le code dans le bloc finally
sera toujours exécuté.
En regardant cela du point de vue de la gestion des ressources:
- Nous déclarons la "ressource" (c.-à
reader
variablereader
) avant le bloctry
afin qu'elle soit possible pour le blocfinally
. - En mettant le
new FileReader(...)
, lecatch
est capable de gérer toute exceptionIOError
lors de l'ouverture du fichier. - Nous avons besoin d'un
reader.close()
dans le blocfinally
car il existe des chemins d'exception que nous ne pouvons pas intercepter ni dans le bloctry
ni dans le bloccatch
. - Cependant, une exception pouvant avoir été émise avant l'initialisation du
reader
, nous avons également besoin d'un testnull
explicite. - Enfin, l’appel
reader.close()
pourrait (hypothétiquement) lancer une exception. Nous ne nous soucions pas de cela, mais si nous n'attrapons pas l'exception à la source, nous aurons besoin de la traiter plus haut dans la pile des appels.
Java 7 et les versions ultérieures fournissent une syntaxe alternative à l'utilisation des ressources qui simplifie considérablement le nettoyage des ressources.
La clause 'throws' dans une déclaration de méthode
Le mécanisme d' exception vérifié de Java nécessite que le programmeur déclare que certaines méthodes peuvent générer des exceptions vérifiées spécifiées. Ceci est fait à l' aide de la throws
clause. Par exemple:
public class OddNumberException extends Exception { // a checked exception
}
public void checkEven(int number) throws OddNumberException {
if (number % 2 != 0) {
throw new OddNumberException();
}
}
throws OddNumberException
déclenche une throws OddNumberException
indiquant qu'un appel à checkEven
peut checkEven
une exception de type OddNumberException
.
Une throws
clause peut déclarer une liste des types, et peut inclure des exceptions non vérifiées ainsi que les exceptions vérifiées.
public void checkEven(Double number)
throws OddNumberException, ArithmeticException {
if (!Double.isFinite(number)) {
throw new ArithmeticException("INF or NaN");
} else if (number % 2 != 0) {
throw new OddNumberException();
}
}
Quel est l'intérêt de déclarer des exceptions non vérifiées comme levées?
La throws
clause dans une déclaration de méthode a deux objectifs:
Il indique au compilateur quelles exceptions sont générées pour que le compilateur puisse signaler les exceptions non détectées (vérifiées) en tant qu'erreurs.
Il indique à un programmeur qui écrit du code qui appelle la méthode quelles exceptions attendre. À cette fin, il est souvent logique d'inclure des exceptions non vérifiées dans une liste de
throws
.
Remarque: la liste de throws
est également utilisée par l’outil javadoc lors de la génération de la documentation de l’API, ainsi que par les astuces de la méthode typique de texte de survol de l’EDI.
Les jetons et la méthode
Les throws
formes clause partie de la signature d'une méthode dans le but de passer outre la méthode. Une méthode de substitution peut être déclarée avec le même ensemble d'exceptions vérifiées que celles générées par la méthode substituée ou avec un sous-ensemble. Toutefois, la méthode de remplacement ne peut pas ajouter des exceptions vérifiées supplémentaires. Par exemple:
@Override
public void checkEven(int number) throws NullPointerException // OK—NullPointerException is an unchecked exception
...
@Override
public void checkEven(Double number) throws OddNumberException // OK—identical to the superclass
...
class PrimeNumberException extends OddNumberException {}
class NonEvenNumberException extends OddNumberException {}
@Override
public void checkEven(int number) throws PrimeNumberException, NonEvenNumberException // OK—these are both subclasses
@Override
public void checkEven(Double number) throws IOExcepion // ERROR
La raison de cette règle est que si une méthode de substitution peut lancer une exception vérifiée que la méthode surchargée ne peut pas lancer, cela pourrait casser la substituabilité du type.