Programmation orientée objet TP 5
L2 MPCIE Exceptions et entrées/sorties
Exercice 1 (gestion d'exceptions)
Modifier le programme précédent pour qu'il puisse gérer des exceptions. Ajouter les exceptions suivantes :
PileVideException si on tente d'accéder à la pile et qu'elle est vide. Les méthodes sommet() et
depiler() de l'interface Pile sont concernées. Cette exception ne doit renseigner ni de message ni
avoir de cause.
OperateurException si à la construction d'une instance de Operateur, la chaîne de caractères
correspondant à l'opérateur n'en est pas un, ou bien si une opération de calcul ne peut s'appliquer à un
opérateur (méthodes calcul(…)). Cette exception doit renseigner l'opérateur erroné et ne doit pas
avoir de cause.
MathematiquesException lorsque qu'une erreur de calcul se produit. Les méthodes
calcul(Double a, Double b) et calcul(Double a) de la classe Operateur sont susceptibles
de lever une telle exception. Cette exception ne peut avoir de cause et ne peut être instanciée.
DivisionZeroException si une division par zéro se produit. Cette exception dispose de deux
champs a et b qui sont utilisés pour former le message de l'exception. Cette classe hérite de
MathematiquesException.
RacineNegativeException si le calcul de la racine d'un nombre négatif se produit. Cette exception
dispose d'un champ a qui est utilisé pour former le message de l'exception. Cette classe hérite de
MathematiquesException.
ExpressionException si l'expression mathématique postfixée est mal formée. Cette exception devra
être levée chaque fois que les erreurs décrites à l'exercice 2 sont rencontrées. La méthode eval() ne
lance que des exceptions de type ExpressionException. Cette exception peut avoir une cause de
nature variable qu'il faudra renseigner.
À présent, on est capable de prendre en compte le fait qu'une expression contient un membre qui n'est ni un
nombre, ni un opérateur. Pour cela, on va utiliser l'exception NumberFormatException levée par
Double.parseDouble(Double) lorsque la chaîne de caractères à convertir en double ne représente pas un
nombre.
Exercice 2 (entrées / sorties)
Modifier la fonction main() pour qu'elle ne lise plus une expression envoyée en paramètre, mais une
expression entrée dans la console par l'utilisateur une fois le programme exécuté.
L2 MPCIE – Programmation orientée objet TP 5 1/4
Annexes
Mécanisme d'exceptions
Une exception est un objet qui est instancié lors d'un incident, par exemple lorsque les arguments d'une
méthode ne peuvent être manipulés dans provoquer une erreur, par exemple lors d'une division par 0. on dit
qu'une exception est levée (thrown). Le traitement du code est interrompu et l'exception est propagée à travers la
pile d'exécution de méthode appelée en méthode appelante. Si aucune méthode ne capture l'exception, celle-ci
remonte jusqu'à la méthode de fond de pile (la méthode main()). Le programme se termine alors avec une
erreur.
Exception in thread "main" java.lang.ArithmeticException: / by zero
at Operations.division(Attrape.java:3)
at Operations.main(Attrape.java:18)
Lever une exception
Dans le code suivant, on aimerait s'assurer que b est bien différent de 0 et, si c'est le cas, renvoyer une erreur.
On ne peut se contenter d'afficher l'erreur dans le console : on aimerait pouvoir savoir s'il y a une erreur et la
traiter en cas de problème de manière automatique de façon à ce que tout soit géré par le programme.
class Operations {
public static double division(double a, double b) {
return a / b;
}
public static void main(String[] args) {
System.out.println(division(
Double.parseDouble(args[0]),
Double.parseDouble(args[1])
));
}
}
Il faut que la méthode division() puisse 'lever' un objet spécial qui indique qu'une erreur a eu lieu. Pour
cela, on utilise le mot-clé throw suivi de l'objet à lever.
En Java, tout objet levable hérite de la classe java.lang.Throwable. L'API Java distingue deux catégories
d'objets levables :
la sous-classe java.lang.Error de java.lang.Throwable correspond à des erreurs graves
(manque de mémoire…) dues à la machine virtuelle Java, difficilement gérables par le programmeur
qui conduisent généralement à l’arrêt du programme ;
la sous-classe java.lang.Exception de java.lang.Throwable correspond à des événements
anormaux que l’on souhaite traiter (capturer) pour qu’ils ne provoquent pas l’arrêt du programme ; c'est
le type d'objets qui nous intéresse.
Lorsqu'une méthode est capable de lever une exception, il faut indiquer dans sa signature quel type d'exception
elle est capable de lever. On utilise pour cela le mot-clé throws au niveau de la signature de la fonction. On
peut aussi indiquer que la méthode peut lancer différentes classes d'exceptions en les séparant par des virgules.
Autre ces caractéristiques, une exception est un objet comme un autre. Elle s'instancie donc avec le mot-clé
new. Elle prend généralement en paramètre un message permettant de décrire plus précisément l'erreur, dans le
but d'aider l'utilisateur ou le programmeur à la corriger.
La méthode division() précédente devient donc :
public static double division(double a, double b) throws Exception {
if (b == 0)
throw new Exception(a + "/" + b);
return a / b;
}
Notez que maintenant que la méthode division() peut lever une exception, la méthode main() du
programme précédent peut également en lever une. Il faut donc soit l'indiquer dans sa signature, soit capturer
l'exception pour éviter qu'elle ne se propage. Le paragraphe suivant explique comment réaliser une telle
opération.
Il existe un grand nombre de classes d'exceptions déjà définies dans l'API. Le programmeur peut créer ses
propres classes d'exceptions en étendant la classe java.lang.Exception.
L2 MPCIE – Programmation orientée objet TP 5 2/4
class DivisionZeroException extends Exception {
public DivisionZeroException() {
super();
}
public DivisionZeroException(String message) {
super(message);
}
public DivisionZeroException(Throwable cause) {
super(cause);
}
public DivisionZeroException(String message, Throwable cause){
super(message,cause);
}
}
Le champ cause permet de spécifier la raison du déclenchement de cette exception, généralement une autre
exception précédemment capturée. Elle n'a donc pas vraiment de sens dans le cas d'une division par zéro.
Capturer une exception
Une fois levée, une exception va se propager jusqu'à la méthode main(). Pour pouvoir traiter l'erreur sans
interrompre le programme, il faut capturer l'exception levée. Pour cela, on utilise en Java trois clauses spéciales :
try : bloc dans lequel on désire capturer des exceptions éventuellement levées. Les instructions du bloc
sont lues et exécutées jusqu'à la première exception levée. Le contrôle est alors passé à une clause
catch. Ce bloc est le seul obligatoire. Il doit cependant être accompagné d'au moins un bloc catch ou
un bloc finally.
catch : bloc dans lequel on gère les exceptions capturées. Pour cela, on commence par spécifier le nom
et le type de l'exception à capturer, puis le bloc d'instructions à exécuter en cas de capture. Il peut y
avoir plusieurs blocs catch pour un seul bloc try.
finally : bloc appelé quoiqu'il arrive après le bloc try ou un bloc catch, même si l'un de ces blocs
contient un return ou un throw. Il est généralement utilisé pour libérer des ressources qui ont été
utilisées dans un bloc try ou un bloc catch.
Dans notre exemple, on capture l'exception levée par la méthode division dans la méthode main(). On en
profite pour gérer la validité des arguments :
si un argument n'est pas un nombre, Double.parseDouble() lèvera une exception de type
NumberFormatException ;
si trop peu d'arguments ont été spécifiés, args[0] lèvera une exception de type
IndexOutOfBoundsException.
Ces deux classes d'exceptions héritent de la classe Exception et seront donc traitées par le dernier bloc
catch.
class Operations {
public static double division(double a, double b)
throws DivisionZeroException {
if (b == 0)
throw new DivisionZeroException(a + "/" + b);
return a / b;
}
public static void main(String[] args) {
try {
System.out.println(division(
Double.parseDouble(args[0]),
Double.parseDouble(args[1])
));
}
catch (Exception e) {
System.err.println(
e.getClass().getName() + ": " + e.getMessage()
);
}
}
}
Le programme précédent affichera :
L2 MPCIE – Programmation orientée objet TP 5 3/4
$java Operations 3 2
1.5
$java Operations 3 0
DivisionParZeroException: 3/0
$java Operations 3 abc
java.lang.NumberFormatException: For input string: "abc"
$java Operations 3
java.lang.ArrayIndexOutOfBoundsException: 1
À noter que contrairement à une exception de type DivisionZeroException, les exceptions
NumberFormatException et IndexOutOfBoundsException n'ont pas besoin d'être attrapées pour que le
programme compile. En effet, ces exceptions héritent d'une classe spéciale définie dans l'API Java,
RuntimeException. Cette classe désigne les exceptions capturées au moment de l'exécution (runtime). Leur
particularité est donc qu'il n'est pas nécessaire des les capturer pour que le programme compile, ni de les déclarer
dans la signature des méthodes susceptibles de les lever. On parle alors d'exceptions non contrôlées.
Attention avec l'utilisation de ce type d'exceptions : le compilateur ne renverra aucune erreur si une
exception non contrôlée n'est pas capturée et n'affichera aucun avertissement. Si une exception non contrôlée est
cependant levée, elle conduira donc généralement à l'arrêt du programme puisqu'elle n'est que très rarement
capturée. Les exceptions non contrôlées sont donc généralement des exceptions utilisées pour indiquer un code
mal écrit qui ne prend pas en compte tous les cas de figure : l'erreur vient très souvent du programmeur qui doit
absolument la corriger.
Lire la console
Comme la plupart des langages de programmation, Java permet de lire des commandes entrées dans la console
pendant l'exécution du programme.
Pour lire des données depuis l'entée standard, le plus simple est d'utiliser la classe java.util.Scanner. Cette
classe permet d'extraire des données primitives à partir d'un flux de caractères. Pour lire un entier i, on utilise le
code suivant :
Scanner sc = new Scanner(System.in);
int i = sc.nextInt();
sc.close(); // /!\ important, pour libérer les ressources
D'autres méthodes existes pour récupérer d'autres types de données, telles que nextDouble(), next() (pour
une chaîne de caractères)…
Il est très important de fermer un Scanner après l'avoir instancié. Bien que Java dispose d'un garbage
collector qui prend en charge la plupart des problèmes de mémoire, Scanner fait appel à des ressources
systèmes non gérées automatiquement. Il faut donc libérer explicitement ces ressources.
À noter que dans les versions précédentes de Java, lire des données depuis la console était beaucoup plus
complexe. Il est possible de rencontrer du code semblable à celui-ci :
BufferedReader br = null;
try {
br = new BufferedReader(new InputStreamReader(System.in));
String line = br.readLine(); // /!\ peut lever une IOException
int i = Integer.parseInt(line);
} finally {
if (br != null)
br.close(); // libération des ressources
}
Tout comme un Scanner, un BufferedReader doit être fermé lorsqu'il n'est plus utilisé. La méthode
readLine() peut potentiellement lever une IOException. Pour éviter que la méthode s'interrompe sans faire
appel à la méthode close(), on utilise une clause finally. La clause finally est toujours exécutée après une
sortie du bloc try, que ce soit à la fin de son exécution normale ou bien après une sortie explicite (return ou
throw).
L2 MPCIE – Programmation orientée objet TP 5 4/4
1 / 4 100%
La catégorie de ce document est-elle correcte?
Merci pour votre participation!

Faire une suggestion

Avez-vous trouvé des erreurs dans linterface ou les textes ? Ou savez-vous comment améliorer linterface utilisateur de StudyLib ? Nhésitez pas à envoyer vos suggestions. Cest très important pour nous !