Gestion des exceptions en Java

Introduction

Une exception est un événement d’erreur qui peut se produire pendant l’exécution d’un programme et perturber son flux normal. Java offre une manière robuste et orientée objet de gérer les scénarios d’exception, connue sous le nom de gestion des exceptions en Java.

Les exceptions en Java peuvent survenir dans différentes situations, telles qu’une entrée de données incorrecte par l’utilisateur, une défaillance matérielle, une défaillance de la connexion réseau ou un serveur de base de données hors service. Le code qui spécifie quoi faire dans des scénarios d’exception spécifiques est appelé gestion des exceptions.

Lancer et Attraper les Exceptions

Java crée un objet exception lorsqu’une erreur se produit pendant l’exécution d’une instruction. L’objet exception contient beaucoup d’informations de débogage telles que la hiérarchie des méthodes, le numéro de ligne où l’exception s’est produite et le type d’exception.

Si une exception se produit dans une méthode, le processus de création de l’objet d’exception et de sa remise à l’environnement d’exécution est appelé “lancer l’exception”. Le flux normal du programme s’arrête et l’Environnement d’Exécution Java (JRE) tente de trouver le gestionnaire pour l’exception. Le gestionnaire d’exception est le bloc de code qui peut traiter l’objet d’exception.

  • La logique pour trouver le gestionnaire d’exception commence par la recherche dans la méthode où l’erreur s’est produite.
  • Si aucun gestionnaire approprié n’est trouvé, alors il passera à la méthode appelante.
  • Et ainsi de suite.

Donc, si la pile d’appels de méthode est A->B->C et qu’une exception est levée dans la méthode C, alors la recherche du gestionnaire approprié se déplacera de C->B->A.

Si un gestionnaire d’exception approprié est trouvé, l’objet d’exception est transmis au gestionnaire pour le traiter. Le gestionnaire est dit “attraper l’exception”. S’il n’y a pas de gestionnaire d’exception approprié, alors le programme se termine et imprime des informations sur l’exception dans la console.

Le cadre de gestion des exceptions Java est utilisé pour gérer uniquement les erreurs d’exécution. Les erreurs de compilation doivent être corrigées par le développeur écrivant le code sinon le programme ne s’exécutera pas.

Mots-clés de Gestion des Exceptions Java

Java fournit des mots-clés spécifiques à des fins de gestion des exceptions.

  1. throw – Nous savons que si une erreur se produit, un objet exception est créé, puis le runtime Java commence à les gérer. Parfois, nous voulons générer des exceptions explicitement dans notre code. Par exemple, dans un programme d’authentification utilisateur, nous devrions générer des exceptions pour les clients si le mot de passe est null. Le mot-clé throw est utilisé pour lancer des exceptions vers le runtime afin de les gérer.

  2. throws – Lorsque nous lançons une exception dans une méthode et que nous ne la gérons pas, nous devons utiliser le mot-clé throws dans la signature de la méthode pour informer le programme appelant des exceptions qui pourraient être lancées par la méthode. La méthode appelante peut gérer ces exceptions ou les propager à sa méthode appelante en utilisant le mot-clé throws. Nous pouvons fournir plusieurs exceptions dans la clause throws, et elle peut être utilisée avec la méthode main() également.

  3. try-catch – Nous utilisons le bloc try-catch pour la gestion des exceptions dans notre code. try marque le début du bloc et catch se trouve à la fin du bloc try pour gérer les exceptions. Nous pouvons avoir plusieurs blocs catch avec un bloc try. Le bloc try-catch peut également être imbriqué. Le bloc catch nécessite un paramètre qui doit être de type Exception.
  4. Enfin – le bloc finally est facultatif et peut être utilisé uniquement avec un bloc try-catch. Puisque l’exception interrompt le processus d’exécution, nous pourrions avoir des ressources ouvertes qui ne seront pas fermées, donc nous pouvons utiliser le bloc finally. Le bloc finally est toujours exécuté, que qu’une exception se soit produite ou non.

Un exemple de gestion des exceptions

ExceptionHandling.java
package com.journaldev.exceptions;

