Mon exemple n'a révélé qu'une seule cause. Les exceptions générées par des applications réelles non triviales peuvent contenir de longues chaînes de nombreuses causes. Vous pouvez accéder à ces causes en utilisant une boucle telle que la suivante :
catch (Exception exc)
{
Throwable t = exc.getCause();
while (t != null)
{
System.out.println
t = t.getCause();
}
}
Essayer avec des ressources
Les applications Java accèdent souvent à des fichiers, des connexions de base de données, des sockets et d'autres ressources qui dépendent de ressources système associées (par exemple, des handles de fichiers). La rareté des ressources système implique qu'elles doivent éventuellement être libérées, même lorsqu'une exception se produit. Lorsque les ressources système ne sont pas libérées, l'application finit par échouer lorsqu'elle tente d'acquérir d'autres ressources, car aucune autre ressource système associée n'est disponible.
Dans mon introduction aux bases de la gestion des exceptions, j'ai mentionné que les ressources (en fait, les ressources système dont elles dépendent) sont libérées dans un finally bloquer. Cela peut conduire à un code standard fastidieux, tel que le code de fermeture de fichier qui apparaît ci-dessous :
finally
{
if (fis != null)
try
{
fis.close();
}
catch (IOException ioe)
{
// ignore exception
}
if (fos != null)
try
{
fos.close();
}
catch (IOException ioe)
{
// ignore exception
}
}
Non seulement ce code standard ajoute du volume à un fichier de classe, mais la fastidieuse écriture de celui-ci peut conduire à un bug, voire même à l'échec de la fermeture d'un fichier. JDK 7 a introduit try-with-resources pour surmonter ce problème.
Les bases de l'essai avec des ressources
La construction try-with-resources ferme automatiquement les ressources ouvertes lorsque l'exécution quitte la portée dans laquelle elles ont été ouvertes et utilisées, qu'une exception soit levée ou non à partir de cette portée. Cette construction a la syntaxe suivante :
try (resource acquisitions)
{
// resource usage
}
Le try Le mot-clé est paramétré par une liste séparée par des points-virgules d'instructions d'acquisition de ressources, où chaque instruction acquiert une ressource. Chaque ressource acquise est disponible pour le corps du try bloc et est automatiquement fermé lorsque l'exécution quitte ce corps. Contrairement à un bloc normal try déclaration, try-with-resources ne nécessite pas catch des blocs et/ou un finally bloc à suivre try()bien qu'ils puissent être spécifiés.
Considérez l’exemple orienté fichier suivant :
try (FileInputStream fis = new FileInputStream("abc.txt"))
{
// Do something with fis and the underlying file resource.
}
Dans cet exemple, un flux d’entrée vers une ressource de fichier sous-jacente (abc.txt) est acquis. Le try le bloc fait quelque chose avec cette ressource, et le flux (et le fichier) est fermé à la sortie du try bloc.
Utilisation de « var » avec « try-with-resources »
JDK 10 a introduit la prise en charge de varun identifiant avec une signification particulière (c'est-à-dire pas un mot-clé). Vous pouvez utiliser var avec try-with-resources pour réduire le code standard. Par exemple, vous pouvez simplifier l'exemple précédent comme suit :
try (var fis = new FileInputStream("abc.txt"))
{
// Do something with fis and the underlying file resource.
}
Copie d'un fichier dans un contexte try-with-resources
Dans l'article précédent, j'ai extrait le copy() méthode d'une application de copie de fichier. Cette méthode finally Le bloc contient le modèle de fermeture de fichier présenté précédemment. Le listing 8 améliore cette méthode en utilisant try-with-resources pour gérer le nettoyage.
Liste 8. Copy.java
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class Copy
{
public static void main(String() args)
{
if (args.length != 2)
{
System.err.println("usage: java Copy srcfile dstfile");
return;
}
try
{
copy(args(0), args(1));
}
catch (IOException ioe)
{
System.err.println("I/O error: " + ioe.getMessage());
}
}
static void copy(String srcFile, String dstFile) throws IOException
{
try (FileInputStream fis = new FileInputStream(srcFile);
FileOutputStream fos = new FileOutputStream(dstFile))
{
int c;
while ((c = fis.read()) != -1)
fos.write(c);
}
}
}
copy() utilise try-with-resources pour gérer les ressources des fichiers source et de destination. Le code entre crochets ronds qui suit try tente de créer des flux d'entrée et de sortie de fichiers vers ces fichiers. En supposant que cela réussisse, son corps s'exécute, copiant le fichier source vers le fichier de destination.
Qu'une exception soit levée ou non, try-with-resources garantit que les deux fichiers sont fermés lorsque l'exécution quitte le try bloc. Étant donné que le code de fermeture de fichier standard qui a été montré précédemment n'est pas nécessaire, le Listing 8 copy() la méthode est beaucoup plus simple et plus facile à lire.
Concevoir des classes de ressources pour prendre en charge try-with-resources
La construction try-with-resources nécessite qu'une classe de ressources implémente le java.lang.Closeable interface ou le JDK 7 introduit java.lang.AutoCloseable superinterface. Classes pré-Java 7 comme java.io.FileInputStream mis en œuvre Closeablequi offre une void close() méthode qui lance IOException ou une sous-classe.
À partir de Java 7, les classes peuvent implémenter AutoCloseabledont le single void close() la méthode peut lancer java.lang.Exception ou une sous-classe. throws La clause a été élargie pour tenir compte des situations dans lesquelles vous pourriez avoir besoin d'ajouter close() méthodes qui peuvent générer des exceptions en dehors de la IOException hiérarchie; par exemple, java.sql.SQLException.
La liste 9 présente une CustomARM application qui vous montre comment configurer une classe de ressources personnalisée afin que vous puissiez l'utiliser dans un contexte d'essai avec des ressources.
Liste 9. CustomARM.java
public class CustomARM
{
public static void main(String() args)
{
try (USBPort usbp = new USBPort())
{
System.out.println(usbp.getID());
}
catch (USBException usbe)
{
System.err.println(usbe.getMessage());
}
}
}
class USBPort implements AutoCloseable
{
USBPort() throws USBException
{
if (Math.random() < 0.5)
throw new USBException("unable to open port");
System.out.println("port open");
}
@Override
public void close()
{
System.out.println("port close");
}
String getID()
{
return "some ID";
}
}
class USBException extends Exception
{
USBException(String msg)
{
super(msg);
}
}
La liste 9 simule un port USB dans lequel vous pouvez ouvrir et fermer le port et renvoyer l'ID du port. J'ai utilisé Math.random() dans le constructeur afin que vous puissiez observer try-with-resources lorsqu'une exception est levée ou non levée.
Compilez cette liste et exécutez l'application. Si le port est ouvert, vous verrez le résultat suivant :
port open
some ID
port close
Si le port est fermé, vous pourriez voir ceci :
unable to open port
Suppression des exceptions dans try-with-resources
Si vous avez une certaine expérience en programmation, vous avez peut-être remarqué un problème potentiel avec try-with-resources : supposons que try Le bloc génère une exception. Cette construction répond en invoquant l'objet ressource close() méthode pour fermer la ressource. Cependant, la close() La méthode peut également générer une exception.
Quand close() génère une exception (par exemple, FileInputStream's void close() la méthode peut lancer IOException), cette exception masque ou cache l'exception d'origine. Il semble que l'exception d'origine soit perdue.
En fait, ce n'est pas le cas : try-with-resources supprime close()'s exception. Il ajoute également l'exception au tableau d'exceptions supprimées de l'exception d'origine en appelant java.lang.Throwable's void addSuppressed(Throwable exception) méthode.
La liste 10 présente une SupExDemo application qui montre comment réprimer une exception dans un contexte d'essai avec des ressources.
Liste 10. SupExDemo.java
import java.io.Closeable;
import java.io.IOException;
public class SupExDemo implements Closeable
{
@Override
public void close() throws IOException
{
System.out.println("close() invoked");
throw new IOException("I/O error in close()");
}
public void doWork() throws IOException
{
System.out.println("doWork() invoked");
throw new IOException("I/O error in work()");
}
public static void main(String() args) throws IOException
{
try (SupExDemo supexDemo = new SupExDemo())
{
supexDemo.doWork();
}
catch (IOException ioe)
{
ioe.printStackTrace();
System.out.println();
System.out.println(ioe.getSuppressed()(0));
}
}
}
Liste 10 doWork() la méthode lance un IOException pour simuler une sorte d'erreur d'E/S. close() la méthode lance également le IOExceptionqui est supprimé pour ne pas masquer doWork()L'exception de.
Le catch le bloc accède à l'exception supprimée (lancée depuis close()) en invoquant Throwable's Throwable() getSuppressed() méthode qui renvoie un tableau d'exceptions supprimées. Seul le premier élément est accessible car une seule exception est supprimée.
Compilez le listing 10 et exécutez l'application. Vous devriez observer le résultat suivant :
doWork() invoked
close() invoked
java.io.IOException: I/O error in work()
at SupExDemo.doWork(SupExDemo.java:16)
at SupExDemo.main(SupExDemo.java:23)
Suppressed: java.io.IOException: I/O error in close()
at SupExDemo.close(SupExDemo.java:10)
at SupExDemo.main(SupExDemo.java:24)
java.io.IOException: I/O error in close()
Blocs de capture multiples (multi-catch)
À partir du JDK 7, il est possible de codifier un seul catch bloc qui intercepte plus d'un type d'exception. Le but de ce multi-prise La fonctionnalité est de réduire la duplication du code et de réduire la tentation d'attraper des exceptions trop larges (par exemple, catch (Exception e)).
Supposons que vous ayez développé une application qui vous donne la flexibilité de copier des données dans une base de données ou un fichier. La liste 11 présente une CopyToDatabaseOrFile classe qui simule cette situation et démontre le problème avec catch duplication de code de bloc.
Liste 11. CopyToDatabaseOrFile.java
import java.io.IOException;
import java.sql.SQLException;
public class CopyToDatabaseOrFile
{
public static void main(String() args)
{
try
{
copy();
}
catch (IOException ioe)
{
System.out.println(ioe.getMessage());
// additional handler code
}
catch (SQLException sqle)
{
System.out.println(sqle.getMessage());
// additional handler code that's identical to the previous handler's
// code
}
}
static void copy() throws IOException, SQLException
{
if (Math.random() < 0.5)
throw new IOException("cannot copy to file");
else
throw new SQLException("cannot copy to database");
}
}
JDK 7 résout ce problème de duplication de code en vous permettant de spécifier plusieurs types d'exceptions dans un catch bloc où chaque type successif est séparé de son prédécesseur en plaçant une barre verticale (|) entre ces types :
try
{
copy();
}
catch (IOException | SQLException iosqle)
{
System.out.println(iosqle.getMessage());
}
Maintenant, quand copy() lance une exception, l'exception sera interceptée et gérée par le catch bloc.
Lorsque plusieurs types d'exceptions sont répertoriés dans un catch l'en-tête du bloc, le paramètre est implicitement considéré comme final. Par conséquent, vous ne pouvez pas modifier la valeur du paramètre. Par exemple, vous ne pouvez pas modifier la référence stockée dans le fragment de code précédent iosqle paramètre.
Dernier lancer
À partir de JDK 7, le compilateur Java est capable d'analyser les exceptions renvoyées plus précisément que dans les versions Java précédentes. Cette fonctionnalité ne fonctionne que lorsqu'aucune affectation n'est effectuée sur une exception renvoyée catch paramètre de bloc, qui est considéré comme étant efficace final. Lorsqu'un précédent try block lève une exception qui est un supertype/sous-type du type du paramètre, le compilateur lève le type réel de l'exception interceptée au lieu de lever le type du paramètre (comme cela était fait dans les versions Java précédentes).
Le but de ceci relance finale La fonctionnalité est de faciliter l'ajout d'un try–catch Instruction autour d'un bloc de code pour intercepter, traiter et relancer une exception sans affecter l'ensemble d'exceptions déterminées statiquement levées à partir du code. De plus, cette fonctionnalité vous permet de fournir un gestionnaire d'exceptions commun pour gérer partiellement l'exception à proximité de l'endroit où elle est levée, et de fournir des gestionnaires plus précis ailleurs qui gèrent l'exception relancée. Considérez la liste 12.
Liste 12. MonitorEngine.java
class PressureException extends Exception
{
PressureException(String msg)
{
super(msg);
}
}
class TemperatureException extends Exception
{
TemperatureException(String msg)
{
super(msg);
}
}
public class MonitorEngine
{
public static void main(String() args)
{
try
{
monitor();
}
catch (Exception e)
{
if (e instanceof PressureException)
System.out.println("correcting pressure problem");
else
System.out.println("correcting temperature problem");
}
}
static void monitor() throws Exception
{
try
{
if (Math.random() < 0.1)
throw new PressureException("pressure too high");
else
if (Math.random() > 0.9)
throw new TemperatureException("temperature too high");
else
System.out.println("all is well");
}
catch (Exception e)
{
System.out.println(e.getMessage());
throw e;
}
}
}
Le listing 12 simule le test d'un moteur de fusée expérimental pour voir si la pression ou la température du moteur dépasse un seuil de sécurité. Il effectue ce test via le monitor() méthode d'aide.
monitor()'s try lancers de blocs PressureException lorsqu'il détecte une pression extrême et lance TemperatureException lorsqu'il détecte une température extrême. (Comme il s'agit uniquement d'une simulation, des nombres aléatoires sont utilisés — le java.lang.Math classe static double random() (la méthode renvoie un nombre aléatoire compris entre 0,0 et (presque) 1,0.) try le bloc est suivi d'un catch bloc conçu pour gérer partiellement l'exception en affichant un message d'avertissement. Cette exception est ensuite renvoyée afin que monitor()La méthode d'appel de peut terminer la gestion de l'exception.
Avant JDK 7, vous ne pouviez pas spécifier PressureException et TemperatureException dans monitor()'s throws clause parce que le catch blocs e le paramètre est de type java.lang.Exception et la relance d'une exception était traitée comme la relance du type du paramètre. JDK 7 et les JDK successeurs ont permis de spécifier ces types d'exception dans le throws clause car leurs compilateurs peuvent déterminer que l'exception levée par throw e vient de la try bloquer, et seulement PressureException et TemperatureException peut être lancé à partir de ce bloc.
Parce que vous pouvez désormais spécifier static void monitor() throws PressureException, TemperatureExceptionvous pouvez fournir des gestionnaires plus précis où monitor() est appelé, comme le montre le fragment de code suivant :
try
{
monitor();
}
catch (PressureException pe)
{
System.out.println("correcting pressure problem");
}
catch (TemperatureException te)
{
System.out.println("correcting temperature problem");
}
En raison de la vérification de type améliorée offerte par la re-throw finale dans JDK 7, le code source compilé sous des versions précédentes de Java peut ne pas être compilé sous des JDK ultérieurs. Par exemple, considérez la liste 13.
Liste 13. BreakageDemo.java
class SuperException extends Exception
{
}
class SubException1 extends SuperException
{
}
class SubException2 extends SuperException
{
}
public class BreakageDemo
{
public static void main(String() args) throws SuperException
{
try
{
throw new SubException1();
}
catch (SuperException se)
{
try
{
throw se;
}
catch (SubException2 se2)
{
}
}
}
}
Le listing 13 est compilé sous JDK 6 et versions antérieures. Cependant, il ne parvient pas à compiler sous les JDK ultérieurs, dont les compilateurs détectent et signalent le fait que SubException2 n'est jamais jeté dans le corps du correspondant try déclaration. Il s'agit d'un petit problème que vous ne rencontrerez probablement pas dans vos programmes, et d'un compromis intéressant pour que le compilateur détecte une source de code redondant. La suppression des redondances permet d'obtenir un code plus propre et des fichiers de classe plus petits.
StackWalker et l'API StackWalking
Obtention d'une trace de pile via Threadou Throwable's getStackTrace() Cette méthode est coûteuse et a un impact sur les performances. La JVM capture avec empressement un instantané de l'ensemble de la pile (à l'exception des trames de pile cachées), même lorsque vous n'avez besoin que des premières trames. De plus, votre code devra probablement traiter des trames qui ne présentent aucun intérêt, ce qui prend également du temps. Enfin, vous ne pouvez pas accéder à la pile réelle java.lang.Class instance de la classe qui a déclaré la méthode représentée par un cadre de pile. Pour accéder à cette Class objet, vous êtes obligé d'étendre java.lang.SecurityManager pour accéder au protected getClassContext() méthode qui renvoie la pile d'exécution actuelle sous forme de tableau de Class objets.
JDK 9 a introduit le java.lang.StackWalker classe (avec ses imbriquées Option classe et StackFrame interface) comme une alternative plus performante et plus capable de StackTraceElement (plus SecurityManager). Pour en savoir plus sur StackWalker et ses types associés, voir mon introduction à l'API StackWalking.
En conclusion
Cet article complète mon introduction en deux parties au framework de gestion des exceptions de Java. Vous souhaiterez peut-être renforcer votre compréhension de ce framework en consultant la leçon sur les exceptions d'Oracle dans les didacticiels Java. Une autre bonne ressource est le didacticiel sur la gestion des exceptions en Java de Baeldung, qui inclut des anti-modèles dans la gestion des exceptions.



GIPHY App Key not set. Please check settings