Exceptions en Java, partie 1: principes de base de la gestion des exceptions

Les exceptions Java sont des types de bibliothèques et des fonctionnalités de langage utilisées pour représenter et gérer l'échec du programme. Si vous souhaitez comprendre comment l'échec est représenté dans le code source, vous êtes au bon endroit. En plus d'un aperçu des exceptions Java, je vais vous familiariser avec les fonctionnalités du langage Java pour lancer des objets, essayer du code qui peut échouer, attraper des objets lancés et nettoyer votre code Java après qu'une exception a été levée.

Dans la première moitié de ce didacticiel, vous découvrirez les fonctionnalités de base du langage et les types de bibliothèques qui existent depuis Java 1.0. Dans la seconde moitié, vous découvrirez les fonctionnalités avancées introduites dans les versions Java les plus récentes.

Notez que les exemples de code de ce didacticiel sont compatibles avec JDK 12.

télécharger Obtenir le code Téléchargez le code source des exemples d'applications dans ce didacticiel. Créé par Jeff Friesen pour JavaWorld.

Quelles sont les exceptions Java?

Un échec se produit lorsqu'un comportement normal d'un programme Java est interrompu par un comportement inattendu. Cette divergence est connue comme une exception . Par exemple, un programme essaie d'ouvrir un fichier pour lire son contenu, mais le fichier n'existe pas. Java classe les exceptions en quelques types, considérons donc chacun d'eux.

Exceptions vérifiées

Java classe les exceptions résultant de facteurs externes (comme un fichier manquant) comme des exceptions vérifiées . Le compilateur Java vérifie que ces exceptions sont soit gérées (corrigées) là où elles se produisent, soit documentées pour être gérées ailleurs.

Gestionnaires d'exceptions

Un gestionnaire d'exceptions est une séquence de code qui gère une exception. Il interroge le contexte - ce qui signifie qu'il lit les valeurs enregistrées à partir des variables qui étaient dans la portée au moment où l'exception s'est produite - puis utilise ce qu'il apprend pour restaurer le programme Java à un flux de comportement normal. Par exemple, un gestionnaire d'exceptions peut lire un nom de fichier enregistré et inviter l'utilisateur à remplacer le fichier manquant.

Exceptions d'exécution (non contrôlées)

Supposons qu'un programme tente de diviser un entier par un entier 0. Cette impossibilité illustre un autre type d'exception, à savoir une exception d'exécution . Contrairement aux exceptions vérifiées, les exceptions d'exécution proviennent généralement d'un code source mal écrit et doivent donc être corrigées par le programmeur. Étant donné que le compilateur ne vérifie pas que les exceptions d'exécution sont gérées ou documentées pour être gérées ailleurs, vous pouvez considérer une exception d'exécution comme une exception non vérifiée .

À propos des exceptions d'exécution

Vous pouvez modifier un programme pour gérer une exception d'exécution, mais il est préférable de corriger le code source. Les exceptions d'exécution proviennent souvent du passage d'arguments invalides aux méthodes d'une bibliothèque; le code d'appel bogué devrait être corrigé.

les erreurs

Certaines exceptions sont très graves car elles mettent en péril la capacité d'un programme à poursuivre l'exécution. Par exemple, un programme tente d'allouer de la mémoire à partir de la JVM mais il n'y a pas assez de mémoire libre pour satisfaire la demande. Une autre situation grave se produit lorsqu'un programme tente de charger un fichier de classe via un Class.forName()appel de méthode, mais que le fichier de classe est corrompu. Ce type d'exception est appelé erreur . Vous ne devez jamais essayer de gérer les erreurs vous-même, car la machine virtuelle Java pourrait ne pas être en mesure de récupérer.

Exceptions dans le code source

Une exception peut être représentée dans le code source sous la forme d'un code d'erreur ou d'un objet . Je vais vous présenter les deux et vous montrer pourquoi les objets sont supérieurs.

Codes d'erreur par rapport aux objets

Les langages de programmation tels que C utilisent des codes d'erreur basés sur des nombres entiers pour représenter l'échec et les raisons de l'échec, c'est-à-dire les exceptions. Voici quelques exemples:

if (chdir("C:\\temp")) printf("Unable to change to temp directory: %d\n", errno); FILE *fp = fopen("C:\\temp\\foo"); if (fp == NULL) printf("Unable to open foo: %d\n", errno);

La fonction C chdir()(changement de répertoire) renvoie un entier: 0 en cas de succès ou -1 en cas d'échec. De même, la fonction C fopen()(fichier ouvert) renvoie un pointeur non nul (adresse entière) vers une FILEstructure en cas de succès ou un pointeur nul (0) (représenté par une constante NULL) en cas d'échec. Dans les deux cas, pour identifier l'exception à l'origine de l'échec, vous devez lire le errnocode d'erreur basé sur un entier de la variable globale .