import java.io.FileNotFoundException;
import java.io.IOException;

public class ExceptionHandling {

	public static void main(String[] args) throws FileNotFoundException, IOException {
		try {
			testException(-5);
			testException(-10);
		} catch(FileNotFoundException e) {
			e.printStackTrace();
		} catch(IOException e) {
			e.printStackTrace();
		} finally {
			System.out.println("Releasing resources");
		}
		testException(15);
	}

	public static void testException(int i) throws FileNotFoundException, IOException {
		if (i < 0) {
			FileNotFoundException myException = new FileNotFoundException("Negative Integer " + i);
			throw myException;
		} else if (i > 10) {
			throw new IOException("Only supported for index 0 to 10");
		}
	}
}
  • La méthode testException() lance des exceptions en utilisant le mot-clé throw. La signature de la méthode utilise le mot-clé throws pour indiquer au programme appelant le type d’exceptions qu’elle pourrait lancer.
  • Dans la méthode main(), je gère les exceptions en utilisant un bloc try-catch. Lorsque je ne les gère pas, je les propage à l’exécution avec la clause throws dans la méthode main().
  • La méthode testException(-10) ne s’exécute jamais en raison de l’exception, puis le bloc finally est exécuté.

La méthode printStackTrace() est l’une des méthodes utiles de la classe Exception à des fins de débogage.

Ce code produira la sortie suivante :

Output
java.io.FileNotFoundException: Negative Integer -5 at com.journaldev.exceptions.ExceptionHandling.testException(ExceptionHandling.java:24) at com.journaldev.exceptions.ExceptionHandling.main(ExceptionHandling.java:10) Releasing resources Exception in thread "main" java.io.IOException: Only supported for index 0 to 10 at com.journaldev.exceptions.ExceptionHandling.testException(ExceptionHandling.java:27) at com.journaldev.exceptions.ExceptionHandling.main(ExceptionHandling.java:19)

Quelques points importants à noter :

  • Nous ne pouvons pas avoir de clause catch ou finally sans un bloc try.
  • A try statement should have either catch block or finally block, it can have both blocks.
  • Nous ne pouvons pas écrire de code entre les blocs try-catch-finally.
  • Nous pouvons avoir plusieurs blocs catch avec une seule instruction try.
  • Les blocs try-catch peuvent être imbriqués de manière similaire aux instructions if-else.
  • Nous ne pouvons avoir qu’un seul bloc finally avec une instruction try-catch.

Hiérarchie des exceptions Java

Comme indiqué précédemment, lorsqu’une exception est déclenchée, un objet d’exception est créé. Les exceptions Java sont hiérarchisées et l’héritage est utilisé pour catégoriser différents types d’exceptions. Throwable est la classe parente de la hiérarchie des exceptions Java et elle a deux objets enfants – Error et Exception. Les Exceptions sont ensuite divisées en Exceptions vérifiées et Exceptions d’exécution.

  1. Erreurs: Les Errors sont des scénarios exceptionnels qui échappent à la portée de l’application et il n’est pas possible de les anticiper et de s’en remettre. Par exemple, une défaillance matérielle, un crash de la machine virtuelle Java (JVM) ou une erreur d’insuffisance de mémoire. C’est pourquoi nous avons une hiérarchie distincte de Errors et nous ne devrions pas essayer de gérer ces situations. Certaines des Errors courantes sont OutOfMemoryError et StackOverflowError.
  2. Exceptions vérifiées: Les exceptions vérifiées sont des scénarios exceptionnels que nous pouvons anticiper dans un programme et essayer de récupérer. Par exemple, FileNotFoundException. Nous devrions capturer cette exception et fournir un message utile à l’utilisateur et le journaliser correctement à des fins de débogage. L’Exception est la classe parent de toutes les Exceptions vérifiées. Si nous lançons une Exception vérifiée, nous devons la capturer dans la même méthode, ou nous devons la propager à l’appelant en utilisant le mot-clé throws.
  3. Exception de Runtime: Les exceptions de Runtime sont causées par une mauvaise programmation. Par exemple, essayer de récupérer un élément d’un tableau. Nous devrions d’abord vérifier la longueur du tableau avant d’essayer de récupérer l’élément, sinon cela pourrait déclencher ArrayIndexOutOfBoundException à l’exécution. RuntimeException est la classe parent de toutes les exceptions de Runtime. Si nous lançons une Exception de Runtime dans une méthode, il n’est pas nécessaire de les spécifier dans la clause de signature de méthode throws. Les exceptions de runtime peuvent être évitées avec une meilleure programmation.

