Notes de laboratoire

publicité
Contents
1 L’orienté objet et l’héritage
1.1 Introduction . . . . . . . . . . . . . . . . . . . .
1.2 Convention de nommage des classes et variables
1.3 Une première classe . . . . . . . . . . . . . . .
1.4 Les paquets . . . . . . . . . . . . . . . . . . . .
1.5 Interaction avec l’utilisateur . . . . . . . . . . .
1.6 Java et pointeurs . . . . . . . . . . . . . . . . .
1.7 Constructeurs : initialisation d’objets de classe
1.8 Setters, getters et fonction toString() . . . . .
1.9 Gestion des tableaux . . . . . . . . . . . . . . .
1.10 Héritage . . . . . . . . . . . . . . . . . . . . . .
1.11 Portée des attributs et méthodes de classe . . .
1.12 Mot-clé final . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
3
3
3
4
4
5
5
6
8
10
10
12
12
2 Polymorphisme : superclasses abstraites et
2.1 Introduction au polymorphisme . . . . . . .
2.2 Supeclasses abstraites . . . . . . . . . . . .
2.3 Interface . . . . . . . . . . . . . . . . . . . .
2.4 Superclasses abstraites VS interfaces . . . .
interfaces
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
13
13
13
15
16
3 Traitement des exceptions
3.1 Introduction . . . . . . . . . . . .
3.2 Capturer une erreur . . . . . . .
3.3 Les clause throws et throw . . .
3.4 La fonction printStackTrace()
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
17
17
18
19
20
4 Fichiers et flots de données
4.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.2 Fichiers et flux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.3 Écriture et lecture à partir d’un fichier à accès séquentiel . . . . . . . . . . . . . .
21
21
21
22
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
-1-
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
-2-
Chapter
1
L’orienté objet et l’héritage
1.1
Introduction
Avec le langage de programmation Java, l’orienté objet est un concept primordial étant donné
que tout y est objet. En C/C++, l’emploi des objets n’est pas obligatoire. L’utilisateur déclare
une fonction main() qui est appelée au lancement du programme et qui contient l’ensemble des
instructions du programme. En Java, cette fonction main() est incluse en tant que fonction
static (opération de classe) au sein d’une classe. Il n’est donc pas nécessaire de créer une
instance d’objet pour appeler cette fonction, mais il n’est pas possible de déclarer des fonctions
ou des variables en dehors des classes. Ce chapitre rappellera donc les notions relatives à l’orienté
objet.
1.2
Convention de nommage des classes et variables
Avant d’aller plus loin dans la présentation des concepts relatifs à l’orienté objet, il faut présenter
les conventions de nommage des classes et des variables au sein de Java. Afin d’améliorer la
lisibilité et la compréhensibilité du code, la convention de nommage CamelCase a été adoptée
pour nommer les classes et variables. Elle n’est pas obligatoire, mais fortement suggérée et
communément adoptée par la communauté des développeurs. Elle est dès lors imposée au cours
des séances de laboratoire. Cette convention amène les règles suivantes :
ˆ Le nom d’une classe commence par une majuscule.
ˆ Le nom d’une variable ou d’une fonction commence par une minuscule.
ˆ Lorsque que le nom d’une classe, d’une variable ou d’une fonction est composé de plusieurs
mots, l’initial de chaque mot est mise en majuscule (à l’exception du premier mot s’il s’agit
du nom d’une variable, afin de respecter la règle précédente). L’emploi du caractère ” ”
pour séparer les mots et donc contre-indiqué.
Exemples :
ˆ public class EtudiantMaster
ˆ int anneeNaissance
ˆ float calculerMoyenne()
-3-
1.3
Une première classe
Le listing 1.1 présente une première classe assez simple qui permet d’afficher le texte ”Hello
World” au terminal. En Java, chaque classe est impérativement programmée dans un fichier
distinct qui porte le nom de la classe et dont l’extension est ”.java”. Il y a ainsi un fichier nommé
HelloWorld.java qui contient le code de la classe HelloWord.
Listing 1.1: La classe HelloWorld
1
public class HelloWorld {
2
/* *
* @param args the command line arguments
*/
public static void main ( String [] args ) {
System . out . println ( " Hello World " );
}
3
4
5
6
7
8
9
10
}
La fonction main() est nécessairement déclarée comme étant publique (public) et statique
(static) afin de pouvoir être appelée sans devoir instancier la classe. Les méthodes et les
variables statiques sont appelées méthodes et variables de classe. Les variables de classes sont
partagées entre les instances de la classe. Si une instance modifie la valeur d’une variable de
classe, cette modification sera d’application pour toutes les instances de la classe considérée.
les méthodes de classe spécifient que l’action réalisée n’est pas liée à une instance particulière
de cette classe. Il n’est donc pas nécessaire d’instancier la classe, de créer un objet, pour
utiliser la méthode. Pour appeler une telle méthode, il suffit de faire directement référence
au nom de la classe. Par exemple, pour appeler la fonction main() de la classe HelloWorld :
HelloWorld.main(null);
Comme la fonction main() de la classe HelloWorld ne renvoie rien, le mot-clé void est placé
avant le nom de la fonction.
Pour afficher du texte dans la console, HelloWorld utilise les outils fournit par la classe
System. La classe System met à disposition des objets de flux qui fournissent des canaux
de communication entre un programme et un périphérique bien déterminé. En l’occurrence,
System.out permet d’afficher des données dans la console. Il fournit plusieurs méthodes à cet
escient. La méthode utilisée, println(), est un raccourci pour print line. Cette méthode affiche
dans la console la chaı̂ne de caractère passée en paramètre et insère un retour à la ligne. Il est
possible d’afficher une chaı̂ne de caractères sans retour à la ligne avec la méthode print().
1.4
Les paquets
En Java, les classes sont réunies dans des paquets qu’il faut importer pour pouvoir les employer,
à l’instar des fichiers d’en-tête en C++ qui fournissent les définitions de fonctions et de classes.
Dans l’exemple du listing 1.1, la classe System est issue du package java.lang. Celui-ci n’est
pas importé par une instruction spécifique au début du programme, car il est automatiquement
importé dans tous les programmes Java. Le paquet java.lang est le seul paquet à être importé
automatiquement. Tout autre paquet doit être importé avec le mot-clé import. Par exemple,
l’emploi de la classe JOptionPane, qui sera présenté plus loin, nécessite l’importation du paquet
javax.swing.
Il y a deux manières d’accéder à une classe placée dans un paquet. Soit en important la
classe elle-même avec une ligne de code comme :
import javax.swing.JOptionPane;
-4-
soit en important l’entièreté du paquet qui la contient à l’aide du signe ’*’ :
import javax.swing.*;
1.5
Interaction avec l’utilisateur
Nous avons déjà vu comment afficher une chaı̂ne de caractères dans la console à l’aide de la
classe System. Pour saisir des informations au clavier, c’est la classe JOptionPane qui est
employée. Cette classe fait partie du package javax.swing et fournit un ensemble de méthodes
qui permettent à l’utilisateur d’interagir via des boı̂tes de dialogue.
Pour saisir du texte via une boı̂te de dialogue, il faut utiliser la fonction showInputDialog.
Cette fonction renvoie un pointeur vers la chaı̂ne de caractères saisie par l’utilisateur.
String nom = JOptionPane.showInputDialog("Veuillez entrer votre nom");
Pour afficher du texte au sein d’une boı̂te de dialogue, il suffit d’utiliser la fonction
showMessageDialog() :
JOptionPane.showMessageDialog(null, "Votre nom est " + nom);
Pour manipuler autre chose que des chaı̂nes de caractères, il est nécessaire d’utiliser les
classes des types primitifs. Java fournit des classes pour chaque type primitif (int, float ...)
qui encapsulent une instance du type primitif considéré et qui offrent un ensemble de méthodes
pour manipuler les données. Ainsi, si un utilisateur souhaite saisir un entier, il peut utiliser la
fonction Integer.parseInt() qui prend en argument une chaı̂ne de caractères et qui renvoie
un entier :
int age = Integer.parseInt(JOptionPane.showInputDialog("Veuillez entrer votre
age"));
^
Le même genre de méthode peut être trouvé pour les autres types primitifs.
1.6
Java et pointeurs
Pour rappel, un pointeur est une variable qui contient l’adresse à laquelle se trouve une donnée.
Dans Java, tous les objets sont accédés par l’intermédiaire de pointeurs. À chaque fois qu’une
déclaration d’objet est faite, il s’agit en fait de la déclaration d’un pointeur.
Par exemple, les chaı̂nes de caractères sont gérées en Java par la classe String. Une chaı̂ne
de caractères est donc déclarée comme toute variable primitive :
String monNom;
A ce stade cependant, il n’est pas encore possible de manipuler de chaı̂nes de caractères. La
variable monNom est un pointeur et ne permet que de stocker l’adresse de la chaı̂ne de caractères.
Il est donc nécessaire d’allouer de la place pour celle-ci et de stocker l’adresse de cette espace
mémoire dans la variable monNom. Cette opération se fait à l’aide de l’instruction new :
monNom = new String();
Les fonctions ne reçoivent pas de copies locales des objets, mais directement les adresses des
objets passés en paramètres. Toute modification apportée sur un objet au sein d’une fonction
est donc aussi effective en dehors de cette fonction.
-5-
1.7
Constructeurs : initialisation d’objets de classe
Le constructeur est une fonction qui est automatiquement appelée lors de l’instanciation d’un
objet. Il porte le même nom que la classe et ne spécifie pas de type de retour. Il est possible de
tout y mettre, mais son but premier est d’initialiser les membres d’un objet pour éviter des erreurs lors de la manipulation de l’objet. Par exemple, les classes présentées aux listings 1.2 et 1.3
présentent la différence entre un constructeur mal implémenté et un constructeur correct. Ces
classes sont testées par la classe TestProduit présentée au listing 1.4. Dans le cas du constructeur incomplet, le pointeur contenant le nom du produit n’est jamais initialisé. Il vaut donc null
et le code de la classe TestProduit va entraı̂ner une erreur de type NullPointerException. Si
le constructeur est bien implémenté, comme c’est le cas au listing 1.3, il initialise le pointeur afin
d’éviter ce genre d’erreur. Cette initialisation est obligatoire uniquement si son absence peut
entraı̂ner une erreur lors de la manipulation d’instances de la classe. Par exemple, la mise à 0 de
la variable quantite n’est pas ici obligatoire, il s’agit surtout d’une bonne pratique de programmation. Par contre, si une fonction ajouter() permet d’augmenter la quantité d’un produit et
que la variable quantite n’est pas initialisée, le résultat de cette opération sera indéterminé.
Listing 1.2: La classe Produit avec un constructeur mal implémenté
1
public class Produit {
2
String nom ;
3
int quantite ;
4
5
public Produit () {
6
}
7
public String getNom () {
return nom ;
8
9
}
10
11
14
public void setNom ( String nom ) {
if ( nom != null )
this . nom = nom ;
15
}
12
13
16
18
public int getQuantite () {
return quantite ;
19
}
17
20
public void setQuantite ( int quantite ) {
if ( quantite > -1)
this . quantite = quantite ;
21
22
23
}
24
25
}
Listing 1.3: La classe Produit avec un constructeur correctement implémenté
1
public class Produit {
2
String nom ;
3
int quantite ;
4
5
6
public Produit () {
nom = new String ();
quantite = 0;
7
8
}
9
10
public String getNom () {
-6-
return nom ;
11
}
12
13
16
public void setNom ( String nom ) {
if ( nom != null )
this . nom = nom ;
17
}
14
15
18
20
public int getQuantite () {
return quantite ;
21
}
19
22
public void setQuantite ( int quantite ) {
if ( quantite > -1)
this . quantite = quantite ;
23
24
25
}
26
27
}
Listing 1.4: Application testant la classe Produit
1
public class TestProduit {
2
/* *
* @param args the command line arguments
*/
public static void main ( String [] args ) {
Produit unProduit = new Produit ();
System . out . println ( " Hello " + unProduit . getNom (). toUpperCase ());
}
3
4
5
6
7
8
9
10
11
}
12
13
14
15
16
run :
Exception in thread " main " java . lang . NullPointerException
at javaapplication3 . TestProduit . main ( TestProduit . java :15)
Java Result : 1
Une classe peut avoir plusieurs constructeurs s’ils sont surchargés. Surcharger un constructeur revient à créer un nouveau constructeur qui comprend un ensemble de paramètres dont le
nombre ou le type est différent des paramètres des constructeurs déjà existant. Par exemple, le
listing 1.5 présente une version de la classe Produit qui fournit un constructeur permettant de
donner le nom et la quantité du produit à la création de l’objet. L’emploi de ce constructeur
est présenté au listing 1.6
Listing 1.5: Exemple de constructeur surchargé avec la classe Produit
1
public class Produit {
2
String nom ;
3
int quantite ;
4
5
6
public Produit () {
nom = new String ();
quantite = 0;
7
8
}
9
12
public Produit ( String nom , int quantité ) {
this . nom = nom ;
this . quantite = quantite ;
13
}
10
11
-7-
14
public String getNom () {
return nom ;
15
16
}
17
18
21
public void setNom ( String nom ) {
if ( nom != null )
this . nom = nom ;
22
}
19
20
23
25
public int getQuantite () {
return quantite ;
26
}
24
27
public void setQuantite ( int quantite ) {
if ( quantite > -1)
this . quantite = quantite ;
28
29
30
}
31
32
}
Listing 1.6: Application utilisant le constructeur surchargé de la classe Produit
1
public class TestProduit {
2
/* *
* @param args the command line arguments
*/
public static void main ( String [] args ) {
Produit unProduit = new Produit ( " Paquet de pommes " , 25);
System . out . println ( unProduit . getNom () + " est présent en "
+ unProduit . getQuantite () + " exemplaires " );
}
3
4
5
6
7
8
9
10
11
12
}
13
14
15
run :
Paquet de pommes est présent en 25 exemplaires
1.8
Setters, getters et fonction toString()
Le programmeur est libre de mettre les fonctions qu’il désire dans les classes qu’il crée, mais il
existe un ensemble de fonctions récurrentes qui facilitent et systématisent la manipulation des
classes. Dans le contexte de la programmation orientée objet, il est recommandé de rendre privés
les attributs de classes, comme c’est le cas pour le nom et l’âge de la classe Produit présentée
au listing 1.5. Cette pratique force l’emploi de méthodes de classe sécurisées pour accéder à
ces paramètres. Ces méthodes de classe peuvent vérifier que la manipulation des attributs ne
violent pas certaines règles (exploitation d’un pointeur null, attribution d’un entier négatif à une
variable représentant l’âge d’une personne ...). Pour accéder aux attributs de classes, on utilise
des fonctions nommées accesseurs. Il s’agit de fonctions similaires à toutes autres fonctions,
mais qui commencent par get ou set et qui permettent d’accéder aux attributs de classes
respectivement en lecture et en écriture. Les setters se nomment set + ’nom de l’attribut’.
Les getters se nomment get + ’nom de l’attribut’. Un exemple de telles fonctions se trouve au
listing 1.7.
-8-
Listing 1.7: Classe Produit completée avec les setters, les getters et la fonction toString()
1
public class Produit {
2
String nom ;
3
int quantite ;
4
5
public Produit () {
6
}
7
10
public Produit ( String nom , int quantite ) {
this . nom = nom ;
this . quantite = quantite ;
11
}
8
9
12
14
public String getNom () {
return nom ;
15
}
13
16
19
public void setNom ( String nom ) {
if ( nom != null )
this . nom = nom ;
20
}
17
18
21
23
public int getQuantite () {
return quantite ;
24
}
22
25
28
public void setQuantite ( int quantite ) {
if ( quantite > -1)
this . quantite = quantite ;
29
}
26
27
30
31
@Override
32
public String toString () {
return " Produit { " + " nom = " + nom + " , quantite = " + quantite + ’} ’;
33
}
34
35
}
Une autre fonction intéressante présentée dans le listing 1.7 est la fonction toString(). Cette
fonction est appelée automatiquement à chaque fois qu’un objet est passé à une fonction là où
celle-ci attend une chaı̂ne de caractère. Ainsi, si vous passez un objet à la fonction println() :
System.out.println(aProduct);, la fonction va afficher la chaı̂ne de caractères renvoyée par
la fonction toString(). Un exemple est présenté au listing 1.8.
Listing 1.8: Application utilisant la fonction toString() de la classe Personne
1
2
3
public class TestProduit {
public static void main ( String [] args ){
Produit unProduit = new Produit ( " Paquet de pommes " , 25);
System . out . println ( unProduit );
4
}
5
6
}
7
8
9
run :
Produit { nom = Paquet de pommes , quantite =25}
-9-
1.9
Gestion des tableaux
Pour créer un tableau en Java, il faut d’abord déclarer un pointeur puis définir la taille du
tableau qui lui est assigné :
int unTableau[] = new int[10];
Le parcours du tableau peut se faire de deux manières :
1. En utilisant une variable identifiant l’indice de l’élément du tableau qui doit être accédé.
2. En utilisant un pointeur qui pointe successivement les différents éléments du tableau.
Ces deux méthodes sont illustrées au listing 1.9. Il n’est pas nécessaire de stocker la taille du
tableau dans une variable séparée, car la taille est un attribut du tableau.
Listing 1.9: Emploi des tableaux en Java
1
2
3
4
public class TestTableau {
public static void main ( String [] args ){
int unTableau [] = new int [3];
for ( int i =0; i < unTableau . length ; ++ i ){
unTableau [ i ]= i ;
5
6
}
7
for ( int element : unTableau ){
System . out . println ( element );
8
}
9
}
10
11
}
12
13
14
15
16
run :
1
2
3
Pour les objets, l’exploitation d’un tableau est légèrement différente. Comme les objets sont
gérés à l’aide de pointeurs, l’allocation de l’espace nécessaire pour un tableau d’objets se déroule
en deux étapes. Premièrement, il faut allouer de la place pour accueillir les adresses des objets :
Produit unTableau[] = new Produit[10];
Ensuite, il est faut allouer de la place pour chaque élément du tableau :
unTableau[i] = new Produit();
Une fois ces deux étapes accomplies, la manipulation d’un tableau d’objet est similaire à
l’exploitation d’un tableau d’éléments de type de bases.
1.10
Héritage
Lorsqu’une classe doit réutiliser la plupart des fonctionnalités d’une autre classe, mais en modifier
certaines et/ou en ajouter d’autres, elle peut utiliser la notion d’héritage. L’héritage permet à la
sous-classe (la classe qui hérite) d’avoir accès à toutes les méthodes de la classe parente (superclasse) qui sont déclarées comme étant public ou protected. En Java, l’héritage multiple est
interdit. Une classe ne peut hériter que d’une seule autre classe.
Le mot-clé extends est utilisé à la déclaration de la sous-classe pour indiquer qu’elle hérite
d’une super-classe :
- 10 -
public class Livre extends Produit
La classe Livre hérite alors de tous les attributs et fonctions déclarés comme étant public
ou protected de la classe Produit. La sous-classe peut accéder aux méthodes de la super-classe
à l’aide du mot-clé super :
super.toString();
Au sein du constructeur de la sous-classe, ce mot-clé sert aussi à appeler les constructeurs de la
super-classe :
super();
Les constructeurs de la sous-classe doivent toujours pouvoir appeler un constructeur de la superclasse. Le constructeur de la superclasse ne prenant aucun argument est implicitement employé
si aucun constructeur n’est explicitement appelé dans le constructeur de la sous-classe. Si un
autre constructeur doit être employé, son appel spécifique doit être la première instruction du
constructeur de la sous-classe.
Si la sous-classe déclare une fonction avec le même en-tête que la superclasse, elle redéfinit la
fonction. C’est alors la fonction de la sous-classe qui sera appelée et non celle de la superclasse.
Un exemple de sous-classe est présenté au listing 1.10. Un exemple d’utilisation de cette sousclasse est présenté au listing 1.11.
Listing 1.10: Classe Livre, sous-classe héritant de la classe Produit.
1
public class Livre extends Produit {
2
String auteur ;
3
int nombrePage ;
4
5
6
public Livre () {
super ();
nombrePage = 0;
auteur = new String ();
7
8
9
}
10
14
public Livre ( String nom , int quantite , String auteur , int nombrePage ) {
super ( nom , quantite );
this . nombrePage = nombrePage ;
this . auteur = auteur ;
15
}
11
12
13
16
18
public int getNombrePage () {
return nombrePage ;
19
}
17
20
22
public void setNombrePage ( int nombrePage ) {
this . nombrePage = nombrePage ;
23
}
21
24
26
public String getAuteur () {
return auteur ;
27
}
25
28
30
public void setAuteur ( String auteur ) {
this . auteur = auteur ;
31
}
29
32
33
@Override
34
public String toString () {
return super . toString () + " \ nLivre { " + " auteur = " + auteur + " ,
35
- 11 -
Portée
public
protected
pas de portée explicite
private
Classe
Oui
Oui
Oui
Oui
Paquet
Oui
Oui
Oui
Non
Sous-classe
Oui
Oui
Non
Non
Monde
Oui
Non
Non
Non
Table 1.1: Accessibilité des attributs et fonctions de classe en fonction de leur portée.
nombrePage = " + nombrePage + ’} ’;
36
}
37
38
}
Listing 1.11: Application utilisant la fonction toString() de la classe Personne
1
2
3
4
5
public class TestProduit {
public static void main ( String [] args ){
Produit desProduits [] = new Produit [2];
desProduits [0] = new Produit ( " Paquet de pommes " , 25);
desProduits [1] = new Livre ( " Comment programmer en Java " , 10 ,
" Deitel & Deitel " , 1546);
6
for ( Produit unProduit : desProduits )
7
System . out . println ( unProduit );
8
}
9
10
}
11
12
13
14
15
run :
Produit { nom = Paquet de pommes , quantite =25}
Produit { nom = Comment programmer en Java , quantite =10}
Livre { auteur = Deitel & Deitel , nombrePage =1546}
1.11
Portée des attributs et méthodes de classe
Les attributs et les méthodes de classe peuvent avoir différentes portées déterminées par les
mots-clés public, protected et private. Leur portée respective est rappelée au Tableau 1.1.
1.12
Mot-clé final
Le mot-clé final est utilisé pour identifier un attribut, une méthode ou une classe qui ne pourra
plus être modifié après sa déclaration.
Une classe finale (public final class) ne pourra jamais devenir une superclasse. Aucune
classe ne pourra en hériter et redéfinir ses fonctions.
Une fonction finale (portée final typeRetour) ne pourra jamais être redéfinie. Une sousclasse héritant de la classe contenant la fonction finale devra l’employer telle quelle sans possibilité de modification.
La valeur d’un attribut final (portée final typeAttribut) ne peut pas être modifiée. Elle
est attribuée à la déclaration de l’attribut et ne changera jamais.
- 12 -
Chapter
2
Polymorphisme : superclasses abstraites et
interfaces
2.1
Introduction au polymorphisme
Le polymorphisme, selon Wikipedia, est le concept consistant à fournir une interface unique à des
entités pouvant avoir différents types. Le polymorphisme qui sera étudié ici est le polymorphisme
par sous-typage : des classes différentes héritent de la même interface, des mêmes noms de
fonctionnalités, mais ces fonctionnalités peuvent être implémentées de manières différentes par
chacune de ces classes.
Les superclasses abstraites et les interfaces définissent un ensemble de fonctions qui ne sont
pas nécessairement implémentées. Il s’agit d’un contrat passé avec les classes qui héritent des
superclasses abstraites ou qui implémentent les interfaces. Ces contrats garantissent que les sousclasses fourniront un ensemble d’opérations identifiées. Par exemple, une interface Vendable
pourrait imposer aux classes qui l’implémentent de fournir une fonction getPrix() qui renvoie
le prix de l’objet. Ceci permet de traiter de manière homogène des objets instanciant des
classes différentes. Ceci facilite l’extension des systèmes informatiques. Le système manipule
des objets Vendable sans se soucier de savoir s’il s’agit d’instance de la classe Livre ou de
la classe JeuxVideo. S’il faut ajouter une classe CompactDisc au système, il suffit que cette
nouvelle classe implémente l’interface Vendable pour qu’elle soit directement manipulable par
le système sans modification supplémentaire.
Ce chapitre présente deux manières de gérer le polymorphisme par sous-typage : les superclasses abstraites et les interfaces.
2.2
Supeclasses abstraites
Une superclasse abstraite est une classe qui ne peut pas être implémentée, à la différence des
classes concrètes qui peuvent instancier des objets. Une superclasse abstraite est identifié grâce
à l’emploi du mot-clé abstract dans sa définition. Cette classe contient au moins une opération
dont l’implémentation est différée et qui est aussi identifiée par le mot clé abstract. Ces
fonctions ne possèdent pas d’implémentations, ce sont les sous-classes de la classe abstraite qui
doivent fournir leurs implémentations. Un exemple est présenté au listing 2.1
Listing 2.1: Exemple de l’emploi des superclasses abstraites. L’implémentation de la fonction
manger est différée jusqu’à la connaissance du régime alimentaire des animaux.
1
2
public abstract class Animal {
String nom ;
- 13 -
int poids ;
3
4
5
public Animal () {
6
}
7
10
public Animal ( String nom , int poids ) {
this . poids = poids ;
this . nom = nom ;
11
}
8
9
12
14
public int getPoids () {
return poids ;
15
}
13
16
18
public void setPoids ( int poids ) {
this . poids = poids ;
19
}
17
20
22
public String getCouleur () {
return nom ;
23
}
21
24
26
public void setCouleur ( String nom ) {
this . nom = nom ;
27
}
25
28
public abstract void manger ();
29
30
31
@Override
32
public String toString () {
return " Animal { " + " nom = " + nom + " , poids = " + poids + ’} ’;
33
}
34
35
}
36
37
public class Carnivore extends Animal {
38
40
public Carnivore ( String nom , int poids ) {
super ( nom , poids );
41
}
39
42
43
@Override
44
public void manger () {
System . out . println ( " Je mange de la viande " );
45
}
46
47
48
}
49
50
public class Vegetarien extends Animal {
51
53
public Vegetarien ( String nom , int poids ) {
super ( nom , poids );
54
}
52
55
56
@Override
57
public void manger () {
System . out . println ( " Je mange des végétaux . " );
58
}
59
60
61
}
62
63
64
65
public class TestAnimal {
public static void main ( String [] args ){
Animal zoo [] = new Animal [2];
- 14 -
zoo [0] = new Carnivore ( " Lion " , 190);
zoo [1] = new Vegetarien ( " Lapin " , 2);
for ( int i =0; i < zoo . length ; ++ i ){
System . out . println ( zoo [ i ]);
zoo [ i ]. manger ();
}
66
67
68
69
70
71
}
72
73
}
74
75
76
77
78
79
run :
Animal { nom = Lion , poids =190}
Je mange de la viande
Animal { nom = Lapin , poids =2}
Je mange des végétaux .
Dans l’exemple, on comprend l’utilité de déclarer la fonction manger comme étant abstraite.
Le régime alimentaire est spécifique aux animaux et ne pourrait être implémenté de manière
générale. Dès lors, il faut utiliser le mot-clé abstract pour spécifier que la fonction ne peut être
implémentée à ce niveau d’abstraction, mais qu’elle devra l’être lorsque les classes héritant de
cette superclasse abstraite deviennent concrètes.
2.3
Interface
Une interface est une structure qui ne comprend que des fonctions publiques et abstraites (public
abstract) et des données publiques, statiques et finales (public static final). Le mot-clé
final indique que la valeur de la donnée ne peut être changée. La donnée doit donc être
initialisée lors de sa déclaration.
La déclaration d’une interface est similaire à celle d’une classe. Le mot-clé class est simplement remplacé par le mot-clé interface. Pour utiliser une interface, une classe doit spécifier
qu’elle l’implémente avec le mot-clé implements et elle doit implémenter toutes les méthodes de
l’interface (ou être déclarée comme étant une classe abstraite).
L’interface est un ”contrat” à remplir par les classes l’implémentant. Elle garantit ainsi
la présence de tout un ensemble de méthodes au sein de ces classes. À nouveau, cela permet
de manipuler un ensemble de classes différentes via une interface commune. Par convention,
les interfaces sont nommées avec le suffixe ”-able”. Une interface Vendable fournira toutes les
méthodes jugées nécessaires pour qu’un objet soit ”vendable” comme une méthode retournant
le prix du produit, la quantité vendue...
Un exemple d’interface est présenté au listing 2.2.
Listing 2.2: Exemple de l’emploi des interfaces. Toute classe ”Vehiculable” doit fournir une
méthode qui permet d’accélérer, et une méthode qui permet de freiner.
3
public interface Vehiculable {
public abstract void accelerer ();
public abstract void freiner ();
4
}
1
2
5
6
public class Velo implements Vehiculable {
7
8
@Override
9
public void accelerer () {
System . out . println ( " Je pédale plus vite !! " );
10
11
}
12
13
@Override
14
public void freiner () {
- 15 -
System . out . println ( " Je serre les freins !! " );
15
}
16
17
18
}
19
20
public class Voiture implements Vehiculable {
21
22
@Override
23
public void accelerer () {
System . out . println ( " J ’ appuie sur le champignon !! " );
24
}
25
26
27
@Override
28
public void freiner () {
System . out . println ( " J ’ appuie sur la pédale de frein !! " );
29
}
30
31
32
}
33
34
35
36
37
38
39
public class TestVehiculable {
public static void main ( String [] args ) {
Vehiculable desVehicules [] = new Vehiculable [2];
desVehicules [0] = new Velo ();
desVehicules [1] = new Voiture ();
for ( Vehiculable unVehicule : desVehicules ){
unVehicule . accelerer ();
unVehicule . freiner ();
40
41
}
42
}
43
44
}
45
46
47
48
49
50
run :
Je pédale plus vite !!
Je serre les freins !!
J ’ appuie sur le champignon !!
J ’ appuie sur la pédale de frein !!
2.4
Superclasses abstraites VS interfaces
Au vu du polymorphisme, les superclasses abstraites et les interfaces permettent toutes deux de
manipuler un ensemble de classes différentes via une interface commune. Cependant, elles ont
chacune leurs avantages et inconvénients. Voici un comparatif non exhaustif des deux solutions
:
ˆ Les interfaces peuvent être utilisées pour remplacer l’héritage multiple. Elles ne permettent
pas l’héritage de fonctions implémentées, mais elles permettent à une classe d’”hériter” de
diverses interfaces.
ˆ Les superclasses abstraites permettent l’héritage de fonctions implémentées mais empêchent
l’héritage d’autres classes.
ˆ Les superclasses abstraites permettent la déclaration de membres non publiques, ce qui
n’est pas possible avec les interfaces.
ˆ Si de nouvelles méthodes doivent être ajoutées après le développement d’un projet, il est
possible de les ajouter dans les superclasses abstraites sans pour autant devoir modifier
les sous-classes en héritant (tant que ces méthodes ne sont pas abstraites). Dans le cas
des interfaces, l’ajout de nouvelles méthodes entraı̂ne d’office la nécessité d’implémenter
ces méthodes dans chaque classe implémentant ces interfaces.
- 16 -
Chapter
3
Traitement des exceptions
3.1
Introduction
Il arrive que des erreurs arrivent au cours de l’exécution des programmes lorsque la sécurité
de ceux-ci est trop faible, lorsqu’ils ont été mal conçus ou lorsque l’utilisateur utilise mal le
programme. Ainsi, une fonction division qui ne contrôle pas la valeur du dénominateur pourrait
entraı̂ner une erreur si celui-ci vaut zéro. Aussi, une exception sera levée si un utilisateur saisi
du texte dans une zone de texte qui est vouée à être convertie en chiffres.
Par défaut, ces exceptions vont interrompre le programme en imprimant la stack trace, ou pile
d’appels en français. La pile d’appels contient la succession d’appels de fonctions qui a mené à la
levée de l’exception. Ces appels identifient les classes où se situent ces fonctions, les numéros de
lignes où les appels ont lieu et le numéro de ligne de l’instruction fautive. Un exemple d’exception
levée lors du dépassement des limites d’un tableau est présentée au listing 3.1. Plutôt que de
laisser l’exception arrêter brutalement l’exécution du programme, il est possible de l’attraper
et d’adapter l’exécution du programme à la levée de l’exception. Par exemple, dans le cas de
la saisie d’un texte au lieu de chiffres, il est possible de demander à l’utilisateur de corriger sa
saisie. Ce chapitre présente les méthodes qui permettent de programmer de tels comportements.
Listing 3.1: Exemple d’erreur dûe au dépassement des bornes d’un tableau.
1
public class Panier {
2
Produit [] lePanier ;
3
int indice ;
4
public Panier () {
lePanier = new Produit [3];
5
6
indice = 0;
7
}
8
9
public void addProduit ( Produit newProduit ){
10
lePanier [ indice ++]= newProduit ;
11
}
12
13
}
14
15
16
17
18
19
20
21
public class TestExceptions {
public static void main ( String [] args ) {
Panier monPanier = new Panier ();
monPanier . addProduit ( new Produit ( " One Piece vol . 80 " , 1));
monPanier . addProduit ( new Produit ( " The Dark Knight " , 1));
monPanier . addProduit ( new Produit ( " Game of Thrones Season 6 " , 1));
monPanier . addProduit ( new Produit ( " The Hypnoflip Invasion " , 1));
}
22
23
}
- 17 -
24
25
26
27
28
29
run :
Exception in thread " main " java . lang . A r r a y I n d e x O u t O f B o u n d s E x c e p t i o n : 3
at javaapplication3 . Panier . addProduit ( Panier . java :22)
at javaapplication3 . TestExceptions . main ( TestExceptions . java :18)
Java Result : 1
3.2
Capturer une erreur
Pour capturer une erreur, il faut place les instructions susceptibles de causer l’erreur à l’intérieur
d’un bloc try. Ce bloc try sera directement suivi d’un bloc catch qui identifie les erreurs qui
doivent être attrapées et gérées par le programme. Il peut y avoir plusieurs bloc catch pour capturer plusieurs erreurs différentes. Les bloc catch peuvent aussi avoir un ordre hiérarchique du
plus spécifique au plus général (ArrayIndexOutOfBoundsException → IndexOutOfBoundsException → RuntimeException → Exception).
Lorsqu’une erreur identifiée par le bloc catch est déclenché dans le bloc try, le contrôle de
programme quitte le bloc try et poursuit au premier bloc catch correspondant à l’erreur. Un
exemple est présenté au listing 3.2. L’erreur ArrayIndexOutOfBoundsException y est explicitement capturée et un message invite le programmeur à créer un tableau de plus grande taille. Il
est aussi possible de placer un bloc finally qui sera exécuté en toutes circonstances, qu’il y ait
eu erreur ou pas, à la fin de l’exécution du bloc try ou du bloc catch.
Le listing 3.2 indique comment la classe Panier peut utiliser la gestion d’erreur pour éviter
de faire crashe le programme en cas d’erreur dans la fonction addProduit().
Listing 3.2: Exemple de traitement d’erreur à l’aide du bloc try/catch
1
public class Panier {
2
Produit [] lePanier ;
3
int indice ;
4
public Panier () {
lePanier = new Produit [3];
5
6
indice = 0;
7
}
8
9
public void addProduit ( Produit newProduit ){
try {
10
11
lePanier [ indice ++]= newProduit ;
12
13
}
14
catch ( A r r a y I n d e x O u t O f B o u n d s E x c e p t i o n e ){
System . out . println ( " Le tableau n ’ est pas assez grand ,
veuillez agrandir sa taille . " );
15
16
17
}
18
catch ( Exception e ){
System . out . println ( " Une autre exception a été levée . " );
19
20
}
21
finally {
System . out . println ( " Et voilà , on sort de la fonction . " );
22
}
23
}
24
25
}
26
27
28
29
30
31
public class TestExceptions {
public static void main ( String [] args ) {
Panier monPanier = new Panier ();
monPanier . addProduit ( new Produit ( " One Piece vol . 80 " , 1));
monPanier . addProduit ( new Produit ( " The Dark Knight " , 1));
- 18 -
monPanier . addProduit ( new Produit ( " Game of Thrones Season 6 " , 1));
monPanier . addProduit ( new Produit ( " The Hypnoflip Invasion " , 1));
32
33
}
34
35
}
36
37
38
39
40
41
42
run :
Et voilà , on sort de
Et voilà , on sort de
Et voilà , on sort de
Le tableau n ’ est pas
Et voilà , on sort de
3.3
la fonction .
la fonction .
la fonction .
assez grand , veuillez agrandir sa taille .
la fonction .
Les clause throws et throw
La clause throws reprend une liste des exceptions qu’une méthode est susceptible de lancer.
Cette liste peut être non exhaustive. Java effectue une distinction importante entre les Exception
vérifiées, les RuntimeException non vérifiées et les Error. Les RuntimeException sont des exceptions qui peuvent être levée à n’importe quel moment lors de l’exécution du programme,
comme ArrayIndexOutOfBoundsException. La tentative d’utilisation d’un pointeur null va
également lever une RuntimeException de type NullPointerReference. Comme il serait fastidieux pour le programmeur de citer toutes ces exceptions dans la clause throws, il n’est pas
obligatoire toutes les reprendre. C’est pourquoi elles sont qualifiées de non vérifiées. Il en est
de même pour les Error (débordement de la pile d’appel, espace mémoire insuffisant ...).
Les autres erreurs (IOException, IllegalAccessException ...) doivent soit être capturées
au sein d’un bloc try/catch, soit être reprise dans la liste de la clause throws. Ces exceptions
sont ainsi vérifiées par le compilateur.
La clause throws se place après le nom de la méthode :
int nomMéthode(listeParamètres) throws TypeException1, TypeException2,
TypeException3, ...
Le listing 3.3 reprend l’exemple de la classe Panier où est indiquée une RuntimeException dans
la clause throws, ce qui n’est donc pas obligatoire.
Listing 3.3: Exemple d’utilisation de la clause throws.
1
public class Panier {
2
Produit [] lePanier ;
3
int indice ;
4
public Panier () {
lePanier = new Produit [3];
5
6
indice = 0;
7
}
8
9
public void addProduit ( Produit newProduit )
throws A r r a y I n d e x O u t O f B o u n d s E x c e p t i o n {
10
11
lePanier [ indice ++]= newProduit ;
12
}
13
14
}
La clause throw est utilisée par l’utilisateur pour lancer explicitement une exception :
throw new Exception("Tu as fait une erreur ici !!");
- 19 -
3.4
La fonction printStackTrace()
Les exceptions fournissent des méthodes indiquant des informations concernant l’origine de
l’erreur. Ainsi la fonction printStackTrace() imprime la liste des appels de fonctions qui
ont mené à l’erreur, ainsi que leur localisation dans le code source. Le listing 3.4 illustre l’emploi
de cette fonction.
Listing 3.4: Exemple d’utilisation de la fonction printStackTrace().
1
public class Panier {
2
Produit [] lePanier ;
3
int indice ;
4
public Panier () {
lePanier = new Produit [3];
5
6
indice = 0;
7
}
8
9
public void addProduit ( Produit newProduit ){
try {
10
11
lePanier [ indice ++]= newProduit ;
12
13
}
14
catch ( A r r a y I n d e x O u t O f B o u n d s E x c e p t i o n e ){
e . printStackTrace ();
15
16
}
17
finally {
System . out . println ( " L ’ exécution continue !! " );
18
}
19
}
20
21
}
22
23
24
25
26
27
28
29
30
run :
L ’ exécution continue !!
L ’ exécution continue !!
L ’ exécution continue !!
java . lang . A r r a y I n d e x O u t O f B o u n d s E x c e p t i o n : 3
at javaapplication3 . Panier . addProduit ( Panier . java :23)
at javaapplication3 . TestExceptions . main ( TestExceptions . java :19)
L ’ exécution continue !!
- 20 -
Chapter
4
Fichiers et flots de données
4.1
Introduction
Les données contenues dans les variables des programmes ne sont pas permanentes. Elles peuvent
être écrasées une fois que le contrôle du programme sort du bloc où elles ont été déclarées ou
que le programme se termine. Pour rendre les données permanentes, il faut les placer dans un
fichier. Ce chapitre présente comment placer des données dans un fichier en Java.
4.2
Fichiers et flux
En informatique, les données sont représentées par des bits qui peuvent valoir 0 ou 1. Pour
pouvoir représenter plus que deux valeurs, les bits sont rassemblés en groupes de 8 bits appelés
octets. Un octet peut représenter 256 valeurs différentes. Deux octets sont nécessaires pour
représenter les caractères à l’aide de la norme Unicode. Pour les données plus complexes, comme
les chiffres, les caractères sont regroupés pour former des champs. Un entier nécessite ainsi
deux caractères, ou quatre octets. Lorsque plusieurs champs sont regroupés, on parle alors d’
enregistrements. Finalement, un fichier est destiné à accueillir un ensemble d’enregistrements.
Pour Java, chaque fichier est un flux séquentiel d’octets. La fin d’un fichier peut être connue
soit à l’aide d’une marque spécifique, soit à l’aide d’un nombre d’octets précis consigné dans la
structure de données administrative maintenue par le fichier. En programmation, la fin d’un
fichier est indiquée soit par la levée d’une exception, soit par une valeur particulière retournée
par la fonction de lecture.
Au démarrage d’un programme quelconque, Java crée à priori trois objets de flux qu’il
associe aux périphériques au début de son exécution : System.in, System.out et System.err.
L’objet System.in permet à un programme de récupérer les octets saisis au clavier, l’objet
System.out permet de sortir les données à l’écran et l’objet System.err permet de sortir des
messages d’erreurs à l’écran. Chaque flux peut être redirigé. Ainsi, il serait possible de rediriger
System.err afin qu’il enregistre les messages d’erreur dans un fichier plutôt que de les afficher à
l’écran. Ainsi, lorsqu’un programme plante, les informations relatives aux erreurs qui ont mené
à l’arrêt brutal du programme sont conservées dans un fichier.
Plusieurs classes sont employées pour rediriger les sorties appropriées vers des fichiers. Les
classes abstraites InputStream et OutputStream définissent les méthodes qui prennent respectivement en charge des entrées et des sorties d’octets. Les classes FileInputStream et
FileOuptuStream implémentent ces classes abstraites et permettent de faire des entrées/sorties
d’octets à partir de fichiers. Pour convertir les classes en flux d’octets, ou inversement, il faut
utiliser les classes ObjectInputStream et ObjectOutputStream. Ces classes redirigent les flux
d’octets vers des InputStream ou des OutputStream. Il faut donc les chaı̂ner avec des instances des classes FileInputStream et FileOutputStream pour lire et écrire des classes dans
- 21 -
des fichiers.
Pour être compatible avec les classes ObjectInputStream et ObjectOutputStream, une
classe doit implémenter l’interface Serializable. Cette interface ne possède ni méthode, ni
champs. Elle sert juste à baliser les classes comme étant Serializable. Toutes les variables
d’instances de la classe doivent être de type Serializable. C’est le cas pour les types de base
fournis par Java (float, int, String ...). Il s’agira surtout de vérifier que vos classes dont des
instances sont contenues dans une classe Serializable soient elles aussi Serializable.
4.3
Écriture et lecture à partir d’un fichier à accès séquentiel
La classe JFileChooser du package javax.swing fournit tout un ensemble de méthodes permettant d’accéder à des fichiers à accès séquentiel. La fonction showSaveDialog() permet
de se déplacer dans l’arborescence de fichier de la machine et de créer un nouveau chemin de
fichier. La fonction showOpenDialog() permet de récupérer le chemin d’un fichier existant.
Ces deux fonctions retournent un entier indiquant le résultat de l’opération. Si cet entier vaut
JFileChooser.CANCEL OPTION, cela indique que l’utilisateur a annulé la saisie du chemin et qu’il
est inutile d’essayer d’exploiter le résultat de la fonction. Si tout s’est bien passé, la fonction
getSelectedFile() de la classe JFileChooser permet de créer ou d’ouvrir le fichier identifié
par le chemin saisi par l’utilisateur. Le retour de cette fonction est une instance de la classe
File qui permet d’accéder au fichier.
Ce fichier doit alors être utilisé comme argument du constructeur des chaı̂nes de classes
FileInputStream → ObjectInputStream ou FileOutputStream → ObjectOutputStream pour
ouvrir des flux qui permettront respectivement de lire ou d’écrire dans le fichier. Dans le cas de
l’écriture, toutes les données contenues a priori dans le fichier seront écrasées. Pour ajouter du
contenu dans un fichier existant, il faut d’abord l’ouvrir en lecture, récupérer son contenu, ouvrir
le fichier en écriture, réécrire le contenu du fichier et ajouter les nouvelles données. L’écriture et
la lecture sont effectués avec respectivement les fonctions writeObject et readObject des classes
FileOutputStream et FileInputStream. Concernant la fonction readObject, elle renvoie un
objet de type Object; il faut donc le transtyper suivant le type de l’objet lu.
Un exemple d’écriture et de lecture d’un objet est présenté au listing 4.1.
Listing 4.1: Exemple de lecture et d’écriture d’instances de classe dans un fichier à accès
séquentiel.
1
public class Produit implements Serializable {
2
String nom ;
3
int quantite ;
4
5
public Produit () {
6
}
7
10
public Produit ( String nom , int quantite ) {
this . nom = nom ;
this . quantite = quantite ;
11
}
8
9
12
14
public String getNom () {
return nom ;
15
}
13
16
19
public void setNom ( String nom ) {
if ( nom != null )
this . nom = nom ;
20
}
17
18
21
- 22 -
23
public int getQuantite () {
return quantite ;
24
}
22
25
28
public void setQuantite ( int quantite ) {
if ( quantite > -1)
this . quantite = quantite ;
29
}
26
27
30
31
@Override
32
public String toString () {
return " Produit { " + " nom = " + nom + " , quantite = " + quantite + ’} ’;
33
}
34
35
}
36
37
38
39
40
public class TestGestionFichier {
public static void main ( String [] args ) {
JFileChooser choixFichier = new JFileChooser ();
int resultat = choixFichier . showSaveDialog ( null );
41
42
if ( resultat == JFileChooser . CANCEL_OPTION ){
43
System . out . println ( " Création du fichier annulée . " );
44
return ;
45
}
46
47
File nomFichier = choixFichier . getSelectedFile ();
48
if ( nomFichier == null || nomFichier . getName (). equals ( " " )){
49
System . out . println ( " Nom de fichier incorrect . " );
50
return ;
51
}
52
53
Produit oeuf = new Produit ( " Oeuf " , 6);
54
try {
ObjectOutputStream sortie = new ObjectOutputStream (
new FileOutputStream ( nomFichier ));
sortie . writeObject ( oeuf );
sortie . close ();
55
56
57
58
59
}
60
catch ( Exception e ){
e . printStackTrace ();
61
62
}
63
64
65
resultat = choixFichier . showOpenDialog ( null );
if ( resultat == JFileChooser . CANCEL_OPTION ){
66
System . out . println ( " Création du fichier annulée . " );
67
return ;
68
}
69
70
nomFichier = choixFichier . getSelectedFile ();
71
if ( nomFichier == null || nomFichier . getName (). equals ( " " )){
72
System . out . println ( " Nom de fichier incorrect . " );
73
return ;
74
}
75
76
ObjectInputStream entree = null ;
77
try {
entree = new ObjectInputStream ( new FileInputStream ( nomFichier ));
while ( true ){
78
79
Produit unProduit = ( Produit ) entree . readObject ();
System . out . println ( unProduit );
80
81
}
82
83
}
84
catch ( java . io . EOFException e ){
- 23 -
System . out . println ( " Fin de la lecture du fichier . " );
85
86
}
87
catch ( Exception e ){
e . printStackTrace ();
88
89
}
90
finally {
try {
91
entree . close ();
92
93
}
94
catch ( Exception e ){
e . printStackTrace ();
95
}
96
}
97
}
98
99
100
}
101
102
103
104
run :
Produit { nom = Oeuf , quantite =6}
Fin de la lecture du fichier .
- 24 -
Téléchargement