Guillaume Bouyer
ENSIIE
|
Utiliser la valeur de retour des méthodes pour signaler une erreur à la méthode appelante
int factorielle(int n){
if (n==0)
return 1;
else
if (n<0) {
System.out.println("erreur n négatif"); // lu par aucune méthode
return 0; //valeur anormale pour factorielle
}
else
return n*fact(n-1);
}
Plusieurs mécanismes ou outils notamment
JUnit
)
Système dédié pour gérer, détecter et traiter les anomalies “exceptionnelles” qui se produisent dans le fonctionnement du programme, pour ne pas que le programme se termine brutalement.
Ex : événement rare, cas interdit, erreur de saisie…
Le mécanisme des exceptions suit les étapes suivantes :
Une méthode peut lever (générer, lancer) une exception :
L'exception est envoyée à la méthode appelante
Cette méthode peut attraper (saisir, traiter) l'exception (par un bloc d'instruction spécifique) :
Si aucune méthode ne traite l'exception, provoque la terminaison du programme
Une exception est donc un objet qui possède
main
Exception in thread "main" java.lang.IllegalArgumentException:
output path requires an argument
at exception.Example.parseOutputPath(Example.java:19)
at exception.Example.lambda$0(Example.java:27)
at exception.Example.parseArguments(Example.java:31)
at exception.Example.main(Example.java:39)
Héritent de java.lang.Throwable
Error
“Indicates serious problems that a reasonable application should not try to catch”
Exceptions
prédéfinies“Indicates conditions that a reasonable application might want to catch”
Un grand nombre de classes d'exceptions sont proposées dans l'API Java pour couvrir les cas les plus fréquents
Les relations d'héritage entre ces classes permettent de lever ou d'intercepter des exceptions à différents niveaux de précision
Classe | Description | |||
---|---|---|---|---|
IndexOutOfBoundsException | Accès à un élément inexistant dans un ensemble (ex. sous-classe ArrayIndexOutOfBoundsException ) | |||
IOException | Peut se produire lors d'opérations d'entrées/sorties | |||
ArithmeticException | Quand une condition arithmétique exceptionnelle se produit (ex. division par zéro) | |||
IllegalArgumentException | Passage de paramètre inapproprié à une méthode (ex. NumberFormatException lorsqu'on tente de convertir une String invalide en nombre ) | |||
Classe | Description | ||||||||
---|---|---|---|---|---|---|---|---|---|
NullPointerException | Appel d'une méthode ou d'une variable à partir d'un pointeur null ; pointeur null en paramètre d'une méthode n'acceptant pas cette valeur… | ||||||||
ClassCastException | Erreur lors de la conversion d'un objet en une classe incompatible avec sa vraie classe | ||||||||
FileNotFoundException | Tentative d'ouverture d'un fichier inexistant | ||||||||
NoSuchElementException | Accès à un élement inexistant dans une collection | ||||||||
AWTException | Peut se produire lors d'opérations de type graphique | ||||||||
Surviennent dans des cas exceptionnels, mais prévisibles
IOException
, FileNotFoundException
Peuvent/doivent être traitées correctement par le développeur
Le compilateur vérifie (check) que les méthodes utilisent correctement ces exceptions
Une méthode qui peut lancer une checked exception doit le préciser dans sa déclaration avec la clause throws
(cf plus loin)
Errors
et les RuntimeExceptions
NullPointerException
, IllegalArgumentException
ou dépassement de tableau
throws
Dans la méthode qui peut rencontrer le problème
throws
throw
Dans la méthode qui l'appelle
try ... catch ... finally
throws
Clause throws
en fin de signature de la méthode
Ex. méthode parseInt
de la classe Integer
/* Convertit une chaîne de caractères, qui doit contenir uniquement des chiffres,
en un entier. Une erreur peut se produire si cette chaîne de caractères
ne contient pas que des chiffres. Dans ce cas une exception de la classe
`NumberFormatException` est émise. */
public static int parseInt(String s) throws NumberFormatException { ... }
throw
Levée explicite de l'exception avec mot-clé throw
//if (conditionAnormale) {
// throw new MyException("message");
//}
int factorielle(int n){
if (n<0) throw new IllegalArgumentException("factorielle : argument négatif");
else if (n==0) {return 1;}
else return n*fact(n-1);
}
Création d'une instance de la classe Exception
(ou Throwable
)
/*
* Renvoie le nom du mois correspondant au chiffre donné en paramètre
*/
public static String month(int mois) throws IndexOutOfBoundsException {
if ((mois < 1) || (mois > 12)) {
throw new IndexOutOfBoundsException("le numero du mois " + mois
+ " doit être compris entre 1 et 12");
}
if (mois == 1)
return "Janvier" ;
else if (mois == 2)
return "Février" ;
...
else if (mois == 11)
return "Novembre" ;
else
return "Décembre" ;
}
Avec les classes d'exception existantes, l'information transportée peut être insuffisante pour déterminer une solution satisfaisante au problème
Le programmeur peut avoir à créer ses propres classes d'exception pour s'adapter mieux aux autres classes de l'application
Par convention, les noms de classes d'exception se terminent par Exception
Le mécanisme est le même que pour tout objet. On peut :
public class ComptePleinException extends Exception {
private Compte compte;
public ComptePleinException(Compte compte) {
super("Compte plein : limite atteinte");
this.compte = compte;
}
public ComptePleinException() {}
public ComptePleinException(String message) { super(message); }
public ComptePleinException(Throwable cause) { super(cause); }
public ComptePleinException(String message, Throwable cause) { super(message, cause); }
public Compte getCompte() {return compte;}
}
public class Compte {
...
public void crediter (double n) throws ComptePleinException {
if (solde + n > limite)
throw new ComptePleinException(this);
solde += n;
}
}
try...catch
On attrape les exceptions dans des blocs dits “traite-exception” (ou “handler”)
try {
dupont.crediter(1000);
}
catch (ComptePleinException e) {
System.err.println(e.getMessage());
}
try{
// instructions à surveiller pouvant lever exceptions
}
catch (ClasseException1 e1){
// instructions traitement 1
}
catch (ClasseException2 e2){
// instructions traitement 2
}
try...catch
le corps du try
est exécuté
si aucune exception ne se produit dans le bloc correspondant, le programme se déroule normalement
si une exception est lancée les clauses catch
sont examinées dans l'ordre
l'exécution du programme suit son cours
catch
Le nom de variable correspondant à l'objet exception permet de l'utiliser dans le traitement
L'arbre d'héritage de Throwable
permet de regrouper les traitements des erreurs liées à toutes les sous-classes d'une classe d'exception
catch (IOException e) {...}
permet de regrouper le traitement de toutes les erreurs dues aux entrées/sorties (EOFException
, FileNotFoundException
…)
Le traitement de l'exception peut lui-même lever une exception, de même type ou différent (chaînage)
Rq : A partir de Java 7 on peut attraper plusieurs exceptions dans une même clause
catch (IOException|SQLException e) {...})
finally
On peut ajouer une clause finally
: contient un traitement qui sera exécuté dans tous les cas
try
ait levé ou non une exception
try
ou le bloc catch
se termine par un return
(sauf après System.exit()
)
Rq : Il est possible d'avoir un try-finally
sans catch
et try-catch
sans finally
Si une classe dérivée redéfinit (ou implémente) une méthode, la clause throws
de la méthode redéfinie doit être compatible avec celle d'origine
Soit une méthode m()
de B
qui redéfinit une méthode d'une classe mère A
, elle ne peut pas déclarer renvoyer plus d'exceptions contrôlées que m()
de A
, mais seulement :
Si aucune exception n'est levée, l'impact sur les performances d'un throw
puis d'un bloc try-catch
est négligeable
Par contre la levée d'une exception peut coûter cher car il faut remplir le stacktrace
Donc on n'utilise pas des exceptions dans le flow normal d'exécution
Il faut réserver les exceptions aux traitements des erreurs ou des cas exceptionnels et éviter de les utiliser pour traiter un cas “normal”
Ex. : Si un fichier est lu du début à la fin
EOFException
pour repérer la fin du fichier
catch
avec un affichage de message trop vague (ou pire aucun message !). Ce sont des informations précieuses pour la résolution du problème
=> Pendant le développement, afficher au moins e.printStackTrace
(cf classe Throwable
)
Error
Les Error
sont réservées aux erreurs qui surviennent dans le fonctionnement de la JVM
Ex.
OutOfMemoryError
NoSuchMethodError
RuntimeException
Comme une Error, une exception non contrôlée ne devrait jamais arriver
ArrayIndexOutOfBoundsException
)
RuntimeException
A utiliser quand
IllegalArgumentException
RuntimeException
Une bonne solution est de laisser remonter l‘exception jusqu’à une classe proche du début de l'action qui a provoqué le problème
Pour les cas peu fréquents mais, au contraire des exceptions non contrôlées, pas inattendus
correspondent à des scénarios envisagés par le développeur
participent à la logique de l'application
réservées aux problèmes qui pourront être résolus (au moins partiellement) par une des méthodes de la pile d'exécution
lever l'exception le plus près possible de l'endroit du code à l'origine du problème
Si une exception du JDK convient, il vaut mieux l'utiliser car ces exceptions sont bien connues des développeurs
S'il n'existe pas d'exception du JDK au bon niveau d'abstraction pour l'application, il vaut mieux créer un nouveau type d'exception
Une assertion est une déclaration qui permet de tester des hypothèses sur des points critiques du programme
Contient une expression booléenne qui doit être vraie lorsque l'assertion s'exécutera. Sinon, le système lancera une erreur.
Moyen rapide et efficace pour détecter et corriger les bugs pendant la programmation
En supplément, sert de commentaire
assert assertion [: expression];
assertion
est une expression booléenne
la valeur de expression
est affichée si assert
provoque une erreur
si l'assertion n'est pas vérifiée une AssertionError
est lancée (et l'expression passée à son constructeur)
Ex :
assert i!=0 : "i = " + i + " devrait être non nul";
Ces vérifications ne sont pas actives par défaut : directive de compilation
java -enableassertions [:nompackage]
Elle peuvent être activées en période de test, et désactivées en production
Les assertions désactivées ne coûtent qu'un test de drapeau mais elles prennent de la place dans le code compilé
RuntimeException
(IllegalArgumentException, IndexOutOfBoundsException, NullPointerException
…)
i t[i] > 0
String
, création d'une nouvelle instance si nouvelle affectation de valeur
StringBuilder
Avantages
simples à construire, à tester et à utiliser
besoin ni de constructeur par copie, ni d'une mise en oeuvre de clone (simple copie de référence suffit)
invariants de classe testés à la création seulement
bonnes clés pour certaines collections comme les Map et les Set
automatiquement thread-safe et n'ont pas de problèmes de synchronisation
ont une “failure atomicity” : s'ils lancent une exception, ne se laissent jamais dans un état indésirable ou indéterminé
import java.util.Date;
public final class Person {
private String firstName;
private String lastName;
private Date dob;
public Person( String firstName, String lastName, Date dob){
this.firstName = firstName;
this.lastName = lastName;
this.dob = dob;
}
public String getFirstName(){
return this.firstName;
}
public String getLastName(){
return this.lastName;
}
public Date getDOB(){
return this.dob;
}
}
Date myDate = new Date();
Person myPerson = new Person( "David", "O'Meara", myDate );
myDate.setMonth( myDate.getMonth() + 1 );
Date
est mutable, donc Person
n'est pas immuable !
Règles à suivre :
private final
final
(“strong immutability”)
final
(“weak immutability”, mais tout comportement ajouté pourrait être mutable)
this
ne doit jamais être exportée
Règles à suivre (suite) :
private
public Person(String firstName, String lastName, Date dob){
this.firstName = firstName;
this.lastName = lastName;
this.dob = new Date( dob.getTime() ); // copie défensive
}
Date myDate = myPerson.getDOB();
myDate.setMonth( myDate.getMonth() + 1 );
public Date getDOB(){
return new Date(this.dob.getTime());
}
C. Dubois, ENSIIE
R. Grin, Univ. Nice-Sophia Antipolis
H. Fauconnier, IRIF
R. Forax, Univ. Marne la Vallée
G. Picard, ENSMSE