Quelques méthodes utiles des classes d’exception

Java Exception et toutes ses sous-classes ne fournissent aucune méthode spécifique, et toutes les méthodes sont définies dans la classe de base – Throwable. Les classes Exception sont créées pour spécifier différents types de scénarios d’Exception afin que nous puissions facilement identifier la cause principale et gérer l’Exception selon son type. La classe Throwable implémente l’interface Serializable pour l’interopérabilité.

Certains des méthodes utiles de la classe Throwable sont :

  1. public String getMessage() – Cette méthode renvoie le message String de Throwable et le message peut être fourni lors de la création de l’exception via son constructeur.
  2. public String getLocalizedMessage() – Cette méthode est fournie afin que les sous-classes puissent la remplacer pour fournir un message spécifique à la locale au programme appelant. L’implémentation de cette méthode dans la classe Throwable utilise la méthode getMessage() pour renvoyer le message d’exception.
  3. public synchronized Throwable getCause() – Cette méthode renvoie la cause de l’exception ou null si la cause est inconnue.
  4. public String toString() – Cette méthode renvoie les informations sur Throwable au format String, la String renvoyée contient le nom de la classe Throwable et le message localisé.
  5. public void printStackTrace() – Cette méthode imprime les informations de la trace de la pile sur le flux d’erreur standard. Cette méthode est surchargée, et nous pouvons passer PrintStream ou PrintWriter en argument pour écrire les informations de la trace de la pile dans un fichier ou un flux.

Gestion automatique des ressources Java 7 et améliorations des blocs catch

Si vous catchez beaucoup d’exceptions dans un seul bloc try, vous remarquerez que le code du bloc catch consiste principalement en un code redondant pour journaliser l’erreur. En Java 7, l’une des fonctionnalités était un bloc catch amélioré où nous pouvons attraper plusieurs exceptions dans un seul bloc catch. Voici un exemple du bloc catch avec cette fonctionnalité:

catch (IOException | SQLException ex) {
    logger.error(ex);
    throw new MyException(ex.getMessage());
}

Il y a certaines contraintes telles que l’objet exception est final et que nous ne pouvons pas le modifier à l’intérieur du bloc catch, lisez l’analyse complète sur Améliorations du bloc catch Java 7.

La plupart du temps, nous utilisons le bloc finally simplement pour fermer les ressources. Parfois, nous oublions de les fermer et obtenons des exceptions d’exécution lorsque les ressources sont épuisées. Ces exceptions sont difficiles à déboguer, et nous pourrions avoir besoin de vérifier chaque endroit où nous utilisons cette ressource pour nous assurer que nous la fermons. En Java 7, l’une des améliorations était l’utilisation de try-with-resources, où nous pouvons créer une ressource dans l’instruction try elle-même et l’utiliser à l’intérieur du bloc try-catch. Lorsque l’exécution sort du bloc try-catch, l’environnement d’exécution ferme automatiquement ces ressources. Voici un exemple du bloc try-catch avec cette amélioration :

try (MyResource mr = new MyResource()) {
	System.out.println("MyResource created in try-with-resources");
} catch (Exception e) {
	e.printStackTrace();
}

A Custom Exception Class Example

Java nous fournit de nombreuses classes d’exceptions à utiliser, mais parfois nous devons créer nos propres classes d’exceptions personnalisées. Par exemple, pour informer l’appelant d’un type spécifique d’exception avec le message approprié. Nous pouvons avoir des champs personnalisés pour le suivi, tels que des codes d’erreur. Supposons que nous écrivions une méthode pour traiter uniquement les fichiers texte, nous pouvons fournir à l’appelant le code d’erreur approprié lorsqu’un autre type de fichier est envoyé en entrée.