Les codes d'erreur présentent certains problèmes:

  • Les nombres entiers n'ont pas de sens; ils ne décrivent pas les exceptions qu'ils représentent. Par exemple, que signifie 6?
  • L'association d'un contexte à un code d'erreur est délicate. Par exemple, vous souhaiterez peut-être afficher le nom du fichier qui n'a pas pu être ouvert, mais où allez-vous stocker le nom du fichier?
  • Les entiers sont arbitraires, ce qui peut prêter à confusion lors de la lecture du code source. Par exemple, spécifier if (!chdir("C:\\temp"))( !signifie NON) au lieu de if (chdir("C:\\temp"))tester l'échec est plus clair. Cependant, 0 a été choisi pour indiquer le succès et if (chdir("C:\\temp"))doit donc être spécifié pour tester l'échec.
  • Les codes d'erreur sont trop faciles à ignorer, ce qui peut conduire à un code bogué. Par exemple, le programmeur pourrait spécifier chdir("C:\\temp");et ignorer la if (fp == NULL)vérification. De plus, le programmeur n'a pas besoin d'examiner errno. En ne testant pas d'échec, le programme se comporte de manière erratique lorsque l'une des fonctions renvoie un indicateur d'échec.

Pour résoudre ces problèmes, Java a adopté une nouvelle approche de la gestion des exceptions. En Java, nous combinons des objets qui décrivent des exceptions avec un mécanisme basé sur le lancement et la capture de ces objets. Voici quelques avantages de l'utilisation d'objets par rapport au code d'erreur pour désigner les exceptions:

  • Un objet peut être créé à partir d'une classe avec un nom significatif. Par exemple, FileNotFoundException(dans le java.iopackage) est plus significatif que 6.
  • Les objets peuvent stocker le contexte dans divers champs. Par exemple, vous pouvez stocker un message, le nom du fichier qui n'a pas pu être ouvert, la position la plus récente où une opération d'analyse a échoué et / ou d'autres éléments dans les champs d'un objet.
  • Vous n'utilisez pas d' ifinstructions pour tester l'échec. Au lieu de cela, les objets d'exception sont renvoyés à un gestionnaire distinct du code du programme. En conséquence, le code source est plus facile à lire et moins susceptible d'être bogué.

Throwable et ses sous-classes

Java fournit une hiérarchie de classes qui représentent différents types d'exceptions. Ces classes sont enracinées dans le java.langde paquet Throwablede classe, ainsi que ses Exception, RuntimeExceptionet les Errorsous - classes.

Throwableest la superclasse ultime en matière d'exceptions. Seuls les objets créés à partir de Throwableet de ses sous-classes peuvent être lancés (et ensuite interceptés). Ces objets sont connus sous le nom de jetables .

Un Throwableobjet est associé à un message détaillé qui décrit une exception. Plusieurs constructeurs, dont la paire décrite ci-dessous, sont fournis pour créer un Throwableobjet avec ou sans message de détail:

  • Throwable () crée un Throwablemessage sans détail. Ce constructeur convient aux situations où il n'y a pas de contexte. Par exemple, vous voulez seulement savoir qu'une pile est vide ou pleine.
  • Throwable (String message) crée un Throwableavec messagecomme message détaillé. Ce message peut être envoyé à l'utilisateur et / ou enregistré.

Throwablefournit la String getMessage()méthode pour renvoyer le message détaillé. Il fournit également des méthodes utiles supplémentaires, que je présenterai plus tard.

La classe Exception

