Exceptions en Java (rappels)
Ing39 – Algorithmique Programmation Java
Serge Rosmorduc, Pierre Courtieu, Virginia Aponte
[Link]@[Link]
[Link]@[Link]
[Link]-garcia@[Link]
Conservatoire National des Arts et Métiers
9 septembre 2024
Erreurs à l’exécution
Erreurs à l’exécution inévitables :
▶ utilisateur tape un nom de fichier erroné
▶ réseau en panne
▶ librairie externe buggée
▶ bref : les bugs « externes »
Erreurs évitables
▶ non respect du contrat d’une fonction (ex : division par zéro)
▶ cas non prévus
▶ bref : les bugs « internes »
Comment gérer les erreurs ?
Évitables ou non, il faut traiter les erreurs éventuelles
Comment faire ?
1. Considérer les erreurs comme des résultats :
▶ une méthode retourne un code d’erreur
- problématique si notre méthode retournait déjà un résultat
(sur tout son co-domaine)
- souvent on ne veut/peut pas changer les méthode pour qu’elles
retournent un code d’erreur (ex : méthodes de la bibliothèque)
- dans le code : mélange de gestion des erreurs et des vrais
résultats
2. Utiliser des mécanismes spécifiques du langage
▶ non recommandables : break, jump...
▶ exceptions
Qu’est-ce que les exceptions ?
▶ exception = objet qui représente un message d’erreur
▶ qui interrompt le flux normal de l’exécution,
▶ qui va se propager dans le programme,
▶ et qu’on va (peut-être) traiter
▶ Aussi : mécanisme permettant de découpler les erreurs et leur
traitement
Motivation
lecture de trois entiers dans un fichier
f= ouvrir (nom );
i1= lire_entier (f);
i2= lire_entier (f);
i3= lire_entier (f);
fermer f;
Avec la manière artisanale : erreurs comme résultats…
si ((f= ouvrir (nom )) == 0)
affiche " fichier ␣non␣ ouvert "
exit 1
i1 = lire_entier (f);
si erreur_de_lecture gestion d'erreur intrusive !
affiche " entrée ␣non␣lue"
exit 1
i2 = lire_entier (f);
si erreur_de_lecture
affiche " entrée ␣non␣lue"
exit 1
i3 = lire_entier (f);
si erreur_de_lecture
affiche " entrée ␣non␣lue"
exit 1
fermer f;
Les codes applicatif et de gestion d’erreurs sont entrelacés …
Avec des exceptions
try {
f= ouvrir (nom );
i1= lire_entier (f);
i2= lire_entier (f);
i3= lire_entier (f);
fermer (f);
}
catch ( fichierNonOuvert e) {
afficher (" fichier ␣non␣ ouvert "); gestion séparée.
}
catch ( EntierNonLu e) {
fermer (f);
afficher (" fichier ␣mal␣formé ");
}
Explications
try {
f= ouvrir (nom ); // ouvre nom ou lève fichierNonOuvert
i1= lire_entier (f); // retourne un int ou lève EntierNonLu
i2= lire_entier (f); // retourne un int ou lève EntierNonLu
i3= lire_entier (f); // retourne un int ou lève EntierNonLu
fermer (f);
}
catch ( fichierNonOuvert e) {
afficher (" fichier ␣non␣ ouvert "); intercepte et traite
}
catch ( EntierNonLu e) {
fermer (f);
afficher (" fichier ␣mal␣formé ");
}
Explications
try {
f= ouvrir (nom );
i1= lire_entier (f); code protégé
i2= lire_entier (f);
i3= lire_entier (f);
fermer (f);
}
catch ( fichierNonOuvert e) {
afficher (" fichier ␣non␣ ouvert ");
}
catch ( EntierNonLu e) {
fermer (f);
afficher (" fichier ␣mal␣formé ");
}
Si l’un des appels protégés échoue, le reste est abandonné et on
tente un traitement avec les catch.
Logique du mécanisme d’exceptions
On aura plusieurs types de méthodes :
1. méthodes (propres ou externes) pouvant échouer
▶ elles signalent que leur résultat attendu n’a pas pu être
calculé/effectué
▶ cela signifie, qu’elles ne peuvent pas traiter l’erreur localement
▶ si une de leurs invocation échoue, l’exception est propagée à
travers la pile d’appel
▶ c’est logique : on recherche un traitement dans l’une de ses
méthodes appelantes
2. des méthodes qui les invoquent
▶ certaines contiendront des blocs de traitement
▶ qui interceptent les exceptions propagées par les méthodes qui
échouent
▶ qui proposent des actions de récupération
▶ et qui permettent la poursuite normale de l’exécution
Logique du mécanisme d’exceptions
méthode qui échoue
Recherche/Propagation/
Appel
Echec
méthode sans traitement
Appel
Recherche/Propagation/
Echec
méthode avec traitement
Appel Poursuite normale/ Retour
main
Instructions spécifiques aux exceptions
▶ throw
▶ « lève » une exception (signale une erreur )
▶ interruption de l’exécution du bloc courant (≃ break)
▶ propagation de l’exception : on remonte la pile d’appels à la
recherche d’un traitement
▶ si aucun traitement n’est trouvé, le programme complet
échoue
▶ try catch [finally]
▶ c’est un traitement :
▶ « intercepte »/« rattrape » le throw
▶ si le rattrapage réussi, reprise du flot normal d’exécution
Levée d’exception
1. Façons indirectes :
▶ appel d’une méthode pouvant lever une exception
(ex : [Link]("Abc") sur une chaîne qui ne
correspond pas à un entier)
▶ code pouvant déclencher une exception :
▶ accéder à un objet alors que le pointeur est nul
▶ division par zéro en entiers (pas en réels)
▶ accès à une case inexistante d’un tableau…
2. Explicitement avec l’instruction throw (sans « s ») :
if (x < 0)
throw new IllegalArgumentException (" argument ␣ négatif ␣");
Notez qu’on utilise new pour créer l’exception qui est un objet.
Propagation d’exception
Si, pendant l’exécution d’une méthode P, une exception Exc survient :
1. interruption de l’exécution du code de P,
2. si aucun rattrapage-traitement de Exc n’est trouvé dans P :
▶ abandon des instructions de P restant à exécuter ⇒
2.1 P se termine anormalement, on dit que P échoue ;
2.2 on retourne vers la méthode qui a appelé P en propageant
Eexc ;
2.3 Dans la méthode appelante M, on recommence le processus :
(1) on cherche un traitement dans le code de M ; (2) si non
trouvé, échec de M et propagation vers la méthode G appelant
M;
Un déclenchement exception peut se propager dans la chaîne d’appels et
aboutir (transitivement) à l’échec au programme entier.
Propagation d’une exception (sans traitement)
public class DemoPropagation {
public static int racine (int x) {
if (x < 0)
throw new IllegalArgumentException (" argument ␣ négatif ␣
+ x);
int a = 0;
while (a*a <= x) {
a++;
}
return a - 1;
}
public static void afficherRacine (int x) {
int rac = racine (x);
System .out. println (" racine ␣de␣"+ x+ "␣=␣"+ rac );
}
public static void main( String [] args) {
afficherRacine ( -1);
}
}
Propagation d’une exception (sans traitement)
public class DemoPropagation {
public static int racine (int x) {
if (x < 0)
throw new IllegalArgumentException (" argument ␣ négatif ␣
+ x);
int a = 0;
while (a*a <= x) {
a++;
}
return a - 1;
}
public static void afficherRacine (int x) {
int rac = racine (x);
System .out. println (" racine ␣de␣"+ x+ "␣=␣"+ rac );
}
public static void main( String [] args) {
afficherRacine ( -1); appel d'une méthode...
}
}
Propagation d’une exception (sans traitement)
public class DemoPropagation {
public static int racine (int x) {
if (x < 0)
throw new IllegalArgumentException (" argument ␣ négatif ␣
+ x);
int a = 0;
while (a*a <= x) {
a++;
}
return a - 1;
}
public static void afficherRacine (int x) {
int rac = racine (x);
System .out. println (" racine ␣de␣"+ x+ "␣=␣"+ rac );
}
public static void main( String [] args) {
afficherRacine ( -1);
}
}
Propagation d’une exception (sans traitement)
public class DemoPropagation {
public static int racine (int x) {
if (x < 0)
throw new IllegalArgumentException (" argument ␣ négatif ␣
+ x);
int a = 0;
while (a*a <= x) {
a++;
}
return a - 1;
}
public static void afficherRacine (int x) {
int rac = racine (x);
System .out. println (" racine ␣de␣"+ x+ "␣=␣"+ rac );
}
public static void main( String [] args) {
afficherRacine ( -1);
}
}
Propagation d’une exception (sans traitement)
public class DemoPropagation {
public static int racine (int x) {
if (x < 0)
throw new IllegalArgumentException (" argument ␣ négatif ␣
+ x);
int a = 0;
while (a*a <= x) {
a++; levée de l'exception
}
return a - 1;
}
public static void afficherRacine (int x) {
int rac = racine (x);
System .out. println (" racine ␣de␣"+ x+ "␣=␣"+ rac );
}
public static void main( String [] args) {
afficherRacine ( -1);
}
}
Propagation d’une exception (sans traitement)
public class DemoPropagation {
public static int racine (int x) {
if (x < 0)
throw new IllegalArgumentException (" argument ␣ négatif ␣
+ x);
int a = 0;
while (a*a <= x) { de l'exception
propagation
a++;
}
return a - 1;
}
public static void afficherRacine (int x) {
int rac = racine (x);
System .out. println (" racine ␣de␣"+ x+ "␣=␣"+ rac );
}
public static void main( String [] args) {
afficherRacine ( -1);
}
}
Propagation d’une exception (sans traitement)
public class DemoPropagation {
public static int racine (int x) {
if (x < 0)
throw new IllegalArgumentException (" argument ␣ négatif ␣
+ x);
int a = 0;
while (a*a <= x) { de l'exception
propagation
a++;
}
return a - 1;
}
public static void afficherRacine (int x) {
int rac = racine (x);
System .out. println (" racine ␣de␣"+ x+ "␣=␣"+ rac );
}
public static void main( String [] args) {
afficherRacine ( -1);
}
}
Propagation d’une exception (sans traitement)
public class DemoPropagation {
public static int racine (int x) {
if (x < 0)
throw new IllegalArgumentException (" argument ␣ négatif ␣
+ x);
int a = 0;
while (a*a <= x) { de l'exception
propagation
a++;
}
return a - 1;
}
public static void afficherRacine (int x) {
int rac = racine (x);
System .out. println (" racine ␣de␣"+ x+ "␣=␣"+ rac );
}
public static void main( String [] args) {
afficherRacine ( -1);
}
}
Propagation d’une exception (sans traitement)
public class DemoPropagation {
public static int racine (int x) {
if (x < 0)
throw new IllegalArgumentException (" argument ␣ négatif ␣
+ x);
int a = 0;
while (a*a <= x) {
a++;
}
return a - 1;
}
public static void afficherRacine (int x) {
int rac = arrêt
racinedu
(x);logiciel, affichage de la pile d'appels
System .out. println (" racine ␣de␣"+ x+ "␣=␣"+ rac );
}
public static void main( String [] args) {
afficherRacine ( -1);
}
}
Propagation d’une exception (sans traitement)
public class DemoPropagation {
public static int racine (int x) {
if (x < 0)
throw new IllegalArgumentException (" argument ␣ négatif ␣
+ x);
int a = 0;
while (a*a <= x) {
a++;
}
return a - 1;
}
public static void afficherRacine (int x) {
int rac = racine (x);
System .out. println (" racine ␣de␣"+ x+ "␣=␣"+ rac );
}
public static void main( String [] args) {
afficherRacine ( -1);
}
jamais exécutées
}
Pile d’appels (Stack trace)
Exception in thread "main" [Link]. IllegalArgumentException :
argument négatif -1
at [Link]( DemoPropagation .java :7)
at [Link]( DemoPropagation .java :1
at [Link]( [Link] :21)
Intercepter et traiter une exception
Bloc try-catch
Idée : protéger le code pouvant lever une exception par une construction
de d’interception et traitement d’exceptions, qui prévoit les instructions à
exécuter pour traiter ce type d’erreur .
try {
<code -protege -pouvant -echouer > // A
} catch (Exc e) {
<code - traitement > // B
}
<code -apres -bloc -try -catch > // C
▶ Si A ne lève aucune exception, on poursuit avec C
▶ Si A lève Exc, on exécute B, puis on poursuit avec C
▶ Si A lève une autre exception, le bloc courant échoue, à la recherche
d’un traitement dans un bloc englobant. Si non trouvé, la méthode
courante échoue, et l’exception est propagée à la méthode
appelante.
Comportement selon le placement du try-catch
public static int racine(int x) {
if (x < 0) throw new IllegalArgumentException ("argument␣négatif␣" +
int a = 0;
// etc ...
}
public static void afficherRacine(int x) {
int rac = racine(x);
[Link]("racine␣de␣"+ x+ "␣=␣"+ rac);
}
Dans le main
public static void main(String [] args) {
try { afficherRacine (-1);
} catch ( IllegalArgumentException e) {
[Link]("Calcul␣impossible␣" + [Link] ()); }
[Link]("le␣programme␣se␣termine␣normalement ...");
}
Comportement selon le placement du try-catch
public static int racine(int x) {
if (x < 0) throw new IllegalArgumentException ("argument␣négatif␣" +
int a = 0;
...
}
public static void afficherRacine(int x) { Dans une autre méthode
String affichage;
try {
int rac = racine(x);
affichage = "racine␣de␣" + x + "␣=␣" + rac;
} catch( IllegalArgumentException e) {
affichage = "racine␣de␣" + x + "␣n'existe␣pas"; }
[Link](affichage );
}
ici, main ne voit
pas l'exception
public static void main(String [] args) {
afficherRacine (-1);
[Link]("le␣programme␣se␣termine␣normalement ...");
}
Laisser passer les exceptions
Important !!!!
Quand on ne sait pas quoi faire d’une exception, on la laisse
passer !!!!!
Traitements multiples, finally
... C1 code non protégé
try {
... C2 ...
}
catch (ExcA e) { ... CA ... }
catch (ExcB e) { ... CB ... }
...
finally {
... codefinal ...
}
... C3 code non protégé
▶ catch : exécuté ou non
▶ finally : toujours exécuté, même si aucune exception levée
Traitements multiples, finally
... C1 code non protégé
try { code « protégé »
... C2 ...
} exécuté si throw(ExcA) dans C2
catch (ExcA e) { ... CA ... }
catch (ExcB e) { ... CB ... }
... exécuté si throw(ExcB) dans C2
finally { exclusivement, pas dans CA!
... codefinal ...
} « quoi qu'il arrive »
... C3 code non protégé
Traitements multiples, finally
... C1 code non protégé
try {
... C2 ...
}
catch (ExcA e) { ... CA ... }
catch (ExcB e) { ... CB ... }
...
finally {
... codefinal ...
}
... C3 code non protégé
Traitements multiples, finally
... C1 code non protégé
try {
... C2 ...
}
catch (ExcA e) { ... CA ... }
catch (ExcB e) { ... CB ... }
...
finally {
... codefinal ...
}
... C3 code non protégé
Traitements multiples, finally
... C1 code non protégé
try {
... C2 ...
}
catch (ExcA e) { ... CA ... }
catch (ExcB e) { ... CB ... }
...
finally {
... codefinal ...
}
... C3 code non protégé
Traitements multiples, finally
... C1 code non protégé
try {
... C2 ... pas d'exception
}
catch (ExcA e) { ... CA ... }
catch (ExcB e) { ... CB ... }
...
finally {
... codefinal ...
}
... C3 code non protégé
Traitements multiples, finally
... C1 code non protégé
try {
... C2 ... pas d'exception
}
catch (ExcA e) { ... CA ... }
catch (ExcB e) { ... CB ... }
...
finally {
... codefinal ...
}
... C3 code non protégé
Traitements multiples, finally
... C1 code non protégé
try {
... C2 ... pas d'exception
}
catch (ExcA e) { ... CA ... }
catch (ExcB e) { ... CB ... }
...
finally {
... codefinal ...
}
... C3 code non protégé
▶ Déroulement non exceptionnel
Traitements multiples, finally
... C1 code non protégé
try {
... C2 ...
}
catch (ExcA e) { ... CA ... }
catch (ExcB e) { ... CB ... }
...
finally {
... codefinal ...
}
... C3 code non protégé
Traitements multiples, finally
... C1 code non protégé
try {
... C2 ...
}
catch (ExcA e) { ... CA ... }
catch (ExcB e) { ... CB ... }
...
finally {
... codefinal ...
}
... C3 code non protégé
Traitements multiples, finally
... C1 code non protégé
try {
... C2 ... throw(ExcB)!
}
catch (ExcA e) { ... CA ... }
catch (ExcB e) { ... CB ... }
...
finally {
... codefinal ...
}
... C3 code non protégé
Traitements multiples, finally
... C1 code non protégé
try {
... C2 ... throw(ExcB)!
}
catch (ExcA e) { ... CA ... }
catch (ExcB e) { ... CB ... }
...
finally {
On peut utiliser les données stockées dans e
... codefinal ...
}
... C3 code non protégé
Traitements multiples, finally
... C1 code non protégé
try {
... C2 ... throw(ExcB)!
}
catch (ExcA e) { ... CA ... }
catch (ExcB e) { ... CB ... }
...
finally {
... codefinal ...
}
... C3 code non protégé
Traitements multiples, finally
... C1 code non protégé
try {
... C2 ... throw(ExcB)!
}
catch (ExcA e) { ... CA ... }
catch (ExcB e) { ... CB ... }
...
finally {
... codefinal ...
}
... C3 code non protégé
▶ Exception interceptée
Traitements multiples, finally
... C1 code non protégé
try {
... C2 ...
}
catch (ExcA e) { ... CA ... }
catch (ExcB e) { ... CB ... }
...
finally {
... codefinal ...
}
... C3 code non protégé
Traitements multiples, finally
... C1 code non protégé
try {
... C2 ...
}
catch (ExcA e) { ... CA ... }
catch (ExcB e) { ... CB ... }
...
finally {
... codefinal ...
}
... C3 code non protégé
Traitements multiples, finally
... C1 code non protégé
try {
... C2 ... throw(ExcC)!
}
catch (ExcA e) { ... CA ... }
catch (ExcB e) { ... CB ... }
...
finally {
... codefinal ...
}
... C3 code non protégé
Traitements multiples, finally
... C1 code non protégé
try {
... C2 ... throw(ExcC)!
}
catch (ExcA e) { ... CA ... }
catch (ExcB e) { ... CB ... } aucun catch ne correspond
...
finally {
... codefinal ...
}
... C3 code non protégé
Traitements multiples, finally
... C1 code non protégé
try {
... C2 ... throw(ExcC)!
}
catch (ExcA e) { ... CA ... }
catch (ExcB e) { ... CB ... } aucun catch ne correspond
...
finally {
... codefinal ...
}
... C3 code non protégé
Traitements multiples, finally
... C1 code non protégé
try {
... C2 ... throw(ExcC)!
}
catch (ExcA e) { ... CA ... }
catch (ExcB e) { ... CB ... } aucun catch ne correspond
...
finally {
... codefinal ... Propagation de l'exception ExcC
}
... C3 code non protégé
Traitements multiples, finally
... C1 code non protégé
try {
... C2 ... throw(ExcC)!
}
catch (ExcA e) { ... CA ... }
catch (ExcB e) { ... CB ... } aucun catch ne correspond
...
finally {
... codefinal ... Propagation de l'exception ExcC
}
... C3 code non protégé
Code non exécuté
▶ Exception non interceptée
Traitements multiples, finally
... C1 code non protégé
try {
... C2 ...
}
catch (ExcA e) { ... CA ... }
catch (ExcB e) { ... CB ... }
...
finally {
... codefinal ...
}
... C3 code non protégé
Traitements multiples, finally
... C1 code non protégé
try {
... C2 ...
}
catch (ExcA e) { ... CA ... }
catch (ExcB e) { ... CB ... }
...
finally {
... codefinal ...
}
... C3 code non protégé
Traitements multiples, finally
... C1 code non protégé
try {
... C2 ... throw(ExcA)!
}
catch (ExcA e) { ... CA ... }
catch (ExcB e) { ... CB ... }
...
finally {
... codefinal ...
}
... C3 code non protégé
Traitements multiples, finally
... C1 code non protégé
try {
... C2 ... throw(ExcA)!
}
catch (ExcA e) { ... CA ... }
catch (ExcB e) { ... CB ... }
...
finally {
... codefinal ...
}
... C3 code non protégé
Traitements multiples, finally
... C1 code non protégé
try {
... C2 ... throw(ExcA)!
}
catch (ExcA e) { ... CA ... } throw(ExcB)!
catch (ExcB e) { ... CB ... }
...
finally {
... codefinal ...
}
... C3 code non protégé
▶ Exception de C2 interceptée dans le handler
Traitements multiples, finally
... C1 code non protégé
try {
... C2 ... throw(ExcA)!
}
catch (ExcA e) { ... CA ... } throw(ExcB)!
catch (ExcB e) { ... CB ... }
...
finally {
... codefinal ...
}
... C3 code non protégé
▶ Exception de C2 interceptée dans le handler
▶ mais pas d’interception pour un lancement venant d’un catch
Traitements multiples, finally
... C1 code non protégé
try {
... C2 ... throw(ExcA)!
}
catch (ExcA e) { ... CA ... } throw(ExcB)!
catch (ExcB e) { ... CB ... }
...
finally {
... codefinal ... Propagation de l'exception ExcB
}
... C3 code non protégé
Code non exécuté
▶ Exception de C2 interceptée dans le handler
▶ mais pas d’interception pour un lancement venant d’un catch
▶ finally exécuté puis propagation si exception non interceptée
Exceptions et héritage
▶ les catch sont pris dans l’ordre
▶ on exécute premier compatible (règle de l’affectation) avec
l’exception interceptée
▶ permet de jouer avec l’héritage et d’avoir un grain plus ou
moins fin.
Puisque FileNotFoundException étend IOException, 2 choix :
try { try {
.. code levant des IOExceptions .. code levant des IOExceptions
.. dont FileNotFoundException .. dont FileNotFoundException
} catch (IOException e) { } catch ( FileNotFoundException e) {
//... traite de la même manière // traitement de FileNotFoundExceptio
// } catch (IOException e) {
toutes les IOExceptions // autres IOExceptions
} }
Ordre des catch et héritage
Puisque FileNotFoundException étend IOException, si on
écrit :
try {
... code levant des IOExceptions
... dont FileNotFoundException
} catch (IOException e) {
//... autres IOExceptions
} catch ( FileNotFoundException e) {
//... traitement de FileNotFoundException
}
▶ le catch de FileNotFoundException n’est jamais exécuté ;
▶ le compilateur le détecte et refuse de compiler !
Exceptions déclarées
public static int f1 (int x) throws Exception {
Signature d’une méthode :
▶ attribut(s), type de retour, nom de la méthode, types des
paramètres
public static String lireFichier (File f) throws IOException {
FileReader r = new FileReader (f);
... // autre code qui manipule r...
r. close ();
return resultat ;
}
Exceptions déclarées
public static int f1 (int x) throws Exception {
Signature d’une méthode :
▶ attribut(s), type de retour, nom de la méthode, types des
paramètres
+ exceptions possibles
public static String lireFichier (File f) throws IOException {
FileReader r = new FileReader (f);
... // autre code qui manipule r...
r. close ();
return resultat ;
}
Exceptions déclarées
public static int f1 (int x) throws Exception {
Signature d’une méthode :
▶ attribut(s), type de retour, nom de la méthode, types des
paramètres
+ exceptions possibles
▶ quand on laisse passer une exception, Java force à la déclarer
(mot clef throws, notez le « s »)
▶ sauf les RuntimeException : il y aurait des throws partout
▶ sauf pour les Error : on ne peut pas les traiter
public static String lireFichier (File f) throws IOException {
FileReader r = new FileReader (f);
... // autre code qui manipule r...
r. close ();
return resultat ;
}
Exceptions déclarées
▶ Les exceptions déclarées sont une spécificité de Java ;
▶ leur existence est assez logique ;
▶ mais souvent considérées comme une mauvaise idée ;
▶ spring, par exemple, les évite.
À éviter
▶ catch vide : cache l’exception sous le tapis, ne jamais faire,
sauf si :
1. vous vous attendez à ce que l’exception se produise ;
2. et vous voulez ne rien faire dans ce cas-là
3. …et continuer l’exécution du programme comme si de rien
n’était.
▶ catch du type :
try {
...
} catch ( UneException e) {
e. printStackTrace ();
}
traitement en cours continue comme si pas d’exception.
Du bon usage des exceptions
▶ les exceptions servent à signaler un problème ;
▶ elles ne sont pas une structure de contrôle « normale »
▶ préférer quand c’est possible un test avec un if à une
exception :
plutôt
File f = ....;
if (f. exists ()) {
FileReader r = new FileReader (f);
...
}
que :
File f = ....;
try {
FileReader r = new FileReader (f);
...
} catch ( FileNotFoundException e) {
...
}
(honnêtement, pour ce cas précis, ça passe encore…)