Tout d’abord, créez MyException :

MyException.java
package com.journaldev.exceptions;

public class MyException extends Exception {

	private static final long serialVersionUID = 4664456874499611218L;

	private String errorCode = "Unknown_Exception";

	public MyException(String message, String errorCode) {
		super(message);
		this.errorCode=errorCode;
	}

	public String getErrorCode() {
		return this.errorCode;
	}
}

Ensuite, créez un CustomExceptionExample :

CustomExceptionExample.java
package com.journaldev.exceptions;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

public class CustomExceptionExample {

	public static void main(String[] args) throws MyException {
		try {
			processFile("file.txt");
		} catch (MyException e) {
			processErrorCodes(e);
		}
	}

	private static void processErrorCodes(MyException e) throws MyException {
		switch (e.getErrorCode()) {
			case "BAD_FILE_TYPE":
				System.out.println("Bad File Type, notify user");
				throw e;
			case "FILE_NOT_FOUND_EXCEPTION":
				System.out.println("File Not Found, notify user");
				throw e;
			case "FILE_CLOSE_EXCEPTION":
				System.out.println("File Close failed, just log it.");
				break;
			default:
				System.out.println("Unknown exception occured, lets log it for further debugging." + e.getMessage());
				e.printStackTrace();
		}
	}

	private static void processFile(String file) throws MyException {
		InputStream fis = null;

		try {
			fis = new FileInputStream(file);
		} catch (FileNotFoundException e) {
			throw new MyException(e.getMessage(), "FILE_NOT_FOUND_EXCEPTION");
		} finally {
			try {
				if (fis != null) fis.close();
			} catch (IOException e) {
				throw new MyException(e.getMessage(), "FILE_CLOSE_EXCEPTION");
			}
		}
	}
}

Nous pouvons avoir une méthode distincte pour traiter différents types de codes d’erreur que nous obtenons de différentes méthodes. Certains d’entre eux sont consommés car nous ne voulons peut-être pas informer l’utilisateur, ou certains d’entre eux seront renvoyés pour informer l’utilisateur du problème.

Voici j’étends Exception de sorte que chaque fois que cette exception est produite, elle doit être traitée dans la méthode ou renvoyée au programme appelant. Si nous étendons RuntimeException, il n’est pas nécessaire de le spécifier dans la clause throws.

C’était une décision de conception. L’utilisation des exceptions vérifiées a l’avantage d’aider les développeurs à comprendre les exceptions auxquelles ils peuvent s’attendre et à prendre les mesures appropriées pour les gérer.

Meilleures pratiques pour la gestion des exceptions en Java

  • Utiliser des exceptions spécifiques – Les classes de base de la hiérarchie des exceptions ne fournissent aucune information utile, c’est pourquoi Java a tant de classes d’exceptions, telles que IOException avec des sous-classes supplémentaires telles que FileNotFoundException, EOFException, etc. Nous devrions toujours lancer et attraper des classes d’exception spécifiques afin que l’appelant puisse connaître facilement la cause première de l’exception et les traiter. Cela rend le débogage plus facile et aide les applications clientes à gérer les exceptions de manière appropriée.
  • Lancer tôt ou échouer rapidement – Nous devrions essayer de lancer les exceptions le plus tôt possible. Considérons la méthode processFile() ci-dessus, si nous passons l’argument null à cette méthode, nous obtiendrons l’exception suivante:
Output
Exception in thread "main" java.lang.NullPointerException at java.io.FileInputStream.<init>(FileInputStream.java:134) at java.io.FileInputStream.<init>(FileInputStream.java:97) at com.journaldev.exceptions.CustomExceptionExample.processFile(CustomExceptionExample.java:42) at com.journaldev.exceptions.CustomExceptionExample.main(CustomExceptionExample.java:12)

Pendant le débogage, nous devrons examiner attentivement la trace de la pile pour identifier l’emplacement réel de l’exception. Si nous modifions notre logique d’implémentation pour vérifier ces exceptions plus tôt comme ci-dessous :