Throwablea deux sous-classes directes. L'une de ces sous-classes est Exception, qui décrit une exception résultant d'un facteur externe (comme une tentative de lecture à partir d'un fichier inexistant). Exceptiondéclare les mêmes constructeurs (avec des listes de paramètres identiques) que Throwable, et chaque constructeur invoque son Throwablehomologue. Exceptionhérite Throwabledes méthodes de; il ne déclare aucune nouvelle méthode.

Java fournit de nombreuses classes d'exception directement sous-classe Exception. Voici trois exemples:

  • CloneNotSupportedException signale une tentative de clonage d'un objet dont la classe n'implémente pas l' Cloneableinterface. Les deux types sont dans le java.langpackage.
  • IOException signale qu'une sorte d'échec d'E / S s'est produit. Ce type se trouve dans le java.iopackage.
  • ParseException signale qu'un échec s'est produit lors de l'analyse du texte. Ce type peut être trouvé dans le java.textpackage.

Notez que chaque Exceptionnom de sous-classe se termine par le mot Exception. Cette convention permet d'identifier facilement l'objectif de la classe.

Vous sous-classerez généralement Exception(ou l'une de ses sous-classes) avec vos propres classes d'exception (dont les noms devraient se terminer par Exception). Voici quelques exemples de sous-classes personnalisées:

public class StackFullException extends Exception { } public class EmptyDirectoryException extends Exception { private String directoryName; public EmptyDirectoryException(String message, String directoryName) { super(message); this.directoryName = directoryName; } public String getDirectoryName() { return directoryName; } }

Le premier exemple décrit une classe d'exception qui ne nécessite pas de message détaillé. C'est le constructeur noargument par défaut qui invoque Exception(), qui invoque Throwable().

Le deuxième exemple décrit une classe d'exception dont le constructeur nécessite un message détaillé et le nom du répertoire vide. Le constructeur invoque Exception(String message), qui invoque Throwable(String message).

Les objets instanciés à partir de Exceptionou de l'une de ses sous-classes (sauf pour RuntimeExceptionou l'une de ses sous-classes) sont des exceptions vérifiées.

La classe RuntimeException

Exceptionest directement sous-classée par RuntimeException, qui décrit une exception résultant très probablement d'un code mal écrit. RuntimeExceptiondéclare les mêmes constructeurs (avec des listes de paramètres identiques) que Exception, et chaque constructeur invoque son Exceptionhomologue. RuntimeExceptionhérite Throwabledes méthodes de. Il ne déclare aucune nouvelle méthode.

Java fournit de nombreuses classes d'exception directement sous-classe RuntimeException. Les exemples suivants sont tous membres du java.langpackage:

  • ArithmeticException signale une opération arithmétique illégale, telle qu'une tentative de division d'un entier par 0.
  • IllegalArgumentException signale qu'un argument non conforme ou inapproprié a été passé à une méthode.
  • NullPointerException signale une tentative d'appeler une méthode ou d'accéder à un champ d'instance via la référence null.

Les objets instanciés à partir de RuntimeExceptionou l'une de ses sous-classes sont des exceptions non vérifiées .

La classe Error

Throwable's other direct subclass is Error, which describes a serious (even abnormal) problem that a reasonable application should not try to handle--such as running out of memory, overflowing the JVM's stack, or attempting to load a class that cannot be found. Like Exception, Error declares identical constructors to Throwable, inherits Throwable's methods, and doesn't declare any of its own methods.

You can identify Error subclasses from the convention that their class names end with Error. Examples include OutOfMemoryError, LinkageError, and StackOverflowError. All three types belong to the java.lang package.

Throwing exceptions

A C library function notifies calling code of an exception by setting the global errno variable to an error code and returning a failure code. In contrast, a Java method throws an object. Knowing how and when to throw exceptions is an essential aspect of effective Java programming. Throwing an exception involves two basic steps:

  1. Use the throw statement to throw an exception object.
  2. Use the throws clause to inform the compiler.

Later sections will focus on catching exceptions and cleaning up after them, but first let's learn more about throwables.

The throw statement

Java provides the throw statement to throw an object that describes an exception. Here's the syntax of the throw statement :

throw throwable;

The object identified by throwable is an instance of Throwable or any of its subclasses. However, you usually only throw objects instantiated from subclasses of Exception or RuntimeException. Here are a couple of examples:

throw new FileNotFoundException("unable to find file " + filename); throw new IllegalArgumentException("argument passed to count is less than zero");

The throwable is thrown from the current method to the JVM, which checks this method for a suitable handler. If not found, the JVM unwinds the method-call stack, looking for the closest calling method that can handle the exception described by the throwable. If it finds this method, it passes the throwable to the method's handler, whose code is executed to handle the exception. If no method is found to handle the exception, the JVM terminates with a suitable message.

The throws clause

You need to inform the compiler when you throw a checked exception out of a method. Do this by appending a throws clause to the method's header. This clause has the following syntax:

throws checkedExceptionClassName (, checkedExceptionClassName)*

A throws clause consists of keyword throws followed by a comma-separated list of the class names of checked exceptions thrown out of the method. Here is an example:

public static void main(String[] args) throws ClassNotFoundException { if (args.length != 1) { System.err.println("usage: java ... classfile"); return; } Class.forName(args[0]); }

This example attempts to load a classfile identified by a command-line argument. If Class.forName() cannot find the classfile, it throws a java.lang.ClassNotFoundException object, which is a checked exception.

Checked exception controversy

The throws clause and checked exceptions are controversial. Many developers hate being forced to specify throws or handle the checked exception(s). Learn more about this from my Are checked exceptions good or bad? blog post.