1°/ Préambule & tutoriel sur les exceptions
1.1°/ Principes
- En C, la façon historique pour une méthode de signaler une erreur est qu'elle renvoie une valeur significative, par exemple -1 ou null.
- Malheureusement, il y a des situations où la valeur renvoyée peut effectivement être -1 ou null sans pour autant que cela constitue une erreur, d'où l'utilisation de moyens détournés, comme des variables globales, des paramètres en sortie, etc.
- En C++/Java (et d'autres langages), un autre mécanisme permet de régler ce problème : les exceptions.
- En Java, une exception est un objet qui est créé pour représenter le cas d'erreur.
- Toutes les classes d'exception qu'un code peut générer héritent de la classe Exception (qui hérite elle-même de Throwable), et leur nom représente le type de l'erreur.
- Ces classes contiennent également un attribut String contenant le texte de l'erreur (initialisé via le constructeur) , et une méthode getMessage() pour récupérer ce texte.
- Quand une méthode détecte un cas d'erreur, elle instancie un objet Exception de la classe représentant au mieux le type d'erreur, puis "jette" cet objet grâce à l'instruction throw.
- C'est ensuite à la partie de code qui a appelé cette méthode "d'attraper" l'exception, grâce à un bloc try/catch, ou bien de la propager (explicitement ou implicitement cf 1.3)
Exemple :
FileReader f = null;
try {
f = new FileReader("toto.txt"); // si toto.txt n'existe pas, un objet FileNotFoundException est lancé par FileReader()
...
}
catch(IOException e) { ... }
- Dans l'exemple ci-dessus, on appelle le constructeur de FileReader pour lire un fichier texte. D'après l'API, si le fichier n'existe pas, la méthode génère une FileNotFoundException.
- On peut donc en conclure que quelque part dans le code source de cette méthode, il y a une instruction du type : if ( ! fichier_existe) throw new FileNotFoundException().
- La partie de code qui appelle le constructeur doit attraper cet exception. Pour cela, on inclut la partie de code dans un bloc try, que l'on fait suivre par un bloc catch, qui va capturer l'objet. On remarque qu'il est possible d'utiliser le principe du polymorphisme puisque la variable e est du type IOException, qui est la super-classe de FileNotFoundException.
- Du fait du nombre important de cas d'erreur possibles pour toutes les classes de l'API Java, la hiérarchie des classes d'exception est très volumineuse (NB : on peut de plus l'étendre par héritage)
- Il y a cependant une distinction primaire qui est faite :
- les cas d'erreurs critiques qui doivent interrompre l'exécution. Ils sont difficilement prévisibles, notamment liés à du code mal écrit qui fait des opérations invalides en mémoire ou en calcul. C'est la classe RuntimeException et ses sous-classes (NullPointerException, ArithmeticException, ...) qui les représente. On dit que ce sont des exceptions "non vérifiées" à la compilation.
- les erreurs probables, pas forcément critiques, qui seront plutôt dépendantes du contexte d'exécution. Elles sont représentées par des sous-classes de Exception (mais pas de RuntimeException). On dit que ce sont des exceptions "vérifiées" à la compilation.
- Quand on appelle une méthode, elle peut générer des exceptions de ces 2 catégories ... ou pas si tout se passe bien. Mais, s'il y a exception :
- les exceptions "vérifiées" DOIVENT être explicitement attrapées ou propagées. Si l'exception n'est jamais attrapée, la compilation échouera. C'est de là que vient le terme vérifié.
- les exceptions "non vérifiées" n'ont pas besoin d'être attrapées (c'est même déconseillé). Par exemple, l'API indique que la méthode charAt() de la classe String peut générer une IndexOutOfBoundsException. C'est une exception non vérifiée donc le compilateur ne va pas vérifier si l'appel à cet méthode se trouve dans un try/catch. Généralement, si une telle exception est générée, on laisse la JVM la propager puis arrêter l'exécution (cf. 1.3.1).
- Cas particulier pour les non vérifiées : parfois, on considère que l'exception n'est pas totalement critique et on peut effectivement la capturer. C'est souvent le cas avec par exemple NumberFormatException qui est généré lors de conversions entre chaînes de caractères et entier/double. Capturer l'exception permet notamment de ressaisir la chaîne de caractère invalide.
Exemple :