private static void processFile(String file) throws MyException {
	if (file == null) throw new MyException("File name can't be null", "NULL_FILE_NAME");

	// ... further processing
}

, alors la trace de la pile d’exception indiquera où l’exception s’est produite avec un message clair :

Output
com.journaldev.exceptions.MyException: File name can't be null at com.journaldev.exceptions.CustomExceptionExample.processFile(CustomExceptionExample.java:37) at com.journaldev.exceptions.CustomExceptionExample.main(CustomExceptionExample.java:12)
  • Capture Tardive – Comme Java impose de traiter l’exception vérifiée ou de la déclarer dans la signature de la méthode, parfois les développeurs ont tendance à catch l’exception et à enregistrer l’erreur. Mais cette pratique est nuisible car le programme appelant ne reçoit aucune notification pour l’exception. Nous devrions catch les exceptions uniquement lorsque nous pouvons les gérer de manière appropriée. Par exemple, dans la méthode ci-dessus, je throw les exceptions vers la méthode appelante pour les gérer. La même méthode pourrait être utilisée par d’autres applications qui souhaitent peut-être traiter l’exception de manière différente. Lors de la mise en œuvre de toute fonctionnalité, nous devrions toujours throw les exceptions vers l’appelant et les laisser décider comment les gérer.
  • Fermeture des Ressources – Comme les exceptions interrompent le traitement du programme, nous devrions fermer toutes les ressources dans le bloc finally ou utiliser l’amélioration try-with-resources de Java 7 pour laisser le moteur Java les fermer automatiquement.
  • Enregistrement des exceptions – Nous devrions toujours enregistrer les messages d’exception et, lors du lancement d’exceptions, fournir un message clair afin que l’appelant sache facilement pourquoi l’exception s’est produite. Nous devrions toujours éviter un bloc catch vide qui se contente de consommer l’exception et ne fournit aucun détail significatif de l’exception pour le débogage.
  • Bloc catch unique pour plusieurs exceptions – La plupart du temps, nous enregistrons les détails de l’exception et fournissons un message à l’utilisateur, dans ce cas, nous devrions utiliser la fonctionnalité Java 7 pour gérer plusieurs exceptions dans un seul bloc catch. Cette approche réduira la taille de notre code, et il semblera également plus propre.
  • Utilisation d’exceptions personnalisées – Il est toujours préférable de définir une stratégie de gestion des exceptions au moment de la conception et plutôt que de lancer et de capturer plusieurs exceptions, nous pouvons créer une exception personnalisée avec un code d’erreur, et le programme appelant peut gérer ces codes d’erreur. Il est également judicieux de créer une méthode utilitaire pour traiter différents codes d’erreur et les utiliser.
  • Conventions de nommage et d’emballage – Lorsque vous créez votre exception personnalisée, assurez-vous qu’elle se termine par Exception afin qu’il soit clair dès le nom lui-même qu’il s’agit d’une classe d’exception. Assurez-vous également de les empaqueter comme c’est fait dans le kit de développement Java (JDK). Par exemple, IOException est l’exception de base pour toutes les opérations d’entrée-sortie.
  • Utilisez les exceptions avec discernement – Les exceptions sont coûteuses, et parfois il n’est pas nécessaire de les déclencher du tout. On peut renvoyer une variable booléenne au programme appelant pour indiquer si une opération a réussi ou non. Cela est utile lorsque l’opération est facultative et que vous ne voulez pas que votre programme se bloque en cas d’échec. Par exemple, lors de la mise à jour des cotations boursières dans la base de données à partir d’un service web tiers, nous pouvons vouloir éviter de déclencher des exceptions si la connexion échoue.
  • Documentez les exceptions déclenchées – Utilisez Javadoc @throws pour spécifier clairement les exceptions déclenchées par la méthode. Cela est très utile lorsque vous fournissez une interface pour que d’autres applications l’utilisent.

Conclusion

Dans cet article, vous avez appris le traitement des exceptions en Java. Vous avez appris à propos de throw et throws. Vous avez également appris sur les blocs try (et try-with-resources), catch, et finally.

Source:
https://www.digitalocean.com/community/tutorials/exception-handling-in-java