Introduction ` a la programmation orient´ ee objet et au langage Java

publicité
Arnaud PÊCHER
Université de Bordeaux (LaBRI)
http://www.labri.fr/pecher/pmwiki/pmwiki.php/Teaching/2014
Introduction à la programmation orientée objet
et au langage Java
DUT Informatique - Année Spéciale
2014-2015
Table des matières
1
Premiers pas en Java à l’aide de Processing
1.1 Présentation de l’environnement et du langage Processing
1.2 Programmation impérative : enchaînement d’instructions
1.2.1 Premiers pas . . . . . . . . . . . . . . . . . . .
1.2.2 Variables et affectations . . . . . . . . . . . . .
1.2.3 Structures de contrôle . . . . . . . . . . . . . .
1.2.4 Méthodes et appels de méthodes . . . . . . . . .
1.2.5 Manipulation de chaines de caractères . . . . . .
1.2.6 Manipulations de fichiers . . . . . . . . . . . . .
1.2.7 Tests . . . . . . . . . . . . . . . . . . . . . . .
1.3 Présentation du langage Java . . . . . . . . . . . . . . .
1.3.1 Historique et positionnement . . . . . . . . . . .
1.3.2 Particularités . . . . . . . . . . . . . . . . . . .
1.3.3 Ecrire un programme en Java "pur" . . . . . . .
1.3.4 Affichage : format versus print . . . . . . . .
1.4 Conclusion : quand utiliser Processing ? Java ? . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
3
3
4
4
5
6
8
10
11
12
12
12
13
14
15
16
I
Introduction à la programmation orientée objet
2
Bases de la programmation objet
2.1 De Processing à Java . . . . . . . . . . . . . . . . . . . . .
2.1.1 Premiers pas en Java . . . . . . . . . . . . . . . . .
2.1.2 Réutiliser les bibliothèques de Processing . . . . .
2.2 Le modèle objet . . . . . . . . . . . . . . . . . . . . . . . .
2.2.1 Classes, types, attributs, méthodes . . . . . . . . . .
2.2.2 Déroulement d’un programme objet et mot-clef this
2.2.3 Cycle de vie d’un objet . . . . . . . . . . . . . . . .
2.2.4 Surcharge de méthodes . . . . . . . . . . . . . . . .
2.3 Conversions entre types de bases et leurs classes dédiées . .
2.4 Tableaux, collections d’objets . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
21
21
21
22
24
24
28
29
31
32
33
Instances & Encapsulation
3.1 Instances d’objets . . . . . . . . . . . . . . . . . . . .
3.1.1 Attributs et méthodes statiques . . . . . . . . .
3.1.2 Passage d’objets en paramètres d’une méthode
3.1.3 Copie d’une instance . . . . . . . . . . . . . .
3.2 Encapsulation . . . . . . . . . . . . . . . . . . . . . .
3.2.1 Paquets . . . . . . . . . . . . . . . . . . . . .
3.2.2 Visibilité . . . . . . . . . . . . . . . . . . . .
3.2.3 Accesseurs . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
35
36
36
38
39
41
41
41
42
3
3
19
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
TABLE DES MATIÈRES
4
4
5
II
6
7
III
8
Héritage, classes abstraites, interfaces
4.1 Héritage . . . . . . . . . . . . . . . . . . . . . . . . . .
4.1.1 Redéfinition d’une méthode ou d’un attribut . . .
4.1.2 Les méthodes disponibles pour toutes les classes
4.2 Classes, méthodes abstraites et interfaces . . . . . . . .
4.2.1 Classes et méthodes abstraites . . . . . . . . . .
4.2.2 Interfaces . . . . . . . . . . . . . . . . . . . . .
4.3 Exemple : modélisation générique d’un labyrinthe . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
45
47
48
50
50
50
51
52
Polymorphisme & compléments sur les collections
5.1 Polymorphisme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5.2 Collections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5.2.1 L’interface Collection . . . . . . . . . . . . . . . . . . . . . .
5.2.2 Les listes doublement chainées . . . . . . . . . . . . . . . . . .
5.2.3 les vecteurs ou tableaux dynamiques . . . . . . . . . . . . . . .
5.2.4 Les tables de hachage et les collections partiellement ordonnées
5.2.5 Les ensembles . . . . . . . . . . . . . . . . . . . . . . . . . .
5.2.6 Les associations . . . . . . . . . . . . . . . . . . . . . . . . .
5.2.7 Les vues et les opérations de masse . . . . . . . . . . . . . . .
5.2.8 Algorithmes . . . . . . . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
55
56
58
59
60
61
61
62
62
62
63
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Java Developpment Kit et quelques compléments incontournables
65
Contrôle des erreurs : débogage, tests unitaires et exceptions
6.1 Débogage . . . . . . . . . . . . . . . . . . . . . . . . . .
6.1.1 En ligne de commande : jdb . . . . . . . . . . . .
6.1.2 Dans Eclipse . . . . . . . . . . . . . . . . . . . .
6.2 Tests unitaires . . . . . . . . . . . . . . . . . . . . . . . .
6.3 Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
67
68
68
71
71
72
Entrées-sorties : Flux, Fichiers, Sérialisation, XML
7.1 Flux en entrée/sortie . . . . . . . . . . . . . . .
7.1.1 Traitement des erreurs d’entrée/sortie . .
7.1.2 Les flux d’octets . . . . . . . . . . . . .
7.1.3 Les flux de caractères . . . . . . . . . . .
7.1.4 Flux avec tampons . . . . . . . . . . . .
7.2 Fichiers, réseau et sérialisation (objets persistants)
7.2.1 Fichiers . . . . . . . . . . . . . . . . . .
7.2.2 Réseau . . . . . . . . . . . . . . . . . .
7.2.3 Sérialisation . . . . . . . . . . . . . . .
7.3 RMI : Remote Method Interface . . . . . . . . .
7.3.1 Côté serveur . . . . . . . . . . . . . . .
7.3.2 Coté client . . . . . . . . . . . . . . . .
7.4 XML : eXtensible Mark-up Language . . . . . .
7.4.1 Présentation . . . . . . . . . . . . . . . .
7.4.2 Java et XML . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
79
79
80
80
81
81
82
83
86
89
90
90
91
92
92
94
Java (un peu plus) avancé
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
99
Programmation multi-processus légers
101
8.1 Contrôler un processus : de sa création à sa terminaison . . . . . . . . . . . . . . . . . . . . . . . 101
8.1.1 Création . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
TABLE DES MATIÈRES
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
103
103
103
104
104
Connexion directe avec des bases de données relationnelles avec JDBC
9.1 JDBC : structure de la bibliothèque . . . . . . . . . . . . . . . . . .
9.1.1 Se connecter à une base . . . . . . . . . . . . . . . . . . .
9.1.2 Modifier des données . . . . . . . . . . . . . . . . . . . . .
9.1.3 Regrouper des actions . . . . . . . . . . . . . . . . . . . .
9.1.4 Sélectionner des données . . . . . . . . . . . . . . . . . . .
9.1.5 Types de données et métadonnées . . . . . . . . . . . . . .
9.2 JDBC2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
109
109
110
111
111
112
112
113
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
117
117
117
119
121
123
124
125
125
8.2
9
5
8.1.2 Processus bloqués . . . . . . . . . . . .
8.1.3 Terminaison d’un processus . . . . . . .
8.1.4 Groupes de processus . . . . . . . . . . .
8.1.5 Démons . . . . . . . . . . . . . . . . . .
Coordination de plusieurs processus : les verrous
10 Réagir à des évènements : les écouteurs
10.1 Les écouteurs . . . . . . . . . . . . . . .
10.1.1 La bibliothèque awt.event . . .
10.1.2 Adaptateurs . . . . . . . . . . . .
10.2 Classes internes et anonymes . . . . . . .
10.2.1 Les classes internes simples . . .
10.2.2 Les classes internes aux méthodes
10.2.3 Les classes internes anonymes . .
10.2.4 Les classes internes statiques . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
11 Interfaces graphiques en SWING
129
11.1 Introduction à SWING . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
11.2 Gestionnaire de placement : l’interface LayoutManager . . . . . . . . . . . . . . . . . . . . . . . 130
11.3 Dessiner : la classe Graphics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
12 Interfaces graphiques en SWT
12.1 Découverte sur un exemple type . . . . .
12.2 Multiprocessus et SWT . . . . . . . . . .
12.3 Principaux écouteurs de SWT . . . . . .
12.4 Composants . . . . . . . . . . . . . . . .
12.5 Disposition des composants : Layouts . .
12.6 Composants SWT . . . . . . . . . . . . .
12.6.1 Liste des principaux composants .
12.6.2 Utilisation de quelque composants
12.7 Boites de dialogue . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
135
135
136
137
139
140
142
142
143
147
13 Annotations et trois applications
13.1 Les annotations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
13.1.1 Les annotations standards . . . . . . . . . . . . . . . . . . . . .
13.1.2 Les annotations personnalisées . . . . . . . . . . . . . . . . . . .
13.1.3 Les méta-annotations . . . . . . . . . . . . . . . . . . . . . . . .
13.1.4 Comparaison XML/annotations . . . . . . . . . . . . . . . . . .
13.2 Application : les objets persistants . . . . . . . . . . . . . . . . . . . . .
13.2.1 Les principale annotation de JPA - Java Persistence API . . . . .
13.2.2 En pratique via des annotations : des objets persistants Personne
13.3 Application : les servlets . . . . . . . . . . . . . . . . . . . . . . . . . .
13.3.1 Présentation des servlets . . . . . . . . . . . . . . . . . . . . . .
13.3.2 Annotations pour les servlets . . . . . . . . . . . . . . . . . . . .
13.3.3 En pratique : quelques servlets via des annotations . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
151
152
152
153
153
154
154
154
155
159
159
161
161
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
TABLE DES MATIÈRES
6
13.4 Application : les services webs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
13.4.1 Historique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
13.4.2 Présentation des services webs . . . . . . . . . . . . . . . . . . . . . . . . . . .
13.4.3 Le protocole SOAP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
13.4.4 En pratique via des annotations : service web retournant des dates au format long
IV
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Exercices et travaux pratiques
162
162
162
164
167
173
14 Exercices supplémentaires
175
14.1 Exercices indépendants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175
14.2 Problèmes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 190
15 Travaux pratiques : l’application Labyrinthe
15.1 Affichage du plan d’un labyrinthe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
15.2 Modélisation du labyrinthe, déplacement fluide d’un personnage et éclairage . . . . . . . .
15.3 Restructuration de la modélisation du labyrinthe . . . . . . . . . . . . . . . . . . . . . . .
15.4 Implémentation d’un labyrinthe basée sur une grille . . . . . . . . . . . . . . . . . . . . .
15.5 Polymorphisme : implémentation alternative d’un labyrinthe . . . . . . . . . . . . . . . .
15.6 Collections : modélisation de graphes . . . . . . . . . . . . . . . . . . . . . . . . . . . .
15.7 Modélisation d’un labyrinthe par un graphe . . . . . . . . . . . . . . . . . . . . . . . . .
15.8 Affichage du plus court chemin dans le labyrinthe, via une bibliothèque externe (JGraphT)
15.9 Découverte des principales commandes du JDK et des bibliothèques de Java . . . . . . . .
15.10Débogueur, tests unitaires, exception . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
15.11Eclairage et monstres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
15.12Sauvegarde/restauration d’un labyrinthe et de ses salles . . . . . . . . . . . . . . . . . . .
15.13Serveur de labyrinthes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
16 Corrigés
V
Annexes
A Présentation du protocole HTTP
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
195
196
199
202
204
206
207
209
214
219
220
225
234
239
241
315
317
Introduction
Ce cours est une introduction à la programmation orientée objet ainsi qu’au langage Java. Il existe une multitude d’autres langages orientés objet, dont les plus connus sont C++, C#, Objective C, Python, etc ...
Contenu : le premier chapitre est consacré à une brève description de l’environnement de développement de
Processing, qui nous servira à faire nos premiers pas en Java.
La première partie du cours est une introduction à la programmation objet, dans laquelle les principales notions
de ce paradigme de programmation sont passées en revue. Cette partie est donc assez indépendante du langage
choisi.
Dans la seconde partie, nous abordons des concepts qui, bien que n’étant pas au cœur de la programmation
objets, sont spécifiques à Java ou omniprésents dans le processus de développement.
La troisième et dernière partie contient des aspects de programmation plus avancée et très d’actualité : programmation multi-processus légers pour tirer pleinement partie des processeurs multi-cœurs et usage des annotations pour diversifier la finalité d’un même code source, dont la génération de documentation automatique, la
persistance d’objets dans des bases de données, le déploiement de services web, etc .
Ce que ce cours ne contient pas : la programmation d’interfaces graphiques "complexes" (qui sera étudiée
dans le module de programmation événementielle), JEE (enseigné dans un module spécifique au second semestre
ainsi que dans le module d’architecture multi-niveaux), la programmation web en Java ...
1
2
TABLE DES MATIÈRES
Chapitre 1
Premiers pas en Java à l’aide de Processing
Le langage Processing est une version simplifiée du langage Java, facilitant la programmation d’applications multimédia. Pour nos premiers pas en Java, nous nous baserons sur ce langage et son environnement de
développement.
Sommaire
1.1
1.2
1.3
1.4
1.1
Présentation de l’environnement et du langage Processing
Programmation impérative : enchaînement d’instructions
1.2.1 Premiers pas . . . . . . . . . . . . . . . . . . . . .
1.2.2 Variables et affectations . . . . . . . . . . . . . . .
1.2.3 Structures de contrôle . . . . . . . . . . . . . . . .
1.2.4 Méthodes et appels de méthodes . . . . . . . . . . .
1.2.5 Manipulation de chaines de caractères . . . . . . . .
1.2.6 Manipulations de fichiers . . . . . . . . . . . . . . .
1.2.7 Tests . . . . . . . . . . . . . . . . . . . . . . . . .
Présentation du langage Java . . . . . . . . . . . . . . . .
1.3.1 Historique et positionnement . . . . . . . . . . . . .
1.3.2 Particularités . . . . . . . . . . . . . . . . . . . . .
1.3.3 Ecrire un programme en Java "pur" . . . . . . . . .
1.3.4 Affichage : format versus print . . . . . . . . . .
Conclusion : quand utiliser Processing ? Java ? . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
3
4
4
5
6
8
10
11
12
12
12
13
14
15
16
Présentation de l’environnement et du langage Processing
Le développement de Processing a été lancé en 2001 par Casey Reas et Benjamin Fry (MIT).
Il contient un environnement de développement très basique. En raison de sa simplicité, il est également très
utilisé à des fins pédagogiques. C’est d’ailleurs la raison pour laquelle nous allons l’utiliser dans ce cours comme
préambule à la programmation Java.
Outre ses vertus pédagogiques, Processing possède plusieurs qualités :
— bibliothèques multimédia performantes (2D, OpenGL, son) ;
— multi-cible : un même code permet de générer des binaires java, JavaScript/HTML5 ou même Android ;
Voici les principaux sites consacrés à Processing
— http://processing.org/ : le site officiel ;
— http://processing.org/tutorials/ : une sélection de tutoriels ;
— http://processing.org/reference/libraries/ : des bibliothèques supplémentaires ;
— http://fr.flossmanuals.net/processing/ : un manuel en français rédigé de manière collaborative ;
3
CHAPITRE 1. PREMIERS PAS EN JAVA À L’AIDE DE PROCESSING
4
— http://www.openprocessing.org/ : un dépôt centralisant des applications libres développées avec
Processing.
Pour les autres langages, il existe des projets similaires : mentionnons OpenFrameworks (C++) ou encore
iProcessing (pour iOS).
1.2
Programmation impérative : enchaı̂nement d’instructions
Nous allons utiliser l’environnement de Processing pour appréhender les principales notions de la programmation impérative :
Définition 1.1 (programmation impérative). La programmation impérative est un paradigme de programmation
qui décrit les opérations en termes de séquences d’instructions exécutées par l’ordinateur.
Ce type de programmation est, dans son principe, très proche du fonctionnement du processeur d’un ordinateur.
La programmation objet est une forme de programmation impérative, avec une structuration bien particulière des
programmes (cf chapitres suivants). Les morceaux élémentaires d’un programme objet, dits blocs, sont écrits de
manière impérative.
Les langages impératifs comportent trois types principaux d’instructions : l’assignation, les branchements
inconditionnels et les structures de contrôles.
Les instructions d’assignation permettent d’effectuer des opérations sur des informations en mémoire. Ceci
permet notamment d’enregistrer des valeurs pour un usage ultérieur.
Les branchements inconditionnels provoquent la poursuite du programme à un autre endroit du code : ceci
inclue notamment les appels de méthodes ou fonctions.
Les structures de contrôle permettent à un bloc d’instruction de n’être utilisé que si certaines conditions sont
vérifiées.
1.2.1
Premiers pas
Pour afficher un message, on peut utiliser directement l’instruction println :
println ( " Bonjour " ) ;
A l’exécution, la chaine de caractères Bonjour est affichée dans la console (panneau du bas de l’interface).
Les instructions sont exécutées consécutivement. Ainsi le code
println ( " Bonjour , " ) ;
println ( " tout le monde . " ) ;
println ( " Tout va bien ? " ) ;
affiche
Bonjour,
tout le monde.
Tout va bien?
Processing est prévu pour réaliser des applications graphiques avant tout. Pour afficher un message dans une
fenêtre graphique, voici un code minimal :
size (300 ,100) ; // taille de la fenetre : 300 x100
background (0) ; // couleur de fond : noir
textSize (32) ; // taille de la police : 32
text ( " Bonjour " ,50 ,50) ; // affichage de " Bonjour " , coordonnees : 50 ,50
1.2. PROGRAMMATION IMPÉRATIVE : ENCHAÎNEMENT D’INSTRUCTIONS
5
Exercice 1. (?) Ecrire le texte ci-dessous dans une fenêtre. (corrigé)
Bonjour,
tout le monde.
Tout va bien?
L’environnement de développement de Processing possède un mode dit "expérimental" qui ajoute de nouvelles fonctionnalités, qui n’ont pas encore été suffisamment testées pour être activées par défaut. Par la suite,
nous utiliserons systématiquement ce mode, car il intègre une détection des erreurs de frappe ainsi qu’un outil de
débogage minimaliste.
1.2.2
Variables et affectations
L’élément de base est la notion de variable :
Définition 1.2 (variable). Une variable est définie par un nom, possède un type qui précise la classe à laquelle
elle appartient ou son domaine, et une valeur qui peut changer au cours du temps.
Dans la langage Java, un ensemble de types, dits types primitifs, sont définis :
— byte [8 bits -128 ... 127],
— short [16 bits -32768 ... 32767],
— int [32 bits -2147483648 ... 2147483647],
— long [64 bits -9223372036854775808 ... 9223372036854775807],
— float [nombre réel en précision simple 32-bit],
— double [nombre réel en précision double 64-bit],
— boolean [true | false],
— char [caractère Unicode 16-bit].
Par exemple, le type byte (octet) occupe un espace mémoire de 8 bits, et possède une plage de valeurs de -128
à +127.
Ces types primitifs correspondent aux données "élémentaires" (informellement, celles que l’on peut stocker
directement dans les registres d’un processeur).
On peut attribuer une valeur à une variable en utilisant l’opérateur d’affectation = :
Définition 1.3 (= (affectation)). La déclaration d’une variable réserve un emplacement mémoire où stocker la
variable, et une valeur par défaut (généralement 0, null ou false), pour modifier cette valeur par défaut, il
faut faire une affectation, c’est-à-dire préciser la donnée qui va être stockée à l’emplacement mémoire qui a été
réservé.
Pour cela on utilise l’opérateur d’affectation "=" :
nomDeVariable = valeur ;
Il est aussi possible d’initialiser les valeurs lors de leurs déclarations.
type nomDeVariable = valeur ;
Soit les lignes suivantes :
int i =3;
int j = i ;
j ++;
i =7;
print ( i ) ;
CHAPITRE 1. PREMIERS PAS EN JAVA À L’AIDE DE PROCESSING
6
Exercice 2. (?) Faites l’exécution sur papier pas-à-pas de ce programme, en mentionnant le nom des variables et
leurs valeurs à chaque pas.
Utilisez l’outil de débogage pour contrôler votre réponse à l’exercice précédent : pour cela, placez des points
d’arrêts et utilisez la fenêtre permettant d’inspecter la valeur des variables. (corrigé)
Exercice 3. (?) Processing définit un certains nombres de variables, comme width et height donnant les
dimensions de la fenêtre. Ecrire un programme qui crée une fenêtre puis qui affiche dans le terminal : Les dimensions de la fen^
etre sont a x b où a et b sont les bonnes valeurs. (corrigé)
Exercice 4. (?) Utilisez la fenêtre permettant d’inspecter la valeur des variables pour faire la liste de toutes les
variables pré-définies en Processing. (corrigé)
1.2.3
Structures de contrôle
Les structures de contrôle permettent de soumettre la poursuite de l’exécution du programme à certaines
conditions : si telle condition est vraie, faire ceci ; recommencer cela jusqu’à ce que ...
Choix
Définition 1.4 (choix simple). Le choix simple consiste à définir une condition à réaliser et à préciser les actions
à exécuter selon que la condition est vraie ou fausse.
La syntaxe est la suivante :
if ( condition ) {
// bloc des instructions executees si condition est vraie
}
else {
// bloc des instructions executees si condition est fausse
}
La partie else... est facultative.
Exercice 5. (?) Processing stocke dans les variables displayWidth et displayHeight les dimensions
de l’écran. Ecrire un programme qui affiche "Les dimensions de votre écran sont suffisantes pour
le bon déroulement de l’application" si celles-ci excèdent 1024x768, "Les dimensions de l’écran
sont insuffisantes pour un bon déroulement de l’application" sinon.
Utilisez l’outil de débogage pour suivre le déroulement du programme précédent. (corrigé)
Le choix simple est donc une structure de contrôle qui ne permet de choisir qu’entre deux alternatives d’exécution. Il est possible de définir un nombre quelconque d’alternatives en prenant en compte plusieurs conditions
successivement :
if ( condition1 ) {....}
if ( condition2 ) {...}
if ( condition3 ) {...}
...
Pour une telle structure de choix multiples, l’instruction switch permet de réaliser souvent la même chose de
façon plus lisible :
switch ( expression ) {
case valeur1 :
// bloc 1 d ’ instructions
break ;
case valeur2 :
1.2. PROGRAMMATION IMPÉRATIVE : ENCHAÎNEMENT D’INSTRUCTIONS
7
// bloc 2 d ’ instructions
break ;
...
default :
// instructions a realiser par defaut
}
Exercice 6. (?) Ecrire un programme qui affiche le nom de la chaîne de télévision numérique terrestre en fonction d’un numéro num (pour les 5 premières chaines uniquement) : cf http: // fr. wikipedia. org/ wiki/
Télévision_ numérique_ terrestre_ en_ France .
Utilisez l’outil de débogage pour suivre le déroulement du programme. (corrigé)
Itérations Les itérations permettent de répéter un bloc d’instructions.
Définition 1.5 (Itération selon une condition (while)). Répétition tant qu’une condition est vérifiée ou jusqu’à ce
qu’une condition soit vérifiée.
Syntaxe :
while ( condition ) // tant que condition est vraie , faire ...
{
... instructions
}
do // faire ... tant que condition est vraie
{
} while ( condition )
Exercice 7. (?) La méthode second() de Processing retourne le nombre de secondes de l’heure actuelle. Ecrire
un programme qui affiche le moment où la prochaine minute débute (i.e. lorsque second() retourne 0). (corrigé)
Exercice 8. (??) Ecrire un programme qui affiche le décompte, seconde par seconde, jusqu’à la prochaine minute.
(corrigé)
Dans la seconde forme (do ... while), le bloc d’instructions est exécuté au moins une fois.
Définition 1.6 (Itération selon une énumération (for)). Répétition en parcourant un ensemble de valeurs.
Cet ensemble peut être un ensemble d’entiers, par exemple les entiers compris entre la valeur a et la valeur b,
auquel cas, on écrira :
for ( int i = a ; i <= b ; i ++) {
// i prend successivement toutes les valeurs entre a et b
...
}
Exercice 9. (?) Ecrire un programme qui affiche un décompte de 10 à 0. Utilisez l’outil de débogage pour suivre
le fonctionnement de la boucle. (corrigé)
Lorsque l’ensemble est un ensemble d’objets ens d’une même classe T (cf prochains cours), on peut itérer sur
cet ensemble :
CHAPITRE 1. PREMIERS PAS EN JAVA À L’AIDE DE PROCESSING
8
for ( T o : ens ) {
// o parcourt successivement tous les objets de l ’ ensemble ens
...
}
Par exemple, on peut écrire :
int [] tab = {0 ,1 ,2 ,3};
for ( int i : tab )
println ( i ) ;
Exercice 10. (?) Donner l’affichage exact du programme ci-dessus puis le modifier pour afficher tous les nombres
premiers entre 0 et 20. (corrigé)
1.2.4
Méthodes et appels de méthodes
Une méthode (ou fonction, mais en programmation objet, on parle plutôt de méthodes) permet de faire un
ensemble d’opérations à partir de valeurs passées en paramètre et de retourner éventuellement un résultat.
Nous avons déjà utilisé des méthodes (pré-définies dans Processing) : reprenons l’exemple de base
size (300 ,100) ; // taille de la fenetre : 300 x100
background (0) ; // couleur de fond : noir
textSize (32) ; // taille de la police : 32
text ( " Bonjour " ,50 ,50) ; // affichage de " Bonjour " , coordonnees : 50 ,50
Il s’agit donc de l’appel consécutif de 4 méthodes : ainsi l’instruction size(300,100) exécute la méthode size
en lui passant en paramètre les 2 valeurs 300 et 100.
L’outil de débogage de Processing ne semble pas fonctionner correctement en présence de méthodes ...
Définition 1.7 (méthode / corps d’une méthode). Une méthode précise les opérations que l’on peut faire sur les
attributs de l’objet. Elle peut prendre des paramètres, dont la liste est donnée entre parenthèses. La succession de
ces opérations est écrite dans le corps de la méthode, délimité par {}. Elle peut retourner un résultat.
La syntaxe globale est la suivante :
typeRetour nomMethode ( typeArg arg ) {
// corps de la methode
...
return resultat ; // resultat doit etre un objet de type
typeRetour
}
Voici un exemple de méthode retournant la somme de 2 entiers passés en paramètre :
int somme ( int a , int b ) {
return ( a + b ) ;
}
Le type de retour void est un type particulier qui signifie que la méthode ne va rien retourner : dans ce cas, on
n’écrit pas de ligne return ... à la fin du corps de la méthode.
Au lancement d’une application, Processing exécute tout d’abord la méthode setup si elle existe.
Exercice 11. ? Donner l’affichage exact du programme ci-après. (corrigé)
1.2. PROGRAMMATION IMPÉRATIVE : ENCHAÎNEMENT D’INSTRUCTIONS
9
void bonjour () {
println ( " bonjour " ) ;
}
void aurevoir () {
println ( " au revoir " ) ;
}
void setup () {
aurevoir () ;
bonjour () ;
}
Voici un autre petit exemple : ce code
void fenetreBonjour () {
textSize (32) ; // taille de la police : 32
text ( " Bonjour " ,50 ,50) ; // affichage de " Bonjour " , coordonnees : 50 ,50
}
void setup () {
size (300 ,100) ; // taille de la fenetre : 300 x100
background (0) ; // couleur de fond : noir
fenetreBonjour () ;
}
isole le code affichant un texte dans une fenêtre dans la méthode fenetreBonjour qui affiche un texte dans
une fenêtre, tandis que la méthode setup se charge de préparer la fenêtre avec un fond noir, puis d’invoquer la
méthode fenetreBonjour.
Exercice 12. (?) Modifier l’exemple précédent pour que la méthode fenetreBonjour affiche le message aux
coordonnées passées en paramètre. Comment faire pour afficher ce message à deux endroits différents de la
fenêtre ? (corrigé)
Ce code met en avant deux méthodes importantes dans le fonctionnement de Processing. Pendant l’exécution
du programme, un moteur graphique tourne en parallèle : il se charge de redessiner le contenu des fenêtres.
Au début du programme, le moteur graphique parcourt la méthode setup dans laquelle on peut initialiser les
principaux paramètres (ici, la taille de la fenêtre et la couleur du fond). Ensuite, à intervalles réguliers (plusieurs
fois par seconde), il exécute la méthode draw (ici, affichage d’un texte). Une application Processing est donc
naturellement de type multiprocessus (légers) : un processus prend en charge l’affichage tandis que l’autre exécute
le programme "principal".
Il faut bien garder à l’esprit que la méthode draw est exécutée plein de fois et ne doit contenir que ce qui relève
de l’affichage.
Pour des instructions qui ne doivent être exécutées qu’une seule fois, mettez les dans un premier temps dans
la méthode setup
Exercice 13. (?) Observez le code ci-après : que va-t-il se passer ? Pourquoi ? Exécutez ce code dans Processing
pour vérifier votre réponse. (corrigé)
void setup () {
size (300 ,100) ; // taille de la fenetre : 300 x100
background (0) ; // couleur de fond : noir
}
void draw () {
CHAPITRE 1. PREMIERS PAS EN JAVA À L’AIDE DE PROCESSING
10
println ( " bou ! " ) ;
textSize (32) ; // taille de la police : 32
text ( " Bonjour " ,50 ,50) ; // affichage de " Bonjour " , coordonnees : 50 ,50
}
Exercice 14. (??) Faire un programme déplaçant le texte "Bonjour" de gauche à droite dans la fenêtre. Ajouter
ensuite un rebond sur le bord droit de la fenêtre. (corrigé)
Exercice 15. (?) Idem en ajoutant le contrôle de l’animation par la souris (la variable mousePressed est vraie
lorsque l’utilisateur fait un clic sur la fenêtre). (corrigé)
Exercice 16. (? ? ?) Idem en faisant un rebond progressif (ralentissement lors du rebond puis accélération) sur
chaque bord. Indication : utiliser la fonction mathématique sin. (corrigé)
Exercice 17. (? ? ?) Afficher le texte Bonjour via une animation constituée du déplacement des lettres une par
une. (corrigé)
1.2.5
Manipulation de chaines de caractères
Le type String permet de stocker une chaine de caractères :
String s = " Bonjour " ;
println ( s ) ; // affiche " Bonjour "
On peut effectuer des opérations diverses sur une chaine, comme la découper en sous-chaines dans l’exemple
ci-après :
String s = " Hello World " ;
String [] mot = split (s , " " ) ; // mot [0]=" Hello " , mot [1]=" World "
Exercice 18. (?) Ecrire une méthode afficherMots(String phrase) qui affiche les mots d’une phrase passée
en paramètre, un par ligne. (corrigé)
L’opérateur + pour des chaines de caractères a un rôle de concaténation :
String bienvenue = " Bonjour , " + " tout le monde " ;
, tout le monde "
// bienvenue =" Bonjour
On peut l’utiliser en combinaison avec d’autres types de base : si une des opérandes est de type String, les
autres opérandes sont converties en chaines de caractères, avant de réaliser la concaténation.
Exemple :
int age =20;
println ( " J ’ ai " + age + " ans " ) ; // affiche " J ’ ai 20 ans "
Exercice 19. (?) Donner l’affichage exact du programme ci-après : (corrigé)
int op era ti onM ys ter ie use ( int a , int b ) {
return a + b ;
}
void bonjour () {
println ( " bonjour " ) ;
}
String message ( int greg ) {
1.2. PROGRAMMATION IMPÉRATIVE : ENCHAÎNEMENT D’INSTRUCTIONS
11
int bob =10;
return " A vous deux , vous avez " + o pe rat io nMy st eri eus e ( greg , bob ) + " ans
";
}
void setup () {
bonjour () ;
println ( message (27) ) ;
}
1.2.6
Manipulations de fichiers
Processing offre des méthodes qui permettent de manipuler simplement des fichiers texte.
Exemple de lecture d’un tel fichier :
// Ce code affiche toutes les lignes d ’ un fichier texte
String [] lignes = loadStrings ( " fichier . txt " ) ;
println ( lignes ) ; // affichage des lignes
Exemple de création d’un fichier texte :
String mots = " il etait un petit navire " ;
String [] liste = split ( mots , ’ ’) ;
// Ecriture des mots dans un fichier , un par ligne
saveStrings ( " mots . txt " , liste ) ;
Exercice 20. (?) Testez l’exemple de création d’un fichier texte, puis celui permettant de le lire. (corrigé)
Exercice 21. (?) Ecrire un programme qui, à partir d’un fichier texte entree.txt, crée un fichier texte sortie.txt formé des lignes du texte en entrée, triées selon l’ordre lexicographique (utiliser la fonction sort).
(corrigé)
Lorsque le fichier n’est pas un fichier texte ou bien lorsque l’on souhaite le lire ligne par ligne (par exemple
parce qu’il est trop gros pour être chargé directement en mémoire), on peut faire appel à d’autres méthodes en
Java.
Java introduit un mécanisme pour gérer les erreurs d’exécution, reposant sur la capture d’exceptions.
Considérons l’extrait de code permettant de lire ligne par ligne un fichier texte :
String chemin = " fichier . txt " ;
try { // pour la gestion des erreurs de lecture
java . io . BufferedReader f = new java . io . BufferedReader ( new java . io .
FileReader ( chemin ) ) ; // creation d ’ un lecteur de fichier f par
blocs ( pour les lignes )
String ligne ;
while (( ligne = f . readLine () ) != null ) { // lecture d ’ une ligne
// si la ligne est vide ( null ) , la fin du fichier a ete
atteinte
System . out . println ( ligne ) ;
}
} catch ( IOException e ) { println ( " erreur de lecture du fichier " ) ;} // si
la lecture echoue ( par exemple si le fichier n ’ existe pas ) ,
afficher un message d ’ erreur
CHAPITRE 1. PREMIERS PAS EN JAVA À L’AIDE DE PROCESSING
12
Le fonctionnement est relativement simple : l’instruction try s’applique à tout le bloc qui suit celle-ci. Elle
signifie que si une des instructions de ce bloc provoque une erreur lors de l’exécution (ici une erreur liée à la tentative de lecture d’un fichier) alors le bloc après l’instruction catch doit être exécuté. Les erreurs sont typées par le
type de l’exception qu’elles génèrent : dans cet exemple, ce sont des erreurs de type IOException (entrée/sortie)
qui sont susceptibles d’êtres provoquées.
Exercice 22. (?) Ecrire un programme permettant d’afficher le contenu d’un fichier texte. Tester son bon fonctionnement sur un fichier existant, puis observer la capture de l’exception si on essaie d’accéder à un fichier
inexistant. (corrigé)
Ce code met en avant un mécanisme de gestion des erreurs (les exceptions) que nous expliquerons plus tard
dans ce cours.
1.2.7
Tests
Pour qu’un programme soit robuste, il faut s’assurer dans chaque méthode que les paramètres qui sont passés
soient valides. Lorsque ce n’est pas le cas, il vaut mieux le plus souvent arrêter le déroulement du programme,
plutôt que de le continuer avec des valeurs incorrectes.
L’instruction assert est particulièrement utile pour cela :
assert ( bool condition ) ; // si condition est fausse , le programme est
arrete
Exercice 23. (?) Ecrire une méthode float inverse(float) qui retourne l’inverse d’un nombre décimal.
Gérer le cas de la division par 0 avec assert. (corrigé)
Une bonne pratique de programmation est de concevoir pour chaque méthode importante, un ensemble de
tests, dits unitaires, chargés de s’assurer que la méthode retourne le résultat attendu par rapport à certaines valeurs.
Exemple :
int somme ( int a , int b ) {
return ( a + b ) ;
}
// campagne de tests pour la methode somme
void testSomme () {
assert ( somme (10 ,100) ==110) ;
assert ( somme (1 , -1) ==0) ;
// ...
}
Exercice 24. (?) Ecrire des tests unitaires pour la méthode inverse de l’exercice 23. (corrigé)
1.3
1.3.1
Présentation du langage Java
Historique et positionnement
Le langage Java a été développé initialement au sein de Sun Microsystems, sous l’impulsion de Patrick Naughton en 1990. La motivation principale était de simplifier le langage C++ de façon à limiter les possibilités d’erreur
de programmation. De plus, C++ possédait certaines lacunes quant à la programmation distribuée (i.e. des applications réparties sur un réseau) ou multi-processus.
1.3. PRÉSENTATION DU LANGAGE JAVA
13
En 1994, l’équipe de développement se focalisa sur l’adaptation du langage au développement pour le web.
Une première version fut présentée par Sun à la fin de cette même année, ainsi qu’un navigateur web, appelé
HotJava, entièrement écrit en Java. La première version diffusée du langage date de 1996.
Le support de Java dans les navigateurs (les applets) a été la première possibilité de développer des application
clientes riches.
Aujourd’hui, Java est utilisé côté serveur, notamment dans le cadre de serveurs d’application (J2EE). Côté
client, Java est sans doute moins utilisé, mais offre des plateformes de développement de très bonne qualité,
comme NetBeans ou eclipse. Concernant les terminaux mobiles, il s’agit du principal langage utilisé pour les
développements sous Android.
Selon l’index TIOBE (données de Juillet 2014), le langage Java (16%) est en seconde place, derrière le langage
C (17%), suivi de Objective C (10%), puis de C++ (5%), Visual Basic (4%), C# (4%) PHP (3%) ... Les parts de
marché de Java sont en légère baisse sur un an tandis que celles d’Objective C (utilisé pour les produits Apple)
sont en légère hausse.
Sur le long terme, le classement des langages est le suivant, toujours selon les données TIOBE :
Langage
C
Java
Objective-C
C++
C#
PHP
(Visual) Basic
Python
JavaScript
Transact-SQL
Lisp
1.3.2
2014
1
2
3
4
5
6
7
8
9
10
14
2009
2
1
34
3
7
5
4
6
9
30
21
2004
2
1
42
3
7
6
5
9
8
1999
1
15
1994
1
1989
1
2
21
2
2
3
26
19
3
23
-
7
14
10
6
3
Particularités
Java est un langage de programmation orienté objet (POO), avec pour principaux avantages :
— portabilité : les programmes s’exécutent sans recompilation dans tous les principaux systèmes d’exploitation ;
— robustesse avec notamment une gestion de la mémoire simplifiée (ramasse miettes) ;
— rapidité de développement grâce à des bibliothèques (API - Application Programming Interface) performantes et variées ;
— publication sur internet - Applets, Services webs ;
— documentation automatique via l’utilitaire javadoc ;
— programmation réseau simplifiée ;
— etc .
Java propose pour cela une plate-forme logicielle complète (une plate-forme est un environnement dans lequel
un programme s’exécute) composée
— d’une machine virtuelle : Java Virtual Machine (JVM) permettant d’exécuter (sans recompilation) un programme java sur n’importe quel système d’exploitation, du moment que la JVM soit installée. L’installation
d’une JVM consiste à déployer un environnement d’exécution : Java Runtime Environment (JRE)
— d’un environnement de développement (JDK - Java Development Kit) incluant de nombreux utilitaires
et des bibliothèques (API - Application Programming Interface) permettant de créer rapidement des programmes avancés.
En Java, tout est objet à l’exception des types de base (int, float etc). Chaque type de base possède néanmoins une classe associée, permettant de le manipuler comme un objet classique. Par exemple, pour le type int,
il s’agît de la classe Integer.
CHAPITRE 1. PREMIERS PAS EN JAVA À L’AIDE DE PROCESSING
14
1.3.3
Ecrire un programme en Java ”pur”
L’objet de cette partie est simplement de décrire ce que doit respecter un programme Java pour pouvoir fonctionner.
Pour créer notre premier programme Java, lancez un environnment de développement intégré (EDI) comme
NetBeans ou eclipse.
Créez votre premier projet de nom Exercices : votre EDI génère automatiquement un code servant de base
de départ, avec quelques commentaires intégrés (lignes en bleu ci-après, obtenues en utilisant NetBeans)
/*
* To change this template , choose Tools | Templates
* and open the template in the editor .
*/
package exercices ;
/* *
*
* @author gothmog
*/
public class Exercices {
/* *
* @param args the command line arguments
*/
public static void main ( String [] args ) {
// TODO code application logic here
}
}
La méthode main est le point de départ de l’application.
Pour des raisons techniques qui seront expliquées plus tard, nous utiliserons le code de départ suivant :
package exercices ;
public class Exercices {
static void question () {
// votre code ici .
}
public static void main ( String [] args ) {
question () ;
}
}
System.in et System.out permettent respectivement de récupérer les données en provenance dans le terminal et d’envoyer des données dans le terminal (pour les afficher). La sortie d’erreurs (stderr sous Unix) est
également accessible via System.err.
Ainsi l’extrait de code suivant, demande son nom à l’utilisateur puis lui dit bonjour.
System . out . println ( " Quel est votre nom ? " ) ;
Scanner in = new Scanner ( System . in ) ; // in permettra de lire les
donnees saisies par l ’ utilisateur
1.3. PRÉSENTATION DU LANGAGE JAVA
15
String nom = in . nextLine () ; // la variable nom contient la reponse
saisie par l ’ utilisateur
System . out . println ( " Bonjour " + nom ) ; // affichage de " Bonjour " suivi de
la valeur de nom
Notez le mécanisme de concaténation de deux chaines de caractères avec l’opérateur + :
" Bonjour " + " Bob , "
est identique à :
" Bonjour Bob , "
Exercice 25. (?) Ecrire une méthode politesse contenant ce code et la tester. (corrigé)
1.3.4
Affichage : format versus print
Pour afficher un message textuel, la méthode print (et sa variante println) est très utilisée.
On peut également exploiter la méthode format dont l’usage sera naturel pour les habitués du langage C.
Avec celle-ci, l’exemple précédent :
out . println ( " Bonjour " + nom ) ;
devient
out . format ( " Bonjour % s " , nom ) ;
Voici un autre exemple un peu plus élaboré :
out . format ( " Bonjour % s : vous avez % d ans \ n " ," Bob " ,42) ;
qui affiche
Bonjour Bob : vous avez 42 ans
Un dernier exemple,
int age =20;
System . out . format ( " J ’ ai %4 d ans \ n " , age ) ;
age =127;
System . out . format ( " J ’ ai %4 d ans \ n " , age ) ;
donne l’affichage suivant (notez l’alignement des deux lignes) :
J’ai
J’ai
20 ans
127 ans
La méthode format procure une meilleure séparation que print entre ce qui relève de la mise en forme et
des parties variables du contenu. Elle offre également un contrôle plus fin de la mise en forme proprement dite. Cf
http://docs.oracle.com/javase/tutorial/java/data/numberformat.html.
Exercice 26. (??) Ecrire en utilisant une boucle un programme qui affiche les carrés et les cubes des nombres de
1 à 5 selon l’affichage ci-après. (corrigé)
+------+------+------+
|
1 |
1 |
1 |
|
2 |
4 |
8 |
|
3 |
9 |
27 |
|
4 |
16 |
64 |
|
5 |
25 | 125 |
+------+------+------+
16
1.4
CHAPITRE 1. PREMIERS PAS EN JAVA À L’AIDE DE PROCESSING
Conclusion : quand utiliser Processing ? Java ?
Le langage Processing est tout particulièrement adapté pour la réalisation d’applications multimédia et/ou
mobiles. En effet, il offre des bibliothèques performantes (plus que celles officielles fournies par Java). Pour
les applications mobiles, il supporte Android ainsi que JavaScript/HTML5, sans avoir besoin d’écrire un code
spécialisé. De plus, toutes les bibliothèques officielles de Java sont disponibles.
Cependant, l’environnement de développement est très limité et inadapté à la réalisation de grands projets, tout
particulièrement en équipe. Pour ces derniers (dont le développement JEE ou d’applications Android avec le kit
de développement de Google), la programmation en Java "pur" est incontournable. Dans ce cas, les bibliothèques
de Processing restent exploitables, bien que ce soit un peu plus complexe.
1.4. CONCLUSION : QUAND UTILISER PROCESSING ? JAVA ?
17
Fiche de synthèse
Terminologie & concepts de la programmation impérative
— variables, affectation
— structures de contrôle : if/else, switch/case/break/default
— itérations : while, do/while, for ...
— tableaux : int[] tab, String[] tab ...
— méthodes et appels de méthodes
— tests unitaires
Java
—
—
—
—
types primitifs : int, boolean, float ...
le type String (chaines de caractères) et la méthode format
la lecture de fichiers textes ligne par ligne
l’environnement de développement NetBeans et la génération du code de base d’une application (méthode main)
Spécifique à Processing
— méthodes prédéfinies (processing) : size, background, textSize, text ...
— variables prédéfinies : width, height ...
— environnement de développement dont son outil de débogage
— le code de base d’une application graphique (méthodes setup et draw)
— la manipulation de fichiers texte avec loadStrings et saveStrings
Exemple de code (début d’une solution du TP1)
int taille =15;
String [] lignes ;
int bordure =10;
void setup () {
// recuperation des donnees du fichier
lignes = loadStrings ( " laby . txt " ) ;
// lignes [0] contient les dimensions du labyrinthe
String [] dimension = split ( lignes [0] , " " ) ;
// dimension [0] est la largeur du labyrinthe et dimension [1] la
hauteur
int x = Integer . parseInt ( dimension [0]) ; // conversion de la chaine de
caracteres dimension [0] en entier
int y = Integer . parseInt ( dimension [1]) ; // conversion de la chaine de
caracteres dimension [1] en entier
// fenetre graphique de taille adaptee au labyrinthe , fond noir
int largeur = ( taille * x ) +(2* bordure ) ;
int hauteur = ( taille * y ) +(2* bordure ) ;
size ( largeur , hauteur ) ;
background (0) ;
// lecture des salles : a faire ...
}
void dessinerLabyrinthe () {
// a faire ...
}
void draw () {
dessinerLabyrinthe () ;
18
}
CHAPITRE 1. PREMIERS PAS EN JAVA À L’AIDE DE PROCESSING
Première partie
Introduction à la programmation
orientée objet
19
Chapitre 2
Bases de la programmation objet
La programmation objet est un paradigme de programmation favorisant la modularité (le code est réparti dans
les classes), la protection des données (en ne donnant accès aux attributs qu’à travers des méthodes), la séparation
entre implémentation et interfaces (ce qui permet de réduire les erreurs possibles de programmes clients), la
réutilisation du code, etc .
Dans ce chapitre, nous allons aborder les notions élémentaires de la programmation objet : concepts de classes,
d’objets, d’attributs et de méthodes dans la première section ; les ensembles d’objets dans la seconde section.
Nous utiliserons directement Java et l’environnement de développement NetBeans : la méthodologie à suivre
pour continuer à tirer partie des bibliothèques de Processing est expliquée en première partie.
Sommaire
2.1
2.2
2.3
2.4
2.1
De Processing à Java . . . . . . . . . . . . . . . . . . . . .
2.1.1 Premiers pas en Java . . . . . . . . . . . . . . . . .
2.1.2 Réutiliser les bibliothèques de Processing . . . . .
Le modèle objet . . . . . . . . . . . . . . . . . . . . . . .
2.2.1 Classes, types, attributs, méthodes . . . . . . . . . .
2.2.2 Déroulement d’un programme objet et mot-clef this
2.2.3 Cycle de vie d’un objet . . . . . . . . . . . . . . . .
2.2.4 Surcharge de méthodes . . . . . . . . . . . . . . . .
Conversions entre types de bases et leurs classes dédiées .
Tableaux, collections d’objets . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
De Processing à Java
2.1.1
Premiers pas en Java
Programmer directement en Java ne diffère pas grandement de Processing
Considérons le code suivant sous Processing :
class Etudiant {
String nom ;
Etudiant ( String nom ) {
this . nom = nom ;
}
}
21
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
21
21
22
24
24
28
29
31
32
33
CHAPITRE 2. BASES DE LA PROGRAMMATION OBJET
22
void setup () {
Etudiant e = new Etudiant ( " Dark Vador le gentil " ) ;
println ( " Le nom de l ’ etudiant e est : " + e . nom ) ;
}
L’équivalent Java est :
class Etudiant {
String nom ;
Etudiant ( String nom ) {
this . nom = nom ;
}
public static void main ( String [] args ) {
Etudiant e = new Etudiant ( " Dark Vador le gentil " ) ;
System . out . println ( " Le nom de l ’ etudiant e est : " + e . nom ) ;
}
}
Sur cet exemple de base, il y a donc deux différences principales :
— la méthode void setup() est maintenant public static void main(String[] args) : il s’agit du
point de départ du programme Java. Les paramètres args sont des arguments que l’on peut passer au
programme lors de son lancement. La signification des mots clefs public et static est expliquée dans
ce chapitre précisément.
— la méthode de départ main est à l’intérieur d’une classe : en Java, on ne peut pas écrire de code en dehors
d’une classe. C’est en fait aussi le cas dans le langage Processing mais ceci est masqué à l’utilisateur,
via une classe "principale invisible" qui englobe toute l’application.
Outre ces deux différences, notez que l’instruction println n’existe pas en Java : on doit utiliser la méthode
println de l’objet System.out (console), ce qui donne System.out.println.
Exercice 27. (?) Créez un projet Java sous NetBeans ou eclipse appelé ProjetEtudiant et contenant cette
classe Etudiant. Testez son bon fonctionnement puis faites une exécution pas-à-pas avec le débogueur. Testez
également les points d’arrêts. (corrigé)
2.1.2
Réutiliser les bibliothèques de Processing
Il est possible d’écrire une application Java exploitant les bibliothèques de Processing.
Voici un tel exemple minimaliste :
import processing . core .*;
public class Application extends PApplet {
public void setup () {
background (0) ;
size (200 ,100) ;
textSize (24) ;
text ( " Bonjour ! " ,50 ,50) ;
}
public static void main ( String [] args ) {
PApplet . main ( " Application " ) ;
}
}
2.1. DE PROCESSING À JAVA
23
Il est facile de convertir une application Processing vers Java : il suffit de l’exporter à partir de Processing.
Le répertoire généré contient un sous-répertoire source qui contient le code source Java "pur" de l’application.
Exercice 28. (?) Testez l’exportation à partir de processing et faites la liste des principales modifications qui
ont été apportées pour convertir le code au format Java. (corrigé)
Le code Java ainsi généré contient une classe principale qui hérite (mot-clef extends) de la classe PApplet.
Le concept de l’héritage de classe est expliqué dans le prochain chapitre. Pour le moment, retenez que cela signifie
simplement qu’une application Processing s’exécute sous la forme d’une applet (une application prise en charge
par un navigateur web).
Cette observation est suffisante pour pouvoir écrire directement une application Java tirant partie des bibliothèques de Processing.
Prenons par exemple le code de démonstration par Keith Peters (Examples/Topics/Motion/Follow1) :
**
* Follow 1
* based on code from Keith Peters .
*
* A line segment is pushed and pulled by the cursor .
*/
float
float
float
float
x = 100;
y = 100;
angle1 = 0.0;
segLength = 50;
void setup () {
size (640 , 360) ;
strokeWeight (20.0) ;
stroke (255 , 100) ;
}
void draw () {
background (0) ;
float dx = mouseX - x ;
float dy = mouseY - y ;
angle1 = atan2 ( dy , dx ) ;
x = mouseX - ( cos ( angle1 ) * segLength ) ;
y = mouseY - ( sin ( angle1 ) * segLength ) ;
segment (x , y , angle1 ) ;
ellipse (x , y , 20 , 20) ;
}
void segment ( float x , float y , float a ) {
pushMatrix () ;
translate (x , y ) ;
rotate ( a ) ;
line (0 , 0 , segLength , 0) ;
popMatrix () ;
}
Exercice 29. (?) Testez le bon fonctionnement de cette application. (corrigé)
CHAPITRE 2. BASES DE LA PROGRAMMATION OBJET
24
Pour la porter sous NetBeans, il faut ajouter la bibliothèque core (fichier core.jar du répertoire
core/library de Processing) et modifier la méthode main pour qu’elle lance l’application en mode applet :
import processing . core .*;
public class Application extends PApplet {
// idem
public void setup () {
// idem
}
public void draw () {
// idem
}
void segment ( float x , float y , float a ) {
// idem
}
public static void main ( String [] args ) {
PApplet . main ( " Application " ) ;
}
}
Exercice 30. (??) Testez ce portage. (corrigé)
Pour des explications plus complètes sur la réutilisation des bibliothèques de Processing, voir le tutoriel de
Daniel Shiffman : http://processing.org/tutorials/eclipse/
Exercice 31. (??) Prenez l’exemple Topics/Motion/BouncyBubbles et recréez directement cette application en
Java dans NetBeans. (corrigé)
2.2
2.2.1
Le modèle objet
Classes, types, attributs, méthodes
Un objet possède un état (ce sont les données) et des fonctionnalités.
L’état d’un objet est constitué de ses données : au niveau programmation, on les appelle ses variables, ses
champs ou encore ses attributs .
Considérons par exemple la classe Etudiant suivante :
class Etudiant {
String nom ;
}
Cette classe contient un attribut nom de type String (chaîne de caractères).
En java et dans d’autres langages objet, on crée des instances d’une classe à l’aide de l’instruction new.
Ainsi le code :
Etudiant a = new Etudiant () ;
a . nom = " Bob " ;
Etudiant b = new Etudiant () ;
b . nom = " Greg " ;
2.2. LE MODÈLE OBJET
25
crée deux instances (ou objets) a et b de type Etudiant : l’attribut nom de a (noté a.nom) vaut Bob, tandis que
celui de b (noté b.nom) a la valeur Greg.
Etant donné une instance o, la notation o.x désigne l’attribut x de cette instance.
Exercice 32. (?) Avec l’utilitaire de débogage, placez des points d’arrêt sur les lignes 2 et 4 dans l’extrait de code
ci-dessus et observez les valeurs de tous les attributs à ces points d’arrêt. Quelle est l’occupation mémoire prise
par ces deux instances d’étudiants ? (corrigé)
Exercice 33. (?) Donnez l’affichage exact de l’extrait de code ci-après. (corrigé)
Etudiant b = new Etudiant () ;
b . nom = " bob " ;
Etudiant g = new Etudiant () ;
g . nom = " greg " ;
System . out . println ( " Je suis " + b . nom + " et je connais bien " + g . nom ) ;
Notez qu’un attribut peut être lui même un objet : par exemple, l’attribut nom d’un Etudiant est un objet de
la classe String. Ceci implique également que toute classe définit un type.
Par exemple, il est possible de définir une classe Binome formée de deux étudiants et une classe Projet qui
contient un binôme d’étudiants, un titre et une note.
class Binome {
Etudiant etu1 ;
Etudiant etu2 ;
}
class Projet {
Binome b ;
String titre ;
float note ;
}
Méthodes Un objet peut effectuer des opérations, via des méthodes. Le comportement d’un objet est constitué
de méthodes, de requêtes.
Considérons la classe
class Etudiant {
String nom ;
void bonjour ( String s ) {
System . out . println ( " Bonjour " + s ) ;
System . out . println ( " Moi , je m ’ appelle " + nom ) ;
}
}
Soit une instance e d’un étudiant. L’instruction e.bonjour("Greg") lance l’exécution de la méthode
bonjour pour l’objet e.
Ainsi le code
Etudiant e = new Etudiant () ;
e . nom = " Edouard " ;
e . bonjour ( " Greg " ) ;
affiche
CHAPITRE 2. BASES DE LA PROGRAMMATION OBJET
26
Bonjour Greg
Moi , je m ’ appelle Edouard
Exercice 34. (?) Qu’affiche le code ci-dessous ? (corrigé)
Etudiant a = new Etudiant () ;
a . nom = " Adele " ;
Etudiant b = new Etudiant () ;
b . nom = " Basile " ;
Etudiant c = new Etudiant () ;
c . nom = " Cloclo " ;
Etudiant d = new Etudiant () ;
d . nom = " Dagobert " ;
Etudiant e = new Etudiant () ;
d . nom = " Eglantine " ; // ce n ’ est pas e . nom =" Eglantine " ...
a . bonjour ( b . nom ) ;
b . bonjour ( c . nom ) ;
c . bonjour ( a . nom ) ;
e . bonjour ( d . nom ) ;
Voici un autre exemple de méthode (d’une classe quelconque) :
public float mystere ( Projet p ) {
System . out . println ( " Titre du projet : " + p . titre ) ;
System . out . println ( " Premier membre : " + p . b . etu1 . nom ) ;
System . out . println ( " Second membre : " + p . b . etu2 . nom ) ;
return p . note ;
}
Exercice 35. (??) Expliquer ce que fait cette méthode mystere et écrire un programme appelant cette méthode.
Quelle est l’occupation mémoire d’un projet ? (corrigé)
La méthode toString Prenons à nouveau pour exemple notre classe Etudiant et munissons-la d’une méthode toString chargée de renvoyer une chaine de caractères décrivant l’état de l’objet :
class Etudiant {
String nom ;
public String toString () {
return " Je suis un etudiant et je m ’ appelle " + nom ;
}
}
On peut alors créer un étudiant et afficher son état de cette manière :
Etudiant b = new Etudiant () ;
b . nom = " bob " ;
System . out . println ( b . toString () ) ; // affiche " Je suis un etudiant et je
m ’ appelle bob "
La méthode toString est en fait une méthode bien particulière de Java :
Définition 2.1. méthode String toString() Cette méthode est appelée implicitement en Java lorsqu’on souhaite obtenir une représentation de l’objet sous forme texte (par exemple, via la méthode println). Elle existe
pour toutes les classes et renvoie par défaut le nom de la classe de l’objet suivi de son adresse mémoire.
2.2. LE MODÈLE OBJET
27
On peut donc simplifier l’exemple précédent en écrivant directement, car la méthode println prend en argument un objet de type String :
System . out . println ( b ) ; // fait le meme chose que " System . out . println ( b .
toString () ) ;"
Exercice 36. (?) Tester. Recommencer en supprimant la méthode toString dans la classe Etudiant. Créer
ensuite deux instances d’étudiant a et b, puis faites println(a+" / "+b). Qu’observez-vous ? (corrigé)
Exercice 37. (?) Ecrire une méthode toString pour la classe Projet dont le contenu retourné est défini de
manière analogue à l’affichage généré par la méthode mystere vue précédemment. (corrigé)
Exercice 38. (?) Donner l’affichage exact généré par le programme ci-après. (corrigé)
class Etudiant {
String nom ;
public String toString () {
return " [ Etudiant ] " + nom ;
}
}
class Binome {
Etudiant etu1 ;
Etudiant etu2 ;
public String toString () {
return " [ Binome ]: " + etu1 + " / " + etu2 ;
}
}
class Application {
public static void main ( String [] args ) {
// etudiants
Etudiant b = new Etudiant () ;
b . nom = " bob " ;
Etudiant a = new Etudiant () ;
a . nom = " bobby " ;
// Binome
Binome bi = new Binome () ;
bi . etu1 = a ;
bi . etu2 = b ;
System . out . println ( bi ) ;
}
}
Le plus souvent, on utilise la valeur retournée par une méthode pour faire une affectation dans une variable ;
la syntaxe globale est donc la suivante :
var = unObjet . uneMethode ( variable1 , variable2 , ...) ;
Exemple :
class Etudiant {
String nom ;
CHAPITRE 2. BASES DE LA PROGRAMMATION OBJET
28
String getNom () {
return nom ;
}
}
Etudiant b = new Etudiant () ;
b . nom = " bob " ;
String s = b . getNom () ; // s =" Bob "
Exercice 39. (?) Ecrire une méthode similaire getTitre pour la classe Projet et la tester. (corrigé)
En résumé
Définition 2.2 (classe). Une classe est la structure d’un objet, i.e. l’ensemble des entités qui forme l’objet
(état+comportement).
Définition 2.3 (objet/instance d’une classe). Un objet est une réalisation d’une classe. On appelle également
instance (d’une classe), un objet.
Définition 2.4 (attribut & type). Un attribut est une donnée d’un objet. Un attribut possède un type qui spécifie le
domaine des valeurs possibles et une valeur prise dans ce domaine.
Les attributs d’une classe sont donc des variables. Il existe des langages objet (comme SmallTalk par exemple)
où toute variable est une instance. En Java, une variable est soit une instance, soit une variable de type primitif
(pour la manipulation des nombres ou des caractères).
2.2.2
Déroulement d’un programme objet et mot-clef this
Le déroulement d’un programme objet se fait en ”traversant” de multiples instances. Le point de départ est
toujours dans une méthode main (Java) ou setup (Processing) : les instructions contenues dans le corps de celle-ci
sont exécutées les unes à la suite des autres.
Lorsqu’une instruction est formée d’un appel à une autre méthode (éventuellement dans un autre objet), l’exécution se poursuit dans le corps de celle-ci. Ainsi à un instant donné, il n’y a toujours qu’une seule instance active,
i.e. en cours d’exécution.
Exercice 40. (?) Expliquer pas-à-pas l’exécution du programme de l’exercice 38 en explicitant la succession des
objets actifs, les lignes de code exécutées, et la valeur des attributs. Vérifier ensuite votre explication avec l’outil
de débogage. (corrigé)
Définition 2.5 (this). Le mot-clef this désigne une instance particulière : celle qui est active.
Ce mot-clef est utile pour lever des ambiguïtés : considérons la classe Etudiant légèrement modifiée cidessous :
class Etudiant {
String nom ;
void setNom ( String nom ) {
nom = nom ;
}
}
Que signifie la ligne nom=nom ? Cette ligne va affecter la valeur du paramètre nom à lui-même (i.e. ne rien
faire), alors que le développeur souhaitait affecter la valeur du paramètre nom à l’attribut nom de l’objet. Comme
this.nom désigne explicitement l’attribut nom de l’objet, on peut écrire :
2.2. LE MODÈLE OBJET
29
class Etudiant {
String nom ;
void setNom ( String nom ) {
this . nom = nom ;
}
}
et obtenir le résultat souhaité.
Exercice 41. (?) Tester en enlevant this puis en le rajoutant. (corrigé)
2.2.3
Cycle de vie d’un objet
Dans un programme orienté objet, on crée des objets. Lorsque ceux-ci ne sont plus utilisés, ils sont détruits.
Une instance a donc un cycle de vie.
Création Comme déjà mentionné, la création d’une instance se fait avec l’instruction new. Cette instruction
provoque l’appel d’un constructeur :
Définition 2.6 (constructeur). Un constructeur est une méthode particulière, dont le rôle est d’initialiser l’état
d’un objet lors de sa création.
D’un point de vue syntaxique, un constructeur porte le même nom que la classe à laquelle il appartient, et on
ne spécifie pas de type de retour. Ce constructeur existe toujours même si on ne l’écrit pas explicitement.
Voici un exemple où le constructeur est écrit explicitement :
class Etudiant {
String nom ;
Etudiant () {
nom = " Bisounours " ;
}
}
Dans cet exemple, une instance d’étudiant a un attribut nom dont la valeur est fixée lors de sa création à
“Bisounours”. On peut bien sûr la modifier ensuite :
Etudiant bob = new Etudiant () ; // bob . nom =" Bisounours " ...
bob . nom = " Bob " ; // on a modifie la valeur de l ’ attribut nom
Lorsqu’on déclare un attribut dans une classe, il n’est pas initialisé tant qu’un appel à un constructeur n’a pas
été effectué. Il est dans l’état null :
Définition 2.7 (null). Le mot clé null définit une référence nulle, c’est-à-dire ne désignant aucun objet en mémoire. Il s’agit de la valeur par défaut des objets non-initialisés.
Le code
class Binome {
Etudiant etu1 ;
Etudiant etu2 ;
}
est équivalent à :
CHAPITRE 2. BASES DE LA PROGRAMMATION OBJET
30
class Binome {
Etudiant etu1 = null ;
Etudiant etu2 = null ;
}
Exercice 42. (?) Tester le code ci-dessous et expliquer le résultat obtenu. Recommencer en décommentant la ligne
// System.out.println(bi.etu1.nom);. (corrigé)
class Etudiant {
String nom ;
public String toString () {
return " [ Etudiant ] " + nom ;
}
}
class Binome {
Etudiant etu1 ;
Etudiant etu2 ;
public String toString () {
return " [ Binome ]: " + etu1 + " / " + etu2 ;
}
}
class Application {
public static void main ( String [] args ) {
Binome bi = new Binome () ;
// System . out . println ( bi . etu1 . nom ) ;
System . out . println ( bi ) ;
}
}
Portée Toute instance créée dans un bloc d’instructions délimité par des accolades est détruit à la fin de ce bloc :
on dit que sa portée est limitée au bloc dans laquelle elle a été créée.
Considérons ce programme :
class Etudiant {
String nom ;
}
class Binome {
Etudiant etu1 ;
Etudiant etu2 ;
}
class Projet {
Binome b ;
String titre ;
float note ;
}
2.2. LE MODÈLE OBJET
31
class Application {
public String nomEtudiants ( Etudiant a , Etudiant b ) {
String resultat ;
resultat = a . nom + " " + b . nom ;
return resultat ;
}
public static void main ( String [] args ) {
Projet p = new Projet () ;
p . b = new Binome () ;
p . b . etu1 = new Etudiant () ;
p . b . etu1 . nom = " Saurion " ;
p . b . etu2 = new Etudiant () ;
p . b . etu2 . nom = " Bilbo " ;
String noms = nomEtudiants ( p . b . etu1 , p . b . etu2 ) ;
System . out . println ( noms ) ;
}
}
Exercice 43. (??) Donner l’affichage du programme ci-dessus et expliciter la portée des différents objets. Contrôler la portée des objets en réalisant une exécution pas-à-pas avec l’outil de débogage. (corrigé)
Destruction Dans certains langages comme C++, il faut veiller à libérer les ressources mémoires occupées par
un objet à l’endroit du code où celui-ci va être détruit.
En Java, la libération de la mémoire d’un objet qui ne sera plus utilisé est prise en charge par un “ramassemiettes” : régulièrement ce processus établit la liste des objets dont le cycle de vie est terminé et libère les ressources mémoires associées. Il est possible de spécifier dans une méthode particulière, de nom finalize, des
instructions à effectuer juste avant que l’objet ne soit détruit.
Il n’y a cependant aucune garantie que cette méthode soit exécutée à un moment ou un autre, puisque le
programme peut se terminer avant même que le ramasse-miette ne se soit décidé à traiter l’objet en question ...
2.2.4
Surcharge de méthodes
La surcharge de méthodes n’est pas une notion intrinsèque à la programmation objet.
Définition 2.8 (surcharge de méthode). Une méthode surchargée désigne une méthode qui porte le même nom
qu’une autre de méthode de l’objet, mais dont la signature (le type des arguments) diffère
Le compilateur sélectionne la bonne méthode en fonction des arguments lors de l’appel.
Considérons l’exemple suivant :
int somme ( int a , int b ) {
return ( a + b ) ;
}
int somme ( int a , int b , int c ) {
return ( a + b + c ) ;
}
Exercice 44. (?) Expliquer pourquoi ces 2 méthodes sont des méthodes utilisant la surcharge de méthode et
donner un exemple d’appel pour chacune. (corrigé)
CHAPITRE 2. BASES DE LA PROGRAMMATION OBJET
32
Les constructeurs peuvent également être surchargés.
Exemple :
class Etudiant {
String nom ;
Etudiant () {
nom = " Bisounours " ;
}
Etudiant ( String nom ) {
this . nom = nom ;
}
}
Voici un exemple de deux instances d’Etudiant, qui ne vont pas exploiter le même constructeur, grâce à la
surcharge de méthodes :
Etudiant bob = new Etudiant () ; // bob . nom =" Bisounours "
Etudiant greg = new Etudiant ( " Greg " ) ; // greg . nom =" Greg "
Exercice 45. (?) Ecrire un constructeur surchargé pour la classe Binome permettant d’affecter les deux etudiants
du binôme. Ecrire également un constructeur surchargé pour la classe Projet permettant d’affecter tous ses
attributs. (corrigé)
2.3
Conversions entre types de bases et leurs classes dédiées
En Java, les types de base (int, float, char, boolean ...) correspondent aux données "élémentaires" (informellement, celles que l’on peut stocker directement dans les registres d’un processeur). Ce ne sont pas des objets,
pour des raisons de performance essentiellement. Cependant pour chaque type de base correspond une classe, dite
"enveloppe". Ces classes apportent de nouvelles fonctionnalités, via leurs méthodes.
Par exemple, la classe correspondant au type de base int est la classe Integer.
Exemple : (valeur 235 stockée dans un int ou bien un Integer)
int i =235;
Integer j = new Integer (235) ;
Les classes "enveloppe" offrent notamment des fonctionnalités de conversion. Quelques exemples :
Integer j = new Integer (235) ;
int i = j . intValue () ; // recuperation de la valeur de j sous la forme d
’ un type de base
int k = Integer . parseInt ( " 127 " ) ; // conversion d ’ une chaine de
caracteres en int ( tres utile !)
On peut affecter directement un type de base dans un objet de son enveloppe et vice-versa : Java réalise les
conversions de manière implicite.
Exemple :
int i = 127;
Integer z = i ; // equivalent a " Integer z = new Integer ( i ) "
int j = z ; // equivalent a " int j = z . intValue () "
Exercice 46. (?) Donner l’affichage exact du programme ci-après. (corrigé)
2.4. TABLEAUX, COLLECTIONS D’OBJETS
33
int i = Integer . parseInt ( " 127 " ) ;
System . out . println ( i ) ;
Integer z = i +1;
System . out . println ( z ) ;
int j = z . intValue () ;
System . out . println ( j ) ;
Integer k = 127+73;
System . out . println ( k ) ;
int l = Integer . parseInt ( " 127+73 " ) ;
System . out . println ( l ) ;
2.4
Tableaux, collections d’objets
Les tableaux et les collections permettent de regrouper un ensemble d’objets de même type. On utilise les
tableaux lorsque le nombre d’objets à regrouper est fixe et connu. Les collections sont plus souples : leur taille en
particulier n’est pas fixée.
Définition 2.9 (tableau). Ensemble de n objets. On déclare un tableau avec :
type [] tableau = new type [ n ];
On accède au i-ème élément d’un tableau avec la notation tableau[i-1].
Les collections en Java forment une bibliothèque très riche : elle sera étudiée plus en détail dans un chapitre
dédié. Il existe plusieurs types d’implémentations de collections (en fonction de l’usage que l’on souhaite faire de
la collection d’objets).
Définition 2.10 (la collection ArrayList (tableau dynamique)). ArrayList est une des implémentations les plus
utilisées en Java : elle fournit toutes les fonctionnalités d’un tableau classique, mais sa taille n’est pas figée.
Exemple de syntaxe :
import java . util . ArrayList ; // ArrayList est dans le paquet java . util
ArrayList < TypeObjet > al = new ArrayList < TypeObjet >() ; // declaration d ’
une collection vide pouvant stocker des objets de type TypeObjet
al . add ( o1 ) ; // on ajoute un objet o1 ( suppose de type TypeObjet )
al . add ( o2 ) ; // on ajoute un autre objet
// parcours de la collection ( marche aussi sur un tableau )
for ( TypeObjet o : al ) {
// o vaut o1 dans le premier passage , o2 dans le second
...
}
Exercice 47. (?) Créer une collection contenant 5 étudiants, puis parcourir cette collection en affichant le nom
de chacun des étudiants. (corrigé)
Exercice 48. (?) Même exercice que le précédent, en utilisant un tableau d’étudiants. (corrigé)
CHAPITRE 2. BASES DE LA PROGRAMMATION OBJET
34
Fiche de synthèse
Concepts de base de la programmation objet
— classe, objet/instance, attribut, type
— méthode, méthode String toString()
— objet actif this
— création d’un objet : new
— cycle de vie d’un objet, constructeurs, état null, portée
— surcharge de méthodes, constructeurs surchargés
— ensembles d’objets
Java
— destructeur finalize
— types de base et leurs enveloppes
— la collection java.util.ArrayList
Exemple de code (classes Etudiant, Binome et Projet ; collection d’étudiants) :
import java . util . ArrayList ;
class Etudiant {
String nom ;
}
class Binome {
Etudiant etu1 ;
Etudiant etu2 ;
}
class Projet {
Binome b ;
String titre ;
float note ;
}
class Application {
public void main ( String [] args ) {
Projet p = new Projet () ; // instanciation d ’ un objet Projet
p . b = new Binome () ; // creation d ’ un binome et affectation au
binome du projet
p . b . etu1 = new Etudiant () ; // creation d ’ un etudiant et affectation
au premier etudiant du binome du projet
p . b . etu1 . nom = " Saurion " ; // initialisation du nom de cet etudiant
p . b . etu2 = new Etudiant () ; // idem pour le second etudiant
p . b . etu2 . nom = " Bilbo " ;
// creation d ’ une collection formee des deux etudiants du projet
ArrayList < Etudiant > al = new ArrayList < Etudiant >() ;
al . add ( p . b . etu1 ) ;
al . add ( p . b . etu2 ) ;
// affichage des noms des etudiants de la collection
for ( Etudiant e : al )
System . out . println ( e . nom ) ;
}
}
Chapitre 3
Instances & Encapsulation
Dans ce chapitre, nous allons approfondir la notion d’instances d’une classe, en considérant la copie d’une instance, le passage d’instances en paramètre, les méthodes et les attributs de classe. La dernière partie est consacrée
à l’encapsulation qui permet de protéger l’information contenue dans un objet.
Sommaire
3.1
3.2
Instances d’objets . . . . . . . . . . . . . . . . . . .
3.1.1 Attributs et méthodes statiques . . . . . . . . .
3.1.2 Passage d’objets en paramètres d’une méthode
3.1.3 Copie d’une instance . . . . . . . . . . . . . .
Encapsulation . . . . . . . . . . . . . . . . . . . . .
3.2.1 Paquets . . . . . . . . . . . . . . . . . . . . .
3.2.2 Visibilité . . . . . . . . . . . . . . . . . . . .
3.2.3 Accesseurs . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Le code de base est le suivant :
class Etudiant {
String nom ;
Etudiant ( String s ) {
this . nom = s ;
}
}
class Binome {
Etudiant etu1 ;
Etudiant etu2 ;
public Binome ( Etudiant etu1 , Etudiant etu2 ) {
this . etu1 = etu1 ;
this . etu2 = etu2 ;
}
}
class Projet {
Binome b ;
String titre ;
35
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
36
36
38
39
41
41
41
42
CHAPITRE 3. INSTANCES & ENCAPSULATION
36
float note ;
public Projet ( Binome b , String titre , float note ) {
this . b = b ;
this . titre = titre ;
this . note = note ;
}
}
class Application {
/* *
* @param args the command line arguments
*/
public static void main ( String [] args ) {
Etudiant a = new Etudiant ( " Saurion " ) ;
Etudiant b = new Etudiant ( " Bilbo " ) ;
Binome bi = new Binome (a , b ) ;
Projet p = new Projet ( bi , " Le projet qui dechire " ,0.05 f ) ;
}
}
L’archive instances.zip contient le code de ces classes et peut être importée directement dans
NetBeans/eclipse.
3.1
Instances d’objets
Avant d’aborder des notions nouvelles, rappelons que les instances sont créées en faisant appel à un constructeur à l’aide de l’instruction new.
Ainsi en Java, le code suivant ne crée qu’une seule instance au final :
Coucou c1 ; // c1 est une reference nulle sur un objet Coucou
c1 = new Coucou (() ; // appel explicite du constructeur par defaut
Coucou c2 = c1 ; // copie de la reference - * un * * seul * objet
Coucou c3 ;
c3 = c1 ; // copie de la reference - * toujours * un seul objet
Remarque : ceci n’est pas forcément valable pour d’autres langages comme C++ par exemple.
3.1.1
Attributs et méthodes statiques
Définition 3.1 (attribut statique (ou attribut de classe)). Un attribut statique est un attribut partagé par l’ensemble
des instances d’une classe. Il ne requiert donc pas d’instance d’une classe : pour cette raison, on peut accéder à
un attribut statique avec la syntaxe :
Classe . nomAttribut
On déclare un attribut comme statique en le précédent avec le mot clef static :
static type nomAttribut ;
Par exemple, System.in, System.out et System.err sont trois attributs statiques de la classe System qui
désignent respectivement les flux "standard" en entrée, en sortie "normale" et en sortie pour les messages d’erreurs.
3.1. INSTANCES D’OBJETS
37
Exercice 49. (?) On suppose que dans notre application, tous nos étudiants seront des étudiants de l’IUT de
Bordeaux. On souhaite stocker cette information dans un attribut IUT de valeur "IUT de Bordeaux".
Modifiez la classe Etudiant en fonction, créez deux instances Bob et Greg, puis faites afficher dans le terminal
la valeur de l’attribut IUT pour chacune de ces instances.
Quelle est la place mémoire occupée par l’ensemble de ces deux instances ? (corrigé)
Définition 3.2 (méthode statique (ou méthode de classe)). Une méthode statique est une méthode qui n’utilise que
des attributs statiques de la classe et ne fait appel qu’à d’autres méthodes statiques. Une méthode est déclarée
comme statique à l’aide du mot-clef static :
static typeRetour nomMethode ( typeArg1 arg1 , typeArg2 arg2 ...)
Une méthode statique ne requiert donc pas une instance d’une classe pour être exécutée : pour cette raison, on
peut appeler une méthode statique avec la syntaxe :
Classe . nomMethode ( args ) ;
Par exemple, la méthode currentTimeMillis() est une méthode statique de la classe System qui retourne
la date actuelle en nombre de millisecondes depuis le 1er Janvier 1970. Cette donnée est intrinsèquement une
donnée qui ne peut dépendre d’une instance. Elle est donc enregistrée dans un attribut statique et la méthode qui
retourne sa valeur peut donc être statique.
Exemple d’utilisation :
long nbMilliSecondes = System . currentTimeMillis () ;
System . out . println ( " Nb de millisecondes = " + nbMilliSecondes ) ;
La méthode main (en Java) est un exemple incontournable de méthode statique. Elle est le point de départ
d’une application Java.
Exercice 50. (?) Expliquer pourquoi la classe ci-après n’est pas correcte. (corrigé)
class MainAppli {
Etudiant bob ;
MainAppli () {
System . out . println ( " Bienvenue " ) ;
}
public static void main ( String [] args ) {
new MainAppli () ;
bob = new Etudiant ( " Bob " ) ;
}
}
Exercice 51. (??) Proposez un correctif pour l’exercice 50. (corrigé)
Exercice 52. (?) Dans la classe Etudiant, ajouter une méthode statique afficherIUT() qui affiche la valeur
de IUT (cf exercice 49) dans le terminal, et donnez deux exemples de syntaxe différente d’appel de cette méthode.
(corrigé)
Exercice 53. (? ? ?) On souhaite comptabiliser le nombre d’instances d’étudiants dans un attribut statique nbTotalEtudiants de la classe Etudiant. Comment faire pour actualiser celle-ci correctement ? (corrigé)
CHAPITRE 3. INSTANCES & ENCAPSULATION
38
3.1.2
Passage d’objets en paramètres d’une méthode
Les paramètres sont passés par valeur :
— dans le cas d’un type de base, une copie de la donnée est mise à disposition de la méthode - il s’agit d’un
passage en entrée ;
— dans le cas d’un objet, une copie de la référence originale est effectuée : la méthode accède donc directement à l’objet et peut modifier la valeur de ses variables/attributs - il s’agit donc d’un passage en
entrée-sortie.
Par exemple, le code :
static void passageInt ( int i ) {
System . out . println ( " Debut methode : i vaut " + i ) ;
i =12345;
System . out . println ( " Fin methode : i vaut " + i ) ;
}
public static void main ( String [] args ) {
int i = -27;
System . out . println ( " Avant methode : i vaut " + i ) ;
passageInt ( i ) ;
System . out . println ( " Apres methode : i vaut " + i ) ;
}
affiche :
Avant methode :
Debut methode :
Fin methode : i
Apres methode :
i vaut -27
i vaut -27
vaut 12345
i vaut -27
En effet, la variable i dans la méthode passageInt est une copie de celle de l’appel (car int fait partie des
types primitifs) : après l’appel à la méthode passageInt, la variable i de MainAppli a conservé sa valeur : -27.
Autre exemple, le code :
static void passageEtudiant ( Etudiant e ) {
System . out . println ( " Debut methode : le nom de l ’ etudiant e vaut
" + e . nom ) ;
e . nom = " Tetouillou le terrible dragon " ;
System . out . println ( " Fin methode : le nom de l ’ etudiant e vaut " +
e . nom ) ;
}
public static void main ( String [] args ) {
Etudiant e = new Etudiant ( " Dark Vador le gentil " ) ;
System . out . println ( " Avant methode : le nom de l ’ etudiant e vaut
" + e . nom ) ;
passageEtudiant ( e ) ;
System . out . println ( " Apres methode : le nom de l ’ etudiant e vaut
" + e . nom ) ;
}
affiche :
Avant methode: le nom de l’etudiant e vaut Dark Vador le gentil
Debut methode: le nom de l’etudiant e vaut Dark Vador le gentil
Fin methode: le nom de l’etudiant e vaut Tetouillou le terrible dragon
3.1. INSTANCES D’OBJETS
39
Apres methode: le nom de l’etudiant e vaut Tetouillou le terrible dragon
En effet, la méthode passageEtudiant a modifié l’attribut nom de l’instance e passée en paramètre.
Exercice 54. (?) Testez le fonctionnement des deux exemples précédents, en plaçant des points d’arrêt judicieusement pour observer les changements de valeur des variables. (corrigé)
3.1.3
Copie d’une instance
Une solution pour que la méthode passageEtudiant ne modifie pas l’instance passée en paramètre est de lui
passer une copie de l’instance, et non l’instance elle-même.
En Java, tout objet possède une méthode clone() qui réalise une copie d’une instance, en dupliquant les
attributs de type primitifs.
On pourrait donc écrire 1 :
Etudiant e = new Etudiant ( " Dark Vador le gentil " ) ;
Etudiant copie = ( Etudiant ) e . clone () ;
passageEtudiant ( copie ) ;
Cependant cette méthode clone() possède certaines limites dans des cas d’utilisation impliquant des attributs
de type non-primitifs.
Nous utiliserons une autre solution, exploitée dans d’autres langages objets comme C++ par exemple, qui
consiste à implémenter un constructeur par copie, i.e. un constructeur qui prend un objet de même type en paramètre et qui recopie la valeur de chacun des attributs du paramètre dans les siens :
Voici une telle implémentation pour notre classe Etudiant :
public class Etudiant {
String nom ;
static String IUT = " IUT de Bordeaux " ;
Etudiant ( String nom ) {
this . nom = nom ;
}
Etudiant ( Etudiant e ) {
nom = e . nom ;
}
}
On peut maintenant faire l’appel en passant une copie :
Etudiant e = new Etudiant ( " Dark Vador le gentil " ) ;
Etudiant copie = new Etudiant ( e ) ;
passageEtudiant ( copie ) ;
ou plus simplement :
Etudiant e = new Etudiant ( " Dark Vador le gentil " ) ;
passageEtudiant ( new Etudiant ( e ) ) ;
Considérons maintenant la classe Professeur suivante :
1. la méthode clone retourne un Object, le code (Etudiant) permet ici de preciser que c’est un étudiant que l’on copie et recupère.
CHAPITRE 3. INSTANCES & ENCAPSULATION
40
public class Professeur {
String nom ;
Professeur ( String nom ) {
this . nom = nom ;
}
}
et ajoutons un attribut Professeur à la classe Etudiant :
public class Etudiant {
String nom ;
static String IUT = " IUT de Bordeaux " ;
Professeur profPOO ;
Etudiant ( String nom ) {
this . nom = nom ;
}
Etudiant ( Etudiant e ) {
nom = e . nom ;
}
}
Soit la méthode :
void passageEtudiant ( Etudiant e ) {
System . out . println ( " Debut methode : le nom de l ’ etudiant e vaut " + e .
nom ) ;
e . nom = " Tetouillou le terrible dragon " ;
e . profPOO . nom = " Gothmog " ;
System . out . println ( " Fin methode : le nom de l ’ etudiant e vaut " + e . nom
);
}
Le code :
Etudiant e = new Etudiant ( " Dark Vador le gentil " ) ;
e . profPOO = new Professeur ( " Alphonse " ) ;
System . out . println ( " Avant methode : le nom de l ’ etudiant e vaut " + e . nom )
;
Etudiant copie = new Etudiant ( e ) ;
passageEtudiant ( copie ) ;
System . out . println ( " Apres methode : le nom de l ’ etudiant e vaut " + e . nom )
;
provoque le message d’erreur :
Exception in thread " main " java . lang . Nul lP oin ter Ex cep ti on
Exercice 55. (??) Pourquoi ? Comment faire pour empêcher cela ? (corrigé)
3.2. ENCAPSULATION
3.2
41
Encapsulation
Définition 3.3 (encapsulation). L’encapsulation permet de protéger l’information contenue dans un objet (en
particulier, ses attributs) et de ne proposer que des méthodes de manipulation de cet objet.
Ainsi, les propriétés associées à l’objet sont assurées par les méthodes de l’objet. L’utilisateur extérieur ne peut
pas modifier directement l’information et risquer de mettre en péril les propriétés de l’objet.
L’objet est ainsi vu de l’extérieur comme une boîte noire ayant certaines propriétés et ayant un comportement
spécifié. La manière dont ces propriétés ont été implémentées est cachée aux utilisateurs de la classe.
En Java, l’encapsulation intervient dans la structuration des classes dans une hiérarchie de paquets, ainsi que
via des règles de visibilités pour les attributs et les méthodes d’un objet.
3.2.1
Paquets
Définition 3.4 (paquet). Un paquet (package) est un ensemble de classes réunies dans un répertoire. On déclare
le paquet (qui peut être un sous-paquet d’un paquet) avec le mot-clef package.
Par exemple, pour déclarer une classe dans le sous-paquet monpackage du paquet coucou, on écrit en première
ligne du code source de la classe :
package coucou . monpackage ;
Il est possible d’utiliser ces classes via un import :
import coucou . monpackage .*
À noter : coucou est un répertoire contenant le sous-répertoire monpackage. Les binaires (les fichiers .class)
doivent être placés dans le répertoire correspondant à leur paquet. Les fichiers sources (.java) ne sont pas soumis
à cette contrainte.
Exercice 56. (?) Mettre la classe MainAppli dans un paquet application et les deux classes Etudiant et
Professeur dans un paquet personnes (corrigé)
.
3.2.2
Visibilité
Déclaration d’une classe La visibilité d’une classe peut être définie à l’aide d’un mot-clef précédant class :
[ rien , public , final , abstract ] class MyClass {
...
}
La signification de ces mots-clefs est la suivante :
— rien : accessible uniquement au sein du paquet (package)
— public : accessible par tout le monde
— final : la classe ne peut pas être dérivée (pas de classe fille)
— abstract : classe abstraite (ne peut pas être instanciée)
Le nom de la classe peut être suivi également d’autres mots-clefs : extends (héritage), implements (implémentation d’interface). Nous verrons le rôle de ces mots-clefs ultérieurement.
Par exemple :
public class Etudiant extends Individu implements Studieux {
...
}
CHAPITRE 3. INSTANCES & ENCAPSULATION
42
Comme la classe Etudiant est publique, Java impose que son code soit dans un fichier de nom
Etudiant.java. D’une manière générale, il est recommandé de toujours appliquer cette convention, y compris
pour les classes qui ne sont pas publiques.
Déclaration des variables Prototype de déclaration d’une variable :
[ Specificateur acces ][ Specificateurs particuliers ] Type NomVariable ;
Spécificateur d’accès :
— public : accessible depuis partout où la classe est accessible ;
— private : accessible uniquement depuis la classe elle-même ;
— protected : accessible depuis la classe elle-même et ses classes filles ;
— rien (package private) : accessible uniquement depuis les classes du paquet.
Spécificateurs particuliers :
— rien
— static : définit une variable de classe (et non d’instance).
— final : impose l’initialisation lors de la déclaration, ne peut pas être modifié.
Déclaration d’une méthode Syntaxe :
[ Specif . d ’ acces ][ Specif . particuliers ] TypeDeRetour NomMethode ([
parametres ]) [ exceptions ]
{
...
}
Spécificateurs d’accès : rien, public, private, protected (même signification que pour une variable)
Spécificateurs particuliers : rien, final (la méthode ne peut pas être redéfinie dans une classe fille), static (méthode
statique)
3.2.3
Accesseurs
Définition 3.5 (Accesseurs). Les accesseurs sont des méthodes, et normalement les seules, qui accèdent directement aux attributs d’une instance.
Ainsi, ces méthodes sont utilisées dans les constructeurs, mais aussi dans toute autre méthode qui nécessite
une donnée particulière de l’objet.
Par convention, les accesseurs permettant d’accéder aux valeurs des attributs ont toujours pour nom get + le
nom de la variable et les accesseurs permettant de modifier les valeurs des variables set + le nom de l’attribut.
Le grand intérêt des accesseurs est de rendre indépendant tout le reste du code de la représentation de l’objet.
Exercice 57. (?) Déclarez l’attribut nom de la classe Etudiant comme privé, puis créez les accesseurs pour cet
attribut. Adaptez le reste du code en fonction. Recommencez pour la classe Professeur en utilisant les outils
dédiés de NetBeans/eclipse. (corrigé)
On souhaite maintenant que nos étudiants puissent jouer au Seigneur des Anneaux. Le problème est de créer
une classe Anneau qui certifie qu’il n’y aura qu’au plus une instance d’un anneau. Autrement dit, l’anneau dans
l’application sera unique, quoi que fasse les autres classes.
Exercice 58. (??) Comment faire ? Justifiez votre réponse. (corrigé)
3.2. ENCAPSULATION
Fiche de synthèse
Concepts de base de la programmation objet
— attributs statiques
— méthodes statiques
— constructeur par copie
— encapsulation : visibilité d’une classe, de ses attributs, de ses méthodes
— accesseurs
Java
—
—
—
—
l’environnement de développement NetBeans
utilisation de bibliothèques de Processing en Java
la fameuse méthode statique main
passages de paramètres à une méthode : par copie pour un type de base, par copie de la référence
pour les objets (donc en modification de l’objet référencé)
— niveaux de visibilité : rien, public, protected, orivate
— paquets et les règles de visibilité associées : package, import
Exemple de code (utilisation d’un attribut statique pour comptabiliser le nombre d’instances) :
public class Etudiant {
String nom ;
static int nbTotalEtudiants =0;
Etudiant ( String nom ) {
this . nom = nom ;
nbTotalEtudiants ++;
}
}
public class MainAppli {
MainAppli () {
Etudiant bob = new Etudiant ( " bob " ) ;
Etudiant greg = new Etudiant ( " Greg " ) ;
Etudiant Ralf = new Etudiant ( " Ralf " ) ;
System . out . println ( Etudiant . nbTotalEtudiants ) ;
}
public static void main ( String [] args ) {
new MainAppli () ;
}
}
43
44
CHAPITRE 3. INSTANCES & ENCAPSULATION
Chapitre 4
Héritage, classes abstraites, interfaces
Dans ce chapitre, une notion fondamentale de la programmation objet est introduite : l’héritage. L’héritage est
la raison d’être de deux nouvelles sortes de classes : les classes abstraites et les interfaces. Celles-ci sont étudiées
dans la seconde partie de ce chapitre. Dans la troisième partie, comme exemple, nous définissons une modélisation
abstraite d’un labyrinthe.
Sommaire
4.1
4.2
4.3
Héritage . . . . . . . . . . . . . . . . . . . . . . . . .
4.1.1 Redéfinition d’une méthode ou d’un attribut . . .
4.1.2 Les méthodes disponibles pour toutes les classes
Classes, méthodes abstraites et interfaces . . . . . . .
4.2.1 Classes et méthodes abstraites . . . . . . . . . .
4.2.2 Interfaces . . . . . . . . . . . . . . . . . . . . .
Exemple : modélisation générique d’un labyrinthe . .
Le code initial pour illustrer notre propos est le suivant :
package scolarite ;
public class Etudiant
{
private String nom ;
private static int nbTotalEtudiants =0;
Etudiant ( String nom ) {
this . nom = nom ;
nbTotalEtudiants ++;
}
/* *
* @return the nom
*/
public String getNom () {
return nom ;
}
/* *
* @param nom the nom to set
*/
public void setNom ( String nom ) {
45
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
47
48
50
50
50
51
52
CHAPITRE 4. HÉRITAGE, CLASSES ABSTRAITES, INTERFACES
46
this . nom = nom ;
}
/* *
* @return the nbTotalEtudiants
*/
public static int get NbTotalE tudiant s () {
return nbTotalEtudiants ;
}
}
package scolarite ;
public class Professeur {
private String nom ;
public Professeur ( String nom ) {
this . nom = nom ;
}
/* *
* @return the nom
*/
public String getNom () {
return nom ;
}
/* *
* @param nom the nom to set
*/
public void setNom ( String nom ) {
this . nom = nom ;
}
}
package scolarite ;
public class MainAppli {
MainAppli () {
Etudiant e = new Etudiant ( " Arsene Lupin " ) ;
System . out . println ( " L ’ etudiant e a pour nom : " + e . getNom () ) ;
}
/* *
* @param args the command line arguments
*/
public static void main ( String [] args ) {
new MainAppli () ;
4.1. HÉRITAGE
47
}
}
L’archive heritage.zip contient le code de ces trois classes et peut être importée dans NetBeans/eclipse.
4.1
Héritage
Définition 4.1 (Héritage, classe fille, classe mère). L’héritage est un mécanisme permettant de créer une nouvelle
classe à partir d’une autre, par extension : la nouvelle classe possède les attributs et les méthodes de la classe
initiale. La nouvelle classe est dite classe fille de la classe initiale, qui est elle-même appelée classe mère.
On dit aussi qu’une classe héritant d’une autre classe dérive de celle-ci.
Une classe mère peut avoir plusieurs filles. La figure 4.1 donne un exemple d’héritage : les classes Chat et
Chien sont deux classes filles de la classe mère Animal.
F IGURE 4.1 – Exemple d’héritage
Exercice 59. (?) Donner la liste des attibuts et des méthodes de la classe Chat. (corrigé)
Définition 4.2 (Héritage multiple/simple). Lorsqu’une classe fille possède plusieurs classes mères, l’héritage est
dit multiple. Lorsqu’elle ne possède qu’une seule classe mère, l’héritage est simple.
Ainsi, dans la figure 4.2, la classe D a deux classes mères : B et C. Il s’agit donc d’un héritage multiple.
L’héritage multiple peut conduire à des problèmes d’ambiguïté. Si B et C possèdent une méthode de même nom
f : que désigne la méthode f de D ?
En java (contrairement à C++), il n’y a pas d’héritage multiple : une classe ne peut avoir qu’une seule classe
mère. La hiérarchie d’héritage forme donc un arbre. La racine de l’arbre est la classe Object (i.e. toute classe
hérite de la classe Object).
En java, le mot-clef pour déclarer un héritage est extends :
class Fille extends Mere { ...
Exercice 60. (?) Les classes Etudiant et Professeur ont l’attribut nom en commun. Créez une nouvelle classe
Personne contenant cet attribut et faites hériter les deux classes Etudiant et Professeur de cette classe.
(corrigé)
Si le constructeur de la classe mère sans arguments existe, alors il est appelé lors de la création d’une instance
d’une classe fille.
Par exemple : si la classe Personne est munie du constructeur
CHAPITRE 4. HÉRITAGE, CLASSES ABSTRAITES, INTERFACES
48
F IGURE 4.2 – Exemple d’héritage multiple
public Personne () {
System . out . println ( " Constructeur Personne () " ) ;
}
alors la ligne
Etudiant e = new Etudiant ( " Arsene Lupin " ) ;
provoque l’affichage de Constructeur Personne() car le constructeur Personne() a été exécuté avant celui
Etudiant(String).
Exercice 61. (?) On suppose que le corps du constructeur Etudiant(String ) contient la ligne : System.out.println("Constructeur Etudiant(String)"); Quel sera l’affichage généré par le programme
ci-dessus ? (corrigé)
Exercice 62. (??) Rajoutez des variables statiques privées nbProfesseurs, nbPersonnes dans les classes Professeur et Personne sur le modèle de la variable nbEtudiants de la classe Etudiant. Exploitez le mécanisme
d’appel du constructeur Personne() décrit ci-dessus pour mettre à jour la variable nbPersonnes (de toute façon, cette variable étant privée, elle ne peut pas être modifiée dans les classes filles). (corrigé)
4.1.1
Redéfinition d’une méthode ou d’un attribut
Définition 4.3 (Redéfinition (ou masquage) d’une méthode). La redéfinition intervient lorsqu’une classe dérivée
fournit une nouvelle définition d’une méthode d’une classe ascendante. Cette nouvelle méthode doit posséder non
seulement le même nom que la méthode de la classe ascendante, mais également la même signature (les même
types d’arguments).
Dans l’exemple suivant :
public
class Personne {
...
public String toString () {
return " [ Personne ] Je suis " + getNom () + " , une grande personne " ;
}
...
public class Etudiant extends Personne {
4.1. HÉRITAGE
49
...
public String toString () {
return " [ Etudiant ] Je suis " + getNom () + " , tres studieux " ;
}
...
la méthode toString de la classe Etudiant redéfinit la méthode toString de la classe Personne.
Exercice 63. (?) Que va afficher le code suivant ? (corrigé)
Etudiant a = new Etudiant ( " Arsene Lupin " ) ;
Personne p = new Personne ( " Bugs bunny " ) ;
System . out . println ( a ) ; // equivalent a System . out . println ( a . toString () )
System . out . println ( p ) ; // equivalent a System . out . println ( p . toString () )
La redéfinition d’un attribut est similaire à celle d’une méthode.
Notez que la redéfinition d’une méthode n’a pas de sens pour une méthode statique, puisque la méthode
statique appelée est déterminée explicitement par la classe.
Exercice 64. (?) Changez le nom de la méthode getNbTotalPersonnes en getNbTotal dans la classe Personne. Que retourne la méthode Etudiant.getNbTotal ? Procédez de manière similaire dans la classe Etudiant : que retourne la méthode Etudiant.getNbTotal ? Procédez de manière similaire dans la classe Professeur : que retourne la méthode Professeur.getNbTotal ? Conclure. (corrigé)
Définition 4.4 (Le mot-clef super). Il est possible d’appeler une méthode de la classe mère en utilisant le mot-clef
super. Ceci s’applique également aux constructeurs de la classe-mère.
String reponse = super . toString () ; // appel de la methode toString de
la classe mere
super ( reponse ) ; // appel du constructeur de la classe mere prenant une
chaine de caracteres en parametre
Exercice 65. (?) Modifiez la méthode toString de la classe Etudiant pour qu’elle retourne la chaine formée
du retour de la méthode toString de sa classe mère concaténée avec le texte " et je suis un etudiant
tres studieux." (corrigé)
A noter : en Java, si une classe mère possède un constructeur avec arguments qui est redéfini dans une classe
fille, alors le constructeur sans argument doit aussi être implémenté. D’une manière générale, il vaut donc mieux
toujours écrire un constructeur sans argument dans toute classe qui possède des classes filles.
Exercice 66. (?) Pour en avoir le cœur net, essayez d’enlever le constructeur sans arguments de la classe Personne. (corrigé)
Définition 4.5 (le mot clef final). Le mot-clé final en Java indique qu’un élément ne peut être changé dans la
suite du programme. Il peut s’appliquer aux méthodes et attributs d’une classe, ainsi qu’à une classe elle-même.
Une méthode indiquée comme finale ne peut être redéfinie dans une classe dérivée.
Un attribut de type primitif indiqué comme final devient une constante. Dans le cas d’un attribut de type objet,
c’est la référence vers l’objet qui devient constante et non sa valeur.
public final class MaClass {
public final double PI = 3.14159; // Impossible de modifier la
valeur
public final Etudiant e = new Etudiant ( " Casimir " ) ;
public void uneMethode () {
CHAPITRE 4. HÉRITAGE, CLASSES ABSTRAITES, INTERFACES
50
e . setNom ( " Zeus " ) ; // OK
e = new Etudiant ( " Einstein " ) ; // interdit
}
}
On peut aussi déclarer une classe comme finale, pour empêcher l’héritage de celle-ci par d’autres filles.
final class A { ... }
L’intérêt de déclarer une classe comme finale est d’empêcher l’utilisateur d’en faire une utilisation nonappropriée dans la classe dérivée. Ainsi de nombreuses classes de la libraire standard sont déclarées comme
finales telles que java.lang.System ou java.lang.String.
Exercice 67. (?) Testez le mot-clef final sur les méthodes toString des classes Personne et Etudiant. Que
se passe-t-il si vous déclarez un constructeur de Personne comme final ? (corrigé)
4.1.2
Les méthodes disponibles pour toutes les classes
La classe Object possède quelques méthodes, qui sont donc disponibles pour l’ensemble des classes :
— public String toString() : cette méthode permet d’obtenir une représentation de l’objet sous forme
d’une chaine de caractères (par défaut : l’adresse mémoire de l’objet).
— public boolean equals (Object o) : cette méthode permet de comparer un objet à l’objet courant
pour savoir s’ils sont égaux ou non (par défaut : elle se base sur les adresses mémoires des objets).
— protected Object clone() : cette méthode permet d’obtenir une copie de l’objet et non pas une copie
de la référence vers l’objet (par défaut : copie bit-à-bit des variables de type de base).
— protected void finalize() : cette méthode est appelée lors de la destruction de l’objet.
Exercice 68. (??) On considère que deux personnes sont identiques si elles possèdent le même nom. Comment
faire ? Testez votre solution. (corrigé)
La méthode finalize d’une classe fille n’appelle pas celle de sa classe mère. Il faut le faire explicitement
avec le mot clef super.
Exercice 69. (??) Dans la classe Personne, donnez une implémentation de la méthode finalize qui affiche
"Bye bye de la part de (nom)". Testez. comme votre application ne crée pas beaucoup d’instances, la machine virtuelle n’est pas très pressée de libérer des ressources. Vous avez donc certainement observé que la méthode finalize n’avait pas été appelée. Recommencez en créant cette fois beaucoup d’instances, par exemple
avec le code ci-après. (corrigé)
for ( int i =0; i <=1000000; i ++)
new Personne ( " Robert Bidochon numero " + i ) ;
4.2
Classes, méthodes abstraites et interfaces
Nous avons vu que le mécanisme d’héritage permet à une classe fille de proposer une implémentation spécifique d’une méthode, en la redéfinissant. Une méthode abstraite tire partie de ce mécanisme, en délégant aux
classes filles le soin de fournir une implémentation d’une méthode.
4.2.1
Classes et méthodes abstraites
Définition 4.6. Méthode abstraite Une méthode est dite abstraite lorsqu’elle ne possède pas d’implémentation
(i.e. elle ne contient pas de corps délimité par {}). En java, une méthode abstraite est déclarée comme telle avec
le mot-clef abstract :
4.2. CLASSES, MÉTHODES ABSTRAITES ET INTERFACES
51
abstract typeRetour nomMethode ( args ) ;
Une classe qui possède une méthode abstraite ne peut être instanciée et doit être déclarée comme abstraite.
Définition 4.7. Classe abstraite Classe ne pouvant être instanciée et déclarée avec le mot-clef abstract
abstract class nomClasse { ...
En java, une classe peut être définie comme abstraite sans contenir de méthodes abstraites, ce qui empêche de
l’instancier.
Une classe abstraite peut être implémentée en partie ou en totalité par une classe fille. Lorsque l’implémentation est partielle, la classe fille est elle-aussi abstraite.
Considérons par exemple la classe abstraite suivante :
abstract class ExtraTerrestre {
abstract int nbJambes () ;
}
Cette classe est abstraite car le développeur ne souhaite pas proposer de nombres de jambes par défaut, la connaissance humaine des variétés d’extra-terrestres étant pour l’heure trop limitée.
Cependant, on peut écrire raisonnablement la classe fille suivante (le nombre de jambes étant statistiquement
égal au nombre de pieds) :
class Bipede extends ExtraTerrestre {
int nbJambes () {
return 2;
}
}
Exercice 70. (?) Rajouter la méthode abstraite boolean aLeBac() dans la classe Personne et donner une
implémentation dans les classes Etudiant et Professeur. (corrigé)
4.2.2
Interfaces
Une classe est un mélange de conception (modèle) et d’implémentation. En java, une interface est une abstraction de pure conception : uniquement des constantes et des méthodes abstraites, aucune implémentation. Techniquement, une interface est proche d’une classe abstraite dont toutes les méthodes seraient abstraites.
Définition 4.8 (interface). Une interface est un type de classe abstraite, qui ne peut contenir que des déclarations
de méthodes (signatures) et des constantes. On déclare une interface avec le mot-clef interface :
interface nomClasse {
// declaration de constantes
// declaration de methodes
}
Définition 4.9 (le mot-clef implements). Une classe implémente une interface lorsqu’elle fournit une implémentation pour chacune des méthodes de l’interface ou déclare les méthodes non-implémentées comme abstraites. On
déclare une telle classe avec le mot-clef implements.
class nomClasse implements nomInterface { ...
CHAPITRE 4. HÉRITAGE, CLASSES ABSTRAITES, INTERFACES
52
Une interface est un élément essentiel dans une application : elle spécifie des fonctionnalités minimales pour
le fonctionnement de l’application. Grâce à ce mécanisme, chacun peut donner alors sa propre implémentation
d’une interface et ces implémentations sont librement interchangeables.
Une interface peut étendre, par héritage, une autre interface.
Reprenons notre exemple d’extra-terrestre, mais en utilisant cette fois une interface :
interface ExtraTerrestre {
int nbJambes () ;
}
class Bipede implements ExtraTerrestre {
int nbJambes () {
return 2;
}
}
Une classe peut implémenter plusieurs interfaces tout en héritant d’une autre classe.
Exemple de déclaration :
class Bipede extends Vivant implements ExtraTerrestre , SeDeplace {...
Exercice 71. (?) Declarer une interface Individu avec une seule méthode getNbTotal() et l’implémenter avec
la classe Personne. (corrigé)
4.3
Exemple : modélisation générique d’un labyrinthe
Un labyrinthe est formé d’un ensemble de salles. Dans le labyrinthe, on peut placer un héros à l’entrée ou
l’informer s’il est à la sortie. De plus, on peut déterminer l’ensemble des salles dans lesquelles le héros peut se
déplacer à partir de sa position courante.
Exercice 72. (?) Vérifier que les interfaces ci-dessous permettent de spécifier toutes ces fonctionnalités. (corrigé)
public interface Labyrinthe {
// cree le labyrinthe a partir d ’ un fichier
public void creerLabyrinthe ( String file ) ;
// place le heros a l ’ entree du labyrinthe
public void entrer ( Personnage heros ) ;
// fait sortir le heros , s ’ il est a la sortie
public boolean sortir ( Personnage heros ) ;
// renvoie les salles accessibles pour le heros
public Collection < Salle > sallesAccessibles ( Personnage heros ) ;
// accesseur sur les salles du labyrinthes
public Collection < Salle > getSalles () ;
// accesseurs sur l ’ entree
public Salle getEntree () ;
// accesseur sur la sortie
public Salle getSortie () ;
}
4.3. EXEMPLE : MODÉLISATION GÉNÉRIQUE D’UN LABYRINTHE
53
Pour permettre au héros de se déplacer dans le labyrinthe, une salle peut recevoir le héros :
public interface Salle {
public boolean recevoir ( Personnage heros ) ;
}
Enfin, le héros peut choisir une salle parmi les salles accessibles, et des accesseurs sont disponibles sur sa
position :
public interface Personnage {
// renvoie une salle parmi sallesAccesibles
public Salle faitSonChoix ( Collection < Salle > sallesAccessibles ) ;
// renvoie sa position courante
public Salle getPosition () ;
// definit sa position courante
public void setPosition ( Salle s ) ;
}
CHAPITRE 4. HÉRITAGE, CLASSES ABSTRAITES, INTERFACES
54
Fiche de synthèse
Concepts de base de la programmation objet
— héritage : classe fille/mère
— héritage multiple/simple
— redéfinition d’une méthode, d’un attribut
— méthodes abstraites
Java
—
—
—
—
—
—
—
héritage : extends - pas d’héritage multiple
appel du constructeur sans arguments de la classe mère
protection contre la redéfinition : final
la classe Object et ses méthodes
méthodes abstraites : abstract
interfaces : interface
implémentation (multiple) : implements
Exemple de code (héritage) :
public class Personne {
private String nom ;
private static int nbTotalPersonnes =0;
public Personne () { nbTotalPersonnes ++;}
public Personne ( String nom ) { this () ; setNom ( nom ) ; }
public void setNom ( String nom ) { this . nom = nom ;}
public static int getNbTotal () { return nbTotalPersonnes ;}
}
public class Professeur extends Personne {
private static int nbTotalProfesseurs =0;
public Professeur ( String nom ) { setNom ( nom ) ; nbTotalProfesseurs ++;
}
public static int getNbTotal () { return nbTotalProfesseurs ; }
}
public class Etudiant extends Personne {
private static int nbTotalEtudiants =0;
Etudiant ( String nom ) { setNom ( nom ) ; nbTotalEtudiants ++; }
public static int getNbTotal () { return nbTotalEtudiants ; }
}
public class Main {
Main () {
Etudiant a = new Etudiant ( " Arsene Lupin " ) ;
Etudiant b = new Etudiant ( " Dark Vador " ) ;
Professeur d = new Professeur ( " Jojo Lapin " ) ;
System . out . println ( Personne . getNbTotal () + " personnes dont " +
Etudiant . getNbTotal () + " etudiants " + " et " + Professeur .
getNbTotal () + " professeurs " ) ;
}
public static void main ( String [] args ) {
new Main () ;
}
}
Chapitre 5
Polymorphisme & compléments sur les
collections
Dans ce chapitre, la notion de polymorphisme est introduite. Cette notion est particulièrement adaptée aux
collections d’objets, car elle permet pour une collection d’objets hétérogène (i.e. de types différents) mais héritant
d’une classe commune d’appliquer pour chaque objet la version la plus spécialisée d’une méthode. Une description
plus approfondie des fonctionnalités des collections en Java est donnée en seconde partie.
Sommaire
5.1
5.2
Polymorphisme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Collections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5.2.1 L’interface Collection . . . . . . . . . . . . . . . . . . . . . .
5.2.2 Les listes doublement chainées . . . . . . . . . . . . . . . . . .
5.2.3 les vecteurs ou tableaux dynamiques . . . . . . . . . . . . . . .
5.2.4 Les tables de hachage et les collections partiellement ordonnées
5.2.5 Les ensembles . . . . . . . . . . . . . . . . . . . . . . . . . .
5.2.6 Les associations . . . . . . . . . . . . . . . . . . . . . . . . .
5.2.7 Les vues et les opérations de masse . . . . . . . . . . . . . . .
5.2.8 Algorithmes . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Le code de base pour cette partie est le suivant :
public
class Personne {
private String nom ;
private static int nbTotalPersonnes =0;
public Personne () {
nbTotalPersonnes ++;
}
public Personne ( String nom ) {
this () ;
setNom ( nom ) ;
}
public String getNom () {
return nom ;
}
55
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
56
58
59
60
61
61
62
62
62
63
CHAPITRE 5. POLYMORPHISME & COMPLÉMENTS SUR LES COLLECTIONS
56
public void setNom ( String nom ) {
this . nom = nom ;
}
public static int getNbTotal () {
return nbTotalPersonnes ;
}
public String toString () {
return " [ Personne ] Je suis " + getNom () + " , une grande personne " ;
}
}
public class Etudiant extends Personne {
private static int nbTotalEtudiants =0;
Etudiant ( String nom ) {
setNom ( nom ) ;
nbTotalEtudiants ++;
}
public static int getNbTotal () {
return nbTotalEtudiants ;
}
public String toString () {
return " [ Etudiant ] Je suis " + getNom () + " , tres studieux " ;
}
}
public class Professeur extends Personne {
private static int nbTotalProfesseurs =0;
public Professeur ( String nom ) {
setNom ( nom ) ;
nbTotalProfesseurs ++;
}
public static int getNbTotal () {
return nbTotalProfesseurs ;
}
}
L’archive polymorphisme.zip contient le code de ces trois classes et peut être importée directement dans
NetBeans/eclipse.
5.1
Polymorphisme
Si une classe B hérite du type A, on peut instancier un objet de la classe B avec le type de A :
5.1. POLYMORPHISME
57
A o = new B () ;
Dans ce cas, seuls les méthodes et les attributs de la classe A sont disponibles dans l’instance o.
Exercice 73. (?) Ecrire une méthode void jamesBond() dans la classe Etudiant qui affiche "[Etudiant]
Je m’appelle (nom), (nom) Bond", puis vérifier que celle-ci n’est pas accessible pour Casimir déclaré avec
Personne casimir = new Etudiant("Casimir"). Ecrire ensuite une méthode void jamesBond() dans la
classe Professeur qui affiche "[Professeur] How do you do, Miss Moneypenny?". (corrigé)
Si B redéfinit une méthode f de la classe A, alors, en Java, l’appel :
o . f () ;
provoque l’exécution du corps de la méthode f de la classe B (au lieu de A) : la méthode la plus spécialisée est
appelée.
En ce sens, le typage en Java est dynamique : bien que l’instance o ait été déclarée comme de type A, le type
effectif à l’exécution tient compte du type utilisé lors de la création de l’objet référencé. Dans notre exemple, on
dit que le type statique de l’instance o est A tandis que son type dynamique est B.
Cas des méthodes statiques Les méthodes statiques ne sont pas concernées par ce mécanisme : Java ne tient
compte que du type statique de l’objet pour déterminer quelle méthode statique doit être appelée. D’une manière
générale, comme nous l’avons déjà signalé, il est maladroit d’utiliser un objet pour appeler une méthode statique.
Il vaut mieux utiliser directement le nom de la classe, ce qui évite toute ambiguité : le polymorphisme décrit dans
cette section ne s’applique qu’aux méthodes d’instances.
Exercice 74. (?) Ecrire maintenant une méthode void jamesBond() dans la classe Personne qui affiche "[Personne] Miss Moneypenny, je vous saurais gré de ne pas dire "le vieux" en parlant de moi.". Vérifier qu’elle n’est pas appelée par le code casimir.jamesBond(), où casimir est l’instance d’une Personne de l’exercice précédent. (corrigé)
Ce mécanisme est un cas particulier de la notion de polymorphisme :
Définition 5.1 (Polymorphisme). Le polymorphisme permet d’autoriser le même code à être utilisé avec différents
types, ce qui permet des implémentations plus abstraites et générales.
On peut spécifier explicitement un type (dynamique) à utiliser à un moment précis, sous réserve que ce type
vérifie soit "compatible" avec l’instance :
Définition 5.2 (Transtypage ou conversion de type). Pour un type de base, une conversion de type est autorisée
dans certains cas. La valeur de l’attribut n’est pas forcément préservée :
int i = 2; float j = ( float ) i ; // j =2.0 ( conversion de int vers float )
double f = 2.5; int n = ( int ) f ; // n =2 ( conversion de double vers int
en prenant la partie entiere )
Pour une instance d’une classe, il existe deux possibilités de conversion de type :
— conversion ascendante : elle permet de prendre une instance d’une classe fille B pour une instance directe
d’une classe parente A. Dans certains langages, cela peut servir à appeler une méthode f de la classe A
au lieu de la méthode f de la classe B, lorsqu’on souhaite utiliser l’implémentation de la méthode f de
A. En java, avec une conversion ascendante, les seules méthodes de la classe parente seront accessibles,
mais les méthodes appelées seront toujours celles les plus spécialisées.
— conversion descendante : elle permet de déclarer un type dynamique d’une classe fille du type statique,
sous réserve que l’instance est été créée avec un type dynamique "suffisamment bas" dans la hiérarchie
d’héritage.
Exemple :
CHAPITRE 5. POLYMORPHISME & COMPLÉMENTS SUR LES COLLECTIONS
58
// on suppose que
//
B herite de A
//
C herite de B
//
A , B et C possedent une methode f
B o = new B () ;
A a = ( A ) o ; // conversion ascendante de o
A z = new B () ;
B b = ( B ) z ; // conversion descendante de z ( OK )
C c = ( C ) z ; // conversion descendante de z : echec car o a ete cree
avec un type B seulement
o . f () ; // appel de la methode f de B
(( A ) o ) . f () ; // Java : appel de la methode f de B ...
z . f () ; // appel de la methode f de B ( polymorphisme )
(( A ) z ) . f () ; // Java : appel de la methode f de B
Exercice 75. (??) Tester les conversions ascendante et descendante en exploitant les classes Personne et Etudiant. (corrigé)
En java, l’instruction instanceof permet de tester le type d’une instance :
Définition 5.3 (L’instruction instanceof). Cette instruction renvoie un booléen qui indique si une instance hérite
d’une classe.
if o instanceof A { ... }
Par conséquent, instanceof Object est toujours vrai, quel que soit l’instance à laquelle on l’applique.
L’instruction instanceof est utile notamment lorsqu’on souhaite appeler des méthodes spécifiques à certaines
classes.
Supposons par exemple que la classe Etudiant possède une méthode int getIdentifiantEtudiant().
Soit le code suivant :
public void afficherCollection ( Collection < Personne > personnes ) {
for ( Personne p : personnes )
if ( p instanceof Etudiant ) {
System . out . println ((( Etudiant ) p ) . g e tI d e n t if i a n tE t u d i an t () ) ;
}
}
La méthode afficherCollection affichera les identifiants pour les personnes de la collection qui sont des
étudiants.
Avertissement : il est assez tentant d’utiliser instanceof dans de multiples situations. En fait, cette instruction
est à utiliser avec parcimonie car elle ajoute de la rigidité au code, en introduisant des dépendances pour son
fonctionnement à des types explicites. Dans la plupart des cas, il existe des solutions plus élégantes que l’emploi
de instanceof, en général en exploitant le polymorphisme.
5.2
Collections
Cette partie est consacrée à l’API des Collections en Java. Comme cette libraire est très vaste, il ne s’agit
pas d’une étude exhaustive.
5.2. COLLECTIONS
5.2.1
59
L’interface Collection
L’interface Collection est une des principales interfaces de Java. Elle sert pour le stockage et le parcours
d’un ensemble d’objets, pas nécessairement de même type.
Cette interface possède deux méthodes essentielles :
boolean add ( Object obj )
Iterator iterator ()
La méthode add ajoute un objet dans la collection. Elle renvoie true si l’ajout de l’objet a effectivement
modifié la collection, false sinon.
La méthode iterator renvoie un objet implémentant l’interface Iterator, qui permet notamment de parcourir une collection. Cette interface possède trois méthodes principales :
Object next ()
boolean hasNext ()
void remove ()
En appelant plusieurs fois la méthode next, vous pouvez parcourir tous les éléments de la collection un par un. Lorsque la fin de la collection est atteinte, la méthode next déclenche une exception
NoSuchElementException 1 . Enfin, la méthode remove supprime l’élément renvoyé par le dernier appel à next.
Exemple de parcours d’une collection c en utilisant un itérateur :
Iterator < Object > i = c . iterator () ;
while ( i . hasNext () ) {
Object o = i . next () ;
}
Pour le parcours d’une collection, les boucles for sont bien adaptées.
Exemple de parcours d’une collection c avec une boucle for :
for ( Object o : c ) {
...
}
Le polymorphisme est tout particulièrement intéressant pour une collection d’objets qui héritent d’un même
type. Par exemple, dans le code suivant,
public void afficherCollection ( Collection < Personne > personnes ) {
for ( Personne p : personnes )
p . jamesBond () ;
}
pour chacun des objets de la collection, la méthode jamesBond() exécutée est fonction de son type dynamique.
Exercice 76. (?) Tester avec une collection (par exemple de type ArrayList) hérérogène de personnes. (corrigé)
L’interface Collection déclare aussi ces méthodes, dont les prototypes sont suffisamment explicites :
int size ()
boolean isEmpty ()
boolean containsObject ( Object )
boolean containsAll ( Collection )
boolean equals ( Object )
boolean addAll ( Collection )
boolean remove ( Object )
1. Les exceptions sont l’objet du chapitre 6.
CHAPITRE 5. POLYMORPHISME & COMPLÉMENTS SUR LES COLLECTIONS
60
boolean removeAll ( Collection )
void clear ()
boolean retainAll ( Collection )
Object [] toArray ()
La classe AbstractCollection définit les méthodes fondamentales size et Iterator comme abstraites
et implémente toutes les autres. Une classe de collection concrète peut donc se limiter à l’implémentation des
méthodes fondamentale.
5.2.2
Les listes doublement chainées
La classe LinkedList est l’implémentation Java des listes doublement chainées. Elle implémente les interfaces Collection et List.
La figure 5.1 donne une illustration de la structure de données d’une liste doublement chainée.
F IGURE 5.1 – Exemple d’une liste doublement chainée, contenant les éléments 154, 45, 11 et 458.
Les avantages/inconvénients d’une liste doublement chainée sont les suivants :
— Avantages : suppression, ajout d’un élément rapide (à l’endroit de la liste où ’on est positionné) ;
— Inconvénients : la récupération d’un élément nécessite le parcours des éléments qui le "précède".
La méthode add ajoute un élément en fin de liste.
Pour ajouter un élément au sein de la liste, il faut utiliser un objet ListIterator (une interface héritant de
l’interface Iterator) qui permet l’insertion d’un élément à la position courante, via sa méthode add.
La classe ListIterator ajoute également 2 méthodes previous and hasPrevious qui permettent le parcours de la liste à rebours.
Exemple (ajout de la chaine "bob", en deuxième position)
LinkedList < String > ll = new LinkedList < >() ;
...
ListIterator li = ll . listIterator () ;
li . next () ;
li . add ( " Bob " ) ;
Exercice 77. (??) Dans les questions ci-après, afficher à chaque étape le contenu (via une boucle) de votre liste
doublement chainée afin de contrôler le bon fonctionnement de vos opérations :
1. créer une liste doublement chainée contenant deux étudiants a et b ;
2. insérer un étudiant c entre a et b ;
3. insérer un étudiant d entre a et c ;
4. supprimer l’étudiant c.
(corrigé)
5.2. COLLECTIONS
61
Exercice 78. (??) Ecrire une méthode afficherInverse qui affiche le contenu d’une liste doublement chainée
en la parcourant en sens inverse. (corrigé)
La classe ListIterator possède aussi une méthode set qui permet de substituer un objet à l’élément courant. La classe LinkedList dispose d’une méthode get(int i) retournant l’élément d’indice i, mais si vous
l’utilisez, c’est que vos données ne devraient pas être stockées sous forme d’une liste chainée !
5.2.3
les vecteurs ou tableaux dynamiques
La classe ArrayList encapsule un tableau classique Object[] dans un tableau dynamique (i.e. sans restriction sur le nombre d’éléments). Elle implémente les interfaces List et Collection. Les méthodes get et set
sont donc disponibles pour accéder directement aux éléments d’un ArrayList.
La classe ArrayList est probablement l’implémentation d’une collection la plus utilisée, car elle offre des
fonctionnalités très complètes, incluant toutes celles d’un tableau classique et la souplesse d’un stockage d’un
nombre quelconque d’éléments.
La figure 5.2 donne une illustration de la structure de données d’un tableau.
F IGURE 5.2 – Exemple d’un tableau, contenant les éléments 45, 154, 58, 78, 31, 5 et 74.
Les avantages/inconvénients d’un tableau sont les suivants :
— Avantages : accès rapide à n’importe quel élément ;
— Inconvénients : suppression/insertion d’un élément lent.
Lorsque deux processus sont susceptibles de faire des accès "simultanés" sur un tableau, il est possible d’utiliser la classe Vector au lieu de ArrayList. La classe Vector gère les accès concurrents par des processus, mais
offre en contre-partie de moins bonnes performances.
Exercice 79. (?) Refaire l’exercice 77, en utilisant un tableau (statique) comme structure de données. (corrigé)
5.2.4
Les tables de hachage et les collections partiellement ordonnées
Les listes chaînées et les tableaux vous permettent de spécifier l’ordre (linéaire) dans lequel vous organisez
vos éléments. Si l’ordre n’a pas d’importance, il existe des structures de données qui vous permettent de retrouver
un élément beaucoup plus rapidement.
Une structure de données classique pour retrouver simplement un élément est la table de hachage. Une table
de hachage calcule un nombre entier, appelé code de hachage, pour chacun des éléments. La table de hachage est
constituée en général d’un tableau de listes chaînées.
En java, le code de hachage d’un objet est donné par la méthode int hashCode().
Remarque importante : lorsque l’on redéfinit la méthode equals d’une classe, il faut toujours redéfinir conjointement la méthode hashCode, pour garantir le bon fonctionnement des tables de hachage pour les objets de cette
classe.
Un élément est stocké dans la liste correspondant à son code de hachage modulo le nombre de listes. Ainsi
pour retrouver un élément, il suffit de réduire son code de hachage modulo le nombre de listes, et parcourir la liste
à sa recherche.
CHAPITRE 5. POLYMORPHISME & COMPLÉMENTS SUR LES COLLECTIONS
62
La classe TreeSet repose sur une table de hachage et permet de stocker des ensembles partiellement ordonnés,
moyennant une petite pénalité.
La comparaison entre deux éléments repose sur la méthode compareTo de l’interface Comparable.
Pour plus d’informations sur les tables de hachage, voir votre cours d’algorithmique.
5.2.5
Les ensembles
Un ensemble correspond à une collection d’éléments ne figurant qu’une seule fois chacun dans la collection.
La méthode add n’ajoute un élément que s’il n’est pas déjà présent. Naturellement, c’est la méthode equals qui
est utilisée pour tester si un objet est égal à un autre.
La classe HashSet offre une implémentation de l’interface Set (qui correspond aux ensembles).
Exercice 80. (??) Faites en sorte en redéfinissant les bonnes méthodes que la collection hs définie ci-dessous ne
contienne que 2 étudiants au final (et non pas 3) : (corrigé)
Etudiant a = new Etudiant ( " Arsene Lupin " ) ;
Etudiant b = new Etudiant ( " Dark Vador " ) ;
Etudiant b2 = new Etudiant ( " Dark Vador " ) ;
HashSet hs = new HashSet () ;
hs . add ( b ) ;
hs . add ( a ) ;
hs . add ( b2 ) ;
5.2.6
Les associations
L’interface Map modélise des associations (i.e. des applications au sens mathématique) : à chaque clef est
associée au plus une valeur. La classe HashMap fournit une implémentation de cette interface.
On peut l’utiliser par exemple pour stocker un dictionnaire dont les clefs sont des noms et les valeurs sont les
descriptions associées aux noms.
Extrait de code :
HashMap < String , String > dictionnaire = new HashMap < String , String >() ;
dictionnaire . put ( " Bateau " , " Objet flottant " ) ;
dictionnaire . put ( " Sous - marin " , " Bateau ayant connu un souci " ) ;
System . out . println ( dictionnaire . get ( " Bateau " ) ) ;
System . out . println ( dictionnaire . get ( " Sous - marin " ) ) ;
Exercice 81. (? On souhaite pouvoir associer des remarques à certains étudiants : comment procéder ? Donner
un exemple d’utilisation. (corrigé)
5.2.7
Les vues et les opérations de masse
Les méthodes des collections ne sont pas synchronisées, ce qui signifie que pour améliorer les performances, il
n’y pas pas de protection gérant les accès concurrents de la part des processus. Il est possible néanmoins d’obtenir
des vues synchronisées, en faisant appel à certaines méthodes de la classe Collections, comme dans l’exemple
ci-dessous :
HashMap hm = new HashMap () ;
Map map = Collections . synchronizedMap ( hm ) ;
Vous pouvez également obtenir une collection en lecture seule (vue non modifiable), comme dans cet exemple
5.2. COLLECTIONS
63
List l = new LinkedList () ;
List l2 = new Collections . unmodifiableList ( l ) ;
Il est aussi possible de travailler directement avec un sous-ensemble (vue restreinte) : ainsi le code
List groupe2 = etudiants . subList (10 ,20) ;
définit le groupe 2 comme les éléments d’indice 10 à 19.
Une opération de masse permet de manipuler directement un ensemble d’éléments d’une liste. Par exemple
pour calculer l’intersection entre deux ensembles a et b, vous pouvez utiliser :
a . retainAll ( b ) ;
Autre exemple, pour ajouter les 10 premiers éléments d’une liste b dans une liste a :
a . addAll ( b . subList ( 0 ,10 ) ) ;
Toute opération de masse peut être appliquée à un sous-ensemble, et elle se reflète automatiquement à la liste
entière.
Exercice 82. (??) Créez deux listes de personnes distinctes et faites ensuite une troisième liste formée de la
concaténation de ces deux listes. (corrigé)
5.2.8
Algorithmes
La méthode Collections.sort permet de trier une collection ordonnée (interface List). Par défaut, elle
utilise comme critère de tri l’implémentation de compare de la collection à trier. Il est possible de lui préciser un
autre critère de tri directement, en lui fournissant une implémentation de l’interface Comparator.
Exemple de comparateur pour des salariés, selon leur salaire :
public class Comparateur implements Comparator < Salarie >{
public int compare ( Salarie a , Salarie b ) {
double differenceSalaires = a . getSalaire () - b .
getSalaire () ;
if ( differenceSalaires < 0 ) return -1;
if ( differenceSalaires > 0 ) return 1;
return 0;
}
}
Ainsi, la ligne :
Collections . sort ( cadres , new Comparateur () ) ;
trie une liste cadres d’objets de type Salarie en fonction de leur salaire.
Tout ceci s’applique aussi aux méthodes génériques de
— calcul d’un plus grand élément : Collections.max ;
— recherche dichotomique d’un élément : Collections.binarySearch (à n’appliquer qu’à des collections triées ;-))
Exercice 83. (? ? ?) Triez une collection de Personne en fonction de l’ordre alphabétique de leurs noms. (corrigé)
CHAPITRE 5. POLYMORPHISME & COMPLÉMENTS SUR LES COLLECTIONS
64
Fiche de synthèse
Concepts de base de la programmation objet
— polymorphisme
— types statique et dynamique
— conversions de type (transtypage) ascendant et descendant
Java
— test du type : instanceof
— ensemble d’objets :
— l’interface Collection, les listes doublement chainées LinkedList, les tableaux dynamiques Vector/ArrayList, les tables de hachage, les ensembles HashSet, les associations
Map
— les vues, les opérations de masse
— les algorithmes de la classe Collections
Exemple de code :
public class Main {
Main () {
// Collection d ’ etudiants
Etudiant a = new Etudiant ( " a " ) ;
Etudiant b = new Etudiant ( " b " ) ;
HashSet < Etudiant > etudiants = new HashSet < Etudiant >() ;
etudiants . add ( a ) ; etudiants . add ( b ) ;
System . out . println ( " Liste d ’ etudiants : " ) ;
afficher ( etudiants ) ;
// Collection de professeurs
Professeur e = new Professeur ( " e " ) ;
Professeur f = new Professeur ( " f " ) ;
ArrayList < Professeur > profs = new ArrayList < Professeur >() ;
profs . add ( e ) ; profs . add ( f ) ;
System . out . println ( " Liste de professeurs : " ) ;
afficher ( profs ) ;
// Reunion des deux collections
ArrayList < Personne > tous = new ArrayList < Personne >() ;
tous . addAll ( etudiants ) ;
tous . addAll ( profs ) ;
System . out . println ( " Tous : " ) ;
afficher ( tous ) ;
}
void afficher ( Collection < Personne > personnes ) {
for ( Personne p : personnes )
System . out . println ( p . toString () ) ;
}
/* *
* @param args the command line arguments
*/
public static void main ( String [] args ) {
new Main () ;
}
}
Deuxième partie
Java Developpment Kit et quelques
compléments incontournables
65
Chapitre 6
Contrôle des erreurs : débogage, tests unitaires
et exceptions
A partir de ce chapitre, nous allons utiliser Eclipse comme outil de développement, au lieu de NetBeans. Le
fonctionnement d’Eclipse est très proche de celui de NetBeans. Cependant Eclipse offre plus de fonctionnalités (via de multiples extensions) et possède une plus grande popularité.
Pour transférer un projet sous NetBeans, dans Eclipse, procédez avec la manière suivante :
1. recopiez le dossier correspondant dans votre répertoire des projets de NetBeans (i.e. NetbeansProjects)
dans celui de Eclipse (i.e. workspace) ;
2. créez un nouveau projet Java dans Eclipse portant le même nom que le dossier que vous avez recopié,
puis laissez vous guider en validant les propositions par défaut.
Ce chapitre est consacré à trois mécanismes distincts permettant de détecter, gérer et/ou corriger des erreurs ;
le débogage, les tests unitaires et les exceptions. Ces mécanismes s’inscrivent donc pleinement dans une démarche
qualité.
Le débogage permet de suivre le déroulement pas-à-pas d’un programme de manière à s’assurer que le fonctionnement est conforme à celui attendu, au plus bas niveau. Il s’agit donc de vérifier que l’implémentation d’un
algorithme est correcte.
Les tests unitaires effectuent un contrôle de plus haut niveau : un test unitaire permet de vérifier que la valeur
de retour d’une méthode est valide pour un jeu de données de paramètres précis. On ne considère donc pas
l’implémentation en elle-même, mais son comportement, en éprouvant la validité des résultats qu’elle retourne.
Ainsi les tests unitaires servent à détecter un dysfonctionnement éventuel, tandis que le débogage est une
analyse du dysfonctionnement en lui-même.
Les exceptions permettent de traiter des déroulements "inattendus" : ce mécanisme des exceptions offre un
moyen simple pour réagir à des événements qui risquent de survenir, comme par exemple, une erreur de lecture
sur un fichier.
Sommaire
6.1
6.2
6.3
Débogage . . . . . . . . . . . . . .
6.1.1 En ligne de commande : jdb
6.1.2 Dans Eclipse . . . . . . . .
Tests unitaires . . . . . . . . . . .
Exceptions . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
68
68
71
71
72
Le code de départ pour illustrer les notions de cours est presque identique à celui du chapitre précédent.
L’archive erreurs.zip contient ce code et peut être importée directement dans eclipse, en utilisant le mode
d’importation : "General/Existing Projects into Workspace".
67
CHAPITRE 6. CONTRÔLE DES ERREURS : DÉBOGAGE, TESTS UNITAIRES ET EXCEPTIONS
68
6.1
Débogage
Un bug est un défaut de conception d’un programme informatique à l’origine d’un dysfonctionnement. Ce
nom vient d’un des premiers incidents informatique qui a été causé par un insecte.
Un débogueur est un logiciel qui aide un développeur à analyser les bugs d’un programme. Pour cela, il permet
d’exécuter le programme pas-à-pas, d’afficher la valeur des variables à tout moment, de mettre en place des points
d’arrêt sur des conditions ou sur des lignes du programme ...
En java, on peut déboguer un programme en utilisant le débogueur en ligne de commande fourni : jdb. Les
outils de développement Eclipse et NetBeans ajoutent une interface graphique à celui-ci.
6.1.1
En ligne de commande : jdb
La documentation en ligne de l’utilitaire jdb est à : http://docs.oracle.com/javase/7/docs/
technotes/tools/solaris/jdb.html. Cet utilitaire contient également une description de ses commandes
très complète, accessible via l’instruction help.
Dans cette section, nous allons faire un survol des principales fonctionnalités et instructions de jdb.
Lancement d’une session de débogage : jdb MaClasse
Cette commande démarre une machine virtuelle et charge dans celle-ci la classe MaClasse, puis le débogueur
se met en attente au début de la méthode main de la classe MaClasse. L’invite de commande permet alors de
contrôler le déroulement du programme, via les instructions décrites ci-après.
Lancement du déroulement du programme : run
Cette instruction provoque le déroulement du programme jusqu’au premier point d’arrêt qui a été défini.
Reprise du déroulement du programme : cont
Cette instruction permet de reprendre le déroulement du programme jusqu’au point d’arrêt suivant.
Définition d’un point d’arrêt : stop
Cette instruction dépose un point d’arrêt à l’endroit spécifié.
stop at MaClasse : xx // mettre un point d ’ arret a la ligne numero xx de
MaClasse . java
stop in MaClasse . nomMethode // mettre un point d ’ arret au debut de la
methode nomMethode de MaClasse
Déroulement pas-à-pas : step, next
L’instruction step avance à la ligne suivante (du code source) en suivant les appels de méthodes, tandis que
l’instruction next redonne directement la main après l’appel de méthode (i.e. on passe à la ligne suivante de
l’appel à la méthode).
Affichage de la valeur d’un attribut ou d’un objet : print, dump
L’instruction print affiche la valeur d’un attribut de type primitif et une description courte pour un objet.
L’instruction dump donne une description plus complète pour un objet, en donnant les valeurs de chacun de ses
attributs.
Exemples :
6.1. DÉBOGAGE
print
print
print
print
69
MaClasse . unAttributStatique
monObj . unAttribut
i + j + k // i ,j , k : attributs
monObj . uneMethode () // affiche la valeur retournee par la methode
Exemple de session de débogage : détection d’une erreur dans la classe Professeur
Considérons la classe Professeur légèrement modifiée ci-dessous :
package scolarite ;
public class Professeur extends Personne {
private static int nbTotalProfesseurs =0;
public Professeur ( String nom ) {
setNom ( nom ) ;
nbTotalProfesseurs +=2;
}
public static int getNbTotal () {
return nbTotalProfesseurs ;
}
}
Lorsque l’on crée un Professeur, le nombre de professeur nbTotalProfesseurs est incrémenté de deux
unités (au lieu d’une). Si bien que l’exécution du programme principal ci-dessous :
1 package scolarite ;
2
3 /* *
4 *
5 * @author professors team
6 */
7 public class Main {
8
9
10
Main () {
11
Etudiant a = new Etudiant ( " Arsene Lupin " ) ;
12
Etudiant b = new Etudiant ( " Dark Vador " ) ;
13
Etudiant c = new Etudiant ( " Saurion " ) ;
14
Professeur d = new Professeur ( " Jojo Lapin " ) ;
15
Professeur e = new Professeur ( " Barbapapa " ) ;
16
System . out . println ( Personne . getNbTotal () + " personnes dont " +
Etudiant . getNbTotal () + " etudiants " + " et " + Professeur . getNbTotal () + "
professeurs " ) ;
17
}
18
19
20
/* *
21
* @param args the command line arguments
22
*/
23
public static void main ( String [] args ) {
24
25
new Main () ;
CHAPITRE 6. CONTRÔLE DES ERREURS : DÉBOGAGE, TESTS UNITAIRES ET EXCEPTIONS
70
26
27 }
}
affiche :
5 personnes dont 3 etudiants
et 4 professeurs
Nous allons maintenant effectuer une session de débogage pour identifier l’endroit fautif.
Lancement de la session :
$ jdb - classpath
bin - sourcepath src scolarite . Main
Retranscription de la session :
> stop at scolarite . Main :14 // mise en place d ’ un point d ’ arret au
debut de la ligne 14
Deferring breakpoint scolarite . Main :14.
> run // lancement de l ’ execution du programme jusqu ’ au point d ’ arret
run scolarite . Main
VM Started : Set deferred breakpoint scolarite . Main :14
Breakpoint hit : " thread = main " , scolarite . Main . < init >() , line =14 bci =34
14
Professeur d = new Professeur ( " Jojo Lapin " ) ;
// point d ’ arret atteint
> print scolarite . Personne . nbTotalPersonnes
scolarite . Personne . nbTotalPersonnes = 3 // 3 personnes : ok ( les 3
etudiants )
> print scolarite . Etudiant . nbTotalEtudiants
scolarite . Etudiant . nbTotalEtudiants = 3 // 3 etudiants : ok
> print scolarite . Professeur . nbTotalProfesseurs
scolarite . Professeur . nbTotalProfesseurs = null // ok : pas encore de
professeurs
> next // on execute la ligne 14 : creation du premier professeur
Step completed : " thread = main " , scolarite . Main . < init >() , line =15 bci =45
15
Professeur e = new Professeur ( " Barbapapa " ) ;
> print scolarite . Professeur . nbTotalProfesseurs
scolarite . Professeur . nbTotalProfesseurs = 2 // !!! 2 professeurs :
valeur incorrecte !!!
> print scolarite . Personne . nbTotalPersonnes
scolarite . Personne . nbTotalPersonnes = 4 // ok (3 etudiants + 1
professeur )
Cette session a donc mis en évidence que la mise à jour de la variable nbTotalProfesseurs dans la classe
Professeur est incorrecte puisque la création du premier professeur donne déjà une valeur incorrecte.
Exercice 84. (?) Reproduisez cette session de débogage. Identifiez ensuite la ligne précise qui provoque le dysfonctionnement à l’aide d’une autre session de débogage en utilisant l’instruction step pour suivre ce qui se
passe lorsque l’on crée un Professeur. (corrigé)
6.2. TESTS UNITAIRES
6.1.2
71
Dans Eclipse
Pour utiliser les fonctionnalités d’aide au débogage d’Eclipse, il faut activer la perspective Debug. La mise
en place de point d’arrêts se fait simplement avec un clic droit sur la ligne souhaitée. La vue Breakpoints donne
la liste des points d’arrêts. La vue Variables permet de visualiser les valeurs des attributs.
Pour une description plus complète du débogueur d’Eclipse, vous pouvez consulter le cours en
ligne de Jean-Michel Doudoux : http://jmdoudoux.developpez.com/cours/developpons/eclipse/
chap-eclipse-debug.php
Exercice 85. (?) Reproduisez avec Eclipse la session de débogage permettant de mettre en évidence l’erreur
dans la classe Professeur de la section précédente. Pour cela, il est nécessaire de rajouter les attributs statiques
dans la vue Variables, de manière à visualiser la valeur de nbTotalProfesseurs de la classe Professeur.
(corrigé)
6.2
Tests unitaires
Selon Wikipedia,
le test unitaire est un procédé permettant de s’assurer du fonctionnement correct d’une partie déterminée d’un logiciel ou d’une portion d’un programme (appelée « unité » ou « module »).
On écrit un test pour confronter une réalisation à sa spécification. Le test définit un critère d’arrêt (état
ou sorties à l’issue de l’exécution) et permet de statuer sur le succès ou sur l’échec d’une vérification.
Grâce à la spécification, on est en mesure de faire correspondre un état d’entrée donné à un résultat
ou à une sortie. Le test permet de vérifier que la relation d’entrée / sortie donnée par la spécification
est bel et bien réalisée.
JUnit est une bibliothèque dédiée au test d’une application Java. Avec JUnit, on peut créer un ensemble de
classes dans le même projet, pour tester les classes propres au projet. JUnit sert donc à séparer le code pour réaliser
les tests du code de l’application.
Un développeur peut définir un ensemble de contraintes que doit satisfaire une classe. Si un autre développeur
change le code de la classe, il n’a besoin que de quelques clics (sous Eclipse) pour s’assurer que la classe est
toujours valide.
Considérons à nouveau la classe Professeur de la section précédente, avec son erreur dans le constructeur
sur la mise à jour de l’attribut nbTotalProfesseurs.
Pour tester le "bon" fonctionnement de la classe Professeur, on peut écrire la classe de test suivante :
package tests ;
import static org . junit . Assert .*;
import org . junit . Test ;
import scolarite . Professeur ;
public class ProfesseurTest {
@Test
public void testProfesseurNom () {
Professeur p = new Professeur ( " test " ) ;
// on verifie si le nom du professeur est bien egal a " test "
assertEquals ( p . getNom () ," test " ) ;
}
@Test
public void testProfesseurNb () {
int nbTotalProfesseurs = Professeur . getNbTotal () ;
CHAPITRE 6. CONTRÔLE DES ERREURS : DÉBOGAGE, TESTS UNITAIRES ET EXCEPTIONS
72
Professeur p = new Professeur ( " test " ) ;
// on verifie si le nombre de professeurs a bien augmente d ’ un
assertEquals ( Professeur . getNbTotal () , nbTotalProfesseurs +1) ;
}
}
Les méthodes qui constituent des tests à réaliser doivent être marquées par l’annotation @Test.
A l’exécution, le test testProfesseurNom passe tandis que celui testProfesseurNb ne passe pas : ces tests
unitaires permettent de détecter la mauvaise implémentation de la classe Professeur.
JUnit procure les méthodes de test suivantes : assertEquals(a,b), assertFalse(a), assertNotNull(a),
assertNotSame(a,b), assertNull(a), assertSame(a,b), , assertTrue(a).
Exercice 86. (?) Tester ce code de test dans Eclipse. Ecrire des tests similaires pour les classes Etudiant et
Personne, puis lancer les tests. (corrigé)
Exercice 87. (??) Ecrire une classe Groupe avec des méthodes void add(Personne p), int size() et
String toString() qui permet de créer des groupes de personnes, dont les membres ont des noms tous différents.
Ecrire une classe de tests GroupeTest qui permet de contrôler le bon fonctionnement de chacune des trois
méthodes de la classe Groupe. (corrigé)
Pour aller plus loin, ce tutoriel en ligne http://www.vogella.com/articles/JUnit/article.html présente de manière plus complète les fonctionnalités de la bibliothèque JUnit.
6.3
Exceptions
Considérons la classe suivante décrivant une Promotion d’étudiants :
public class Promotion {
int annee ;
String intitule ;
private int nbEtudiants ;
public Promotion ( String intitule , int annee , int nbEtudiants ) {
this . intitule = intitule ;
this . annee = annee ;
setNbEtudiants ( nbEtudiants ) ;
}
public int getNbEtudiants () {
return nbEtudiants ;
}
public void setNbEtudiants ( int nbEtudiants ) {
if ( nbEtudiants <0)
erreur ( " Le nombre d ’ etudiants doit etre positif ou nul " ) ;
this . nbEtudiants = nbEtudiants ;
}
protected void erreur ( String message ) {
System . err . println ( " ERREUR dans la classe " + getClass () . getName
() + " : " + message ) ;
System . exit (1) ;
6.3. EXCEPTIONS
73
}
}
Que se passe-t-il lorsque l’on crée une promotion avec la ligne ci-dessous ?
Promotion as2012 = new Promotion ( " DUT Informatique - Annee Speciale " ,
2012 , -1) ;
Lors de l’appel à setNbEtudiants dans le constructeur avec une valeur du paramètre incorrecte :
— la méthode erreur est appelée,
— un message décrivant l’erreur est affiché,
— le programme s’interrompt (brutalement).
Dans le cas présent, l’arrêt brutal n’est pas justifié ; nous aimerions refaire un appel avec un paramètre correct
et continuer l’exécution du programme, sans surcharger le code avec des instructions rendant les algorithmes
illisibles. Vous avez certainement constaté la multiplication de clauses if(...)... afin de gérer les cas limites
qui peuvent intervenir dans vos programmes : division par zéro,...
Le plus souvent, la méthode constatant l’erreur n’est pas habilitée à prendre la meilleure décision pour la
traiter :
— on redemande une saisie ?
— on arrête ?
— on affiche un message et on continue (vaille que vaille) ?
Il est donc nécessaire d’avoir un mécanisme de gestion des erreurs, prenant en charge la détection une erreur,
le traitement de celle-ci, ainsi que la reprise de l’exécution du programme.
Revenons à notre exemple. Considérons la classe nbEtudiantsException suivante :
public class N bE tud ian ts Exc ep tio n extends Exception {
private int nbErr ;
public Nb Etu di ant sE xce pti on ( int nbErr ) {
this . nbErr = nbErr ;
}
public String toString () {
return " Nb etudiants errone : " + nbErr ;
}
}
et modifions notre classe Promotion ainsi :
public class Promotion {
...
public void setNbEtudiants ( int nbEtudiants ) throws
Nb Et udi an tsE xce pt ion {
if ( nbEtudiants <0)
throw new NbE tu dia nt sEx cep ti on ( nbEtudiants ) ;
this . nbEtudiants = nbEtudiants ;
}
...
}
Quels sont les changements ?
74
CHAPITRE 6. CONTRÔLE DES ERREURS : DÉBOGAGE, TESTS UNITAIRES ET EXCEPTIONS
— On détecte l’erreur (clause if).
— On prévient l’environnement (via la création d’une exception NbEtudiantsException).
La méthode ayant détectée l’erreur ne la gère pas : il y a séparation entre la détection et la gestion de l’erreur.
Plus précisément, que se passe-t’il lors de l’exécution des instructions suivantes ?
throw new NbE tu dia nt sEx cep ti on ( nbEtudiants ) ;
Ces instructions déclenchent successivement ces différentes opérations :
i. Un objet de la classe NbEtudiantsException est instancié.
ii. L’instruction throw lève une exception.
iii. La méthode courante (setNbEtudiants) est stoppée.
iv. Le gestionnaire d’exceptions prend la main : setNbEtudiants a levé une exception. Il enlève l’environnement associé à la méthode setNbEtudiants. de la pile d’exécution
v. Il rend (éventuellement) la main à la méthode ayant appelé setNbEtudiants. En fait, le gestionnaire
d’exceptions va remonter la pile d’appels jusqu’à trouver une méthode capable de gérer l’exception.
Regardons de plus près, la déclaration de la méthode setNbEtudiants :
public void setNbEtudiants ( int nbEtudiants ) throws Nb Etu di ant sEx ce pti on
Sa signature stipule que la méthode est susceptible de lever une exception du type NbEtudiantsException et
elle précise au compilateur que toute méthode appelant setNbEtudiants devra se préoccuper de cette exception :
soit en la traitant, soit en la propageant.
Exercice 88. (?) Ecrire la classe Promotion et observer le message d’erreur du compilateur. (corrigé)
Le compilateur indique une erreur dans le constructeur de la classe Promotion :
public Promotion ( String intitule , int annee , int nbEtudiants ) {
this . intitule = intitule ;
this . annee = annee ;
setNbEtudiants ( nbEtudiants ) ; // cette ligne ne compile pas
}
Le compilateur affiche le message d’erreur : "Unhandled exception type nbEtudiantException".
En effet, le constructeur Promotion appelant setNbEtudiants ne gère pas l’exception
NbEtudiantsException susceptible d’être levée. Le constructeur doit soit traiter l’exception, soit la propager.
Modifions l’entête du constructeur Promotion afin de propager l’exception :
public Promotion ( String intitule , int annee , int nbEtudiants ) throws
Nb Et udi an tsE xce pt ion {
this . intitule = intitule ;
this . annee = annee ;
setNbEtudiants ( nbEtudiants ) ;
}
Bien, nous propageons l’exception mais qui la traite et comment ?
Cela se fait via le bloc d’instructions try... catch... finally :
try {
Promotion as2012 = new Promotion ( " DUT Informatique - Annee Speciale "
, 2012 , -1) ;
// si le constructeur n ’a pas leve d ’ exception , poursuite de l ’
execution ici
}
6.3. EXCEPTIONS
75
catch ( N bE tud ia nts Exc ep tio n nbe ) {
// exception capturee : on la traite ( ici affichage )
System . err . println ( nbe ) ;
}
finally {
// bloc optionnel , execute quoi qu ’ il advienne
System . out . println ( " finally " ) ;
}
// poursuite normale du programme ici
System . out . println ( " yo " ) ;
Le déroulement étape par étape est donc le suivant :
i. appel du constructeur
ii. appel de setNbEtudiants(-1), qui lève une exception
iii. dépilement de l’environnement de setNbEtudiants et propagation de l’exception au constructeur
iv. le constructeur ne capturant pas l’exception, son environnement est dépilé (aucun objet instancié) ; l’exception est propagée à la méthode appelante (ici dans la classe Main)
v. la clause catch de capture l’exception et provoque l’exécution du bloc qui lui est associé.
Exercice 89. (?) Ecrire le bloc try ... catch décrit ci-dessus dans une classe Main et tester le traitement de
l’exception par celle-ci. (corrigé)
Exercice 90. (??) (adaptation d’un exercice conçu par Irène Charon) (corrigé)
http: // perso. telecom-paristech. fr/ ~charon/ coursJava/ exercices/ fact+ Exc. html Considérons le programme ci-dessous permettant de calculer la factorielle d’un nombre passé en argument. Que se
passe-t’il si :
— on ne passe pas d’argument ?
— on passe en paramètre un argument non-entier ?
— on passe en argument un entier négatif ?
— on passe en argument l’entier 20 ?
Dans les deux premiers cas, une exception est signalée. Dans les deux derners cas, le résultat est faux.
Modifier le programme pour que, dans chacun de ces cas, l’erreur soit gérée par le programme et signalée à
l’utilisateur :
— s’il n’y a pas de paramètre sur la ligne de commande, il s’agit d’une exception de type ArrayIndexOutOfBoundsException ; dans ce cas, le programme devra afficher "Nombre entier positif manquant" puis
se terminer.
— Si le paramètre passé ne représente pas un entier, il s’agit d’une exception de type NumberFormatException ; dans ce cas, le programme devra afficher "L’argument doit être entier" puis se terminer.
— si le paramètre passé est négatif, le programme devra afficher "Le paramètre est négatif" puis se terminer ;
on utilisera une exception personnalisée de type ExceptionNegatif.
— si le paramètre est trop grand, le programme devra afficher "Le paramètre est trop grand pour ce programme", puis se terminer ; on utilisera une exception personnalisée de type ExceptionTropGrand. En
Java, Integer.MAX_VALUE désigne le plus grand entier.
public class Factorielle {
public static int calcul ( int n ) {
int res = 1;
for ( int i = 2; i <= n ; i ++) res *= i ;
return res ;
}
public static void main ( String [] arg ) {
76
CHAPITRE 6. CONTRÔLE DES ERREURS : DÉBOGAGE, TESTS UNITAIRES ET EXCEPTIONS
int n ;
n = Integer . parseInt ( arg [0]) ;
System . out . println ( " Factorielle de " + n + " : " +
calcul ( n ) ) ;
}
}
Exercice 91. (??) Ecrire deux tests unitaires pour l’exercice précédent permettant de tester la levée des exceptions
ExceptionNegatif et ExceptionTropGrand par la méthode calcul. (corrigé)
En résumé
— Une exception est un objet qui est instancié lors d’un incident : une exception est levée.
— Le traitement du code de la méthode 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 l’ensemble de la pile d’exécution ; l’exécution
se termine avec une indication d’erreur.
— La capture est effectuée avec les clauses try... catch.
— La clause try définit un bloc d’instructions pour lequel on souhaite capturer les exceptions éventuellement
levées. Si plusieurs exceptions peuvent se produire, l’exécution du bloc est interrompue lorsque la première
est levée. Le contrôle est alors passé à la clause catch.
— La clause catch définit l’exception à capturer, en référençant l’objet de cette exception par un paramètre
puis le bloc à exécuter en cas de capture.
Compléments sur la bibliothèque des exceptions Toutes les exceptions héritent de la classe Throwable :
— Throwable(String) : la chaîne passée en paramètre sert à décrire l’incident
— getMessage() : obtenir l’information associée à l’incident
— printStackTrace() : exception + état de la pile d’exécution
Throwable possède deux classes filles Error et Exception :
— Error : erreurs graves de la machine virtuelle (état instable, récursivité infinie, classe en chargement non
trouvée,...).
— Exception : ensemble des erreurs pouvant être gérées par le programmeur (RunTimeExeption
(NullPointerException, IndexOutOfBoundsException, ArithmeticException), IOException,
...).
6.3. EXCEPTIONS
77
Fiche de synthèse
Exceptions
— try : pour spécifier une section de code pouvent lever une exception (une erreur) ;
— catch sert à spécifier le code à exécuter lors de l’interception d’une exception ;
— finally (optionnel) ; pour le code à toujours exécuter ;
— throws : pour ne pas traiter une exception, et la relayer à la méthode appelante.
— throw : pour lever une exception ;
— création d’exceptions personnalisées par héritage de la classe Exception.
try { // des lignes de code susceptible de lever une exception }
catch ( IOException e ) { // traitement de l ’ exception de type
IOException }
finally { // sera toujours execute }
void fonction throws IOException { ...}
DeplacementInterditException
package application ;
public class D e p l a c e m e n t I n t e r d i t E x c e p t i o n extends Exception {
private Salle d ; private Salle pos ;
public D e p l a c e m e n t I n t e r d i t E x c e p t i o n ( String text ) { super ( text ) ;
}
public D e p l a c e m e n t I n t e r d i t E x c e p t i o n ( Salle destination , Salle
position ) {
this . d = destination ; this . pos = position ;
}
public String toString () {
return " D e p l a c e m e n t I n t e r d i t E x c e p t i o n : Destination = " + d + " Position " + pos ;
}
}
JUnit
—
—
—
—
JUnit est une librairie dédiée aux tests unitaires d’une application Java. ;
permet de séparer le code dédié aux tests du code de l’application ;
permet de définir des contraintes que doit satisfaire une classe ;
en cas de modification du code d’une classe, relancer les tests pour s’assurer que la classe est
toujours valide.
TestCreationLabyrinthe
package tests ;
import static org . junit . Assert .*;
import org . junit . Test ;
import application .*;
public class T es t C r e at i o n La b y r i nt h e {
@Test
public void t es t La rg e ur L ab yr i nt he () {
LabyrintheDefaut labyrinthe = new LabyrintheDefaut () ;
labyrinthe . creerLabyrinthe ( " labys / level9 . txt " ) ;
assertTrue ( labyrinthe . getLargeur () == 40) ;
}
}
78
CHAPITRE 6. CONTRÔLE DES ERREURS : DÉBOGAGE, TESTS UNITAIRES ET EXCEPTIONS
Chapitre 7
Entrées-sorties : Flux, Fichiers, Sérialisation,
XML
Dans ce chapitre, la gestion des entrées-sorties en Java est abordée. La manipulation des fichiers regroupe
l’ensemble des concepts de base pour gérer les entrées-sorties. Une fois celle-ci acquise, il est aisé d’établir des
communications réseaux, d’exploiter la sérialisation pour créer des objets persistants, de transmettre ces objets
persistants via le réseau ...
Dans un souci d’interopérabilité avec d’autres langages, le choix du format XML est tout indiqué. La seconde
partie du cours est consacrée à ce format de fichier.
Sommaire
7.1
7.2
7.3
7.4
Flux en entrée/sortie . . . . . . . . . . . . . . . . .
7.1.1 Traitement des erreurs d’entrée/sortie . . . .
7.1.2 Les flux d’octets . . . . . . . . . . . . . . .
7.1.3 Les flux de caractères . . . . . . . . . . . . .
7.1.4 Flux avec tampons . . . . . . . . . . . . . .
Fichiers, réseau et sérialisation (objets persistants)
7.2.1 Fichiers . . . . . . . . . . . . . . . . . . . .
7.2.2 Réseau . . . . . . . . . . . . . . . . . . . .
7.2.3 Sérialisation . . . . . . . . . . . . . . . . . .
RMI : Remote Method Interface . . . . . . . . . .
7.3.1 Côté serveur . . . . . . . . . . . . . . . . .
7.3.2 Coté client . . . . . . . . . . . . . . . . . .
XML : eXtensible Mark-up Language . . . . . . .
7.4.1 Présentation . . . . . . . . . . . . . . . . . .
7.4.2 Java et XML . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
79
80
80
81
81
82
83
86
89
90
90
91
92
92
94
Le code de départ pour illustrer les notions de cours est contenu dans l’archive io.zip, qui peut être importée
directement dans eclipse.
7.1
Flux en entrée/sortie
Les informations présentées dans cette partie sont reprises de ce tutoriel d’Oracle : http://docs.oracle.
com/javase/tutorial/essential/io/index.html
Un flux est une suites séquentielle de données. On distingue les flux en entrée qui permettent à une application
de lire les données une par une, des flux en sortie qui permettent d’envoyer des données, toujours une par une. Les
flux en entrée et en sortie sont modélisés respectivement par les interfaces InputStream et OutputStream.
79
CHAPITRE 7. ENTRÉES-SORTIES : FLUX, FICHIERS, SÉRIALISATION, XML
80
7.1.1
Traitement des erreurs d’entrée/sortie
Le traitement des erreurs lors de la lecture ou de l’écriture de données se fait naturellement via le mécanisme
des exceptions. La classe IOException est la classe mère de toutes les erreurs d’entrée/sortie. Il suffit donc de la
gérer pour prendre en charge n’importe quel type d’exception liée aux entrées/sorties.
On peut affiner le traitement d’une erreur en capturant une des exceptions filles de IOEXception, dont voici
les principales :
— CharacterCodingException (encodage du fichier non-conforme)
— EOFException (fin du fichier atteinte)
— FileNotFoundException (fichier manquant)
— InterruptedIOException (communication interrompue)
Le schéma global d’un code réalisant des entrées/sorties est dont le suivant :
try {
... des entrees / sorties ...
} catch ( IOException ioe ) {
System . err . println ( " Erreur lors de la communcation : " + ioe ) ;
}
7.1.2
Les flux d’octets
Les flux d’octets sont les flux "bruts", i.e. les flux de plus bas niveau, offrant le moins de fonctionnalités
mais susceptibles de transporter tout type de données. Les flux de données plus "évolués" exploitent de manière
implicite ces flux d’octets.
Il existe plusieurs classes pour les flux d’octets, qui fonctionnent toutes de manière similaire. Dans le cas de
flux basé sur des fichiers, on utilise les classes FileInputStream et FileOutputStream. La seconde partie de
ce chapitre décrit de manière détaillée la manipulation des fichiers.
Voici un exemple de code, qui recopie les données du fichier xanadu.txt, octet par octet.
public class CopyBytes {
public static void main ( String [] args ) {
FileInputStream in = null ;
FileOutputStream out = null ;
try {
in = new FileInputStream ( " xanadu . txt " ) ;
out = new FileOutputStream ( " outagain . txt " ) ;
int c ;
while (( c = in . read () ) != -1) {
out . write ( c ) ;
}
if ( in != null )
in . close () ;
if ( out != null )
out . close () ;
}
catch ( IOException ioe ) {
System . err . println ( " Erreur lors de la communcation : " +
ioe ) ;
}
7.1. FLUX EN ENTRÉE/SORTIE
81
}
}
Notez que la valeur -1 est renvoyée par la méthode read lorsqu’il n’y a plus de données à lire.
Il est de plus important de toujours les libérer les ressources liées aux flux en invoquant la méthode close.
Exercice 92. (?) Testez la classe CopyBytes. (corrigé)
Les flux d’octets ne sont pas recommandés car ils sont trop rudimentaires. Dans notre exemple, le fichier est
un fichier texte et peut donc être vu comme un flux de caractères, pour lesquels il existe des flux plus adaptés que
nous décrivons dans la sous-section suivante.
7.1.3
Les flux de caractères
Le stockage des caractères en interne est effectué avec le codage unicode, qui est universel. Java effectue les
traductions nécessaires lorsque les codages utilisés par l’émetteur et le récepteur diffèrent.
Les flux de caractères sont modélisés par les interfaces Reader et Writer. Dans le cas des fichiers, les classes
correspondantes sont FileReader et FileWriter.
Avec celles-ci, notre exemple précédent s’écrit :
public class CopyCharacters {
public static void main ( String [] args )
{
FileReader inputStream = null ;
FileWriter outputStream = null ;
try {
inputStream = new FileReader ( " xanadu . txt " ) ;
outputStream = new FileWriter ( " characteroutput . txt " ) ;
int c ;
while (( c = inputStream . read () ) != -1) {
outputStream . write ( c ) ;
}
if ( inputStream != null )
inputStream . close () ;
if ( outputStream != null )
outputStream . close () ;
}
catch ( IOException ioe ) {
System . err . println ( " Erreur lors de la communcation : " +
ioe ) ;
}
}
}
Exercice 93. (?) Testez la classe CopyCharacters en utilisant différents encodages pour le fichier en entrée.
(corrigé)
7.1.4
Flux avec tampons
La lecture ou l’écriture de données une par une pose des problèmes de performance : il est plus judicieux
d’envoyer ou de récupérer des données par blocs.
CHAPITRE 7. ENTRÉES-SORTIES : FLUX, FICHIERS, SÉRIALISATION, XML
82
Pour cela, on utilise des flux avec tampon, via les classes BufferedReader ou BufferedWriter. On instancie
ces classes à partir de flux "classiques" :
inputStream = new BufferedReader ( new FileReader ( " xanadu . txt " ) ) ;
outputStream = new BufferedWriter ( new FileWriter ( " characteroutput . txt " )
);
Dans le cas de flux en écriture avec tampons, il faut veiller à vider le tampon via la méthode flush() pour
s’assurer que les données ont bien été toutes envoyées avant de fermer le flux.
Pour gérer les fichiers textes, on peut lire une ligne en entier via la méthode readLine et utiliser la classe
PrintWriter pour écrire des données ligne par ligne.
Notre exemple précédent s’écrit alors :
public class CopyLines {
public static void main ( String [] args ) {
BufferedReader inputStream = null ;
PrintWriter outputStream = null ;
try {
inputStream = new BufferedReader ( new FileReader ( " xanadu . txt
"));
outputStream = new PrintWriter ( new FileWriter ( "
characteroutput . txt " ) ) ;
String l ;
while (( l = inputStream . readLine () ) != null ) {
outputStream . println ( l ) ;
}
if ( inputStream != null )
inputStream . close () ;
if ( outputStream != null )
outputStream . close () ;
}
catch ( IOException ioe ) {
System . err . println ( " Erreur lors de la communcation : " +
ioe ) ;
}
}
}
Exercice 94. (?) Testez la classe CopyLines. (corrigé)
7.2
Fichiers, réseau et sérialisation (objets persistants)
Dans cette partie, nous décrivons tout d’abord comment lire et écrire dans des fichiers. Les communications via
un réseau sont ensuite traitées ainsi que le mécanisme de sérialisation des objets (i.e. encodage de l’état d’un objet
dans le but de pouvoir le transmettre). La transmission d’un objet sérialisé via le réseau permet naturellement la
conception d’applications réparties (sur plusieurs machines). Ainsi nous abordons brièvement en clôture de cette
partie le mécanisme dédié en Java : RMI (Remothe Method Invocation).
7.2. FICHIERS, RÉSEAU ET SÉRIALISATION (OBJETS PERSISTANTS)
7.2.1
83
Fichiers
Java7 vient d’introduire une nouvelle bibliothèque (NIO2) pour la gestion des systèmes de fichiers. Nous
allons nous baser sur cette dernière, car elle offre des fonctionnalités plus complètes que les versions antérieures.
Les informations présentées dans cette partie sont issues du document "Java 7 : découverte de NIO2 pour l’accès
aux fichiers" par Alexis Hassier : http://www.jtips.info/index.php?title=Java7/NIO2-FileSystem.
Avant Java 7, la classe centrale pour les accès aux fichier était la classe File de la bibliothèque java.io.
En Java 7, cette classe existe toujours mais ses fonctionnalités sont accessibles via de nouvelles classes de la
bibliothèque java.nio.file.
Manipulation de chemins L’interface Path permet de représenter un chemin dans le système de fichiers.
Voici un exemple d’utilisation de cette interface :
Path p = Paths . get ( " / home / gothmog / workspace / JavaCours " ) ;
Path homePath = Paths . get ( System . getProperty ( " user . home " ) ) ; // homePath
= repertoire personnel (=/ home / gothmog )
Path cheminRelatif = homePath . relativize ( p ) ; // chemin relatif vers p a
partir de homePath
System . out . println ( cheminRelatif ) ; // workspace / JavaCours
Path repertoireCourant = Paths . get ( " " ) . toAbsolutePath () ; // chemin
complet vers la position actuelle
System . out . println ( repertoireCourant ) ; // / home / gothmog / workspace /
JavaCours
Il est aisé de passer à l’ancienne classe File à partir de Path et vice-versa :
File myFile = myPath . toFile () ; // de Path vers File
Path myPath = myFile . toPath () ; // de File vers Path
Lecture du contenu d’un fichier La lecture du contenu d’un fichier est réalisable avec un objet implémentant
l’interface BufferedReader (le tampon permet de prendre en charge des fichiers volumineux).
Exemple de code pour lire et afficher le contenu d’un fichier texte utilisant l’encodage UTF8 :
Charset UTF8 = Charset . forName ( " UTF -8 " ) ;
Path codeMain = Paths . get ( " ./ src / scolarite / Main . java " ) ;
try {
BufferedReader br = Files . newBufferedReader ( codeMain , UTF8 ) ;
String ligne = br . readLine () ;
while ( ligne != null ) {
System . out . println ( ligne ) ;
ligne = br . readLine () ;
}
br . close () ;
}
catch ( IOException ioe ) {
System . err . println ( " Erreur de lecture : " + ioe ) ;
}
Exercice 95. (?) Tester ce code. Que se passe-t-il si on donne un chemin incorrect lors de l’initialisation de
codeMain ? (corrigé)
CHAPITRE 7. ENTRÉES-SORTIES : FLUX, FICHIERS, SÉRIALISATION, XML
84
Pour les petits fichiers, la classe Files permet de récupérer directement tout le contenu d’un fichier via les
méthodes statiques readAllBytes (pour un fichier binaire) et readAllLines pour les fichiers texte.
Ainsi, le code précédent peut s’écrire de manière plus compacte :
Charset UTF8 = Charset . forName ( " UTF -8 " ) ;
Path codeMain = Paths . get ( " ./ src / scolarite / Main . java " ) ;
try {
List < String > lignes = Files . readAllLines ( codeMain , UTF8 ) ;
for ( String ligne : lignes )
System . out . println ( ligne ) ;
}
catch ( IOException ioe ) {
System . err . println ( " Erreur de lecture : " + ioe ) ;
}
Lecture d’un flux L’interface InputStream modélise les flux en lecture, i.e. les données que l’on peut lire
mais qui arrivent au fil et à mesure. Ceci est le cas typiquement de communications réseau ou lorsque l’on attend
des données saisies au clavier (System.in).
On peut analyser le flux de données arrivant d’un objet InputStream à l’aide de la classe Scanner.
Exemple de lecture de données au clavier :
InputStream is = System . in ; // le flux is est un flux en lecture sur le
clavier
Scanner sc = new Scanner ( is ) ; // sc est un analyseur attache au flux is
System . out . println ( " Entrez un nombre au clavier " ) ;
int reponse = sc . nextInt () ; // on recupere le premier entier arrivant
du flux is
System . out . println ( " Votre nombre est " + reponse ) ;
Exemple de lecture d’un fichier texte contenant des nombres :
Path fichier = Paths . get ( " ./ unFichier . txt " ) ;
InputStream is = null ;
try {
is = Files . newInputStream ( fichier ) ;
Scanner sc = new Scanner ( is ) ;
while ( sc . hasNextInt () )
System . out . println ( sc . nextInt () ) ;
if ( is != null )
is . close () ;
}
catch ( IOException ioe ) {
System . err . println ( " Echec lecture du fichier : " + ioe ) ;
}
Exercice 96. (?) Tester ce code sur un fichier dont le contenu est : (corrigé)
10 378 38397
3673
3367 happy
end
7.2. FICHIERS, RÉSEAU ET SÉRIALISATION (OBJETS PERSISTANTS)
85
Modification du contenu d’un fichier La modification du contenu d’un fichier est analogue à l’opération de
lecture. On utilise un objet implémentant l’interface BufferedWriter.
Exemple de code pour créer un fichier texte encodé en UTF8 :
Charset UTF8 = Charset . forName ( " UTF -8 " ) ;
Path fichier = Paths . get ( " ./ unFichier . txt " ) ;
try {
BufferedWriter bw = Files . newBufferedWriter ( fichier , UTF8 ) ;
bw . write ( " Ceci est \ n un essai . " ) ;
bw . write ( " \ nImpressionnant , non ? " ) ;
bw . flush () ; // ecrire toutes les donnees en attente ( vider le
tampon )
bw . close () ;
}
catch ( IOException ioe ) { System . err . println ( " Erreur d ’ ecriture : " + ioe )
;}
Après exécution, le fichier test.txt est créé avec le contenu :
Ceci est
un essai.
Impressionnant, non?
Exercice 97. (?) Tester. (corrigé)
Exercice 98. (?) Dans la classe Personne, écrire une méthode void save(String) qui sauvegarde la personne
dans le fichier dont le nom est passé en paramètre. (corrigé)
Exercice 99. (?) Ecrire une méthode statique Personne load(String) qui réalise l’opération inverse : elle
retourne une personne initialisée à partir du contenu du fichier dont le nom est passé en paramètre. (corrigé)
Ecriture dans un flux L’interface OutputStream modélise les flux en écriture, i.e. les données que l’on
envoie par le biais d’un flux. Ceci concerne les communications réseau mais aussi l’affichage dans un terminal
(System.out).
La classe PrintWriter est adaptée à l’écriture de données de type texte, et peut être rattachée à un flux en
écriture.
Exemple d’écriture dans un fichier texte en utilisant un flux :
Charset UTF8 = Charset . forName ( " UTF -8 " ) ;
Path fichier = Paths . get ( " / home / gothmog / test . txt " ) ;
OutputStream os ;
try {
os = Files . newOutputStream ( fichier ) ;
PrintWriter pw = new PrintWriter ( os ) ; // pw est rattache au
flux os
pw . write ( " Ceci est \ n un essai . " ) ;
pw . write ( " \ nImpressionnant , non ? " ) ;
pw . flush () ; // ecrire toutes les donnees en attente ( vider le
tampon )
if ( os != null )
os . close () ;
pw . close () ;
}
catch ( IOException ioe ) { System . err . println ( " Echec ecriture : " + ioe ) ;}
CHAPITRE 7. ENTRÉES-SORTIES : FLUX, FICHIERS, SÉRIALISATION, XML
86
Fichiers de paramètres : la classe Properties Les fichiers sont souvent utilisés pour sauvegarder un ensemble de paramètres. Par exemple, pour une personne avec un age de 23 ans, de nom "Cloclo" et prénom "Claudette", on peut enregistrer son état sous la forme :
nom=Cloclo
prenom=Claudette
age=23
La classe Properties modélise les ensembles de paramètres. Ses principales méthodes sont :
— String getProperty(String key) : retourne la valeur associée à key ;
— void load(InputStream is) : charge un ensemble de paramètres à partir d’un flux ;
— void store(OutputStream out, String comments) : enregistre l’ensemble des paramètres dans un
flux ;
— Object setProperty(String key, String value) : ajoute le paramètre key=value ;
Exercice 100. (??) Créez l’ensemble de paramètres correspondant à l’exemple ci-dessus, puis enregistrez le dans
un fichier texte. Consultez avec un éditeur de texte le contenu de ce fichier. Réciproquement, récupérez en Java les
valeurs des paramètres stockés dans ce fichier. (corrigé)
Parcours d’un répertoire La méthode statique newDirectoryStream de la classe Files permet de parcourir
l’ensemble des noms de fichiers et des sous-répertoires contenu dans un répertoire.
Par exemple, ce code affiche l’ensemble du contenu du répertoire /home/gothmog :
Path dir = Paths . get ( " / home / gothmog / " ) ;
try {
DirectoryStream < Path > fichiers = Files . newDirectoryStream ( dir ) ;
for ( Path p : fichiers )
System . out . println ( p ) ;
if ( fichiers != null )
fichiers . close () ;
}
catch ( IOException ioe ) { System . err . println ( " Echec lecture : " + ioe ) ;}
Exercice 101. (?) Tester sur votre répertoire personnel. (corrigé)
7.2.2
Réseau
En Java, effectuer des communications réseaux se fait de manière analogue à lire ou écrire dans des fichiers.
Dans cette partie, nous abordons brièvement le cas de la programmation réseau dans le cadre du réseau Internet
(IP), avec les protocoles de transport TCP ou UDP.
Exceptions spécifiques En raison des particularités des communications réseaux, les incidents déclenchent
des exceptions spécifiques (héritant de IOException), dont :
—
—
—
—
SocketException : erreur lors de la création d’une socket ;
MalformedURLException : URL de syntaxe incorrecte ;
SSLException : erreur dans le protocole SSL (cryptage) ;
RemoteException : erreur du côté de la machine distante.
7.2. FICHIERS, RÉSEAU ET SÉRIALISATION (OBJETS PERSISTANTS)
87
Adressage IP : la classe InetAddress
Définition 7.1 (InetAddress). Classe permettant de gérer les adresses IP et les noms associés.
On peut créer une instance de cette classe à partir d’une machine dont on connait le nom.
Exemple :
InetAddress ia = InetAddress . getByName ( " info - ssh2 . iut .u - bordeaux1 . fr " ) ;
Outre la méthode statique getByName, cette classe offre plusieurs autres méthodes statiques utiles :
— public static InetAddress getLocalHost() : adresse IP de la machine locale ;
— public static InetAddress[] getAllByName(String hostname) : toutes les adresses IP d’une
machine dont on connait le nom :
A partir d’une instance de InetAddress, on peut
— connaitre le nom de la machine : public String getHostName()
— récupérer les 4 octets de l’adresse : public byte[] getAddress()
Exemple :
System . out . println ( " adresse IP du serveur web du LaBRI : " + InetAddress .
getByName ( " www . labri . fr " ) ) ;
Exercice 102. (?) Affichez le nom et les adresses IP de votre machine. (corrigé)
Protocole de transport UDP Le protocole UDP (User Datagram Protocol) est un protocole rapide de transport des données, sans connexion, mais non-fiable (des paquets peuvent être perdus et/ou ne pas arriver dans
l’ordre dans lequel ils ont été transmis).
Définition 7.2 (DatagramPacket). Cette classe permet de créer des objets qui contiendront les données d’un
datagramme UDP.
Deux constructeurs sont disponibles, l’un pour les paquets à recevoir, l’autre pour les paquets à envoyer :
— public DatagramPacket(byte buffer[], int taille) : cela construit un objet pour recevoir un
datagramme ; le paramètre taille correspond à la taille maximale des datagrammes à recevoir.
— public DatagramPacket(byte buffer[], int taille, InetAddress a, int p) : ce constructeur crée un objet pour envoyer un datagramme, à la machine d’adresse a sur le port p.
Définition 7.3 (DatagramSocket). Cette classe permet de créer des "sockets" (prises) UDP pour l’envoi et la
réception des datagrammes UDP.
Ses principales méthodes sont :
— public DatagramSocket(int port) : constructeur créant un objet attaché au port UDP local passé en
paramètre ;
— public void send(DatagramPacket data) : pour envoyer le datagramme data ;
— public void receive(DatagramPacket data) : pour réceptionner un datagramme, stocké dans data.
Exemple d’envoi d’un message
InetAddress address = InetAddress . getByName ( " raoul . labri . fr " ) ;
int port = 4321;
String ch = " Le message " ; int chl = ch . length () ;
byte [] message = new byte [ chl ]; ch . getBytes (0 , chl , message , 0) ;
DatagramPacket dp = new DatagramPacket ( message , chl , address , port ) ;
DatagramSocket ds = new DatagramSocket () ;
ds . send ( dp ) ;
Exemple de réception de messages
CHAPITRE 7. ENTRÉES-SORTIES : FLUX, FICHIERS, SÉRIALISATION, XML
88
byte [] buffer = new byte [1024]; String ch ;
DatagramPacket dp = new DatagramPacket ( buffer , buffer . length ) ;
DatagramSocket ds = new DatagramSocket (4321) ;
ds . receive ( dp ) ;
ch = new String ( buffer , 0 , 0 , dp . getLength () ) ;
System . out . println ( " Paquet recu : message = " + ch +
" - envoyeur = " + dp . getAddress () . getHostName () +
" - port = " + dp . getPort () ) ;
Exercice 103. (??) Ecrire 2 classes ClientUDP.java et ServeurUDP.java permettant le transfert d’un message
de l’un à l’autre, caractère par caractère. Mesurer le temps de transmission. La transmission de 1000 messages
est-elle fiable ? (corrigé)
TCP Le principe général pour une communication TCP/IP est d’établir une connexion, puis ensuite de rattacher
à celle-ci un flux en lecture (resp. écriture) de type InputStream (resp. OutputStream) pour recevoir (resp.
émettre) des données.
Définition 7.4 (la classe Socket). Elle permet de gérer et exploiter une connexion TCP.
On crée une instance de cette classe en passant en paramètre le nom de la machine distante et le port de la
machine distante sur lequel la socket (prise) doit être rattachée :
Socket s = new Socket ( String nomMachineDistante , int portDistant ) ;
A partir d’une instance, on peut récuperer des flux en lecture ou écriture, pour recevoir ou transmettre des
données, via les deux méthodes :
public InputStream getInputStream () throws IOException ;
public OutputStream getOutputStream () throws IOException ;
En outre, la classe Socket expose un certain nombre d’informations sur la connexion, via les méthodes :
public
public
public
public
InetAddress getInetAddress () ; // adresse IP machine distante
InetAddress getLocalAddress () ; // adresse IP machine locale
int getPort () ; // port de la machine distante
int getLocalPort () ; // port de la machine locale
La méthode close() ferme la connexion et libère les ressources du système associées à la connexion.
Exemple de client TCP
try {
Socket s = new Socket ( " nom machine distante " ,1234) ;
OutputStream os = s . getOutputStream () ;
InputStream is = s . getInputStream () ;
os . write (( int ) ’a ’) ; // envoi du caractere a sous la forme d ’ un
entier
System . out . println ( is . read () ) ;
s . close () ;
} catch ( Exception e ) {
// Traitement d ’ erreur
}
La machine distante doit être en écoute lorsque la machine locale demande à établir une connexion. La classe
ServerSocket permet de prendre en charge les demandes de connexion.
Elle prend en paramètre le port sur lequel la machine écoute et le nombre maximal de demande de connexions
qui peuvent être placées sur la file d’attente. Lorsqu’une demande de connexion est acceptée, celle-ci peut être
prise en charge par une instance de la classe Socket en utilisant la méthode accept de la classe ServerSocket.
7.2. FICHIERS, RÉSEAU ET SÉRIALISATION (OBJETS PERSISTANTS)
89
Exemple de serveur TCP
try {
ServerSocket ecoute = new ServerSocket (1234 ,5) ; // ecoute sur le
port 1234 , taille de la file d ’ attente = 5
Socket service = ( Socket ) null ;
while ( true ) {
service = ecoute . accept () ; // une demande de connexion a ete
acceptee - l ’ objet service de la classe Socket la prend en
charge
OutputStream os = service . getOutputStream () ;
InputStream is = service . getInputStream () ;
os . write ( is . read () ) ; // on renvoie ce qui a ete lu ( serveur : "
ping pong ")
service . close () ;
}
} catch ( Exception e ) {
// traitement d ’ erreur
}
Exercice 104. (?) Ecrire 2 classes ClientTCP.java et ServeurTCP.java permettant le transfert d’un message
de l’un à l’autre, caractère par caractère. Mesurer le temps de transmission. La transmission de 1000 messages
est-elle fiable ? (corrigé)
7.2.3
Sérialisation
La sérialisation est un procédé permettant de mettre un objet sous une forme à partir de laquelle il pourra
être reconstitué à l’identique. Ceci permet de sauvegarder des objets sur disque, ou bien de les transmettre via le
réseau. En sérialisant un objet, on peut donc créer un objet persistant, i.e. dont la durée de vie n’est pas liée à celle
de la JVM. Le format utilisé est indépendant du système d’exploitation.
La sérialisation utilise les classes ObjectOutputStream et ObjectInputStream et l’interface
Serializable.
L’interface Serializable Cette interface ne définit aucune méthode. Tout objet qui doit être sérialisé doit
implémenter cette interface. Si l’on tente de sérialiser un objet qui n’implémente pas l’interface Serializable,
une exception NotSerializableException est levée.
La classe ObjectOutputStream Cette classe permet d’obtenir un flux en écriture pour écrire/envoyer des
objets sérialisés.
Exemple de sauvegarde d’un objet sérialisé o dans un fichier objet.ser
Path p = Paths . get ( " objet . ser " ) ;
try {
OutputStream os = Files . newOutputStream ( p ) ;
ObjectOutputStream oos = new ObjectOutputStream ( os ) ;
oos . writeObject ( o ) ;
oos . flush () ;
oos . close () ;
}
catch ( IOException e ) { System . err . println ( e ) ;}
Exercice 105. (?) Ecrire une méthode save2(String nomFichier) dans la classe Personne qui enregistre
dans un fichier l’objet sous forme sérialisée. (corrigé)
CHAPITRE 7. ENTRÉES-SORTIES : FLUX, FICHIERS, SÉRIALISATION, XML
90
Toutes les variables de la classe à sérialiser, doivent-elles même être sérialisables. Par exemple, si l’objet
contient une collection d’éléments, chaque élément de la collection doit être sérialisable.
Exercice 106. (?) Ecrire une méthode save2(String nomFichier) dans la classe Groupe qui enregistre dans
un fichier tout un groupe sous forme sérialisée. (corrigé)
La classe ObjectInputStream Cette classe permet d’obtenir un flux en entrée pour lire/recevoir des objets
sérialisés.
Exemple de lecture d’un objet de type Personne stocké dans le fichier objet.ser
Path p = Paths . get ( " objet . ser " ) ;
Personne res = null ;
try {
InputStream is = Files . newInputStream ( p ) ;
ObjectInputStream ois = new ObjectInputStream ( is ) ;
res =( Personne ) ois . readObject () ;
ois . close () ;
}
catch ( IOException e ) { System . err . println ( e ) ;}
catch ( C la s s N o tF o u n d Ex c e p t io n e ) { System . err . println ( e ) ;}
Exercice 107. (?) Tester le code ci-dessus en écrivant une méthode statique load2 dans la classe Personne
retournant une personne. (corrigé)
Exercice 108. (?) Ecrire une méthode public static Groupe load2(String nomFichier) dans la classe
Groupe qui charge tout un groupe stocké sous forme sérialisé. (corrigé)
Notez que dans le cas d’objets complexes comme ceux de la classe Groupe, la sérialisation est un mécanisme
particulièrement puissant et simple d’emploi.
7.3
RMI : Remote Method Interface
RMI permet d’interagir avec des objets distants.
7.3.1
Côté serveur
L’interface Remote
Un objet distant est un objet qui implémente une interface distante, sachant qu’une telle interface est une
interface héritant de Remote, et dont toutes les méthodes sont susceptibles de lever une exception de type
RemoteException. Les méthodes d’un objet distant doivent avoir pour paramètre et de type de retour des objets tous sérialisables (ce qui inclut donc les types de base).
Exemple
public interface RemoteLabyrinthe extends Remote {
String [] listeLabys () throws RemoteException ;
Labyrinthe getLabyrinthe ( String nom ) throws RemoteException ;
}
Supposons que nous ayons un ensemble de labyrinthes enregistrés sur un serveur dans des fichiers laby1.ser,
laby2.ser, laby3.ser ... Dans l’optique de permettre le chargement de ces labyrinthes à travers le réseau,
nous avons défini deux méthodes côté serveur : listeLabys (qui retournera la liste des noms de fichiers des
labyrinthes disponibles) et getLabyrintheDefaut pour charger le labyrinthe correspondant au nom de fichier
passé en paramètre.
7.3. RMI : REMOTE METHOD INTERFACE
91
Instanciation de l’objet distant
L’objet distant doit naturellement implémenter l’interface que nous avons définie. Avec la méthode
exportObject, on obtient une référence accessible à distance. La classe Registry permet d’accéder au catalogue d’objets distant, et la méthode bind donne un nom l’instance de l’objet distant (identifiant). On peut
instancier directement le magasin avec :
java . rmi . registry . LocateRegistry . createRegistry ( port ) ;
Il faut ensuite utiliser la commande rmic sur le binaire côté serveur pour générer une copie "utilisable" à
distance.
Gestionnaire de sécurité
Il est possible de mettre en place un gestionnaire de sécurité sur le serveur et/ou le client, notamment pour
permettre le chargement dynamique des classes :
System . setSecurityManager ( new RMISecurityManager () ) ;
7.3.2
Coté client
Coté client, il faut demander un accès au catalogue distant puis obtenir une référence sur l’objet distant avec
la méthode lookup en lui précisant le nom de l’objet cible.
Voici un exemple de client pour un serveur ServeurRMI implémentant l’interface RemoteLabyrinthe, en
supposant que celui-ci tourne sur la même machine (soit d’adresse IP "localhost" 127.0.0.1) sur le port 12345 :
public static void loadDistant () {
String serveur = " 127.0.0.1 " ; int port = 12345;
try {
Registry r = LocateRegistry . getRegistry ( serveur , port )
;
RemoteLabyrinthe rl = ( RemoteLabyrinthe ) r . lookup ( "
serveurLabyrinthe " ) ;
String labys [] = rl . listeLabys () ;
if ( labys . length ==0) {
System . out . println ( " Il n ’y a as de labyrinthes
distants disponibles ... " ) ;
System . exit (1) ;
}
System . out . println ( " Labyrinthes distants disponibles : "
);
for ( String lab : labys ) System . out . println ( lab ) ;
String laby = labys [0];
System . out . println ( " Chargement de " + laby ) ;
labyrinthe = ( LabyrintheGrille ) rl . getLabyrinthe ( laby )
;
}
catch ( Exception e ) {
System . err . println ( e ) ;
}
}
CHAPITRE 7. ENTRÉES-SORTIES : FLUX, FICHIERS, SÉRIALISATION, XML
92
7.4
XML : eXtensible Mark-up Language
Dans cette partie, nous abordons l’utilisation de la norme XML pour l’enregistrement et la lecture de données. Dans un premier temps, nous donnons une courte description de cette norme. Nous abordons ensuite la
manipulation de documents XML en Java, et la sérialisation d’objets Java en XML.
7.4.1
Présentation
SGML (Standard Generalized Markup Language) est un méta-langage permettant de définir des langages
balisés. XML est un sous-ensemble de SGML.
Définition 7.5 (XML). XML (eXtensible Mark-up Language) est une norme du W3C http: // www. w3. org/
TR/ REC-xml/ , datant de 1998, servant à stocker des données structurées dans un fichier texte :
— données structurées sous la forme d’un arbre ;
— contenu et balisage explicite ;
— encodage explicite et a priori non limité.
Ainsi, XML est différent de HTML, car il n’est pas limité au Web et la structure du document ne correspond
pas à une mise en page. L’utilisation d’XML permet des économies de développement, car il s’agit d’une norme
non-propriétaire, portable, acceptée par tous les principaux acteurs. De plus, un document XML est réutilisable
(interopérabilité).
Le principal inconvénient d’XML est que les documents produits avec cette norme sont volumineux, comparés
à l’information utile qu’ils contiennent.
Il existe de nombreuses applications spécifiques à XML : analyseurs, éditeurs, validateurs, transformateurs,
bibliothèques de manipulation de documents XML ...
Exemple de fichier XML
<? xml version = " 1.0 " standalone = " yes " encoding = " ISO -8859 -1 " ? >
< conversation >
< greeting mood = " polite " > Quels caract & egrave ; res utiliser ? </
greeting >
< response mood = " angry " > Les caracteres accentues peuvent etre
utilises avec un encodage ISO -8859 -1 </ response >
</ conversation >
Dans cet exemple, la première ligne est l’entête ("presque" obligatoire) : c’est une PI (processing instruction)
déclarant le document conforme à XML version 1.0 et autonome, ainsi que l’encodage utilisé pour les caractères. L’élément conversation est l’élément racine. Le mot-clef mood est un attribut et polite est sa valeur. "Quels
caract&egrave ;res utiliser ?" est le contenu de l’élément greeting.
Notez que chaque élément est défini entre une balise ouvrante et une balise fermante.
Définition 7.6 (document bien formé). Un document XML bien formé respecte les règles de la syntaxe XML, les
balises sont correctement écrites, ne se croisent pas, le document respecte l’encodage précisé.
En raison de son organisation arborescente, un document XML doit comporter exactement un élément appelé
"élément racine" ou élément document, dont aucune partie n’apparaît dans le contenu d’un autre élément. Chaque
sommet de l’arbre (ou élément) est un couple de balises ouvrantes et fermantes.
Les éléments organisent ainsi la structure arborescente du document.
Définition 7.7 (syntaxe d’un élement). Un élement a la forme suivante :
<Name arg1 = ”val1 ” arg2 = ”val2 ” arg3 = ”val3 ” ... >
...
</Name>
7.4. XML : EXTENSIBLE MARK-UP LANGUAGE
93
ou bien, lorsqu’il est vide :
<Name arg1 = ”val1 ” arg2 = ”val2 ” arg3 = ”val3 ” ... / >
Quelques règles sur les éléments :
— Ils sont vides ou contiennent d’autres éléments, du PCDATA (texte analysé) ou du CDATA (texte non
analysé)
— Les noms d’éléments tiennent compte des majuscules et des minuscules. Ils peuvent comporter des lettres,
des chiffres, des tirets, desb traits de soulignement, des deux-points ou des points. Le caractère deuxpoints ( :) ne peut être utilisé que dans le cas particulier où il sert à séparer des espaces de noms. Les noms
d’éléments xml, XML ou une autre combinaison de la casse de ces lettres sont réservés à la norme XML.
— Les caractères <, &, >, " et ’ ne peuvent pas être utilisés dans le texte, car ils sont utilisés dans le balisage.
— Un élément peut comporter aucun, un ou plusieurs attributs. Les caractères autorisés sont les mêmes que
pour les noms d’éléments. Le nom d’un attribut est séparé de sa valeur par le caractère =. La valeur de
l’attribut doit être indiquée entre guillemets simples ’...’ ou doubles "...".
Des commentaires peuvent figurer n’importe où dans un document en dehors des autres balises. La chaîne de
caractères "- -" (deux tirets) ne doit pas figurer à l’intérieur des commentaires.
Les commentaires ont la forme suivante :
<! -- ...
-- >
Définition 7.8 (PI). Les instructions de traitement (PI - Processing instruction) permettent aux documents XML
de contenir des instructions destinées aux applications.
Syntaxe :
<? cmd arg1 arg2 arg3 . . . ? >
Définition 7.9 (CDATA). Les sections CDATA permettent de ne pas traiter les blocs de texte comportant des
caractères qui seraient normalement identifiés comme du balisage.
Syntaxe :
<! [ CDATA [ ...
]] >
Définition 7.10 (DTD). La structure d’un document XML bien formé est précisée dans une DTD (Document Type
Definition).
Une DTD est un document qui permet de déclarer des éléments, des attributs, des entités et des notations. Ce
document peut contenir, tout comme un document XML, des instructions de commande et des commentaires.
Définition 7.11 (Document XML valide). Un document XML est dit valide au regard d’une DTD s’il respecte les
contraintes que lui impose cette DTD.
La déclaration d’une DTD se fait dans le prologue du document XML. Le prologue contient :
1. une déclaration XML :
<? xml version = " 1.0 " encoding = " ISO -8859 -1 " standalone = " yes " ? >
2. une déclaration de type de document de la forme :
<! DOCTYPE Name Déclaration ([DTD]) >
CHAPITRE 7. ENTRÉES-SORTIES : FLUX, FICHIERS, SÉRIALISATION, XML
94
7.4.2
Java et XML
Les informations basées dans cette partie sont basée sur le tutoriel "Java and XML - Tutorial " par Lars Vogel :
http://www.vogella.com/articles/JavaXML/article.html.
Il existe plusieurs bibliothèques pour la lecture et la modification de fichiers XML, dont SAX (Simple API for
XML) et DOM (Document Object Model). Nous allons considérer une troisième bibliotheque : STAX (Streaming
API for XML) introduite dans Java 6. Cette bibliothèque est dans le paquet javax.xml.stream.
Chaque élément d’un document XML est modélisé en Java sous la forme d’un "événement". Il existe deux
interfaces principales : XMLEventReader (lecture) et XMLEventWriter (écriture).
Evènements Un évènement est un objet de type XMLEvent. Les principales méthodes de cette interface sont :
Characters asCharacters () // retourne le contenu d ’ un evenement textuel
EndElement asEndElement () // retourne l ’ element courant sous la forme d
’ un element de type EndElement ( terminaison d ’ un element )
StartElement asStartElement () // retourne l ’ element courant sous la
forme d ’ un element de type StarElement ( debut d ’ un element )
boolean isEndDocument ()
boolean isEndElement ()
boolean isStartDocument ()
boolean isStartElement ()
Dans le cas d’un élément de type StartElement (ce qui correspond à une balise ouvrante du document
XML), on peut récupérer son nom avec la méthode getName().getLocalPart() et un itérateur pour parcourir
ses attributs avec la méthode getAttributes.
Ainsi l’exemple ci-dessous construit une personne de nom Greg à partir d’un événement correspondant à la
balise <Personne nom="Greg">.
Exemple
if ( event . isStartElement () ) {
StartElement startElement = event . asStartElement () ;
if ( startElement . getName () . getLocalPart () . equals ( " Personne " ) ) {
p = new Personne () ;
Iterator < Attribute > attributes = startElement . getAttributes () ;
while ( attributes . hasNext () ) {
Attribute attribute = attributes . next () ;
if ( attribute . getName () . toString () . equals ( " nom " ) ) {
p . setNom ( attribute . getValue () ) ;
}
}
}
}
Exercice 109. (??) Un projet sous Eclipse utilise un fichier build.xml pour spécifier la chaine de compilation
du projet. En supposant qu’un objet e de type XMLEvent corresponde à la balise <project ...> de ce fichier,
écrire une méthode getName(XMLEvent event) qui retourne le nom du projet, tel qu’il est enregistré dans ce
fichier.
Lecture d’un document XML On utilise naturellement l’interface XMLEventReader dont les méthodes sont :
void close () // liberation des ressources
String getElementText () // obtenir le contenu d ’ un element textuel
Object getProperty ( String name ) // obtenir la valeur d ’ un parametre
7.4. XML : EXTENSIBLE MARK-UP LANGUAGE
95
boolean hasNext () // tester s ’ il reste des evenements a traiter
XMLEvent nextEvent () // recuperer l ’ evenement suivant
XMLEvent nextTag () // passer les evenements jusqu ’ au prochain "
START_ELEMENT " ou " END_ELEMENT "
XMLEvent peek () // recuperer l ’ evenement suivant , sans le retirer de la
pile des evenements a traiter
L’exemple ci-après retourne une collection de personnes, qui ont été créées à partir d’un fichier XML. Les
éléments sont de la forme <personne nom="..."> </personne>
Exemple
public List < Personne > readXMLFile ( String fichier ) {
List < Personne > personnes = new ArrayList < Personne >() ;
try {
// creation d ’ un flux en lecture sur le document XML
XMLInputFactory inputFactory = XMLInputFactory . newInstance () ;
InputStream in = new FileInputStream ( fichier ) ;
XMLEventReader eventReader = inputFactory . cr ea teX ML Eve ntR ea der ( in ) ;
// lecture du document XML
while ( eventReader . hasNext () ) {
// nouvel evenement
XMLEvent event = eventReader . nextEvent () ;
// balise ouvrante ?
if ( event . isStartElement () ) {
StartElement startElement = event . asStartElement () ;
// balise personne ?
if ( startElement . getName () . getLocalPart () . equals ( " personne " ) )
{
// parcours des attributs
Iterator < Attribute > attributes = startElement . getAttributes
() ;
while ( attributes . hasNext () ) {
Attribute attribute = attributes . next () ;
if ( attribute . getName () . toString () . equals ( " nom " ) ) {
Personne p = new Personne ( attribute . getValue () ) ;
personnes . add ( p ) ;
}
}
}
}
}
}
catch ( Fi le N ot F ou nd E xc ep t io n e ) { e . printStackTrace () ; }
catch ( XMLStreamException e ) { e . printStackTrace () ; }
return personnes ;
}
Exercice 110. (?) Un projet sous Eclipse utilise un fichier build.xml pour spécifier la chaine de compilation du
projet. Ecrire une méthode getName() qui retourne le nom du projet, tel qu’il est spécifié dans ce fichier.
Ecriture d’un document XML La modification ou la création d’un document XML reposent sur l’interface
XMLEventWriter, dont les principales méthodes sont :
void add ( XMLEvent event ) // ajouter un evenement a ecrire
96
CHAPITRE 7. ENTRÉES-SORTIES : FLUX, FICHIERS, SÉRIALISATION, XML
void add ( XMLEventReader reader ) // ajouter tous les evenements
provenant du flux " reader "
void close () // liberation des ressources
void flush () // ecrire les donnees de maniere effective ( vider les
tampons )
On peut récupérer un objet de cette interface en faisant appel à la fabrique XMLOutputFactory :
XMLOutputFactory outputFactory = XMLOutputFactory . newInstance () ;
L’extrait de code suivant permet d’écrire une balise avec son contenu.
private void createNode ( XMLEventWriter eventWriter , String name , String
value ) throws XMLStreamException {
XMLEventFactory eventFactory = XMLEventFactory . newInstance () ;
XMLEvent end = eventFactory . createDTD ( " \ n " ) ;
XMLEvent tab = eventFactory . createDTD ( " \ t " ) ;
// Balise ouvrante
StartElement sElement = eventFactory . createStartElement ( " " , " " ,
name ) ;
eventWriter . add ( tab ) ;
eventWriter . add ( sElement ) ;
// Contenu
Characters characters = eventFactory . createCharacters ( value ) ;
eventWriter . add ( characters ) ;
// Balise fermante
EndElement eElement = eventFactory . createEndElement ( " " , " " , name ) ;
eventWriter . add ( eElement ) ;
eventWriter . add ( end ) ;
}
Ainsi, en utilisant la méthode createNode précédente, on peut enregistrer les données d’une personne p avec :
XMLOutputFactory outputFactory = XMLOutputFactory . newInstance () ;
XMLEventWriter eventWriter = outputFactory . cre ate XM LEv en tWr ite r ( new
FileOutputStream ( " personne . xml " ) ) ;
XMLEventFactory eventFactory = XMLEventFactory . newInstance () ;
XMLEvent eol = eventFactory . createDTD ( " \ n " ) ; // retour a la ligne
// Element racine
StartDocument startDocument = eventFactory . create StartDo cument () ;
eventWriter . add ( startDocument ) ;
eventWriter . add ( eol ) ;
// Balise < personne >
StartElement se = eventFactory . createStartElement ( " " ," " , " personne " ) ;
eventWriter . add ( se ) ;
eventWriter . add ( eol ) ;
// Balises d ’ une personne
createNode ( eventWriter , " nom " , p . getNom () ) ;
// Balise </ personne >
eventWriter . add ( eventFactory . createEndElement ( " " , " " , " personne " ) ) ;
eventWriter . add ( eol ) ;
// fin du document
eventWriter . add ( eventFactory . createEndDocument () ) ;
eventWriter . close () ;
Le fichier XML généré a un contenu de la forme :
7.4. XML : EXTENSIBLE MARK-UP LANGUAGE
97
<? xml version = " 1.0 " encoding = " UTF -8 " ? >
< personne >
<nom > nom de la personne p </ nom >
</ personne >
Exercice 111. (??) Ecrire une méthode save3(String nomFichier) dans la classe Groupe qui enregistre dans
un fichier XML tout un groupe.
Exercice 112. (??) Ecrire une méthode statique load3(String nomFichier) dans la classe Groupe qui retoure
un groupe enregistré dans un fichier XML.
CHAPITRE 7. ENTRÉES-SORTIES : FLUX, FICHIERS, SÉRIALISATION, XML
98
Fiche de synthèse
Flux en Java
— flux en entrée : interface InputStream
— flux en sortie : interface OutputStream
— traitement des erreurs d’entrée/sortie : try ... catch(IOException){...}
— flux d’octets : FileInputStream, FileOutputStream
— flux de caractères : FileReader, FileWriter
— flux avec tampon : BufferedReader, BufferedWriter
— manipulation de chemin : la classe Path
— fichiers de paramètres : la classe Properties
Programmation réseau
— adressage IP : la classe InerAddress
— transport UDP : les données DatagramSocket, la transmission DatagramSocket
— transport TCP : la connexion côté client Socket , la mise en place de flux via les méthodes
getInputStream() et getOutputStream(), l’écoute côté serveur ServerSocket
Sérialisation
— l’interfacce Serializable
— les flux via les classes ObjectInputStream et ObjectOutputStream
Objets distants via RMI
— côté serveur : l’interface Remote
— côté client : la demande d’accès via la classe Registry et sa méthode lookup
Java et XML
— les évènements : XMLEvent
— le parcours des évènements, des attributs
Exemple de lecture d’un fichier texte contenant des nombres :
Path fichier = Paths . get ( " ./ unFichier . txt " ) ;
try {
InputStream is = Files . newInputStream ( fichier ) ;
Scanner sc = new Scanner ( is ) ;
while ( sc . hasNextInt () )
System . out . println ( sc . nextInt () ) ;
} catch ( IOException ioe ) {
System . err . println ( " Echec lecture du fichier : " + ioe ) ;
}
Exemple d’écriture dans un fichier texte en utilisant un flux :
Charset UTF8 = Charset . forName ( " UTF -8 " ) ;
Path fichier = Paths . get ( " ./ unFichier . txt " ) ;
OutputStream os ;
try {
os = Files . newOutputStream ( fichier ) ;
PrintWriter pw = new PrintWriter ( os ) ; // pw est rattache au
flux os
pw . write ( " Ceci est \ n un essai . " ) ;
pw . write ( " \ nImpressionnant , non ? " ) ;
pw . flush () ; // ecrire toutes les donnees en attente ( vider le
tampon )
} catch ( IOException ioe ) { System . err . println ( " Echec ecriture : " + ioe ) ;}
Troisième partie
Java (un peu plus) avancé
99
Chapitre 8
Programmation multi-processus légers
Les systèmes d’exploitation sont multi-tâches : i.e., ils permettent l’exécution de plusieurs programmes en
simultané.
En pratique, sur un seul cœur, ils utilisent dans le cas le plus courant la technique de l’entrelacement : chaque
processus s’exécute l’un après l’autre, mais sur des tranches de temps suffisamment courtes, pour que l’utilisateur
ait l’illusion de la simultanéité.
Les performances du système d’exploitation sont étroitement liées à l’algorithme d’ordonnancement, et déterminent sa capacité à monter en charge : il est ainsi au cœur des évolutions du noyau de linux par exemple.
Java permet de gérer le multi-tâche à l’intérieur de la machine virtuelle : ce contrôle permet d’éviter les
situations bloquantes, et est très utilisé pour les interfaces graphiques : par exemple, pour que le menu soit toujours
réactif ...
Ce chapitre est consacré à la programmation multi-processus en Java.
Sommaire
8.1
8.2
Contrôler un processus : de sa création à sa terminaison
8.1.1 Création . . . . . . . . . . . . . . . . . . . . . . .
8.1.2 Processus bloqués . . . . . . . . . . . . . . . . .
8.1.3 Terminaison d’un processus . . . . . . . . . . . .
8.1.4 Groupes de processus . . . . . . . . . . . . . . . .
8.1.5 Démons . . . . . . . . . . . . . . . . . . . . . . .
Coordination de plusieurs processus : les verrous . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
101
101
103
103
103
104
104
Bibliographie :
— P. Niemeyer, J. Peck, "Java par la pratique", O’Reilly ;
— C.S. Horstmann, G. Cornell, "Au coeur de Java 2, fonctions avancées", CampusPress
8.1
8.1.1
Contrôler un processus : de sa création à sa terminaison
Création
Définition 8.1 (Création d’un processus). Un nouveau processus est créé lorsqu’une instance de la classe
java.lang.Thread est créé.
L’objet Thread représente un véritable processus de l’interpréteur Java. Il est utilisé pour gérer, contrôler,
synchroniser l’exécution d’un objet ... exécutable ;-)
Définition 8.2 (Objet exécutable). Un objet est dit exécutable s’il implémente l’interface Runnable :
101
CHAPITRE 8. PROGRAMMATION MULTI-PROCESSUS LÉGERS
102
public interface Runnable {
public void run () ;
}
Pour préciser l’objet exécutable d’un processus, on le passe simplement en paramètre au constructeur de l’objet
Thread :
class Film implements Runnable {
...
public void run () { ... }
}
Film film = new Film ( " Les aventures de Maya l \ ’ abeille au pays du
Mordor " ) ;
Thread monProcessus = new Thread ( film ) ;
monProcessus . start () ;
Lancement d’un processus Un nouvel objet Thread reste dans un état inoccupé jusqu’à ce qu’on appelle sa
méthode start(). Le processus se réveille alors et exécute la méthode run() de l’objet cible.
La méthode start() ne peut être appelée qu’une seule fois pendant toute la vie d’un objet Thread. Une fois
qu’un processus est lancé, il continue à s’exécuter jusqu’à ce que la méthode run() de l’objet cible se termine.
Exercice 113. (??) Ecrire une classe Film implémentant l’interface Runnable, avec un constructeur Film
(String titre) et une méthode Run affichant via une boucle i = 1..6, le titre suivi de "quart d’heure numéro i"
Création de processus par composition La manière de faire précédente n’est pas très orientée objet, car la
cible ne gère pas son processsus... Il est préférable de composer la cible avec le Thread la contrôlant.
Exemple
class Film implements Runnable {
private Thread monProcessus = null ;
public Film () {
monProcessus = new Thread ( this ) ;
monProcessus . start () ;
0}
...
public void run () { ... }
}
Film film = new Film ( " Les aventures de Maya l \ ’ abeille au pays du
Mordor " ) ;
Méthode de création à éviter ...
pouvez naturellement écrire :
La classe Thread implémente l’interface Runnable, si bien que vous
class Film extends Thread {
public Film () {
start () ;
}
...
8.1. CONTRÔLER UN PROCESSUS : DE SA CRÉATION À SA TERMINAISON
103
public void run () { ... } // redefinition de la émthode run () de
Thread
}
Film film = new Film ( " Les aventures de Maya l \ ’ abeille au pays du
Mordor " ) ;
Ceci crée une dépendance de Film sur Thread. En particulier, Film ne peut plus hériter d’autre chose... Cette
solution est judicieuse uniquement lorsque l’héritage de type sur Thread est justifiée, autrement dit lorsque votre
objet est conceptuellement un processus, et non simplement un objet contrôlé par un processus. En pratique, c’est
très rarement le cas...
8.1.2
Processus bloqués
Définition 8.3 (Processus bloqué). Un processus rentre dans l’état bloqué lorsque l’une de ces actions suivantes
se produit :
— la méthode statique Thread.sleep(int) est appelée : elle permet d’endormir un processus pendant
une durée exprimée en millisecondes. Elle lève une exception InterruptedException lorsqu’elle est
interrompue par un autre processus ;
— le processus appelle une opération bloquante sur les entrées/sorties ;
— le processus appelle la méthode wait() (de la classe Object) ;
— le processus essaie de verrouiller un objet qui est déjà verrouillé par un autre objet.
Exercice 114. (??) Modifier la classe Film en une classe Film2 pour que
— la création du processus se fasse par composition ;
— le constructeur ait le nombre de quarts d’heure en paramètre ;
— le processus soit lancé par la méthode go ;
— le processus s’endorme une seconde entre chaque quart d’heure.
Déblocage Pour sortir de l’état bloqué, un processus doit emprunter le chemin inverse de celui qui l’a conduit
à l’état bloqué :
— si un processus a été endormi, il doit attendre le nombre spécifié de millisecondes ;
— s’il attend une opération d’entrée-sortie, cette opération doit être terminée ;
— s’il a appelé la méthode wait, un autre processus doit appeler notify ou notifyAll ;
— s’il attend un verrou possédé par un autre objet, il doit attendre sa libération.
8.1.3
Terminaison d’un processus
Un processus peut s’achever pour l’une des deux raisons suivantes :
— la méthode run s’est terminée normalement ;
— une exception non récupérée a mis fin à la méthode run.
Pour savoir si un processus est actif, i.e. s’il est démarré et n’est pas encore terminé, utiliser la méthode
isAlive.
On peut interrompre des processus avec la méthode interrupt(). Lorsque cette méthode d’un processus
bloqué est appelée, le blocage (par sleep ou wait) reçoit une interruption InterruptedException..
8.1.4
Groupes de processus
Un groupe de processus permet de travailler simultanément avec plusieurs processus. L’utilisation des groupes
de processus (classe ThreadGroup) est particulièrement simple.
Le code commenté ci-dessous vous montre comment cela marche :
CHAPITRE 8. PROGRAMMATION MULTI-PROCESSUS LÉGERS
104
ThreadGroup g = new ThreadGroup ( " groupe de processus 1 " ) ;
Thread ta = new Thread (g , ta ) ;
Thread tb = new Thread (g , tb ) ;
...
if ( g . activeCount () ==0) ... // tous les processus sont arretes
g . interrupt () ; // interruption de tous les processus du groupe
...
8.1.5
Démons
Un démon est simplement un processus dont le seul but est de servir d’autres processus. Lorsqu’il ne reste
plus que des processus démons, le programme se termine.
Pour déclarer un processus en tant que processus démon, il suffit d’appeler (avant la méthode start !)
setDaemon ( true ) ;
8.2
Coordination de plusieurs processus : les verrous
La synchronisation permet de coordonner plusieurs processus. Java fournit des structures simples pour coordonner les activités de processus. Elles sont basées sur la notion de verrous.
Un verrou est attaché à une ressource à laquelle un certain nombre de processus peuvent accéder, mais qui ne
doit être accédée que par un processus à la fois.
Méthode synchronisée Pour éviter que plusieurs processus exécutent en même temps une méthode, il suffit
de la déclarer avec le mot clef synchronized :
public synchronized void maMethode()...
Un second processus doit attendre la fin de l’exécution de la méthode par le premier processus.
Le verrou d’une méthode synchronisée est partagé par toutes les méthodes synchronisées de l’objet : si un
processus possède le verrou d’un objet et qu’il appelle une autre méthode synchronisée du même objet, son accès
lui est immédiatement accordé.
Les autres processus ont librement accès aux méthodes non synchronisées.
Un processus libère le verrou uniquement lorsqu’il sort de la dernière méthode synchronisée.
Bloc synchronisé Le mot clef synchronized peut être également utilisé dans une construction particulière pour
garder un bloc de code : il prend alors un argument qui indique l’objet qui procure le verrou :
synchronized ( monObjet ) {... // bloc de code synchronise }
Un tel bloc de code peut se trouver dans n’importe quelle méthode. Lorsqu’il est atteint, le processus doit
acquérir le verrou de monObjet avant de pouvoir continuer.
Remarque : ainsi synchronized void maMethode() est équivalent à :
void maMethode () {
synchronized ( this ) {...}
}
Exercice 115. (? ? ?)
1. Modifier la classe Film2 en une classe Film3 pour que cette classe possède deux processus et mettre en
place un verrou de telle sorte que la boucle dans la méthode trun() (déroulement du film) soit accédée
en alternance par les 2 processus ;
2. Modifier le verrou pour que l’ensemble de la boucle soit protégé.
8.2. COORDINATION DE PLUSIEURS PROCESSUS : LES VERROUS
105
Renoncer à un verrou Tout objet possède les méthodes wait() et notify(). Avec ces méthodes, un objet
peut renoncer à la possession du verrou à un endroit donné, et ensuite attendre qu’un autre processus le lui rende.
En lançant la méthode wait() dans un bloc de code synchronisé, le processus renonce à son verrou et s’endort.
Lorsqu’un autre processus appelle la méthode notify() dans un bloc de code synchronisé sur le même objet, le
premier processus se réveille et essaye d’acquérir à nouveau le verrou.
A chaque appel à la méthode notify(), Java ne réveille qu’une méthode : s’il y a plusieurs processus en
attente, Java choisit le premier processus en fonction d’une file d’attente de type FIFO.
La classe Object fournit également une méthode notifyAll() pour réveiller tous les processus en attente. Celleci est donc préférable dans la plupart des cas.
Mise en veille d’un processus non-égoı̈ste Bien que les spécifications de Java n’interdisent pas à un processus endormi (avec sleep) de passer la main à un autre processus ou de libérer les verrous dont il dispose,
en pratique ils ne le font pas. Pour endormir un processus, il est préférable d’utiliser un verrou "temporaire" et
d’utiliser la méthode wait(int).
Object o = new Object () ;
try
{
synchronized ( o ) { o . wait (100) ; // renoncement au verrou de o }
}
catch ( In te rru pte dE xce pt ion e ) { System . out . println ( e ) ;}
Mentionnons également qu’avec la méthode yield(), un processus rend la main, mais ne se met pas en veille
pour autant !
CHAPITRE 8. PROGRAMMATION MULTI-PROCESSUS LÉGERS
106
Fiche de synthèse
Java
—
—
—
—
—
—
—
création d’un processus : la classe Thread
objet exécutable : l’interface Runnable
lancement d’un processus : la méthode start
processus bloqué
interruption d’un processus avec la méthode interrupt
groupes de processus : la classe ThreadGroup
les verrous : le mot clef synchronized / les méthodes et les blocs synchronisés
Exemple de code :
public class Film3 implements Runnable {
public Thread monProcessus1 = null ;
public Thread monProcessus2 = null ;
private String titre = null ;
private Object verrou = new Object () ;
public int nb = 6;
public int pos = 0;
public Film3 ( String titre , int nb ) {
this . titre = titre ; this . nb = nb ;
monProcessus1 = new Thread ( this ) ;
monProcessus2 = new Thread ( this ) ;
}
public void go () {
monProcessus1 . start () ; monProcessus2 . start () ;
}
public void run () {
System . out . println ( " Debut du film " + titre ) ;
synchronized ( verrou ) // avec ce verrou , les
processus executent la boucle l ’ un apres l ’
autre
try { for ( int pos =1; pos <= nb ; pos ++) {
// si pos n ’ est pas une variable du bloc , la
boucle s ’ execute une fois ( mais les
processus peuvent alterner )
System . out . println ( " [ " + titre + " ] " + " Quart d \ ’ heure
numero " + pos ) ;
System . out . println ( " Processus 1 en vie : " +
monProcessus1 . isAlive () ) ;
System . out . println ( " Processus 2 en vie : " +
monProcessus2 . isAlive () ) ;
Thread . sleep (500) ; // temporisation
}}
catch ( In te rru pt edE xce pt ion e ) { System . out . println ( "
Projection du film interrompue ! " ) ; }
}
public static void main ( String [] args ) {( new Film3 ( " Bob " ,6) ) . go
() ;}
8.2. COORDINATION DE PLUSIEURS PROCESSUS : LES VERROUS
}
107
108
CHAPITRE 8. PROGRAMMATION MULTI-PROCESSUS LÉGERS
Chapitre 9
Connexion directe avec des bases de données
relationnelles avec JDBC
L’API JDBC (Java DataBase Connectivity) permet de formuler des requêtes SQL au sein des sources Java.
Cette bibliothèque est de moins en moins utilisée au profit d’approches plus "automatisées" via notamment
l’usage des annotations pour les objets persistants que nous verrons dans un autre chapitre. La maitrise de cette
bibliothèque permet cependant de mieux comprendre ce que font certains outils de manière sous-jacente.
Sommaire
9.1
9.2
9.1
JDBC : structure de la bibliothèque . .
9.1.1 Se connecter à une base . . . . .
9.1.2 Modifier des données . . . . . . .
9.1.3 Regrouper des actions . . . . . .
9.1.4 Sélectionner des données . . . . .
9.1.5 Types de données et métadonnées
JDBC2 . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
109
110
111
111
112
112
113
JDBC : structure de la bibliothèque
La bibliothèque JDBC est munie d’un gestionnaire de pilotes ; les pilotes sont développés par des tiers (le plus
souvent les entreprises éditrices de la base de données considérée).
Le développement de JDBC s’est inspiré d’ODBC de Microsoft, qui fournit une interface en C pour les accès
aux bases de données : les programmes compatibles avec l’API JDBC communiquent avec le gestionnaire de
pilotes JDBC, qui en retour, se sert des pilotes auxquels il est relié au moment où la communication avec la base
de données est établie.
Pilotes L’ensemble des classes d’implémentation pour une base de donnée est appelée pilote JDBC.
Les pilotes JDBC sont classés en 4 types :
— type 1 : les pilotes accèdent aux données grâce à une technologie de ponts. Les ponts requièrent en général
d’installer du code natif coté client. Exemple : le pont ODBC ;
— type 2 : les pilotes appellent des méthodes C/C++ natives livrées par les éditeurs de SGBD. Il faut donc du
code natif chez le client ;
— type 3 : les pilotes fournissent au client une API réseau générique qui, traduite au niveau du serveur,
permet d’accéder aux bases de données. Le pilote utilise des sockets avec une application intermédiaire
sur le serveur. Avantage : peuvent interagir avec plusieurs SGBD différents ;
109
110
CHAPITRE 9. CONNEXION DIRECTE AVEC DES BASES DE DONNÉES RELATIONNELLES AVEC JDBC
— type 4 : les pilotes interagissent directement avec le SGBD. Ils sont en pur Java, portables. Meilleure
solution, exclusivement fournis par les éditeurs de SGBD.
Interfaces Les principales interfaces de JDBC sont :
— Driver : tous les pilotes doivent respecter cette interface ;
— Statement : la méthode execute sert à exécuter des requêtes SQL. Deux sous-interfaces :
— PreparedStatement : dédiée aux instructions prédéfinies ;
— CallableStatement : dédiée aux procédures stockées.
— ResultSet : pour stocker sous forme ensembliste le résultat d’une requête SQL ;
— ResultSetMetaData : pour accéder aux MetaTables du SGBD.
9.1.1
Se connecter à une base
URL de bases de données La syntaxe générale est : jdbc:nom du protocole:parametres
Le format de parametres est variable en fonction du protocole. La syntaxe recommandée est :
// nom_d ’ hote : port / autre
Exemple : l’url de la base de données maBase au CREMI, avec le connecteur JDBC pour mysql est :
jdbc : mysql :// miage . emi .u - bordeaux1 . fr / maBase
L’absence de numéro de port signifie que l’on désire se connecter sur le port par défaut pour mysql (3306).
Établir une connexion La classe DriverManager est responsable de la sélection des pilotes de base de données et de la création d’une nouvelle connexion à la base de données. Cependant, avant que le gestionnaire de
pilotes puisse activer un pilote, ce dernier doit être enregistré.
Il est possible d’enregistrer un pilote lors de l’exécution en spécifiant le paramètre jdbc.drivers :
java - Djdbc . drivers = pilotes ...
Il est cependant préférable de spécifier le chargement du pilote dans les sources :
pilote = " com . mysql . jdbc . Driver " ;
try {
Class . forName ( pilote ) . newInstance () ;
}
catch ( Exception e ) {
System . out . println ( " echec pilote : " + e ) ;
};
Une fois que les pilotes sont enregistrés, il faut encore ouvrir une connexion vers la base de données grâce à
un code analogue à :
String url = " jdbc : mysql :// miage . emi .u - bordeaux1 . fr / maBase " ;
String user = " consultant " ;
String password = " consultant " ;
try {
c = DriverManager . getConnection ( url , user , password ) ;
}
catch ( SQLException e ) {
System . out . println ( " echec connection a la bdd : " + e ) ;
}
Exercice 116. (??)
9.1. JDBC : STRUCTURE DE LA BIBLIOTHÈQUE
111
1. écrire une classe AccesBDD avec une méthode etablirConnexion() permettant de mettre en place une
connexion vers la base maBase sur un serveur mysql en local ;
2. créer un compte consultant (mot de passe : consultant) avec un accès en lecture seule et un compte
redacteur (mot de passe : redacteur) avec un accès en lecture/écriture ;
3. la base maBase contient une table Etudiant, avec des colonnes name, firstname, login et password.
Indications :
— installer et utiliser phpMyAdmin pour administrer votre base de données
— récupérer un pilote pour mysql sur le site http://dev.mysql.com/downloads/connector/j/ ;
Pour séparer les paramètres de connexion des sources, vous pouvez naturellement créer le fichier texte suivant :
jdbc . url = jdbc : mysql : // localhost / maBase
jdbc . user = consultant
jdbc . password = consultant
et ouvrir la connexion avec le code
Properties props = new Properties () ;
FileInputStream fis = new FileInputStream ( fileName ) ;
props . load ( fis ) ;
String url = props . getProperty ( " jdbc . url " ) ;
String user = props . getProperty ( " jdbc . user " ) ;
String password = props . getProperty ( " jdbc . password " ) ;
try {
c = DriverManager . getConnection ( url , user , password ) ;
}
catch ( SQLException e ) {
System . out . println ( " echec connection à la bdd : " + e ) ;
}
Pour fermer une connexion à une base de données, il faut utiliser la méthode close().
Exercice 117. (?) Modifier la classe AccesBDD pour qu’elle charge les paramètres de connexion dans le fichier
param.txt
9.1.2
Modifier des données
Pour exécuter une commande SQL, il faut commencer par créer un objet Statement, à partir de l’objet
Connection, et l’utiliser via la méthode executeUpdate :
Statement s = c . createStatement () ;
String commande = " UPDATE TroisiemeAge SET Lifting = ’ True ’ WHERE Age
>= ’20 ’ " ;
int nb = s . executeUpdate ( commande ) ;
La méthode executeUpdate retourne le nombre d’enregistrements modifiés par la commande
Exercice 118. (??) Essayez de modifier les mots de passe des étudiants en écrivant une méthode update : que se
passe-t-il ?
9.1.3
Regrouper des actions
Par défaut, une connexion à la base de données est en mode validation automatique, et chaque commande SQL
est validée dès qu’elle est exécutée. Une fois qu’une commande est validée, vous ne pouvez plus l’annuler.
Le mode de validation est fixé avec la méthode setAutoCommit( boolean) de la classe Connection.
112
CHAPITRE 9. CONNEXION DIRECTE AVEC DES BASES DE DONNÉES RELATIONNELLES AVEC JDBC
Pour lancer un ensemble de commandes groupés, on utilise la méthode commit().
Pour les annuler en cas d’erreur (déclenchement d’une exception SQLException), on utilise la méthode
rollback().
Scénario typique :
c . setAutoCommit ( false ) ;
Statement s = c . createStatement () ;
try {
s . executeUpdate ( commande1 ) ;
s . executeUpdate ( commande2 ) ;
s . executeUpdate ( commande3 ) ;
c . commit () ;
}
catch ( SQLException e ) {
c . rollback () ;
System . out . println ( " Transactions annulees " ) ;
}
9.1.4
Sélectionner des données
Pour les requêtes, on utilise la méthode executeQuery et on récupère le résultat dans un objet ResultSet.
Exemple :
ResultSet liftes =
s . executeQuery ( " SELECT * FROM TroisiemeAge
WHERE Lifting like ’ True ’" ) ;
while ( liftes . next () ) { // parcours de chaque ligne
int Age = liftes . getInt ( " Age " ) ;
...
Dans la classe ResultSet, il existe des accesseurs du style getInt pour chaque type de base.
Chaque accesseur possède 2 formes. La première prend un argument entier, précisant le numéro de la colonne
de la table (le premier indice est 1) ; la seconde précise directement le nom de la colonne, comme dans l’exemple.
Exercice 119. (??) Afficher la liste des étudiants (table Etudiant).
Solution
9.1.5
Types de données et métadonnées
Les types de données SQL et leur équivalent Java
9.2. JDBC2
113
SQL
INTEGER, INT
SMALLINT
NUMERIC(m,n), DECIMAL(m,n), DEC(m,n)
FLOAT(n)
REAL
DOUBLE
CHARACTER(n), CAR(n)
VARCHAR(n)
BOOLEAN
DATE
TIME
TIMESTAMP
BLOB
CLOB
ARRAY
Java
int
short
java.sql.Numeric
Double
Float
Double
String
String
Boolean
java.sql.Date
java.sql.Time
java.sql.Timestamp
java.sql.Blob
java.sql.Clob
java.sql.Array
Métadonnées JDBC permet d’obtenir des informations sur les métadonnées, ie les données qui décrivent une
base de données. Pour cela, il faut demander un objet de type DatabaseMetaDate.
Ainsi le code
// on recupere les metadonnees de la connexion
DatabaseMetaData md = c . getMetaData () ;
ResultSet rs = md . getTables ( null , null , null , new String []{ " TABLE " }) ;
while ( rs . next () )
System . out . println ( rs . getString (3) ) ;
rs . close () ;
affiche la liste des tables de la base de données.
Exercice 120. (?) Testez ce code sur votre base de données.
A partir des métadonnées d’un ensemble de résultat, on peut récupérer le nombre de colonnes, ainsi que le
nom de chaque colonne.
ResultSet rs = s . executeQuery (
" SELECT * FROM TroisiemeAge " ) ;
// on recupere les metadonnees
// de l ’ ensemble de resultats
ResultSetMetaData rsmd = rs . getMetaData () ;
for ( int i = 1; i <= rsmd . getColumnCount () ; i ++) {
String nomColonne = rsmd . getColumnName ( i ) ;
...
Exercice 121. (?) Adapter ce code pour afficher les colonnes de votre table Etudiant.
9.2
JDBC2
JDBC2 est une évolution de JDBC datant de 2004, apportant des fonctionnalités accrues permettant de gérer
des transactions SQL plus complexes et plus performantes.
Dans cette section, nous allons décrire les fonctionnalités les plus importantes de JDBC2. La bibliothèque
JDBC a continué d’évoluer. La dernière version en date étant JDBC4.
114
CHAPITRE 9. CONNEXION DIRECTE AVEC DES BASES DE DONNÉES RELATIONNELLES AVEC JDBC
Mises à jour automatisées Grâce à cette technique, un ensemble de commandes SQL est rassemblé puis
validé comme une seule commande (à condition que la base de données le supporte !). Cela ne concerne pas les
requêtes. Le principe est le même que les commandes groupées.
Au lieu d’appeler la méthode executeUpdate, on appelle la méthode addBatch :
c . setAutoCommit ( false ) ;
Statement s = c . createStatement () ;
try {
s . addBatch ( commande1 ) ;
s . addBatch ( commande2 ) ;
s . addBatch ( commande3 ) ;
int [] modifs = s . executeBatch () ;
c . commit () ;
}
catch ( SQLException e ) {
c . rollback () ;
System . out . println ( " Transactions annulees " ) ;
}
Lors de l’appel à la méthode executeBatch, on récupère un tableau d’entiers correspondant aux nombres
d’enregistrements modifiés respectivement par les commandes successives du traitement automatisé.
Requêtes préparées Lorsque vous avez besoin de faire une série de requêtes dont seul la valeur d’un critère
change, il est préférable de préparer une requête paramétrée. Cette technique apporte une amélioration des performances car chaque fois que la base de données exécute une requête, elle commence par déterminer une stratégie
lui permettant d’exécuter la requête de manière efficace.
Exemple :
String requeteParametree =
" SELECT * FROM TroisiemeAge WHERE Lifting = ? " ;
PreparedStatement ps = c . preparedStatement ( requeteParametree ) ;
// requete 1
ps . setBoolean (1 , True ) ; // 1 <-> premier "?"
ResultSet rs = ps . executeQuery () ;
...
ps . setBoolean (1 , False ) ;
rs = ps . executeQuery () ;
...
}
Ensembles de résultats à parcourir Les méthodes des ensembles de résultats à parcourir vous permettent de
parcourir un ensemble de résultats vers l’avant, vers l’arrière, ou encore de se positionner directement à n’importe
quel endroit, grâce à ces méthodes de la classe ResultSet :
previous () ;
relative ( int ) ; // deplacement relatif
absolute ( int ) ; //
int getRow () ; // retourne le numero de l ’ enr .
Ensembles de résultats à mettre à jour Avec les ensembles de résultats à mettre à jour, toute modification
sur un tel ensemble peut être répercutée sur la base de données. Le choix de la nature de l’ensemble de résultats
se fait lors de la création de l’objet Statement :
9.2. JDBC2
115
Statement s = c . createStatement ( type , mode ) ;
où type peut prendre les valeurs suivantes :
— ResultSet.TYPE_FORWARD_ONLY : l’ensemble n’est pas à parcourir ;
— ResultSet.TYPE_SCROLL_INSENSITIVE : l’ensemble est à parcourir, mais il n’est pas sensible aux
modifications de la base de données
— ResultSet.TYPE_SCROLL_SENSITIVE : l’ensemble est à parcourir, et il est sensible aux modifications
de la base de données
et mode peut prendre les valeurs suivantes :
— ResultSet.CONCUR_READ_ONLY : l’ensemble ne peut pas être utilisé pour mettre à jour la base de
données ;
— ResultSet.CONCUR_UPDATABLE : l’ensemble peut être utilisé pour mettre à jour la base de données
Exemple d’utilisation d’un ensemble à mettre à jour :
Statement s = c . createStatement (
ResultSet . TYPE_SCROLL_INSENSITIVE , ResultSet . CONCUR_UPDATABLE ) ;
ResultSet rs = s . executeQuery ( " SELECT * from TroisiemeAge " ) ;
while ( rs . next () ) {
int age = rs . getInt ( " Age " ) ;
rs . updateInt ( " Age " ,2* age ) ;
rs . updateRow () ;
}
Si vous déplacez le curseur sur un autre enregistrement sans avoir appelé la méthode updateRow(), les modifications sont perdues.
Il est également possible d’insérer un nouvel enregistrement, en utilisant d’abord la méthode
moveToInsertRow() pour positionner le curseur sur ce nouvel enregistrement.
Avec les méthodes updateXXX, on renseigne ensuite les valeurs des champs de cet enregistrement, puis on
l’insère avec la méthode insertRox().
Enfin, la méthode moveToCurrentRow() permet de repositionner le curseur sur la position initiale.
La méthode deleteRow() sert à effacer un enregistrement ;-) .
Exercice 122. (??) Ajouter la chaine ", le terrible dragon" aux prénoms des étudiants, en utilisant un ensemble à
mettre à jour.
116
CHAPITRE 9. CONNEXION DIRECTE AVEC DES BASES DE DONNÉES RELATIONNELLES AVEC JDBC
Fiche de synthèse
Interfaces de JDBC
— Driver : tous les pilotes doivent respecter cette interface ;
— Statement : méthode execute() pour requêtes SQL.
— ResultSet : pour stocker sous forme ensembliste le résultat d’une requête SQL ;
— ResultSetMetaData : pour accéder aux MetaTables.
Connexion à une base de donnée SQL : 2 phases
— choix du pilote ;
— établissement de la connexion.
// choix du pilote mysql
pilote = " com . mysql . jdbc . Driver " ;
try { Class . forName ( pilote ) . newInstance () ;}
catch ( Exception e ) { System . out . println ( " echec pilote : " + e ) ;};
// etablissement de la connexion
String url = " jdbc : mysql :// serveur : port / base " ;
String user = " utilisateurBDD " ; String password = " motdepasse " ;
try { c = DriverManager . getConnection ( url , user , password ) ;}
catch ( SQLException e ) { System . out . println ( " echec connex .: " + e ) ;}
Requête sans sélection : 2 phases
— créer un objet Statement à partir de la connexion ;
— exécuter la requête avec executeUpdate.
Statement s = c . createStatement () ;
String commande = " UPDATE TroisiemeAge SET Lifting = ’ True ’ WHERE
AgePretendu <= ’20 ’ " ;
int nb = s . executeUpdate ( commande ) ; // nb = nb modifications
Requête avec sélection : 2 phases
— créer un objet Statement à partir de la connexion ;
— exécuter la requête avec executeQuery ;
— l’ensemble de résultats est dans un objet ResultSet.
Statement s = c . createStatement () ;
ResultSet liftes = s . executeQuery ( " SELECT * FROM TroisiemeAge WHERE
Lifting like ’ True ’" ) ;
while ( liftes . next () ) { // parcours de chaque ligne
int Age = liftes . getInt ( " Age " ) ;
...
Chapitre 10
Réagir à des évènements : les écouteurs
Les écouteurs sont des classes permettant de réagir à des évènements : ils sont donc tout particulièrement
pratique pour la conception d’interface graphique. Ils permettent ainsi de définir la réaction appropriée à une action
effectuée par l’utilisateur, comme un clic de souris, l’appui sur une touche, le survol d’un composant graphique ...
Dans la première section, nous présentons leur fonctionnement en Java.
La deuxième section est consacrée aux classes internes et anonymes : la possibilité de créer des classes à
l’intérieur d’autres classes est en effet particulièrement utile lors de la mise en oeuvre d’écouteurs.
Sommaire
10.1 Les écouteurs . . . . . . . . . . . . . . .
10.1.1 La bibliothèque awt.event . . .
10.1.2 Adaptateurs . . . . . . . . . . . .
10.2 Classes internes et anonymes . . . . . .
10.2.1 Les classes internes simples . . .
10.2.2 Les classes internes aux méthodes
10.2.3 Les classes internes anonymes . .
10.2.4 Les classes internes statiques . . .
10.1
Les écouteurs
10.1.1
La bibliothèque awt.event
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
117
117
119
121
123
124
125
125
En java, les classes dédiées à la gestion des événements sont regroupées dans La bibliothèque awt.event.
Prenons pour exemple cet extrait de code d’un composant graphique Beeper contenant un bouton button :
public class Beeper ... implements ActionListener {
...
button . addActionListener ( this ) ;
...
public void actionPerformed ( ActionEvent e ) {
... // Faire un son ...
}
}
Principes :
— implements ActionListener : la classe Beeper est déclarée comme capable de réagir à des évènements de type Action (qui sont générés lorsque l’on clique sur un bouton par exemple)
117
CHAPITRE 10. RÉAGIR À DES ÉVÈNEMENTS : LES ÉCOUTEURS
118
— button.addActionListener(this) : l’objet this se déclare comme écouteur de l’objet button
— public void actionPerformed(ActionEvent e) : cette méthode est définir par l’interface
ActionListener et doit donc être implémentée par Beeper. Lorsque l’utilisateur clique sur le bouton
button, celui-ci génère un objet de type ActionEvent (incorporant des informations sur l’évènement en
question via ses attributs). Cet objet est "capturé" par la méthode actionPerformed qui est alors exécutée.
Exercice 123. (?) Récupérer le code complet de la classe Beeper : http: // docs. oracle. com/ javase/
tutorial/ uiswing/ examples/ events/ BeeperProject/ src/ events/ Beeper. java et le modifier pour
que lorsque l’utilisateur appuie sur le bouton, le message "Aie !" s’affiche dans le terminal.
La classe Beeper est un exemple de gestionnaire d’évènements : un gestionnaire d’évènements (ou écouteur)
est une instance d’une classe qui implémente une interface dérivée de EventListener.
Un même écouteur peut prendre en charge plusieurs sources d’évènements. Inversement, une source d’événement peut être prise en charge par plusieurs écouteurs différents (cf Fig. 10.1).
c
F IGURE 10.1 – Relation n, n entre écouteurs et sources d’événements (Oracle)
Pour illustrer ceci, considérons une application dont l’interface graphique est donnée dans la figure 10.2.
Lorsque l’utilisateur clique sur le bouton de gauche, le panneau du haut affiche Blah Blah Blah. Lorsque l’utilisateur clique sur le bouton de droite, le panneau du bas affiche You don’t say! et celui du haut affiche aussi
You don’t say!.
Voici un extrait de code significatif de l’application :
public class MultiListener ... implements ActionListener {
...
button1 . addActionListener ( this ) ;
button2 . addActionListener ( this ) ;
button2 . addActionListener ( new Eavesdropper ( bottomTextArea ) ) ;
}
public void actionPerformed ( ActionEvent e ) {
topTextArea . append ( e . getActionCommand () + newline ) ;
}
}
class Eavesdropper implements ActionListener {
...
public void actionPerformed ( ActionEvent e ) {
myTextArea . append ( e . getActionCommand () + newline ) ;
}
}
10.1. LES ÉCOUTEURS
119
c
F IGURE 10.2 – Une application avec plusieurs écouteurs (Oracle)
Exercice 124. (?) Quel écouteur écoute plusieurs sources et quelles sont-elles ? Quelle source est écoutée par
plusieurs écouteurs et quels sont-ils ?
Il existe différents contrôleurs en fonction de la nature des évènements à traiter : ActionListener, MouseListener, MouseMotionListener,....
Tout composant graphique peut être écouté par des écouteurs de type :
— component listener : changements de taille, position ou de visibilité ;
— focus listener : gain ou perte du focus sur le clavier ;
— key listener : appuis sur des touches ;
— mouse listener : clics, appuis sur la souris, survol ;
— mouse motion listener : changements de position ;
— mouse wheel listener : roue de la souris ;
— ...
10.1.2
Adaptateurs
Pour faciliter le développement d’écouteurs, des adaptateurs sont fournis pour la plupart des interfaces définissant les écouteurs. Un adaptateur est une implémentation de l’interface dont le corps de chacune des méthodes est
vide : {}. Ceci permet d’écrire un écouteur particulier en héritant de l’adaptateur et en redéfinissant uniquement
les méthodes qui sont utiles pour l’application (au lieu de toutes celles de l’interface).
Par exemple, la classe MouseAdapter est un adaptateur pour l’interface MouseListener.
Les tableaux 10.1 et 10.2 donnent la liste exhaustive des interfaces dédiées aux évènements, avec leurs adaptateurs et les méthodes à implémenter.
CHAPITRE 10. RÉAGIR À DES ÉVÈNEMENTS : LES ÉCOUTEURS
120
Exercice 125. (??) Considérons le code ci-après dont le fonctionnement sera expliqué dans la partie du cours sur
SWING. Ce code dessine un carré rouge de taille 10x10 aux coordonnées (x ∗ 10, y ∗ 10) où x et y sont les attributs
de la classe Dessin.
Créer un écouteur sur le clavier rattaché à la fenêtre graphique permettant de déplacer le carré avec les
touches z, q, s et d. Pour cela, exploiter l’adaptateur KeyAdapter pour modifier les valeurs de x et de y, en
fonction de la touche pressée, en veillant à demander le rafraichissement du dessin via la méthode repaint.
(corrigé)
class Dessin extends Canvas {
private int x ;
private int y ;
@Override
public void paint ( Graphics g ) {
super . paint ( g ) ;
g . setColor ( Color . red ) ;
g . fillRect ( x *10 , y *10 , 10 , 10) ;
}
/* *
* @return the x
*/
public int getX () {
return x ;
}
/* *
* @param x the x to set
*/
public void setX ( int x ) {
this . x = x ;
}
/* *
* @return the y
*/
public int getY () {
return y ;
}
/* *
* @param y the y to set
*/
public void setY ( int y ) {
this . y = y ;
}
}
public class ExoKeyAdpater {
/* *
* @param args the command line arguments
10.2. CLASSES INTERNES ET ANONYMES
121
*/
public static void main ( String [] args ) {
JFrame jf = new JFrame ( " Exercice KeyAdapter " ) ;
jf . setSize (800 , 600) ;
Dessin d = new Dessin () ;
d . setX (40) ; d . setY (30) ;
jf . add (d , " Center " ) ;
jf . setVisible ( true ) ;
}
}
10.2
Classes internes et anonymes
Les classes internes et des classes internes anonymes sont particulièrement intéressantes pour les écouteurs.
Commençons par présenter succinctement ces notions de classe interne et de classe interne anonyme sur un
exemple. Lorsqu’on écrit de manière classique :
public class MyClass extends MouseAdapter {
...
someObject . addMouseListener ( this ) ;
...
public void mouseClicked ( MouseEvent e ) {
... // implementation ...
}
}
la classe MyClass ne peut pas hériter d’une autre classe de l’application.
On peut contourner ceci via l’usage d’une classe interne :
public class MyClass extends ... {
...
someObject . addMouseListener ( new MyAdapter () ) ;
...
class MyAdapter extends MouseAdapter {
public void mouseClicked ( MouseEvent e ) {
... // implementation
}
}
}
La classe MyAdapter est définie à l’intérieur de MyClass : c’est une classe interne. L’exécution de la ligne
someObject.addMouseListener(new MyAdapter());
provoque la création d’un objet associé de MyAdapter.
Ce procédé permet d’écrire un code plus compact et naturel : il est à utiliser avec parcimonie, car il peut
d’une part engendrer des problèmes de performance en raison de la multiplication des objets, et d’autre part, ceci
empêche la sérialisation de la classe.
L’usage d’une classe interne anonyme engendre en outre l’économie d’un nom pour la classe interne :
public class MyClass extends ... {
...
someObject . addMouseListener ( new MouseAdapter () {
public void mouseClicked ( MouseEvent e ) {
CHAPITRE 10. RÉAGIR À DES ÉVÈNEMENTS : LES ÉCOUTEURS
122
Interface
ActionListener
AncestorListener
Adaptateur
-
CaretListener
CellEditorListener
-
ChangeListener
ComponentListener
ComponentAdapter
ContainerListener
ContainerAdapter
DocumentListener
-
ExceptionListener
FocusListener
FocusAdapter
HierarchyBoundsListener
HierarchyBoundsAdapter
HierarchyListener
HyperlinkListener
InputMethodListener
-
InternalFrameListener
InternalFrameAdapter
ItemListener
KeyListener
KeyAdapter
ListDataListener
-
ListSelectionListener
MenuDragMouseListener
-
MenuKeyListener
-
Méthodes
actionPerformed(ActionEvent)
ancestorAdded(AncestorEvent)
ancestorMoved(AncestorEvent)
ancestorRemoved(AncestorEvent)
caretUpdate(CaretEvent)
editingStopped(ChangeEvent)
editingCanceled(ChangeEvent)
stateChanged(ChangeEvent)
componentHidden(ComponentEvent)
componentMoved(ComponentEvent)
componentResized(ComponentEvent)
componentShown(ComponentEvent)
componentAdded(ContainerEvent)
componentRemoved(ContainerEvent)
changedUpdate(DocumentEvent)
insertUpdate(DocumentEvent)
removeUpdate(DocumentEvent)
exceptionThrown(Exception)
focusGained(FocusEvent)
focusLost(FocusEvent)
ancestorMoved(HierarchyEvent)
ancestorResized(HierarchyEvent)
hierarchyChanged(HierarchyEvent)
hyperlinkUpdate(HyperlinkEvent)
caretPositionChanged(InputMethodEvent)
inputMethodTextChanged(InputMethodEvent)
internalFrameActivated(InternalFrameEvent)v
internalFrameClosed(InternalFrameEvent)
internalFrameClosing(InternalFrameEvent)
internalFrameDeactivated(InternalFrameEvent)
internalFrameDeiconified(InternalFrameEvent)
internalFrameIconified(InternalFrameEvent)
internalFrameOpened(InternalFrameEvent)
itemStateChanged(ItemEvent)
keyPressed(KeyEvent)
keyReleased(KeyEvent)
keyTyped(KeyEvent)
contentsChanged(ListDataEvent)
intervalAdded(ListDataEvent)
intervalRemoved(ListDataEvent)
valueChanged(ListSelectionEvent)
menuDragMouseDragged(MenuDragMouseEvent)
menuDragMouseEntered(MenuDragMouseEvent)
menuDragMouseExited(MenuDragMouseEvent
menuDragMouseReleased(MenuDragMouseEvent)
menuKeyPressed(MenuKeyEvent)
menuKeyReleased(MenuKeyEvent)
menuKeyTyped(MenuKeyEvent)
TABLE 10.1 – Ecouteurs et leurs adapateurs (Source : documentation officielle)
10.2. CLASSES INTERNES ET ANONYMES
123
... // implementation
}
}) ;
...
}
}
Nous allons maintenant décrire de manière plus complète les fonctionnalités et les différents types de classes
internes.
10.2.1
Les classes internes simples
Dans l’exemple vu dans le préambule, nous avons défini une classe interne simple :
Définition 10.1. Classe interne simple Classe définie à l’intérieur d’une autre.
class A {
...
class B {
...
}
}
La compilation génère deux fichiers : A.class et A$B.class. Pour créer une instance de la classe B, il est
nécessaire d’avoir une instance de la classe A. On peut procéder de la manière suivante :
A a = new A () ;
A . B b = a . new B () ;
ou encore
A . B b = new A () . new B () ;
La classe interne B a accès à tous les attributs de la classe A, même s’ils sont privés. Le mot-clef this désigné
l’objet courant de la classe qui l’englobe : à l’intérieur de B, pour faire référence à l’objet de la classe A qui
l’englobe, on peut utiliser A.this.
Voici un exemple : :
public class A {
String message = " Vive A ! " ;
public class B {
String message = " Vive B ! " ;
public void essaiMessage () {
System . out . println ( " this . message : " + this . message ) ; //
affiche " Vive B !"
System . out . println ( " A . this . message : " + A . this . message ) ; //
affiche " Vive A !"
}
}
}
Il y a donc un couplage fort entre la classe interne B et la classe A. L’utilisation de classes internes permet de
renforcer l’encapsulation du code au détriment de la modularité.
CHAPITRE 10. RÉAGIR À DES ÉVÈNEMENTS : LES ÉCOUTEURS
124
10.2.2
Les classes internes aux méthodes
On peut également déclarer des classes internes à l’intérieur d’une méthode. Celle-ci ne peut être instanciée
qu’à l’intérieur du bloc de la méthode qui l’héberge (mais sa portée peut excéder celle-ci) :
Définition 10.2 (Classe interne à une méthode). Classe interne déclarée au sein d’une méthode :
class A {
void essai () {
class B {
void essaiB () {
...
}
}
B b = new B () ;
b . essaiB () ;
}
}
Une classe interne à une méthode peut accéder aux variables constantes de cette méthode (i.e. déclarer comme
final) ou bien aux variables qui sont effectivement constantes 1 (i.e. dont la valeur n’est jamais modifiée au cours
de l’exécution ; le compilateur se chargeant de vérifier cette propriété).
Exemple :
class A {
void essai () {
final int x = 3;
class B {
void essaiB () {
System . out . println ( " essaiB : x = " + x ) ;
}
}
B b = new B () ;
b . essaiB () ;
}
}
public static void main ( String [] args ) {
A a = new A () ;
a . essai () ; // affiche " essaiB : x =3"
}
1. à partir de Java 8
10.2. CLASSES INTERNES ET ANONYMES
10.2.3
125
Les classes internes anonymes
Lorsque la classe interne est à usage "unique", dans le sens où elle n’est utilisée qu’à partir d’un seul endroit
du code, il est judiceux de l’écrire sous forme "anonyme", le plus souvent à l’intérieur d’une méthode. Ces classes
sont utilisées pour implémenter "localement" une interface ou une classe abstraite.
Définition 10.3. Classe interne anonyme Classe interne non-nommée définie de la manière suivante où (I est une
interface ou une classe abstraite) :
new I () {
@Override
...
};
Un exemple complet :
interface I {
public void uneMethode () ;
}
class A {
void essai () {
I i = new I () {
@Override
public void uneMethode () {
System . out . println ( " Bou bou bou " ) ;
}
};
i . uneMethode () ;
}
}
...
public static void main ( String [] args ) {
A a = new A () ;
a . essai () ; // affiche " Bou bou bou "
}
10.2.4
Les classes internes statiques
Il existe un dernier type de classe interne : les classes internes statiques, qui ne sont pas liées à une instance de
la classe englobante.
Définition 10.4 (classe interne statique). Classe interne ne dépendant pas d’une instance de sa classe englobante :
class A {
static class B {
...
}
...
}
CHAPITRE 10. RÉAGIR À DES ÉVÈNEMENTS : LES ÉCOUTEURS
126
Naturellement la classe interne statique B ne peut qu’accéder aux méthodes statiques et aux attributs statiques
de A.
Exemple :
class A {
static String message = " messageA " ;
int nb = 0;
static class B {
public void essai () {
System . out . println ( " message : " + message ) ;
// System . out . println (" nb : "+ nb ) ; // interdit
}
}
}
...
public static void main ( String [] args ) {
new A . B () . essai () ; // affiche " messageA " ( noter la creation de l ’
instance de B sans instance de A )
}
10.2. CLASSES INTERNES ET ANONYMES
Interface
MenuListener
Adaptateur
-
MouseInputListener
MouseInputAdapter
MouseAdapter
MouseListener
MouseAdapter
MouseInputAdapter
MouseMotionListener
MouseWheelListener
PopupMenuListener
MouseMotionAdapter
MouseInputAdapter
MouseAdapter
-
PropertyChangeListener
TableColumnModelListener
-
TableModelListener
TreeExpansionListener
-
TreeModelListener
-
TreeSelectionListener
TreeWillExpandListener
-
UndoableEditListener
VetoableChangeListener
WindowFocusListener
WindowAdapter
WindowListener
WindowAdapter
WindowStateListener
WindowAdapter
127
Méthodes
menuCanceled(MenuEvent)
menuDeselected(MenuEvent)
menuSelected(MenuEvent)
mouseClicked(MouseEvent)
mouseEntered(MouseEvent)
mouseExited(MouseEvent)
mousePressed(MouseEvent)
mouseReleased(MouseEvent)
mouseDragged(MouseEvent)
mouseMoved(MouseEvent)
mouseClicked(MouseEvent)
mouseEntered(MouseEvent)
mouseExited(MouseEvent)
mousePressed(MouseEvent)
mouseReleased(MouseEvent)
mouseDragged(MouseEvent)
mouseMoved(MouseEvent)
mouseWheelMoved(MouseWheelEvent)
popupMenuCanceled(PopupMenuEvent)
popupMenuWillBecomeInvisible(PopupMenuEvent)
popupMenuWillBecomeVisible(PopupMenuEvent)
propertyChange(PropertyChangeEvent)
columnAdded(TableColumnModelEvent)
columnMoved(TableColumnModelEvent)
columnRemoved(TableColumnModelEvent)
columnMarginChanged(ChangeEvent)
columnSelectionChanged(ListSelectionEvent)
tableChanged(TableModelEvent)
treeCollapsed(TreeExpansionEvent)
treeExpanded(TreeExpansionEvent)
treeNodesChanged(TreeModelEvent)
treeNodesInserted(TreeModelEvent)
treeNodesRemoved(TreeModelEvent)
treeStructureChanged(TreeModelEvent)
valueChanged(TreeSelectionEvent)
treeWillCollapse(TreeExpansionEvent)
treeWillExpand(TreeExpansionEvent)
undoableEditHappened(UndoableEditEvent)
vetoableChange(PropertyChangeEvent)
windowGainedFocus(WindowEvent)
windowLostFocus(WindowEvent)
windowActivated(WindowEvent)
windowClosed(WindowEvent)
windowClosing(WindowEvent)
windowDeactivated(WindowEvent)
windowDeiconified(WindowEvent)
windowIconified(WindowEvent)
windowOpened(WindowEvent)
windowStateChanged(WindowEvent)
TABLE 10.2 – Ecouteurs et leurs adapateurs (suite)
128
CHAPITRE 10. RÉAGIR À DES ÉVÈNEMENTS : LES ÉCOUTEURS
Chapitre 11
Interfaces graphiques en SWING
Il existe deux grandes librairies graphiques : SWING, la librairie "officielle" développée initialement par Sun,
et SWT, la librairie développée initialement par IBM, conjointement avec la plateforme de développement Eclipse.
SWING est une solution entièrement en Java, qui s’exécute donc entièrement au sein de la machine virtuelle, tandis
que SWT fait appel au gestionnaire de fenêtre du système d’exploitation. Ceci permet donc à SWT d’offrir de
meilleures performances et une meilleure intégration de l’application dans le système d’exploitation (par exemple
pour réutiliser les boites de dialogue prédéfinies comme celle pour l’impression ...). Cependant, les interfaces
graphiques en SWING offre une meilleure portabilité.
Dans ce chapitre, nous présentons SWING, sachant que la programmation en SWT est assez similaire et détaillée dans le chapitre suivant. Pour comprendre le fonctionnement d’une interface graphique, les développements
seront faits en ligne de commande. Naturellement, il existe des outils de développement qui permettent de développer une interface graphique en SWING ou SWT de manière graphique. Le plus performant est la plateforme
de développement en Java "officielle", à savoir NetBeans.
Sommaire
11.1 Introduction à SWING . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
11.2 Gestionnaire de placement : l’interface LayoutManager . . . . . . . . . . . . . . . . . . . . 130
11.3 Dessiner : la classe Graphics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
Le contenu de ce chapitre reprend un cours rédigé initialement par Mickaël Montassier.
11.1
Introduction à SWING
Une interface graphique est constituée de composants graphiques (boutons, menus, fenêtres,...) disposés dans
des conteneurs (cadres, panneaux, ...) munis d’un gestionnaire de placement, capables de réagir à des évènements
(souris, clavier).
En Java, les fonctionnalités des composants, conteneurs, gestionnaires de placements et évènements sont décrites dans les interfaces ou les classes :
— Component (composants) ;
— Container (conteneurs) ;
— LayoutManager (gestionnaires de placement) ;
— EventListener (écouteurs)
SWING constitue un ajout à une librairie plus ancienne : AWT (Abstract Window Toolkit). Pour développer
une interface graphique, on utilise donc des classes ou interfaces issues d’AWT, ou bien des versions plus récentes
fournies par SWING, le cas échéant. D’une manière générale, les classes de SWING sont préfixées par la lettre
J : par exemple, la classe JButtion (SWING) est à utiliser de préférence ) la classe Button (AWT).
Pour importer ces librairies, il faut écrire :
129
CHAPITRE 11. INTERFACES GRAPHIQUES EN SWING
130
import java . awt .*;
import javax . swing .*;
Composants
Les composants sont les briques élémentaires d’une interface graphique. Quelques exemples :
—
—
—
—
—
JLabel : texte non modifiable
JTextField : ligne de texte éditable
JButton : bouton
JMenu : menu
...
Component vs. Container
Un Container est un Component qui possède la particularité de pouvoir contenir d’autres Component. Par
exemple, les JComponent sont des Container. Il est possible d’imbriquer des conteneurs et de construire une
hiérarchie d’objets graphiques.
Pour ajouter un composant x dans un conteneur y, il suffit d’utiliser la méthode add :
y . add ( x ) ;
Quelques conteneurs :
— JPanel : très simple, convient pour recevoir des boutons,...
— JFrame : fenêtre avec titre et bordures
— JDialog : permet d’obtenir une entrée de l’utilisateur
— ...
11.2
Gestionnaire de placement : l’interface LayoutManager
L’interface LayoutManager est implémentée par un certain nombre de classes de AWT. Un LayoutManager
gère la disposition des composants dans un conteneur.
Les principaux LayoutManager sont :
— FlowLayout : arrange les composants de gauche à droite
— BorderLayout : dispose les composants dans les régions - north, south, east, west, center
— GridLayout : arrange les éléments sur une grille.
Un conteneur possède un attribut LayoutManager affecté par défaut : FlowLayout pour JPanel, BorderLayout
pour JPanel, ... Il est possible de changer le LayoutManager d’un conteneur : par exemple,
p . setLayout ( new BorderLayout () ) ;
ou de faire l’agencement à la main :
p . setLayout ( null ) ;
x . setLocation (a , b ) ;
x . setSize (c , d )
ou
x . setBounds (a ,b ,c , d ) ;
11.3. DESSINER : LA CLASSE GRAPHICS
131
Exemple : fenêtre principale
Dans cet exemple, nous allons créer une fenêtre principale par héritage. Naturellement, on pourrait le faire
aussi par délégation.
import java . awt .*;
import javax . swing .*;
public class Fenetre extends JFrame {
private JButton b1 , b2 ;
private JLabel lbl ;
public Fenetre ( String titre ) {
super ( titre ) ;
setBounds (0 ,0 ,400 ,200) ;
b1 = new JButton ( " B1 " ) ;
b2 = new JButton ( " B2 " ) ;
lbl = new JLabel ( " texte quelconque " ) ;
add ( b1 , " North " ) ;
add ( lbl , " Center " ) ;
add ( b2 , " South " ) ;
setVisible ( true ) ;
}
public static void main ( String [] args ) {
Fenetre f = new Fenetre ( " Exemple 1 " ) ;
}
}
11.3
Dessiner : la classe Graphics
La classe java.awt.graphics est une classe abstraite. Elle représente le contexte graphique permettant de dessiner sur un composant.
Il est possible d’obtenir le contexte graphique d’un composant x par :
x . getGraphics () ;
Il est alors possible de dessiner dans le contexte graphique :
—
—
—
—
—
drawOval(int x, int y, int w, int h)
drawLine(int x1, int y1, int x2, int y2)
drawRect(int x, int y, int w, int h)
setColor(Color c)
...
Considérons un petit exemple : dans une fenêtre, ajoutons un Canvas dans lequel sera dessinée une ellipse.
// Fenetre . java
import java . awt .*;
import javax . swing .*;
public class Fenetre extends JFrame {
private Canvas can ;
public Fenetre ( String titre ) {
CHAPITRE 11. INTERFACES GRAPHIQUES EN SWING
132
super ( titre ) ;
setBounds (0 ,0 ,400 ,200) ;
can = new Canvas () ;
add ( can , " Center " ) ;
setVisible ( true ) ;
Graphics g = can . getGraphics () ;
g . setColor ( Color . black ) ;
g . drawOval (160 ,10 ,65 ,100) ;
}
}
Bizarre... l’ellipse apparaît une fraction de seconde et disparait.
En fait quand un conteneur s’affiche ou se réaffiche, il appelle sa méthode
paint(Graphics g) qui appelle la méthode paint(Graphics g) de chacun des composants présents dans le
conteneur. Or la méthode paint de Canvas remplit le dessin de blanc... Que faire ?
Une réponse possible : Il faut alors créer une classe Dessin dérivée de Canvas et redéfinir la méthode paint
de Canvas dans cette classe.
// Dessin . java
import java . awt .*;
public class Dessin extends Canvas {
public void paint ( Graphics g ) {
super . paint ( g ) ;
g . setColor ( Color . black ) ;
g . drawOval (10 ,10 ,150 ,100) ;
}
}
// Fenetre . java
import java . awt .*;
import javax . swing .*;
public class Fenetre extends JFrame {
private Dessin dessin ;
public Fenetre ( String titre ) {
super ( titre ) ;
setBounds (0 ,0 ,400 ,200) ;
dessin = new Dessin () ;
add ( dessin , " Center " ) ;
setVisible ( true ) ;
}
}
Exercice 126. (??) Dans une fenêtre, ajoutons deux boutons et une ellipse. En réponse au clic sur le premier
bouton, l’ellipse change de couleur et devient rouge. En réponse au clic sur le second bouton, l’ellipse retrouve
sa couleur initiale.
11.3. DESSINER : LA CLASSE GRAPHICS
Fiche de synthèse
Java
— composants graphiques : la classe Component - exemples de composants : JLabel,
JTextField, JButton, JMenu ...
— conteneurs : la classe Container - exemples de conteneurs : JPanel, JFrame, JDialog ...
— gestionnaires de placement : l’interface LayoutManager - exemples de gestionnaires :
FlowLayout, BorderLayout, GridLayout ...
— primitives graphiques : la classe Graphics
Exemple de code (fenêtre et placement de composants) :
public class Fenetre extends JFrame {
private JButton b1 , b2 ;
private JLabel lbl ;
public Fenetre ( String titre ) {
super ( titre ) ;
setBounds (0 ,0 ,400 ,200) ;
b1 = new JButton ( " B1 " ) ;
b2 = new JButton ( " B2 " ) ;
lbl = new JLabel ( " texte quelconque " ) ;
add ( b1 , " North " ) ;
add ( lbl , " Center " ) ;
add ( b2 , " South " ) ;
setVisible ( true ) ;
}
public static void main ( String [] args ) {
Fenetre f = new Fenetre ( " Exemple 1 " ) ;
}
}
Exemple de code (dessin) :
public class Dessin extends Canvas {
public void paint ( Graphics g ) {
super . paint ( g ) ;
g . setColor ( Color . black ) ;
g . drawOval (10 ,10 ,150 ,100) ;
}
}
public class Fenetre extends JFrame {
private Dessin dessin ;
public Fenetre ( String titre ) {
super ( titre ) ;
setBounds (0 ,0 ,400 ,200) ;
dessin = new Dessin () ;
add ( dessin , " Center " ) ;
setVisible ( true ) ;
}
}
133
134
CHAPITRE 11. INTERFACES GRAPHIQUES EN SWING
Chapitre 12
Interfaces graphiques en SWT
Les principales particularités de la bibliothèque SWT (Standard Widget Toolkit) développée par IBM et la
fondation Eclipse sont l’utilisation de composants natifs, lorsqu’ils sont disponibles, tout en conservant une bonne
portabilité.
Sommaire
12.1
12.2
12.3
12.4
12.5
12.6
Découverte sur un exemple type . . . .
Multiprocessus et SWT . . . . . . . . .
Principaux écouteurs de SWT . . . . .
Composants . . . . . . . . . . . . . . .
Disposition des composants : Layouts .
Composants SWT . . . . . . . . . . . .
12.6.1 Liste des principaux composants .
12.6.2 Utilisation de quelque composants
12.7 Boites de dialogue . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
135
136
137
139
140
142
142
143
147
Bibliographie :
— S. Holzner, Eclipse, O’Reilly ;
— J.-M. Doudouc, Développons en Java ;
— A. Van Hemmis, Using the Eclipse GUI outside the Eclipse Workbench, Part 1 : Using JFace and SWT in
stand-alone mode, IBM developperWorks ;
— A. Van Hemmis, Using the Eclipse GUI outside the Eclipse Workbench, Part 2 : Using the JFace image
registry, IBM developperWorks ;
— A. Van Hemmis, Using the Eclipse GUI outside the Eclipse Workbench, Part 3 : Adding actions, menus
and toolbars, IBM developperWorks ;
— L. Kues, K. Radloff, Getting Your Feet Wet with the SWT StyledText Widget , IBM.
12.1
Découverte sur un exemple type
Commençons par un exemple simple :
import org . eclipse . swt .*;
import org . eclipse . swt . widgets .*;
public class SWTExemple {
public static void main ( String [] args ) {
Display d = new Display () ;
135
CHAPITRE 12. INTERFACES GRAPHIQUES EN SWT
136
Shell s = new Shell ( d ) ;
s . setSize (640 ,480) ;
Label l = new Label (s , SWT . CENTER ) ;
l . setText ( " bzzz ( Maya ) " ) ;
l . setBounds ( s . getClientArea () ) ;
s . open () ;
while (! s . isDisposed () ) {
if (! d . readAndDispatch () )
d . sleep () ;
}
d . dispose () ;
}
}
Quelques explications :
1. l’objet Display représente une session SWT : il relie SWT à l’interface graphique du système d’exploitation ;
2. un objet Shell est une fenêtre qui est contrôlée par le gestionnaire de fenêtre de l’OS : remarquer que
c’est lors de l’appel au constructeur que l’on spécifie la session SWT à utiliser ;
3. on fixe la taille de la fenêtre avec la méthode setSize ;
4. on crée une étiquette dont le texte sera centré ;
5. on définit le contenu du texte avec setText ;
6. on adapte l’étiquette aux dimensions de la fenêtre avec setBounds ;
7. enfin, avec la méthode open, on affiche la fenêtre (le processus attendra la fermeture de la fenêtre pour se
terminer) ;
8. dans la boucle, on regarde tant que la fenêtre est ouverte, s’il y a quelque chose à afficher (sinon, on se met
en veille) ;
9. lorsque la fenêtre a été fermée, on libère la session avec la méthode dispose : ceci est nécessaire, car
Session est un objet natif, et n’est donc pas géré par le ramasse-miettes ;
D’une manière générale, en SWT, il faut libérer manuellement toutes les ressources du système d’exploitation
qui ont été allouées (comme les objets Shell).
Exercice 127. (?) Testez le code de l’exemple.
12.2
Multiprocessus et SWT
On ne peut pas manipuler directement l’interface graphique à partir d’un Thread, sous peine de lever une
exception
org . eclipse . swt . SWTException : Invalid thread access
Pour résoudre ce problème, il faut mettre les appels graphiques dans la méthode run d’un processus dédié, en
créant le processus à l’intérieur de la méthode run du processus initial.
Deux possibilités :
— d.syncExec( monProcessusGraphique) : les 2 processus seront synchronisés : la méthode run du
processus maitre sera bloquée jusqu’à la fin de la méthode run du processus graphique. Exemple : message
bloquant pour l’utilisateur ;
— d.asyncExec( monProcessusGraphique ) : la méthode run du processus maitre continuera à s’exécuter en parallèle de la méthode run du processus graphique.
où d désigne le display concerné.
Exemple (affichage dynamique d’une variable i dans un Label) :
12.3. PRINCIPAUX ÉCOUTEURS DE SWT
137
public void run () throws I l l e g a l M o n i t o r S t a t e E x c e p t i o n {
Object v = new Object () ;
// l . setText ( " Start ") ;// Illegal thread access
for ( i =1; i <=5; i ++) {
try { synchronized ( v ) {
v . wait (500) ;}
}
catch ( I nt err upt ed Exc ep tio n e ) { System . out . println ( " Exception " + e
) ;}
parent . getDisplay () . syncExec ( new Runnable () {
public void run () {
l . setText ( " nb = " + i ) ;
System . out . println ( " nb = " + i ) ;
}
}) ;
}
}
Exercice 128. (?) Testez.
12.3
Principaux écouteurs de SWT
Les écouteurs de SWT fonctionnent de façon similaire aux écouteurs de AWT. On utilise le plus souvent des
classes internes anonymes pour les créer et les relier au composant, de la manière suivante :
Button b = new Button ( shell , SWT . push ) ;
b . setText ( " Maya " ) ;
b . add Sel ec tio nL ist ene r ( new SelectionListener () {
public void widgetSelected ( SelectionEvent e ) {
System . out . println ( " bzzz " ) ;
}
public void w id g et D ef au l tS el e ct e d ( SelectionEvent e ) {
widgetSelected ( e ) ;
}
}) ;
Exercice 129. (?) Faire un bouton en SWT et vérifier sa cohérence avec le gestionnaire graphique.
Adapter Comme avec Swing, SWT propose un ensemble de classes Adapter qui implémentent partiellement
une des interfaces Listener. Ceci permet d’implémenter un écouteur, en ne donnant le code que pour certaines
des ses méthodes :
Button b = new Button ( shell , SWT . push ) ;
b . setText ( " Maya " ) ;
b . add Sel ec tio nL ist en er ( new SelectionAdapter () {
public void widgetSelected ( SelectionEvent e ) {
System . out . println ( " bzzz " ) ;
}
}) ;
Exercice 130. (?) Simplifier votre bouton en utilisant un Adapter
CHAPITRE 12. INTERFACES GRAPHIQUES EN SWT
138
FocusListener Cet écouteur prend en charge la gestion du focus, via 2 méthodes focusGained et focusLost.
Text t = new Text (s , SWT . BORDER ) ;
t . setText ( " Bzzzz " ) ;
t . setBounds (10 ,10 ,100 ,25) ;
t . addFocusListener ( new FocusListener () {
public void focusGained ( FocusEvent e ) {
System . out . println ( e . widget + " obtient le focus "
);
}
public void focusLost ( FocusEvent e ) {
System . out . println ( e . widget + " perd le focus " ) ;
}
}
);
Exercice 131. (?) Testez ce code.
KeyListener Cet écouteur prend en charge la gestion du clavier, via 2 méthodes keyPressed et keyReleased.
Text t = new Text (s , SWT . BORDER ) ;
t . setText ( " Bzzzz " ) ;
t . setBounds (10 ,10 ,100 ,25) ;
t . addKeyListener ( new KeyAdapter () {
public void keyReleased ( KeyEvent e ) {
System . out . println ( e . widget + " a çreu un
caractere du clavier " ) ;
switch ( e . character ) {
case SWT . CR : System . out . println ( "
Touche Entree " ) ; break ;
case SWT . DEL : System . out . println ( "
Touche Suppr " ) ; break ;
case SWT . ESC : System . out . println ( "
Touche Echap " ) ; break ;
default : System . out . println ( " Touche " +
e . character ) ;
}
}
}
);
MouseListener, MouseTrackListener Les interfaces MouseListener et MouseTrackListener modélisent
la souris (clics, déplacement, trajectoire).
L’interface MouseListener définit trois méthodes mouseDown, mouseUp, mouseDoubleClick.
Les variable button, x et y contiennent respectivement le numéro du bouton appuyé (dépendant du système
d’exploitation !), et les coordonnées du pointeur.
Text t = new Text (s , SWT . BORDER ) ;
t . setText ( " Bzzzz " ) ;
t . setBounds (10 ,10 ,100 ,25) ;
t . addMouseListener ( new MouseAdapter () {
public void mouseDown ( MouseEvent e ) {
System . out . println ( e . widget + " selectionne " ) ;
12.4. COMPOSANTS
139
System . out . println ( " par le bouton " + e . button + " , x = " + e . x
+" y="+e.y);
}
}
);
L’interface MouseTrackListener contient entre autres 2 méthodes mouseEnter et mouseExit détectant
l’entrée et la sortie de la souris sur un composant.
Exemple (changement de la couleur de fond d’un composant lors du survol)
Text t = new Text (s , SWT . BORDER ) ;
t . setText ( " Bzzzz " ) ;
t . setBounds (10 ,10 ,100 ,25) ;
final Color blanc = new Color (d ,255 ,255 ,255) ;
final Color survol = new Color (d ,200 ,150 ,150) ;
t . ad d Mo us e Tr a ck Li s te ne r ( new MouseTrackAdapter () {
public void mouseEnter ( MouseEvent e ) {
(( Text ) e . widget ) . setBackground ( survol ) ;}
public void mouseExit ( MouseEvent e ) {
(( Text ) e . widget ) . setBackground ( blanc ) ;}
}) ;
Exercice 132. (?) Testez ce code.
PaintListener L’interface PaintListener possède une méthode paintControl appelée lors de la nécessité de redessiner un composant, via un évènement PaintEvent qui possède une variable gc correspondant au
contexte graphique, et des variables height, width, x et y spécifiant la zone à redessiner.
Exercice 133. (?) Que fait le code ci-dessous ?
public static boolean drapeau = true ;
final Color blanc = new Color (d ,255 ,255 ,255) ;
final Color survol = new Color (d ,200 ,150 ,150) ;
...
Text t = new Text (s , SWT . BORDER ) ;
t . setText ( " Bzzzz " ) ; t . setBounds (10 ,10 ,100 ,25) ;
t . addPaintListener ( new PaintListener () {
public void paintControl ( PaintEvent e ) {
if ( drapeau )
(( Text ) e . widget ) . setBackground ( survol )
;
if (! drapeau )
(( Text ) e . widget ) . setBackground ( blanc ) ;
drapeau =! drapeau ;}}) ;
12.4
Composants
Les composants peuvent être classés en deux familles :
— les contrôles, qui sont des composants graphiques classiques et héritent tous de la classe abstraite
Control ;
— les conteneurs, qui permettent de regrouper des contrôles, et héritant tous de la classe Composite.
CHAPITRE 12. INTERFACES GRAPHIQUES EN SWT
140
La classe abstraite Control Lors de la création d’un composant Control, il faut préciser l’objet Composite
parent, en le passant au constructeur. Les principales méthodes de cette classe sont :
— boolean forceFocus() : donne le focus à un composant pour lui permettre de recevoir les entrées clavier
— Display getDisplay()
— Shell getShell()
— void pack() : recalcule la taille du composant
— void setEnabled() : rendre actif
— void setFocus() : idem forceFocus mais en moins "autoritaire"
— void setSize()
— void setVisible()
La classe Composite Elle possède trois styles : SWT.BORDER (bordure), SWT.H_SCROLL (barre de défilement
horizontal), SWT.V_SCROLL (barre de défilement vertical).
Exemple :
Composite c =
new Composite (s , SWT . BORDER | SWT . H_SCROLL | SWT . V_SCROLL ) ;
c . setBounds (10 ,10 ,620 ,400) ;
c . setBackground ( new Color (d ,100 ,150 ,100) ) ;
Label l = new Label (c , SWT . BORDER ) ;
l . setBackground ( new Color (d ,150 ,100 ,100) ) ;
l . setBounds (10 ,10 ,500 ,200) ;
l . setText ( " Bzzz ( Maya ) " ) ;
La classe Group Elle étend les possibilités de la classe Composite. On précise la disposition des composants
avec la méthode setLayout. Un titre peut être préciser avec setText
Exemple :
Group g = new Group (s , SWT . BORDER ) ;
g . setBounds (10 ,10 ,620 ,240) ; g . setText ( " Mes images " ) ;
Label l = new Label ( g , SWT . BORDER ) ; l . setBounds (10 ,10 ,280 ,200) ;
Image i = new Image (d , " image . jpg " ) ; ImageData id = i . getImageData () ;
i = new Image (d , id . scaledTo (280 ,200) ) ;
l . setImage ( i ) ;
Label l2 = new Label ( g , SWT . BORDER ) ; l2 . setBounds (310 ,10 ,280 ,200) ;
Image i2 = new Image (d , " image2 . jpg " ) ; ImageData id2 = i2 . getImageData
() ;
i2 = new Image (d , id2 . scaledTo (280 ,200) ) ;
l2 . setImage ( i2 ) ;
12.5
Disposition des composants : Layouts
Positionnement explicite Il est possible de préciser explicitement avec la méthode setBounds les coordonnées et la taille d’un composant en coordonnées relatives au "conteneur".
Exemple :
Button b = new Button ( shell , SWT . PUSH ) ;
b . setBounds (5 ,10 ,100 ,200) ;
12.5. DISPOSITION DES COMPOSANTS : LAYOUTS
141
Ce code crée un bouton dont le coin supérieur gauche est 5 pixels à droite et 10 pixels plus bas que le coin
supérieur gauche de la fenêtre shell, et de largeur 100 pixels, pour une hauteur 200 pixels.
Ce procédé permet de placer précisément les composants à l’intérieur d’une fenêtre mais oblige à calculer les
coordonnées de tous les composants et le placement est statique.
En général, il est donc préférable de faire appel à un gestionnaire de placement.
Gestionnaire FillLayout Pour une disposition sur une ligne, sans enroulement, en occupant tout l’espace du
conteneur.
Exemple :
Shell sh = new Shell () ;
FillLayout gl = new FillLayout () ;
sh . setLayout ( gl ) ;
for ( int i = 0; i <18; i ++) {
Button b = new Button ( sh , SWT . PUSH | SWT . CENTER ) ;
b . setText ( " Bouton " + i ) ;
b . setSize (100 ,40) ;
}
Gestionnaire GridLayout Pour une disposition sur une grille : il suffit de fixer le nombre de colonnes avec
l’attribut numColumns, ensuite à chaque création d’un composant, il sera placé dans la prochaine cellule de libre
selon l’ordre naturel.
Exemple :
Shell sh = new Shell () ;
GridLayout gl = new GridLayout () ;
gl . numColumns = 4;
gl . ma ke C ol u mn sE q ua lW i dt h = true ;
sh . setLayout ( gl ) ;
for ( int i = 0; i <18; i ++) {
Button b = new Button ( sh , SWT . PUSH | SWT . CENTER ) ;
b . setText ( " Bouton " + i ) ;
b . setSize (100 ,40) ;
}
Gestionnaire RowLayout Pour une disposition sur une seule ligne, s’adaptant aux dimensions du conteneur,
avec enroulement "dynamique".
Exemple :
Shell sh = new Shell () ;
RowLayout gl = new RowLayout () ;
sh . setLayout ( gl ) ;
for ( int i = 0; i <18; i ++) {
Button b = new Button ( sh , SWT . PUSH | SWT . CENTER ) ;
b . setText ( " Bouton " + i ) ;
b . setSize (100 ,40) ;
}
CHAPITRE 12. INTERFACES GRAPHIQUES EN SWT
142
Gestionnaire FormLayout Plus précis : on munit chaque composant d’un objet FormData précisant le décalage
en pixels par rapport à une position sur le conteneur exprimée en pourcentages, via des objets FormAttachment
Exemple :
Label l = new Label ( s , SWT . BORDER ) ; l . setText ( " Maya " ) ; l . setSize
(200 ,100) ;
FormData fd = new FormData () ; fd . left = new FormAttachment (0 ,5) ;
fd . right = new FormAttachment (30 , -5) ; fd . bottom = new FormAttachment
(0 ,5) ;
fd . top = new FormAttachment (30 , -5) ; l . setLayoutData ( fd ) ;
Label l2 = new Label ( s , SWT . BORDER ) ; l2 . setText ( " bzzz " ) ;
l2 . setSize (200 ,100) ;
FormData fd2 = new FormData () ;
fd2 . left = new FormAttachment (50 ,5) ; fd2 . right = new FormAttachment
(80 , -5) ;
fd2 . bottom = new FormAttachment (50 ,5) ; fd2 . top = new FormAttachment
(80 , -5) ;
l2 . setLayoutData ( fd2 ) ;
12.6
Composants SWT
12.6.1
Liste des principaux composants
—
—
—
—
—
—
—
—
—
—
—
—
—
—
—
—
—
—
—
—
—
—
—
—
—
—
—
—
Button : bouton - plusieurs styles : CHECK, PUSH, RADIO, TOGGLE, FLAT, ARROW ...
Canvas : composant support pour d’autres composants. Surface dessinable
Caret : curseur
Combo : combinaison d’un composant de saisie de texte et d’une liste déroulante
Composite : contient d’autres composants
CoolBar : un Composite qui permet le repositionnement des composants par l’utilisateur
CoolItem : composants d’un CoolBar
Group : regroupement de composants, possibilité d’une étiquette et d’une bordure
Label : étiquette
List : sélection d’items dans une liste
Menu : no comment :- ;
MenuItem : item d’un menu ;-)
ProgressBar : barre de progression
Sash : permet à l’utilisateur de redimensionner un ensemble de composants, en déplaçant les lignes de
séparation
Scale : échelle numérique
ScrollBar : représente un intervalle de valeurs positives
Shell : une fénêtre gérée par le gestionnaire de fenêtre du système graphique
Slider : réprésente un intervalle, laissant le choix d’une valeur numérique par positionnement d’un curseur
TabFolder : regroupement d’onglets
TabItem : onglet
Table : tableau
TableColumn : colonne
TableItem : cellule
Text : texte éditable
ToolBar : boîte à outils
ToolItem : outil ;-)
Tree : arbre
TreeItem : feuille
12.6. COMPOSANTS SWT
12.6.2
143
Utilisation de quelque composants
Browser Application remarquable de SWT et de ses choix techniques : il permet de faire appel à un navigateur
web natif : IE sous windows, firefox sous linux, safari sous Mac OS...
Voici un exemple de code ouvrant un navigateur (si aucun navigateur natif n’est disponible, une exception
SWTError est levée)
import org . eclipse . swt . browser .*;
public class Navigateur {
public Navigateur () {
Display d = new Display () ; Shell shell = new Shell ( d ) ;
shell . setText ( " Navigateur " ) ; shell . setSize (800 ,600) ;
// navigateur Mozilla / IE / Safari ...
try { Browser b = new Browser ( shell , SWT . BORDER ) ;
b . setUrl ( " http :// www . csszengarden . com / " ) ;
// ou b . setText ( code HTML dans une chaine ) ;
b . setBounds (1 ,1 ,799 ,599) ;
}
catch ( Exception e ) { System . out . println ( " Pb " ) ; System .
exit ( -1) ;}
shell . open () ;
while (! shell . isDisposed () ) { if (! d . readAndDispatch () ) d
. sleep () ;}
d . dispose () ;
}}
GC Cette classe encapsule un contexte graphique, dans lequel il va être permis de dessiner. Elle est rattachée à un
composant de type Canvas. Pour dessiner, on utilise des primitives de dessin fournies par les méthodes drawXXX
ou fillXXX comme drawText, drawLine, drawOval ...
Il est nécessaire de dessiner lors du rafraichissement du conteneur, en plaçant le code dans un écouteur de type
paintListener.
Exemple :
final Canvas c = new Canvas (s , SWT . BORDER ) ;
c . setBounds (10 ,10 ,620 ,400) ; c . setBackground ( new Color (d ,100 ,150 ,100) ) ;
c . addPaintListener ( new PaintListener () {
public void paintControl ( PaintEvent e ) {
GC gc = new GC ( c ) ; gc . setForeground ( new Color (d , 0 , 0 , 0) ) ;
// gc . setClipping (0 ,0 ,200 ,200) ; // permet d ’ indiquer la zone a
redessiner
gc . drawText ( " Bzz ( Maya ) " ,100 ,10) ; gc . drawLine (10 ,10 ,100 ,100) ;
gc . setForeground ( new Color (d ,150 ,100 ,100) ) ; gc . drawOval
(60 ,60 ,100 ,100) ;
gc . setBackground ( new Color (d ,100 ,100 ,150) ) ; gc . fillOval
(60 ,60 ,100 ,100) ;
gc . dispose () ;
}}) ;
Label Pour représenter une étiquette contenant du texte ou une image. Les possibilités de style sont variées :
BORDER, CENTER, LEFT, RIGHT, WRAP, SEPARATOR... Elle possède trois méthodes principales :
— setAlignement(int) : pour préciser la disposition des éléments ;
— setText(String) ;
CHAPITRE 12. INTERFACES GRAPHIQUES EN SWT
144
— setImage(Image).
Exemple :
Label l = new Label (s , SWT . BORDER | SWT . CENTER ) ;
l . setBackground ( new Color (d ,150 ,100 ,100) ) ;
l . setBounds (10 ,10 ,500 ,200) ;
Image i = new Image (d , " image . jpg " ) ; ImageData id = i . getImageData () ;
Image i2 = new Image (d , id . scaledTo (490 ,190) ) ; l . setImage ( i2 ) ;
List Avec un objet List, on peut sélectionner un (style SINGLE) ou plusieurs (style MULTI) éléments.
Exemple :
final List l = new List (s , SWT . BORDER | SWT . MULTI ) ;
l . setBackground ( new Color (d ,150 ,100 ,100) ) ;
l . setBounds (10 ,10 ,500 ,200) ;
final String [] items = new String []{ " Maya l ’ abeille " ," Sauron " ,"
Tetouillou le Dragon " };
l . setItems ( items ) ;
l . a dd Sel ec tio nL ist ene r ( new SelectionAdapter () {
public void widgetSelected ( SelectionEvent e ) {
System . out . println ( " Items selectionnes : " ) ;
int [] indices = l . g etSelect ionIndi ces () ;
for ( int i =0.. indices . length -1)
System . out . println ( items [ indices [ i ]]) ;
}}) ;
Menu Cette classe possède plusieurs styles : BAR, DROP_DOWN, POP_UP ... On ajoute des entrées à un menu
avec des objets MenuItem : pour définir des sous-menus, on peut associer un Menu à un MenuItem via la méthode
setMenu.
Exemple :
Menu m = new Menu (s , SWT . BORDER | SWT . BAR ) ;
s . setMenuBar ( m ) ;
// sous menu fichier
MenuItem optionFichier = new MenuItem (m , SWT . CASCADE ) ;
optionFichier . setText ( " Fichier " ) ;
Menu menuFichier = new Menu ( s , SWT . DROP_DOWN ) ;
optionFichier . setMenu ( menuFichier ) ;
MenuItem open = new MenuItem ( menuFichier , SWT . PUSH ) ;
open . setText ( " Ouvrir " ) ;
open . ad dS ele ct ion Li ste ner ( new SelectionAdapter () {
public void widgetSelected ( SelectionEvent e ) {
System . out . println ( " Merci de patienter , tududu ... " ) ;
}}) ;
MenuItem fermer = new MenuItem ( menuFichier , SWT . PUSH ) ;
fermer . setText ( " Fermer " ) ;
// sous menu aide
MenuItem optionAide = new MenuItem (m , SWT . CASCADE ) ;
optionAide . setText ( " Aide " ) ;
Menu menuAide = new Menu (s , SWT . DROP_DOWN ) ;
12.6. COMPOSANTS SWT
145
optionAide . setMenu ( menuAide ) ;
MenuItem sorry = new MenuItem ( menuAide , SWT . PUSH ) ;
sorry . setText ( " Sorry " ) ;
ProgressBar C’est une barre de progression avec plusieurs style : INDETERMINATE, SMOOTH, HORIZONTAL, VERTICAL. Dans le style INDETERMINATE, la barre fait des aller-retours indéfiniment. On fixe la valeur
avec setSelection et les bornes avec setMinimum, setMaximum.
Exemple :
ProgressBar pb = new ProgressBar (s , SWT . BORDER | SWT . INDETERMINATE ) ;
pb . setBackground ( new Color (d ,150 ,100 ,100) ) ; pb . setBounds (10 ,10 ,620 ,20)
;
pb . setMinimum (1) ; pb . setMaximum (100) ;
ProgressBar pb2 = new ProgressBar (s , SWT . BORDER | SWT . HORIZONTAL
pb2 . setBackground ( new Color (d ,100 ,150 ,100) ) ; pb2 . setBounds
(10 ,40 ,620 ,20) ;
pb2 . setMinimum (1) ; pb2 . setMaximum (100) ; pb2 . setSelection (40) ;
);
ProgressBar pb3 = new ProgressBar (s , SWT . BORDER | SWT . HORIZONTAL | SWT .
SMOOTH ) ;
pb3 . setBackground ( new Color (d ,100 ,100 ,150) ) ; pb3 . setBounds
(10 ,70 ,620 ,20) ;
pb3 . setMinimum (1) ; pb3 . setMaximum (100) ; pb3 . setSelection (60) ;
Shell Rappelons que c’est une fenêtre gérée directement par le serveur graphique.
Plusieurs styles sont disponibles :
— BORDER (bordure) ;
— H_SCROLL (barre de défilement horizontale) ;
— V_SCROLL (barre de défilement verticale) ;
— CLOSE (bouton de fermeture) ;
— MIN (bouton pour diminuer) ;
— MAX (bouton pour maximiser) ;
— RESIZE (pour permettre l’agrandissement) ;
— TITLE (avec un titre) ;
— ...
StyledText Ce composant est un moyen simple d’afficher/éditer du texte avec mise en forme. On peut ainsi
spécifier la couleur du texte, la couleur de l’arrière plan, le style (normal, italique, gras), Il est utilisé par Eclipse
pour ses éditeurs de code ... On applique un style à une portion du texte à l’aide d’un objet StyleRange et ses
attributs start, length, fontStyle...
Exemple (Maya en gras et bleu, Sauron en gras et rouge) :
StyledText st = new StyledText ( sh , SWT . BORDER ) ;
st . setText ( " Maya : prete a plonger en pique \\ nSauron : bien recu , Roger "
);
StyleRange sr = new StyleRange () ; sr . start =32; sr . length =6;
sr . fontStyle = SWT . BOLD ;
sr . foreground = d . getSystemColor ( SWT . COLOR \ _RED ) ;
st . setStyleRange ( sr ) ;
StyleRange sr2 = new StyleRange () ;
CHAPITRE 12. INTERFACES GRAPHIQUES EN SWT
146
sr2 . start =0; sr2 . length =4; sr2 . fontStyle = SWT . BOLD ;
sr2 . foreground = d . getSystemColor ( SWT . COLOR \ _BLUE ) ;
st . setStyleRange ( sr2 ) ;
TabFolder Cette classe permet de gérer des onglets de manière similaire aux menus, avec l’aide d’objets
TabItem.
Exemple :
TabFolder tb = new TabFolder (s , SWT . BORDER ) ;
tb . setBounds (10 ,10 ,620 ,420) ;
TabItem ti1 = new TabItem ( tb , SWT . BORDER ) ; ti1 . setText ( " Image 1 " ) ;
Label l = new Label ( tb , SWT . BORDER ) ;
Image i = new Image (d , " image . jpg " ) ;
ImageData id = i . getImageData () ; i = new Image (d , id . scaledTo (600 ,400) )
;
l . setImage ( i ) ; ti1 . setControl ( l ) ;
TabItem ti2 = new TabItem ( tb , SWT . BORDER ) ; ti2 . setText ( " Image 2 " ) ;
Label l2 = new Label ( tb , SWT . BORDER ) ;
Image i2 = new Image (d , " image2 . jpg " ) ;
ImageData id2 = i2 . getImageData () ;
i2 = new Image (d , id2 . scaledTo (600 ,400) ) ;
l2 . setImage ( i2 ) ; ti2 . setControl ( l2 ) ;
Table Cette classe permet de créer des tableaux : on utilise les objets TableColmumn (colonnes) et TableItem
(lignes) de la manière suivante :
Table t = new Table (s , SWT . BORDER ) ;
t . setBounds (10 ,10 ,620 ,420) ;
TableColumn tc1 = new TableColumn (t , SWT . CENTER ) ;
tc1 . setText ( " Animal " ) ; tc1 . setWidth (300) ;
TableColumn tc2 = new TableColumn (t , SWT . CENTER ) ;
tc2 . setWidth (300) ; tc2 . setText ( " Cri " ) ;
t . setHeaderVisible ( true ) ; t . setLinesVisible ( true ) ;
TableItem ti1 = new TableItem (t , SWT . BORDER ) ;
ti1 . setText (0 , " Maya " ) ; ti1 . setText (1 , " bzzzz " ) ;
TableItem ti2 = new TableItem (t , SWT . BORDER ) ;
ti2 . setText (0 , " Fleur carnivore " ) ;
Image i2 = new Image (d , " image2 . jpg " ) ;
ImageData id2 = i2 . getImageData () ;
i2 = new Image (d , id2 . scaledTo (150 ,100) ) ;
ti2 . setImage (1 , i2 ) ;
ToolBar Classe analogue à la classe Menu mais spécialisée pour les boites à outils : ainsi on peut simplement
dans une entrée préciser une icône et un label.
Exemple :
ToolBar tb = new ToolBar (s , SWT . BORDER ) ;
tb . setBounds (10 ,10 ,620 ,240) ;
Image i = new Image (d , " image . jpg " ) ;
ImageData id = i . getImageData () ;
i = new Image (d , id . scaledTo (150 ,100) ) ;
12.7. BOITES DE DIALOGUE
147
ToolItem ti1 = new ToolItem ( tb , SWT . CHECK ) ;
ti1 . setImage ( i ) ;
ti1 . setText ( " Snow " ) ;
Image i2 = new Image (d , " image2 . jpg " ) ;
ImageData id2 = i2 . getImageData () ;
i2 = new Image (d , id2 . scaledTo (150 ,100) ) ;
ToolItem ti2 = new ToolItem ( tb , SWT . CHECK ) ;
ti2 . setImage ( i2 ) ;
ti2 . setText ( " Flower " ) ;
Tree Elle permet de créer des arbres dont chaque noeud est un TreeItem : pour créer un noeud fils, il suffit de
passer au constructeur le TreeItem (ou Tree pour la racine) correspondant.
Exemple :
Tree t = new Tree (s , SWT . BORDER ) ;
t . setBounds (10 ,10 ,620 ,420) ;
TreeItem ti1 = new TreeItem (t , SWT . CENTER ) ;
ti1 . setText ( " Animal " ) ;
TreeItem ti2 = new TreeItem (t , SWT . CENTER ) ;
ti2 . setText ( " Images " ) ;
TreeItem animal1 = new TreeItem ( ti1 , SWT . BORDER ) ;
animal1 . setText ( " Maya " ) ;
TreeItem animal2 = new TreeItem ( ti1 , SWT . BORDER ) ;
animal2 . setText ( " Tetouillou " ) ;
TreeItem image = new TreeItem ( ti2 , SWT . BORDER ) ;
Image i = new Image (d , " image . jpg " ) ; ImageData id = i . getImageData () ;
i = new Image (d , id . scaledTo (150 ,100) ) ; image . setImage ( i ) ;
12.7
Boites de dialogue
ColorDialog Cette boite de dialogue permet la sélection d’une couleur dans une palette de couleurs. La méthode setRGB permet de préciser la couleur sélectionnée par défaut. La méthode open ouvre la boite de dialogue
et retourne la couleur sélectionnée sous forme d’un objet de type RGB. Si aucune valeur n’est sélectionnée, on
récupère null.
Exemple :
ColorDialog cd = new ColorDialog ( s ) ;
RGB couleur = cd . open () ;
if ( couleur != null )
s . setBackground ( new Color (d , couleur ) ) ;
DirectoryDialog Pour sélectionner un répertoire. La méthode open ouvre la boite et retourne une chaine de
caractères donnant le répertoire sélectionné (avec son chemin). La méthode setFilterPath permet de présélectionner un dossier.
Exemple :
DirectoryDialog dd = new DirectoryDialog ( s , SWT . OPEN ) ;
dd . setFilterPath ( " / mnt / clef / Travail / CoursPOO / " ) ;
dd . setMessage ( " Exemple DirectoryDialog " ) ;
String rep = dd . open () ;
CHAPITRE 12. INTERFACES GRAPHIQUES EN SWT
148
System . out . println ( " Repertoire éslectionne : " + rep ) ;
d . dispose () ;
FileDialog FileDialog Pour sélectionner un fichier : elle fonctionne comme DirectoryDialog.
Exemple :
FileDialog fd = new FileDialog ( s , SWT . OPEN ) ;
fd . setFilterPath ( " / mnt / clef / Travail / CoursPOO / " ) ;
fd . setText ( " Exemple FileDialog " ) ;
String fic = fd . open () ;
System . out . println ( " Fichier selectionne : " + fic ) ;
d . dispose () ;
FontDialog Pour sélectionner une police de caractère : on récupère via la méthode open un objet FontData
encapsulant les informations sur la police choisie (nom : via getName). Il est alors possible d’instancier la police
sélectionnée en passant au constructeur de Font notre objet FontData .
Exemple :
FontDialog fd = new FontDialog ( s ) ;
FontData police = fd . open () ;
System . out . println ( " Selection de la police : " + police . getName () ) ;
Font f = new Font (d , police ) ;
Text t = new Text (s , SWT . BORDER | SWT . READ \ _ONLY ) ;
t . setText ( " bzzzzz ( Maya ) " ) ;
t . setFont ( f ) ;
MessageBox Boite de message basique. Plusieurs options de personnalisation sont disponibles :
— icône : ICON_ERROR, ICON_INFORMATION, ICON_QUESTION, ICON_WARNING,
ICON_WORKING
— choix utilisateur : OK, CANCEL, YES, NO, ABORT, RETRY, IGNORE
La méthode setMessage permet de préciser le message affiché à l’utilisateur, tandis qu’on récupère le code
du bouton appuyé avec la méthode open.
Exemple :
MessageBox mb = new MessageBox ( s , SWT . ICON_WORKING | SWT . ABORT | SWT .
RETRY | SWT . IGNORE ) ;
int reponse = mb . open () ;
switch ( reponse ) {
case SWT . ABORT : System . out . println ( " Bouton abandonner
selectionne " ) ; break ;
case SWT . RETRY : System . out . println ( " Bouton reessayer
selectionne " ) ; break ;
case SWT . IGNORE : System . out . println ( " Bouton ignorer
selectionne " ) ; break ;
}
d . dispose () ;
12.7. BOITES DE DIALOGUE
149
PrintDialog Pour sélectionner une imprimante configurée sur le système. La méthode open renvoie un objet
PrinterData encapsulant les données sur l’imprimante sélectionnée. Il est alors possible d’instancier un objet
"imprimante" de type Printer en le passant au constructeur.
Exemple :
PrintDialog pd = new PrintDialog (s , SWT . OPEN ) ;
PrinterData imprimante = pd . open () ;
if ( imprimante == null ) {
System . out . println ( " Aucune imprimante selectionnee ! " ) ;
}
else {
Printer p = new Printer ( imprimante ) ;
if ( p . startJob ( " Test " ) ) {
p . startPage () ;
GC gc = new GC ( p ) ;
gc . drawString ( " Bonjour " ,100 ,100) ;
p . endPage () ; p . endJob () ; gc . dispose () ; p . dispose () ;
}
};
Boites de dialogues personnalisées Pour créer sa propre boite de dialogue, il suffit d’instancier un objet de
type Shell, rattaché à la fenêtre principale.
Exemple :
final Shell s = new Shell ( d ) ;
Button b = new Button (s , SWT . PUSH ) ;
b . setSize (200 ,100) ; b . setText ( " Ouvrir " ) ;
b . a dd Sel ec tio nL ist ene r ( new SelectionAdapter () {
public void widgetSelected ( SelectionEvent e ) {
final Shell s2 = new Shell (s , SWT . TITLE | SWT . CLOSE ) ;
s2 . setText ( " Ma boite de dialogue " ) ;
Button b = new Button ( s2 , SWT . PUSH ) ;
b . setSize (200 ,100) ; b . setText ( " Fermer " ) ;
b . add Sel ec tio nL ist ene r ( new SelectionAdapter () {
public void widgetSelected ( SelectionEvent e ) {
System . out . println ( " Fermeture de la boite perso " ) ;
s2 . close () ;
}}) ;
s2 . pack () ; s2 . open () ;
}};
CHAPITRE 12. INTERFACES GRAPHIQUES EN SWT
150
Fiche de synthèse
SWT
— bibliothèque graphique offrant une bonne intégration et de bonnes performances via l’utilisation de composants natifs ;
— portabilité et fiabilité moindre de part l’exécution de portions de code en dehors de la machine
virtuelle ;
— principaux écouteurs : Adapter, FocusListener, KeyListener, MouseListener, MouseTrackListener, PaintListener
— composants : Control, Browser, GC, Label, List, Menu, ProgressBar, StyledText, TabFoler,
Table, ToolBar, Tree
— boites de dialogue ; ColorDialog, DirectoryDialog, FileDialog, FontDialog, MessageBox,
PrintDialog
— conteneurs : Composite, Group
— gestionnaires de placement : FillLayout, GridLayout, RowLayout, FormLayout,
Exemple de code :
import org . eclipse . swt .*;
import org . eclipse . swt . widgets .*;
public class SWTExemple {
public static void main ( String [] args ) {
Display d = new Display () ;
Shell s = new Shell ( d ) ;
s . setSize (640 ,480) ;
Label l = new Label (s , SWT . CENTER ) ;
l . setText ( " bzzz ( Maya ) " ) ;
l . setBounds ( s . getClientArea () ) ;
s . open () ;
while (! s . isDisposed () ) {
if (! d . readAndDispatch () )
d . sleep () ;
}
d . dispose () ;
}
}
Chapitre 13
Annotations et trois applications : objets
persistants, servlets et services web
Les annotations sont de plus en plus utilisés : il s’agit d’une surcouche sémantique récente qui permet au
compilateur d’ajouter des fonctionnalités supplémentaires à l’exécutable qu’il génère. Dans ce chapitre, nous
présentons la mise en œuvre des annotations en Java et considérons, à titre d’exemple, trois applications : la
création d’objets persistants, de servlets et de services web. Pour ces applications, la lecture de l’annexe A est utile
pour la bonne compréhension de leur fonctionnement global.
Sommaire
13.1 Les annotations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
13.1.1 Les annotations standards . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
13.1.2 Les annotations personnalisées . . . . . . . . . . . . . . . . . . . . . . . . . . .
13.1.3 Les méta-annotations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
13.1.4 Comparaison XML/annotations . . . . . . . . . . . . . . . . . . . . . . . . . .
13.2 Application : les objets persistants . . . . . . . . . . . . . . . . . . . . . . . . . . . .
13.2.1 Les principale annotation de JPA - Java Persistence API . . . . . . . . . . . . .
13.2.2 En pratique via des annotations : des objets persistants Personne . . . . . . . .
13.3 Application : les servlets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
13.3.1 Présentation des servlets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
13.3.2 Annotations pour les servlets . . . . . . . . . . . . . . . . . . . . . . . . . . . .
13.3.3 En pratique : quelques servlets via des annotations . . . . . . . . . . . . . . . .
13.4 Application : les services webs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
13.4.1 Historique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
13.4.2 Présentation des services webs . . . . . . . . . . . . . . . . . . . . . . . . . . .
13.4.3 Le protocole SOAP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
13.4.4 En pratique via des annotations : service web retournant des dates au format long
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
152
152
153
153
154
154
154
155
159
159
161
161
162
162
162
164
167
Bibliographie :
— M. Baron, “Java pour le développement d’applications Web’ : J2EE”, http://baron.mick.free.fr/
— X. Blanc, "Web Services", Lip6
— J.-M. Chauvet, "Services Web avec SOAP, WSDL, UDDI, ebXML ...", Eyrolles
— D. Donsez, http://www-adele.imag.fr/~
donsez/cours/
— A. Dulaunoy, "Le cadre des services webs"
— T. Leriche-Dessierie, "Introduction à JPA, application au chargement de données depuis une
base
MySQL",
http://thierry-leriche-dessirier.developpez.com/tutoriels/java/
charger-donnees-mysql-jpa-intro/
— M. Nivesse, "Les annotations en Java", rapport de Licence Pro de l’IUT de Bordeaux 1, 2012
— A. Tanenbaum, "Réseaux"
151
CHAPITRE 13. ANNOTATIONS ET TROIS APPLICATIONS
152
— K. Topley, "Java Web Services in a nutshell", O’Reilly
13.1
Les annotations
Les annotations ont été introduites avec Java5. Seulement trois annotations étaient pré-définies dans cette
version :
@Deprecated @Override @SuppressWarnings
Les annotations peuvent être analysées par le compilateur javac ou bien par des outils tiers comme nous le
verrons dans le cas des services web.
Définition 13.1 (annotation). Les annotations sont des méta-données, que l’on peut rajouter à différents endroits
du code source : attributs, paramètres, méthodes, classes, paquets ... Elles précèdent l’élément auquel elles s’appliquent et sont préfixées par "@".
Exemple :
@SuppressWarnings ( " unchecked " )
public List < User > getUsers ()
{
// Conversion de type non controlee
return ( List < User >) new ArrayList () ;
}
13.1.1
Les annotations standards
L’annotation @Deprecated
Cette annotation sert à indiquer qu’une méthode ne devrait plus être utilisée. Elle a été précédée d’une annotation pour la javadoc permettant d’indiquer pour quelle raison cette méthode dépréciée et quelle est celle à utiliser
à la place.
Exemple :
/* *
* Does some thing in old style .
*
* @deprecated use { @link new () } instead .
*/
@Deprecated
public void old () {
// ...
}
L’annotation @Override
Cette annotation permet d’indiquer au compilateur que la méthode est censée masquer une méthode de la
classe mère.
Exemple : (demande au compilateur de s’assurer qu’il y a bien une methode int dejeuner() dans une des
classes mère)
@Override
int dejeuner ()
13.1. LES ANNOTATIONS
153
L’annotation @SuppressWarnings
Cette annotation indique de ne pas afficher certains avertissements.
Exemple :
@SuppressWarnings ( value = " unchecked " )
void maMethod () {...}
Quelques catégories d’avertissements utiles :
— all : tous
— cast : les conversions de type hasardeuses
— deprecation : utilisation de classes et de méthodes obsolètes
— divzero : possible division par zéro
— unchecked : conversions entre types paramétrés et types bruts
13.1.2
Les annotations personnalisées
Il est possible de définir ces propres annotations. Une annotation correspond à une interface lors de sa déclaration et sera une instance d’une classe implémentant cette interface, lors de son utilisation.
Déclaration d’une annotation MonAnnotation :
public @interface MonAnnotation {}
Utilisation de l’annotation MonAnnotation :
@MonAnnotation
public class MaClasse {...}
13.1.3
Les méta-annotations
Les méta-annotations sont des annotations concernant ... des annotations.
L’annotation @Documentated
Cette annotation stipule que l’annotation doit figurer dans la Javadoc.
L’annotation @Inherit
Cette annotation indique que l’annotation sera héritée par toutes les classes filles.
Exemple :
@Inherit
public @interface MonAnnotation {}
@MonAnnotation
public class UneClasseMere {...}
public class UneClasseFille extens UneClasseMere {
// cette classe herite de l ’ annotation @MonAnnotation
}
CHAPITRE 13. ANNOTATIONS ET TROIS APPLICATIONS
154
13.1.4
Comparaison XML/annotations
XML est très utilisé également pour ajouter des fonctionnalités à un code Java, via des fichiers supplémentaires. Ces fichiers sont analysés par des outils externes pour produire l’exécutable final. Ceci permet par exemple
d’indiquer comment un objet Java peut être enregistré dans une base de données, en précisant les correspondances
entre les attributs et les colonnes de la table concernée.
Principaux avantages des annotations
— prise en charge par les environnements de développement : les annotations étant intégrées dans le code, la
syntaxe et la sémantique des annotations sont contrôlées à la volée, ce qui permet de bénéficier de tous les
outils de l’EDI. Les fichiers XML n’offrent pas cette intégration dans les EDI.
— concision : les mot-clefs sont bien choisis et permettent néanmoins des traitements complexes. XML est
beaucoup plus verbeux.
Principaux désavantages
— pas de séparation avec le code : la modification d’une annotation oblige à recompiler le code. XML n’interfère pas avec le code.
— confusion sémantique : l’ajout d’annotations peut impliquer l’ajout de multiples couches sémantiques,
nuisible à la lisibilité du code. Par exemple, on peut mélanger des annotations standards, des annotations
pour la persistance d’objet, des annotations pour le déploiement de méthodes sous forme de service webs
... Avec XML, chaque couche sémantique sera dans des fichiers XML distincts.
13.2
Application : les objets persistants
Définition 13.2 (Objet persistant). Objet dont l’état peut perdurer après la terminaison de l’application, voire de
la machine virtuelle.
La sérialisation (cf chapitre 7) est un moyen simple pour créer des objets persistants. On peut également
stocker les états d’objets dans des bases de données, ce qui permet de tirer partie des fonctionnalités qu’elles
apportent, via notamment le langage SQL . Il est possible de faire ceci en exploitant JDBC (cf chapitre 9). Cependant cette approche générer des applications difficiles à maintenir. Pour des applications de grande envergure, on
utilise des bibliothèques de plus haut niveau, qui vont automatiser une grande partie des mécanismes assurant la
correspondance entre les objets persistants et leur "traduction" dans la base de données.
Dans cette section, nous allons découvrir les principes généraux de la bibliothèqe JPA (Java Persistence API)
faisant partie de JEE (Java Enterprise Edition), en mettant l’accent sur le rôle des annotations au sein de celle-ci.
Une grande partie des informations présentées dans cette section sont issues du tutoriel de Thierry LericheDessierie : "Introduction à JPA, application au chargement de données depuis une base MySQL".
13.2.1
Les principale annotation de JPA - Java Persistence API
La bibliothèque JPA (intoduite avec Java5) est disponible dans le paquet javax.persistence. Pour reprendre
les mots de Wikipedia, c’est
une interface de programmation Java permettant aux développeurs d’organiser des données relationnelles dans des applications utilisant la plateforme Java.
Cette bibliothèque définit un langage très proche de SQL pour intéragir avec les données : JPQL (Java Persistence Query Language).
Elle introduit un certain nombres d’annotations.
13.2. APPLICATION : LES OBJETS PERSISTANTS
155
@Entity
Annotation indiquant qu’une classe est persistante.
@Entity
public class Personne { ... }
@Embeddable
Spécifie que la classe est persistante et, qu’en tant qu’attribut, elle doit partager l’identifiant de l’objet qui la
contient. Cette annotation doit être utilisée conjointement avec l’annotation @Embedded
@Entity
public class Personne {
@Embedded AdressePersonne ;
...
}
@Embeddable
public class AdressePersonne { ... }
@Id
Indique la clef primaire de la classe
@Entity
public class Personne {
@Id String nom ;
@Id Date dateNaissance ;
}
@Transient
Précise que l’attribut ne doit pas être persistant
@Entity
public class Personne {
@Transient String humeur ;
...
13.2.2
En pratique via des annotations : des objets persistants Personne
La classe Java Personne
Nous allons considérer une classe Personne dont les attributs sont formés de leurs noms, prénoms et date de
naissance.
Considérons donc cette classe Personne :
public class Personne {
private String nom ;
private String prenom ;
private Date dateNaissance ;
}
CHAPITRE 13. ANNOTATIONS ET TROIS APPLICATIONS
156
Exercice 134. (?) Complétez cette classe en la munissant des constructeurs et accesseurs adéquats. Vous allez
obtenir ce qu’on appelle un "JavaBean" ou "Bean" en langage JEE.
La base de données tutojpa et sa table personne
Considérons maintenant les scrips SQL suivants :
create_database.sql
CREATE DATABASE IF NOT EXISTS ‘ tutojpa ‘ DEFAULT CHARACTER SET utf8
COLLATE utf8_general_ci ;
USE ‘ tutojpa ‘;
create_table.sql
DROP TABLE IF EXISTS ‘ personne ‘;
CREATE TABLE IF NOT EXISTS ‘ personne ‘ (
‘id ‘ int (11) NOT NULL AUTO_INCREMENT ,
‘nom ‘ varchar (255) NOT NULL ,
‘ prenom ‘ varchar (255) NOT NULL ,
‘ dateNaissance ‘ date NOT NULL ,
PRIMARY KEY ( ‘ id ‘)
) ENGINE = InnoDB DEFAULT CHARSET = utf8 AUTO_INCREMENT =5 ;
insert_data.sql
INSERT INTO ‘ personne ‘ ( ‘ id ‘ , ‘nom ‘ , ‘ prenom ‘ , ‘ dateNaissance ‘) VALUES
(1 , ’ Gandalf ’ , ’ Bob ’ , ’ 2014 -02 -01 ’) ,
(2 , ’ Sarumane ’ , ’ Greg ’ , ’ 2014 -02 -02 ’) ,
(3 , ’ Dragonair ’ , ’ Gege ’ , ’ 2014 -01 -20 ’) ,
(4 , ’ DungeonMaster ’ , ’ Alf ’ , ’ 2014 -02 -28 ’) ;
Vous l’avez deviné, ces 3 scripts permettent de créer une base de données de nom tutojpa, possédant une
seule table, la table personne, contenant 4 enregistrements.
Exercice 135. (?) Créez cette base avec ces données, par exemple via phpMyAdmin.
L’objectif de cette partie est de mettre en place un "couplage" entre la classe Java Personne et la table
Personne.
Préparation d’eclipse pour la prise en charge de JPA
Exercice 136. (?) Installez tout d’abord le plugin Dali Java Persistence Tool (via le menu Help/Install
New Software).
Le projet tutoJPA
Exercice 137. (??) Créer un nouveau projet de type JPA Project : cette étape est assez délicate car l’assistant demande de multiple informations, dont un pilote pour mysql, les paramètres de connexion à la base, une
bibliothèque utilisateur (il faudra donc la créer à ce moment là), etc ...
Exercice 138. (?) Créer une classe persistante Personne via le menu New/JPA Entities from Tables :
eclipse génère le code de la classe Personne en se basant sur la table de même nom. Le code obtenu dot être
très proche de celui de l’exercice 134. Quelles annotations ont été ajoutées ? Pourquoi ?
13.2. APPLICATION : LES OBJETS PERSISTANTS
157
Exercice 139. (?) Configurer le fichier persistence.xml : la partie connexion ne devrait pas contenir les paramètres de connexion à la base. Choisissez le mode "Resource Local" puis "Populate from connection".
Votre fichier persistence.xml devrait alors ressembler au fichier ci-après.
persistence.xml
<? xml version = " 1.0 " encoding = " UTF -8 " ? >
< persistence version = " 2.0 " xmlns = " http: // java . sun . com / xml / ns /
persistence " xmlns:xsi = " http: // www . w3 . org /2001/ XMLSchema - instance "
xsi:schemaLocation = " http: // java . sun . com / xml / ns / persistence http: //
java . sun . com / xml / ns / persistence / persistence_2_0 . xsd " >
< persistence - unit name = " tutojpa " transaction - type = " RESOURCE_LOCAL " >
< class > Personne </ class >
< properties >
< property name = " javax . persistence . jdbc . url " value = " jdbc:mysql:
// localhost:3306 / tutojpa " > </ property >
< property name = " javax . persistence . jdbc . user " value = " root " > </
property >
< property name = " javax . persistence . jdbc . password " value = "
rootroot " > </ property >
< property name = " javax . persistence . jdbc . driver " value = " com .
mysql . jdbc . Driver " > </ property >
</ properties >
</ persistence - unit >
</ persistence >
Utiliser le couplage
Pour pouvoir exploiter la liaison avec la base de données, il faut utiliser un objet de type entityManager.
Exercice 140. (?) Rajouter la classe MonEntityManager à votre projet : elle permet de créer un objet entityManager, s’il n’existe déjà.
MonEntityManager
public class MonEntityManager {
private static MonEntityManager instance ;
private E nt ity Ma nag erF ac tor y emf ;
private EntityManager em ;
private MonEntityManager () {
emf = Persistence . c r e a t e E n t i t y M a n a g e r F a c t o r y ( " tutojpa " )
;
em = emf . createE ntityMan ager () ;
}
public static synchronized MonEntityManager getInstance () {
if ( instance == null ) {
instance = new MonEntityManager () ;
}
return instance ;
}
CHAPITRE 13. ANNOTATIONS ET TROIS APPLICATIONS
158
public EntityManager getEntityManager () {
return em ;
}
}
Pour récupérer un objet EntityManager, on pourra donc utiliser
EntityManager em = MonEntityManager . getInstance () . getEntityManager () ;
Créer 4 objets de la classe Personne correspondant aux 4 enregistrements de la table personne
Voici un extrait de code permettant de créer une collection de 4 personnes correspondant aux données de la
table Personne
String req = " SELECT p FROM Personne p " ;
Query query = em . createQuery ( req ) ;
List < Personne > personnes = query . getResultList () ;
Exercice 141. (?) Ecrire une classe Application qui contient une méthode main. Celle-ci crée une collection
de personnes à partir de celles de la table personne, puis affiche leurs caractéristiques.
On peut également récupérer directement un objet à partir de la table lorsqu’on connait son identifiant, sans
utiliser de requête.
Exemple :
Personne p = em . find ( Personne . class , 1) ;
modification
Pour modifier des données dans la base à partir des objets Java, il suffit de procéder de cette manière :
// p est un objet Personne correspondant a un enregistrement de la base
em . getTransaction () . begin () ; // debut de la prise en compte des
modifications
p . setPrenom ( " Joe " ) ; // modification de l ’ objet p
em . getTransaction () . commit () ; // mise -a - jour de l ’ enregistrement
correspondant a p dans la base
Ceci est un mécanisme particulièrement puissant : on peut faire des traitements complexes sur les données
directement sur les objets en Java, puis propager par ce biais le résultat vers la base.
Exercice 142. (?) Ajoutez en Java la chaine " Junior" à chaque prénom des personnes dans la base.
suppression
Pour supprimer un enregistrement de la base en correspondance avec un objet o Java, on peut utiliser la
méthode remove de la classe EntityManager :
em . getTransaction () . begin () ;
em . remove ( o ) ;
em . getTransaction () . commit () ;
Exercice 143. (?) Supprimez en Java l’enregistrement de la base dont le nom est "Dragonair". Pour sélectionner
le bon enregistrement, vous pouvez utiliser cette requête JPQL :
SELECT p FROM Personne p WHERE p . nom LIKE ’ Dragonair ’
13.3. APPLICATION : LES SERVLETS
159
Insertion
Pour insérer un nouvel enregistrement dans la base à partir d’un objet o Java, utiliser la méthode persist de
la classe EntityManager :
em . getTransaction () . begin () ;
em . persist ( o ) ;
em . getTransaction () . commit () ;
Exercice 144. (?) Créez en Java un nouvel enregistrement dans la base dont le nom est "Scoubidou" et le prénom
est "Bidou".
13.3
Application : les servlets
Cette partie est basée fortement sur les transparents de cours de Mickaël Baron.
13.3.1
Présentation des servlets
Définition 13.3 (Servlet). Composant logiciel écrit en Java fonctionnant côté serveur.
Les concurrents directs des servlet sont les langages de script PHP, ASP ...
Les principaux atouts d’une Servlet sont :
— Portabilité : technologie indépendante de la plate-forme et du serveur
— Puissance : disponibilité de l’API de Java (manipulation d’images, connectivité aux bases de données ...)
— Efficacité : une Servlet est chargée une seule fois et conserve son état
— Sécurité : typage fort de Java, gestion des erreurs par exception.
Une Servlet s’exécute dans un conteneur de Servlets. Le rôle du conteneur est d’établir un lien entre la Servlet
et le serveur Web.
Il y a deux types de conteneurs :
— conteneurs de Servlets autonomes : serveur web intégrant le support des Servlets ;
— conteneurs de Servlets additionnels : fonctionnent comme un plug-in à un serveur web existant.
Les principaux conteneurs existants sont Tomcat Server (Apache), JBoss, WebSphere Application Server
(IBM), Weblogic (BEA) ...
Tomcat
Tomcat est écrit entièrement en Java et peut donc être utilisé sur n’importe quel système disposant d’une
machine virtuelle. Il est disponible gratuitement sous forme d’une licence Open Source. Il constitue une implémentation de référence de la spécification J2EE. Il fournit à ce titre des bibliothèques permettant de concevoir des
Servlets (javax.servlet.http.HttpServlet).
Définition 13.4 (Rôle). Permet d’ajouter des utilisateurs et de définir des droits sur les Servlets.
On peut ajouter, supprimer et modifier des droits, soit par la Servlet Administration, soit en éditant directement le fichier tomcat-users.xml.
tomcat-users.xml
<? xml version = ’ 1.0 ’ encoding = ’utf -8 ’? >
< tomcat - users >
< user username = " admin " password = " admin " roles = " admin , manager " / >
</ tomcat - users >
CHAPITRE 13. ANNOTATIONS ET TROIS APPLICATIONS
160
La classe HttpServlet
Définition 13.5 (classe HttpServlet). Cerre classe possède une méthode service(...) qui interprète la requête
HTTP et transmet celle-ci à la méthode appropriée doGet(...), doPost(...), doHead(...) ...
On écrit donc le code d’une servlet en redéfinisant les méthodes service, doGet, doPost et doHead.
Les classes HttpServletRequest et HttpServletResponse
Ces classes permettent d’encapsuler les requêtes et les réponses HTTP.
Leurs principales méthodes de la classe HttpServletRequest sont :
—
—
—
—
—
—
—
String getMethod() : retourne le type de requête ;
String getServeurName() : retourne le nom du serveur ;
String getParameter(String name) : retourne la valeur d’un paramètre (saisi dans un formulaire) ;
String[] getParameterNames() : retourne le nom de tous les paramètres ;
String getRemoteHost() : retourne l’IP du client ;
String getServerPort() : retourne le port sur lequel le serveur écoute
String getQueryString() : retourne la chaîne d’interrogation ;
Les principales méthodes de la classe HttpServletResponse sont :
—
—
—
—
void setStatus(int) : définit le code de retour de la réponse ;
void setContentType(String) : définit le type de contenu MIME ;
ServletOutputStream getOutputStream() : flot pour envoyer des données au client ;
void sendRedirect(String) : redirige le navigateur vers l’URL
Voici un exemple de code d’une Servlet, repris de Wikipedia :
import javax . servlet .* ;
import java . io .* ;
public class HelloServlet extends GenericServlet
{
public void service ( HttpServletRequest request , HttpSe rvletRe sponse
response )
{
try
{
PrintWriter out = response . getWriter () ;
out . println ( " <! DOCTYPE html PUBLIC \" -// W3C // DTD HTML 4.01// EN
\" > " ) ;
out . println ( " < title > Bonjour tout le monde & amp ; nbsp ;! </ title > " ) ;
out . println ( " <p > Hello world ! </p > " ) ;
}
catch ( IOException e )
{
e . printStackTrace () ;
}
}
Le cours de Mickaël Baron aborde des notions plus subtiles : cycles de vie des Servlets, sessions (cookies,
classe HttpSession, partage d’informations entre Servlets, partager le contrôle d’une requête Http, authentification ...
13.3. APPLICATION : LES SERVLETS
13.3.2
161
Annotations pour les servlets
Java 6 a introduit un certains nombres d’annotations pour les Servlet, qui font toutes parties de la bilibothèque
javax.servlet.annotation.
@WebServlet
Annotation indiquant qu’une classe est une Servlet et précisant l’URL à laquelle elle est associée.
Dans l’exemple ci-dessous, l’URL /inscription est prise en charge par la classe MaServlet
@WebServlet ( " / inscription " )
public class MaServlet extends HttpServlet {
Il existe une multitude d’autres annotations dédiées aux servlets, que nous ne décrivons pas ici, car elles ne
sont pas indispensables au déploiement d’une servlet basique :
@WebFilter
@WebInitParam
@WebListener
@MultipartConfig
...
13.3.3
En pratique : quelques servlets via des annotations
Dans cette partie, nous allons déployer deux servlets élémentaires en utilisant le conteneur tomcat et eclipse
pour le développement.
Pour des instructions plus détaillées, vous pouvez consulter le tutoriel : http://www.coreservlets.com/
Apache-Tomcat-Tutorial/tomcat-7-with-eclipse.html.
Exercice 145. (?) - déploiement de tomcat
Installer tomcat : cf http: // tomcat. apache. org/ whichversion. html . Lancer ensuite tomcat via le
script fourni (bin/startup.sh sous Unix). Vérifier que tomcat s’est bien lancé en ouvrant la page http:
// localhost: 8080/ . Stopper le serveur avec le script fourni.
Exercice 146. (?) - configuration d’eclipse (liaison avec tomcat)
Installer, si ce n’est déjà fait, la version d’eclipse pour développeurs JEE, car elle contient les extensions pour le
développement web. Ouvrir la perspective Java EE. Informer eclipse de la présence de tomcat : onglet Server,
puis create a new server et choisir Apache/Tomcat.
Exercice 147. (?) - une première servlet
Créer un nouveau projet web dynamique de nom testTomcat dans eclipse, en choissant tomcat comme
cible pour l’exécutable. Créer ensuite une Servlet de nom ServletHello. Une annotation @WebServlet("/ServletHello") est présente dans le code de base qui a été généré. Elle signifie que la Servlet prendra
en charge l’URL http: // localhost: 8080/ testTomcat/ ServletHello .
Déployer la servlet en appuyant sur le bouton "run". Eclipse affiche une page blanche pour l’URL de la
servlet : il s’agit du résultat récupéré par le navigateur web. Vous obtiendrez le même résultat avec un autre
navigateur comme Firefox. Modifiez la servlet pour qu’elle affiche [ServletHello] methode get appelee
dans la console et qu’elle retourne le texte Hello !!! au navigateur lorsque celui demande l’URL http: //
localhost: 8080/ testTomcat/ ServletHello (avec une méthode GET).
Exercice 148. (?) - une page HTML
Le dossier WebContent du projet sert à stocker les ressources web classiques. Créer une page HTML bienvenue.html de titre Bienvenue, qui affiche Pour initier le processus de salutations, veuillez
cliquer ici, où ici est un hyperlien vers ServletHello. Tester.
CHAPITRE 13. ANNOTATIONS ET TROIS APPLICATIONS
162
Le code suivant présente un formulaire HTML basique :
< form method = " post " action = " URLTraitement " >
<p > < input type = " text " name = " pseudo " / > </ p >
<p > < input type = " submit " value = " Envoyer " / > </ p >
</ form >
Exercice 149. (?) - formulaire traité par une Servlet
Ecrire une Servlet ServletTraitementFormulaire qui affiche dans le terminal, [ServletTraitementFormulaire] methode post appelee et retourne une page web avec le message Votre pseudo est xxx, lorsqu’elle est appelée à partir du formulaire précédent. Insérer le formulaire dans la page bienvenue.html pour
tester.
Exercice 150. (??) Ecrire
— une Servlet ServletRedirection qui effectue une redirection vers www.labri.fr.
— une Servlet ServletInfos qui retourne des informations extraites de la requête HTTP utilisée.
13.4
Application : les services webs
13.4.1
Historique
Le premier client web à été conçu en 1990 par Tim Berner Lee. Le web est conçu pour une interface HommeMachine. Dans les années 2000, il y a eu une évolution marquée vers des communications machine-machine, pour
lesquelles les langages du web (HTML) étaient peu adaptés (analyse syntaxique automatique difficile)
Les Services Web reposent sur l’introduction de deux nouvelles normes :
— SOAP : surcouche (à HTTP) pour les communications machine-machine ;
— WSDL : une interface (standardisée) d’accès aux services.
Les principaux ancêtres de SOAP sont :
— SUN Remote Procedure Call (RPC) : c’était une première approche pour standardiser les interactions
entre un client et un serveur ; le serveur offre des procédures (identifiées par un nom) et le client demande
au serveur l’accès à une procédure donnée avec des valeurs (paramètres) ; la représentation binaire des
données est assurée par XDR (eXternal Data Representation).
— Microsoft DCOM (Distributed Component Object Model) : il ne se limite pas uniquement aux procédures
mais offre aussi un interfaçage avec des objets ou appels de méthodes ; en théorie, DCOM est neutre par
rapport à la plateforme, au langage et même au transport cependant il est resté dans l’environnement de
Microsoft pour des questions de standardisation ; c’est de plus un protocole propriétaire.
— CORBA (Common Object Request Broker Architecture) : CORBA fonctionne avec un ordonnanceur
(ORB) pour les requêtes et les transferts vers un serveur donné ; il est utilisé sur des très larges projets ; IDL
(Interface Design Language) est un langage de description des services offerts par l’application ; CORBA
est complexe ce qui source de problèmes..
La motivation dans la création des services webs étaient donc de répondre aux insatisfactions dues à DCOM,
SUN RPC, CORBA, RMI, ... Ainsi les objectifs étaient :
— de sortir des problèmes de compatibilités systèmes, langage et format ;
— de sortir du modèle propriétaire et de créer des standards ;
— d’utiliser l’infrastructure Internet existante ;
— de simplifier et limiter le temps de développement ;
13.4.2
Présentation des services webs
Définition 13.6 (Services web). Ensemble de standards, permettant à des serveurs webs d’offrir des services.
Ces principaux standards sont :
13.4. APPLICATION : LES SERVICES WEBS
163
— XML : toutes les données à échanger sont formatées en XML. Ce codage peut être effectué par SOAP ou
XML-RPC ;
— des protocoles communs : des données en XML peuvent être transportées entre les applications en utilisant
des protocoles communs tels que HTTP, FTP, SMTP et XMPP ;
— WSDL : l’interface publique au service Web est décrite (en XML) par ce protocole ;
— UDDI (annuaire) : permet à des applications de rechercher le service web dont elles ont besoin.
Définition 13.7 (SOAP - Simple Object Access Protocol). Protocole permettant d’appeler les méthodes d’un
objet distant, bati sur XML.
Le transfert se fait le plus souvent à l’aide du protocole HTTP.
Le protocole SOAP est composé de deux parties :
— une enveloppe, contenant des informations sur le message lui-même (pour acheminement et traitement),
— un modèle de données, définissant le format du message (informations à transmettre).
Le protocole SOAP est une recommandation du W3C, initialement défini par Microsoft et IBM.
Définition 13.8 (WSDL - Web Services Description Language). Langage décrivant une interface publique d’accès à un Service Web.
C’est une description basée sur le XML qui indique comment communiquer pour utiliser le service, en précisant :
— le protocole de communication ;
— le format de message requis.
Définition 13.9 (UDDI - Universal Description Discovery and Integration). Technologie d’annuaire basée sur
XML et plus particulièrement destinée aux services web.
Un annuaire UDDI permet de localiser sur le réseau le service Web recherché. Il repose sur le protocole de
transport SOAP.
L’annuaire UDDI est consultable de différentes manières :
— les pages blanches comprennent la liste des entreprises ainsi que des informations associées à ces dernières ;
— les pages jaunes recensent les services web de chacune des entreprises sous le standard WSDL ;
— les pages vertes fournissent des informations techniques précises sur les services fournis.
Avantages des services Web
— les services Web fournissent l’interopérabilité entre divers logiciels fonctionnant sur diverses plateformes ;
— les services Web utilisent des standards et protocoles ouverts ;
— les protocoles et les formats de données sont au format texte dans la mesure du possible, facilitant ainsi la
compréhension du fonctionnement global des échanges ;
— basés sur le protocole HTTP, les services Web peuvent fonctionner au travers de nombreux pare-feux sans
nécessiter des changements sur les règles de filtrage.
Inconvénients des services Web
— les normes de services Web dans les domaines de la sécurité et des transactions sont actuellement très
limitées comparées à des normes ouvertes plus mûres de l’informatique répartie telles que CORBA ;
— les services Web souffrent de performances faibles comparée à d’autres approches de l’informatique répartie telles que le RMI, CORBA, ou DCOM ;
— par l’utilisation du protocole HTTP, les services Web peuvent contourner les mesures de sécurité mises en
place au travers des pare-feux ;
— rien ne permet pour l’instant d’assurer la qualité d’exécution d’un Service Web. Il n’y a donc aucune
qualité de service associée à ces derniers.
CHAPITRE 13. ANNOTATIONS ET TROIS APPLICATIONS
164
Le scénario typique d’usage d’un service web est le suivant (cf Fig. 13.1) :
1. Le client demande un service : une recherche sémantique dans un annuaire UDDI donne la liste des prestataires potentiels ;
2. Une fois la réponse reçue (en XML), le client recherche l’interface du composant référencé dans l’annuaire.
Cette interface est spécifiée en WSDL et décrit l’ensemble des services implémentés par l’objet distant ;
3. Il ne reste plus qu’à effectuer l’invocation du service. Pour ce faire, il faut fournir les paramètres attendus
par l’interface et assurer la communication avec le serveur.
F IGURE 13.1 – Scénario typique des services web (source : http://www.dotnetguru.org/)
13.4.3
Le protocole SOAP
Le protocole SOAP est un protocole très utilisé pour encoder les données transportées lors de l’invocation d’un
service web.
Expérimentation
Pour expliquer le fonctionnement du protocole SOAP, nous allons étudier une expérimentation qui avait été
réalisée en 2006 sur le site http://www.xmethods.net/ (on ne peut plus reproduire cette expérimentation car
les pages correspondantes ont été fermées depuis).
Observons la capture d’écran dans la figure 13.2 : il s’agit d’une requête avec paramètres "EURO" et "US"
(demande de la valeur d’un euro en dollars) et la réponse obtenue est la valeur 1.1794
La capture d’écran de la figure 13.3 montre le contenu réel de la requête qui a été envoyée. Cette requête était
constituée
— d’un entête HTTP, utilisant la méthode POST pour transférer les données, à destination du serveur (du
service web) services.xmethod.net sur le port standard 80 ;
— d’un corps HTTP (les données) en SOAP, contenant deux balises (country1 et country2) de type chaines
de caractères et de valeur EURO et US respectivement.
La trame de la réponse est similaire et décrite dans la figure 13.4.
Exercice 151. (?) Connectez-vous sur http: // www. webservicex. net et testez quelques services :
Stock Quote, Currency Convertor, Global Weather, Translation Engine, Country Details, SendSMSWorld, Validate Email Address, SendFax, GeoIPService
13.4. APPLICATION : LES SERVICES WEBS
165
F IGURE 13.2 – Service web convertisseur de monnaie : les paramètres d’envoi et la réponse
F IGURE 13.3 – Service web convertisseur de monnaie : données XML envoyées par HTTP
Exercice 152. (? ? ?) L’objectif de cet exercice est de réaliser un client d’un Service Web en Java, "bas niveau" :
l’idée est d’examiner le contenu des trames échangées lors de la consommation d’un service web puis d’en générer
des similaires en utilisant la programmation réseau classique (sockets).
Les figures 13.5 et 13.6 montrent respectivement les trames échangées pour la consommation du service web.
Ecrivez un client Java permettant d’invoquer ce service web de taux de change, en utilisant directement les
sockets.
166
CHAPITRE 13. ANNOTATIONS ET TROIS APPLICATIONS
F IGURE 13.4 – Service web convertisseur de monnaie : données XML retournées par HTTP
F IGURE 13.5 – Trame HTTP de la requête
13.4. APPLICATION : LES SERVICES WEBS
167
F IGURE 13.6 – Trame HTTP de la réponse
13.4.4
En pratique via des annotations : service web retournant des dates au
format long
Java6 a intégré un serveur de services webs, utilisable principalement à des fins de tests, exploitant le mécanisme des annotations. Pour réaliser des services web dans des serveurs d’application robustes (comme JBoss),
on utilise plutôt des bibliothèques comme axis (du projet Apache) qui ne sont pas décrites ici.
Nous allons expliquer son fonctionnement à travers un petit exemple : un service web retournant une date dans
un format "long".
Considérons les deux classes ServiceDate et Main ci-après.
ServiceDate
package serveur ;
import java . util . Date ;
import java . text .*;
public class ServiceDate {
private SimpleDateFormat longDate = new SimpleDateFormat ( " EEEE
dd MMMM yyyy " ) ;
private SimpleDateFormat shortDate = new SimpleDateFormat ( " dd /
MM / yyyy " ) ;
public ServiceDate () {
System . out . println ( " Creation du service date " ) ;
CHAPITRE 13. ANNOTATIONS ET TROIS APPLICATIONS
168
}
// retourne la date d ’ aujourd ’ hui au format long
public String today () {
return longDate . format ( new Date () ) ;
}
// entree : une chaine de caracteres au format " dd / MM / yyyy "
// sortie :
public String getLongDate ( String date ) {
String rep = " " ;
try { rep = longDate . format ( shortDate . parse ( date ) ) ;}
catch ( ParseException e ) { return e . toString () ; }
return rep ;
}
}
Main
package serveur ;
public class Main {
public static void main ( String [] args ) {
ServiceDate server = new ServiceDate () ;
System . out . println ( server . getLongDate ( " 13/02/2014 " ) ) ;
System . out . println ( " Aujourd ’ hui : " ) ;
System . out . println ( server . today () ) ;
}
}
Exercice 153. (?) Testez ce code.
On souhaite rendre la méthode String getLongDate(String) accessible sous la forme d’un service web.
Pour cela, il suffit de déclarer la classe ServiceDate comme étant un service web avec l’annotation @ServiceWeb
et la méthode getLongDate( String ) comme étant disponible avec l’annotation @WebMethod.
On obtient donc :
...
import javax . jws . WebMethod ;
import javax . jws . WebService ;
@WebService
public class ServiceDate {
...
// retourne la date d ’ aujourd ’ hui au format long
public String today () {
return longDate . format ( new Date () ) ;
}
@WebMethod
// entree : une chaine de caracteres au format " dd / MM / yyyy "
// sortie :
13.4. APPLICATION : LES SERVICES WEBS
169
public String getLongDate ( String date ) {
...
}
}
On génère ensuite les binaires prenant en charge de manière effective le service web avec l’utilitaire wsgen
(nota : cet utilitaire est déclaré comme obsolète mais nous l’utilisons ici car la procédure de substitution est
actuellement assez complexe).
Exemple (pour le cas où les binaires de l’application sont dans bin (projet Eclipse)) :
wsgen - cp bin serveur . ServiceDate
Ceci crée un répertoire serveur contenant des binaires pour chacune des méthodes exposées en tant que
service web.
En observant le contenu de celui-ci, vous observerez que la méthode today est exposée elle-aussi, alors que
nous ne l’avons pas déclarée avec @WebMethod. En effet par défaut, toutes les méthodes publiques sont exposées
dans une classe annotée avec @WebService.
Pour retirer la méthode today des méthodes exposées, on peut utliser :
@WebMethod ( exclude = true )
public String today () { ...
Exercice 154. (?) Rajoutez cette option et vérifiez que la méthode today ne figure plus dans les binaires générés
dans le répertoire serveur.
L’étape suivante consiste à publier le service web à une URL donnée et se mettre en attente des requêtes. On
peut le faire par exemple dans la méthode main avec ce code :
Endpoint . publish ( " http :// localhost :4040/ date " , new ServiceDate () ) ;
System . out . println ( " Serveur ServiceDate pret " ) ;
while ( true ) ;
Ceci publie donc notre service web à l’URL http://localhost:4040/date.
Le service web qui a été généré avec wsgen contient par défaut des fonctionnalités supplémentaires.
Si vous ouvrez directement l’URL http://localhost:4040/date dans un navigateur (i.e. si vous faites
une requête HTTP directe dessus), il retourne une page web contenant les informations essentielles du service
web dont le nom de la classe Java l’implémentant, l’URL d’accès et une adresse retournant le fichier WSDL
décrivant le service.
Dans notre cas, le fichier WSDL est accessible à : http://localhost:4040/date?wsdl
Exercice 155. (?) Testez. Quelles sont les opérations (=méthodes) du service web décrites dans le fichier WSDL ?
Est-ce normal ?
Notre service web est déployé côté serveur. Il reste à écrire un client pour le tester. Nous allons le faire dans
un paquet séparé : le paquet client.
Nous pouvons utiliser l’utilitaire wsimport pour générer le code de base d’un client, à partir du fichier wsdl :
wsimport - keep http :// localhost :4040/ date ? wsdl
Exercice 156. (?) La commande ci-dessus va générer des binaires et des sources dans un répertoire serveur (car
il s’agit du paquet utilisé par la classe implémentant le service web). Pour distinguer la partie client du serveur,
créer un répertoire client puis placez-vous dedans pour exécuter cette commande wsimport. Quelles classes
sont créées ?
CHAPITRE 13. ANNOTATIONS ET TROIS APPLICATIONS
170
Voici un exemple de client :
package client ;
import serveur .*;
public class Client {
public static void main ( String [] args ) {
ServiceDate sd = new ServiceDateService () .
getServiceDatePort () ;
String reponse = sd . getLongDate ( " 14/02/2014 " ) ;
System . out . println ( " Resultat : " + reponse ) ;
}
}
Exercice 157. (?) Créer une archive jar serveur.jar contenant l’ensemble du répertoire serveur qui a été
généré par wsimport. Créer ensuite un nouveau projet Eclipse, de nom ClientDate contenant l’exemple de
client ci-dessus. Rajouter la bibliothèque serveur.jar à ce projet puis testez le bon fonctionnement du client.
Exercice 158. (??) Le service web Dragon
Le but de cet exercice est de créer un élément indispensable de tout bon jeu de rôle massivement multi-joueur via
un service web : le contrôle de l’ouverture d’un coffre.
Il s’agit donc d’écrire un tel service web dans une classe Dragon, puis de le déployer et d’écrire un code client
pour le consommer.
La classe Dragon comportera une méthode :
String ouvrirCoffre(String login, String motdepasse)
retournant la chaine "le trésor des dragons maudits" si login est égal à "tetouillou" et le mot de passe
est égal à "empereur". Dans le cas contraire, le message retourné est "une fl^
eche empoisonnée".
Pour vous faciliter la tâche, voici un rappel des grandes étapes à suivre :
1. écrire la classe Gardien dans un paquet serveur
2. générer le serveur du service (dans ce paquet) : wsgen -cp . serveur.Gardien
3. écrire la méthode main adéquate pour pouvoir déployer ce service à une URL par exemple http: //
localhost: 4040/ Gardien , puis lancer le serveur
4. tester avec un navigateur le bon fonctionnement, en consultant par exemple la description en WSDL du
service, générée par le service lui-même : http: // localhost: 4040/ Gardien? wsdl
5. écrire le code du client dans une classe Client.java
Facultatif : modifier le service web pour qu’il refuse systématiquement l’ouverture du coffre pour un login
donné, après trois tentatives infructueuses.
Pour conclure, signalons que nous n’avons utilisé qu’une toute petite facette des annotations disponibles dans
le cadre de la création des services webs.
En effet, il existe en outre (liste non-exhaustive) :
@Web ServiceP rovider @Oneway @WebParam @WebResult @HandlerChain
@SOAPBinding @BindingType ...
13.4. APPLICATION : LES SERVICES WEBS
171
Fiche de synthèse sur les services webs
Services web : standards permettant à des serveurs webs d’offrir des services.
— XML : les données à échanger sont formatées en XML (en SOAP ou XML-RPC) ;
— Protocoles communs de transport : HTTP principalement, FTP, SMTP ...
— WSDL : langage (en XML) de description de l’interface publique du service Web ;
— UDDI : permet à des applications de rechercher un service web.
@WebService
public class ServiceDate {
public String getLongDate ( String date ) { ...
public static void main ( String [] args ) {
ServiceDate sd = new ServiceDate () ;
Endpoint endpoint = Endpoint . publish ( " http :// localhost
:4040/ servicedate " , sd ) ; ...
Etapes pour la création d’un serveur de services en Java 6
1. annoter la classe offrant ses méthodes comme service avec @WebService ;
2. générer les binaires de la classe avec l’utilitaire wsgen ;
3. déployer le service web sur une URL avec la méthode
EndPoint.publish(url,instance) ;
4. URL?wsdl contient une description en WSDL du service web.
ServiceDateService service = new ServiceDateService () ;
ServiceDate servicePort = service . getServiceDatePort () ;
System . out . println ( servicePort . getLongDate ( args [0]) ) ;
Etapes pour créer un client d’un service web en Java 6
1. générer les classes supports à partir de la description en WSDL, en utilisantwsimport :
wsimport -keep URL?wsdl ;
2. créer une instance du client ;
3. récupérer une terminaison (port) avec la méthode get...Port() ;
4. invoquer la méthode distante (du service web) via cette terminaison.
172
CHAPITRE 13. ANNOTATIONS ET TROIS APPLICATIONS
Quatrième partie
Exercices et travaux pratiques
173
Chapitre 14
Exercices supplémentaires
Cette annexe contient des exercices élémentaires illustrant des notions du cours.
Sommaire
14.1 Exercices indépendants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175
14.2 Problèmes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 190
14.1
Exercices indépendants
Questions de cours
Exercice 159. (?) - (Echauffement)
Java est un langage
1. compilé ;
2. interprété ;
3. compilé et interprété ;
4. ni compilé, ni interprété.
Exercice 160. (?) - (Visibilité des attributs d’une classe)
Pour chacun des mots-clefs suivants, préciser en quelques mots le niveau de protection associé : public, par
défaut, protected et private.
Exercice 161. (??) -(QCM (chaque question est indépendante))
175
CHAPITRE 14. EXERCICES SUPPLÉMENTAIRES
176
Questions
1. La commande javac permet :
Réponses
de compiler le code source en bytecode
de compiler le code source en langage machine
d’exécuter un programme java
2. Pour pouvoir exécuter un
programme Java, il est nécessaire
de disposer du code source
d’avoir une machine virtuelle Java
d’avoir le bon plugin Eclipse
3. Si Daltonien est une classe du
paquet superHeros,
Daltonien.java doit être dans le répertoire superHeros
Daltonien.class doit être dans le répertoire superHeros
ni l’un, ni l’autre
4. Dans le code suivant :
whiteIsRed est partagée par toutes les instances
static boolean whiteIsRed = true ;
jeVoisLaVieEnRose est partagée par toutes les instances
boolean jeVoisLaVieEnRose = true ;
ni l’un, ni l’autre
5. Dans le code suivant :
Humain h = new Daltonien() ;
h.help() ;
h est de type Daltonien
La méthode help de Daltonien est exécutée (si elle existe)
ni l’un, ni l’autre
6. Une classe abstraite
possède toutes ses méthodes abstraites
implémente une interface
ne peut pas être instanciée
7. Une interface
a au moins une méthode abstraite
a toutes ses méthodes abstraites
n’a pas de méthode abstraite
8. Une classe
peut hériter de plusieurs classes
peut implémenter plusieurs interfaces
ni l’un, ni l’autre
Exercice 162. (?) -(Connaissances générales)
Pour chacun des points ci-dessous, répondre en quelques lignes :
1. donner la définition de la surcharge d’une méthode ;
2. donner la définition de la composition de deux objets ;
3. donner la définition du masquage d’une méthode ;
4. qu’est-ce que le polymorphisme ?
5. qu’est-ce que la librairie JUnit et que permet-elle de faire ?
6. donner un exemple de code qui capture une exception de type IOException (erreur d’entrée-sortie).
Constructeurs
Exercice 163. (?) Source : Sébatien Estienne -http: // sebastien-estienne. developpez. com/
tutoriels/ java/ java-chap7/
Ecrire une classe Livre avec les attributs suivants :
14.1. EXERCICES INDÉPENDANTS
177
— titre : le titre du livre ;
— auteur : l’auteur du livre ;
— prix : le prix du livre ;
— annee : l’année du livre.
La classe Livre doit disposer des constructeurs suivants :
— Livre();
— Livre(titre);
— Livre(titre, auteur);
— Livre(titre, auteur, prix);
— Livre(titre, auteur, prix, annee);
— Livre(Livre);
La classe Livre doit contenir des accesseurs (get) et mutateurs (set) pour les différents attributs. Elle doit
aussi contenir une méthode toString() donnant une représentation de la classe Livre. Ecrire aussi une classe
TestLivre afin de tester la classe Livre.
Instances
Exercice 164. (?) Ecrire une classe Professeur : un professeur a un nom, un prénom et une matière (celle qu’il
enseigne). Un professeur peut enseigner (sa matière) : lorsqu’il le fait, l’ordinateur affiche
“Prenez des notes : bien évidemment, la matière [x] est la plus importante qui soit ...”
où [x] désigne la matière enseignée par le professeur.
Créer des instances de Professeur correspondant à vos enseignants de programmation objet, de mathématiques et d’algorithmique.
Quelles sont les données que doit stocker l’ordinateur lorsque ces 3 instances sont créées ? Quelle est la taille
de la mémoire utilisée en nombre de caractères ?
Exercice 165. (?) - (Affectation)
Combien d’instances de la classe A crée le code suivant ?
A x ,u , v ;
x = new A () ;
A y=x;
A z = new A () ;
Exercice 166. (??) -(Constructeur surchargé)
Pour la classe B définie ci-après, qu’affiche l’instruction B b = new B(2003); ?
class B {
public B () { System . out . print ( ‘ ‘ Ciao ’ ’) ;};
public B ( int i ) { this () ; System . out . println ( ‘ ‘ Bonjour ’ ’+ i ) ;};
}
Exercice 167. (??) -(Variable d’instance vs. variable de classe)
Pour la classe C définie ci-après, qu’affichera le code C x=new C(); C y=new C(); C z= x; Syset
+ z.j); ?
tem.out.println(z.i +
class C {
public static int i ;
public int j ;
public C () { i ++; j = i ; }
}
CHAPITRE 14. EXERCICES SUPPLÉMENTAIRES
178
Exercice 168. (??) - (Lecture de programme)
Donner l’affichage exact du programme lors de l’appel java A4b.
class A4a {
private int nbrA4a = 1;
private int rang ;
A4a () {
rang = nbrA4a ;
nbrA4a ++;
}
public String toString () { return " " + rang ; }
}
class A4b {
public static void main ( String [] a ) {
A4a q0 , q1 , q2 ;
q1 = new A4a () ; q0 = new A4a () ; q2 = new A4a () ;
System . out . print ( q0 + " " + q1 + " " + q2 ) ;
}
}
Héritage
c
Exercice 169. (?) - Irène
Charon, Télécom ParisTech - Paris 2011
http: // perso. telecom-paristech. fr/ ~charon/ coursJava/ exercices/ constructeurs. html
Donner l’affichage exact de l’exécution du programme.
class A {
A () {
System . out . println ( " bonjour de A " ) ;
}
}
class B extends A {
boolean verite ;
int valeur ;
B () {
System . out . println ( " constructeur B () " ) ;
}
B ( int valeur ) {
this () ;
this . valeur = valeur ;
System . out . println ( " constructeur B ( int ) " ) ;
}
B ( boolean verite ) {
this . verite = verite ;
System . out . println ( " constructeur B ( boolean ) " ) ;
}
14.1. EXERCICES INDÉPENDANTS
179
B ( boolean verite , int valeur ) {
this ( valeur ) ;
this . verite = verite ;
System . out . println ( " constructeur B ( boolean , int ) " ) ;
}
public String toString () {
return " B : ( " + verite + " , " + valeur + " ) \ n " ;
}
}
class EssaiConstructeurs {
public static void main ( String [] argv ) {
B b = new B ( true ) ;
System . out . println ( b ) ;
b = new B ( false , 5) ;
System . out . println ( b ) ;
}
}
Exercice 170. (?) - (lecture de code)
Le code ci-après est adapté d’un code écrit par cysboy : http: // www. siteduzero. com/ membres/
cysboy-71287
Donner l’affichage exact de l’exécution du programme.
class Ville {
private
private
private
private
String nomVille ;
String nomPays ;
int nbreHabitants ;
char categorie ;
public Ville () {
nomVille = " Inconnu " ;
nomPays = " Inconnu " ;
nbreHabitants = 0;
this . setCategorie () ;
}
public Ville ( String pNom , int pNbre , String pPays ) {
nomVille = pNom ;
nomPays = pPays ;
nbreHabitants = pNbre ;
this . setCategorie () ;
}
public String getNom ()
{ return nomVille ; }
public String getNomPays () { return nomPays ; }
public int getNombreHabitants () { return nbreHabitants ; }
public char getCategorie () { return categorie ; }
CHAPITRE 14. EXERCICES SUPPLÉMENTAIRES
180
public void setNom ( String pNom ) {
nomVille = pNom ;
}
public void setNomPays ( String pPays ) {
nomPays = pPays ;
}
public void setNombreHabitants ( int nbre ) {
nbreHabitants = nbre ;
this . setCategorie () ;
}
private void setCategorie () {
int bornesSuperieures [] = {0 , 1000 , 10000 , 100000 , 500000 ,
1000000 , 5000000 , 10000000};
char categories [] = { ’? ’ , ’A ’ , ’B ’ , ’C ’ , ’D ’ , ’E ’ , ’F ’ , ’G ’
, ’H ’ };
int i = 0;
while ( i < bornesSuperieures . length && this . nbreHabitants
>= bornesSuperieures [ i ])
i ++;
this . categorie = categories [ i ];
}
public String decrisToi () {
return this . nomVille + " est une ville de " + this . nomPays + " ,
elle comporte : " + this . nbreHabitants + " habitant ( s ) = >
elle est donc de categorie : " + this . categorie ;
}
}
class Capitale extends Ville {
private String monument ;
public Capitale () {
super () ;
monument = " aucun " ;
}
public Capitale ( String nom , int hab , String pays , String
monument ) {
super ( nom , hab , pays ) ;
this . monument = monument ;
}
public String decrisToi () {
14.1. EXERCICES INDÉPENDANTS
181
String str = super . decrisToi () + " \ n == > > " + this . monument
+ " en est un monument " ;
return str ;
}
public String getMonument () { return monument ; }
public void setMonument ( String monument ) {
this . monument = monument ;
}
}
class Main {
public static void main ( String [] args ) {
Ville [] tableau = new Ville [6];
String [] tab = { " Marseille " , " Lille " , " Caen " , " Lyon " , "
Paris " , " Nantes " };
int [] tab2 = {123456 , 78456 , 654987 , 75832165 , 1594 ,
213};
for ( int i = 0; i < 6; i ++) {
if ( i <3) {
Ville V = new Ville ( tab [ i ] , tab2 [ i ] , "
france " ) ;
tableau [ i ] = V ;
}
else {
Capitale C = new Capitale ( tab [ i ] , tab2 [
i ] , " france " , " la tour Eiffel " ) ;
tableau [ i ] = C ;
}
}
for ( Ville v : tableau ) {
System . out . println ( v . decrisToi () + " \ n " ) ;
}
}
}
Exercice 171. (??) - (Modélisation)
Ecrire en langage Java, deux classes A et B telles que :
— la classe B hérite de la classe A ;
— la classe A possède
— un attribut entier x visible seulement par ses classes filles ;
— un constructeur affectant une valeur à l’attribut x ;
— une méthode triple() renvoyant la valeur de x multipliée par 3
— la classe B possède un attribut y visible uniquement dans cette classe
— un constructeur affectant une valeur aux attributs x et y
— une méthode divise() divise renvoyant la valeur de y divisée par le triple de la valeur de x, si x est
non-nul (sinon la méthode renvoie 0)
— une méthode qui masque la méthode triple() de la classe mère, et qui retourne le triple la valeur de y,
à a place.
Ecrire également une classe C possédant la méthode main et qui instancie des exemples d’objets A et B ; préciser ce que devrait afficher votre programme si on l’exécutait. (l’affichage doit mettre en valeur les caractéristiques
CHAPITRE 14. EXERCICES SUPPLÉMENTAIRES
182
des classes A et B).
Classes abstraites
Exercice 172. (??) -(Classes abstraites)
Cet exercice est basé sur le cours “Les classes abstraites”, par cysboy, accessible en ligne à :
http: // www. siteduzero. com/ tutoriel-3-10373-les-classes-abstraites. html
On considère le schéma d’héritage suivant.
Les classes Animal, Canin et Felin sont abstraites. La classe Animal possède deux classes abstraites seulement : deplacement() et crier(). Les classes Canin et Felin n’implémentent que deplacement().
Donner le code des classes Animal, Felin, Canin, Loup de telle sorte que :
— le code soit au maximum réutilisé (en évitant par exemple de masquer de manière inutile des méthodes)
— la méthode main ci-dessous :
public static void main ( String [] args ) {
Loup l = new Loup ( " Gris bleu " , 20) ;
l . boire () ;
l . manger () ;
l . deplacement () ;
l . crier () ;
System . out . println ( l . toString () ) ;
}
donne l’affichage suivant :
Je bois de l ’ eau !
Je mange de la viande
Je me deplace en meute !
J ’ hurle a la lune en faisant ouhouh ! !
Je suis un objet de la classe Loup , je suis Gris bleu , je pese 20
Indications : la méthode getClass permet d’afficher le type de l’objet.
14.1. EXERCICES INDÉPENDANTS
183
Interfaces
Exercice 173. (??) - (Interfaces AbstractCollection et Iterator)
L’interface AbstractCollection possède deux méthodes :
boolean add ( Object obj )
Iterator iterator ()
La méthode add ajoute un objet dans la collection. Elle renvoie true si l’ajout de l’objet a effectivement
modifié la collection, false sinon.
La méthode iterator renvoie un objet qui implémente l’interface Iterator, qui possède trois méthodes :
Object next ()
boolean hasNext ()
void remove ()
1. donner une implémentation MonIterateur de Iterator
2. donner une implémentation MaCollection de AbstractCollection, exploitant MonIterateur.
Polymorphisme
c
Exercice 174. (??) adapté de : Irène
Charon, Télécom ParisTech - Paris 2011
http: // www. infres. enst. fr/ people/ charon/ coursJava/ exercices/ polymorphisme. html
Définir un ensemble de classes pour "modéliser" des oiseaux. Certains oiseaux seront des merles, d’autres des
pies. Chaque oiseau doit avoir une méthode nommée decrire. Ecrire les classes nécessaires pour cette modélisation, en tirant partie de l’héritage et de la redéfinition des méthodes.
Ecrire une classe Main avec la méthode main, dans laquelle une collection de 5 oiseaux (des pies et des merles)
est créée puis la méthode decrire de chacun de ceux-ci est appelé.
Voici un exemple d’affichage provoqué par l’exécution du programmme.
Famille
Famille
Famille
Famille
Famille
des
des
des
des
des
oiseaux
oiseaux
oiseaux
oiseaux
oiseaux
:
:
:
:
:
je
je
je
je
je
suis
suis
suis
suis
suis
un merle
une pie
une pie
un merle
une pie
En quoi cet exercice repose-t-il sur le polymorphisme ?
Collections
Exercice 175. (? ? ?) La compagnie AirMoon dont le slogan est "Demandez nous la lune, nous vous emmènerons
où nous voudrons" souhaite simuler la prise en charge de ses clients à l’aéroport, pour mesurer l’évolution de la
taille de la file d’attente de ses clients VIP. (corrigé)
La simulation repose sur les considérations suivantes :
— le temps de prise en charge d’un client est d’une minute ;
— toutes les minutes, entre 0 et 5 nouveaux clients arrivent dont 30% de clients VIP (ces deux valeurs sont
paramétrables) ;
— tous ces nouveaux clients vont dans la file d’attente principale ;
CHAPITRE 14. EXERCICES SUPPLÉMENTAIRES
184
— si le temps d’attente d’un client VIP dans la file d’attente principale est supérieure à 5 minutes (valeur
paramétrable) alors tous les clients VIP se rendent dans la file d’attente "VIP" (si elle n’existe pas, celle-ci
est créée à la volée : i.e. le comptoir VIP ouvre)
— si la file d’attente VIP est vide, alors elle est supprimée (i.e. le comptoir VIP ferme)
— si le comptoir VIP est fermé alors le comptoir normal peut prendre en charge deux clients simultanément
(i.e. 2 clients / minute)
— si le comptoir VIP est ouvert alors le comptoir normal prend en charge un client / minute et le comptoir
VIP aussi.
Le but est d’afficher l’évolution de la taille des deux files d’attente. Voici un exemple de résultat attendu :
****************************
4 nouveaux clients en approche ...
file principale - taille : 4
file principale - temps attente VIP :
Prise en charge des clients
file principale - taille : 2
****************************
2 nouveaux clients en approche ...
file principale - taille : 4
file principale - temps attente VIP :
Prise en charge des clients
file principale - taille : 2
****************************
4 nouveaux clients en approche ...
file principale - taille : 6
file principale - temps attente VIP :
Prise en charge des clients
file principale - taille : 4
****************************
4 nouveaux clients en approche ...
file principale - taille : 8
file principale - temps attente VIP :
Prise en charge des clients
file principale - taille : 3
file VIP - taille : 3 - tailleMax : 0
****************************
0 nouveaux clients en approche ...
file principale - taille : 3
file VIP - taille : 3 - tailleMax : 0
file principale - temps attente VIP :
Prise en charge des clients
file principale - taille : 2
file VIP - taille : 2 - tailleMax : 3
****************************
0 nouveaux clients en approche ...
file principale - taille : 2
file VIP - taille : 2 - tailleMax : 3
file principale - temps attente VIP :
Prise en charge des clients
file principale - taille : 1
file VIP - taille : 1 - tailleMax : 3
0
0
3
7
0
0
Questions
1. Quelle est la structure de données adéquate pour stocker une file d’attente ?
14.1. EXERCICES INDÉPENDANTS
185
2. Ecrire une interface IClient permettant de modéliser les clients ? (indication : veillez à ne mettre que le
strict nécessaire à l’exercice)
3. Ecrire les deux classes Client (client lambda) et ClientVIP (client VIP) implémentant cette interface.
4. Ecrire une interface IFileAttente permettant de modéliser une file d’attente. (indication : en Java, l’interface Deque est adaptée aux structures de données de type listes doublement chainées ...)
5. Ecrire une classe FileAttente implémentant cette interface.
6. Ecrire la classe principale AirMoon mettant en place l’algorithme principal et les affichages. Celle-ci
contiendra au moins trois méthodes :
— public Collection<IClient> arrivee(int nbClients, int pourcentageVIP) : création
des nouveaux clients arrivants
— public void traitement() : prise en charge des clients (par minute) par le(s) comptoir(s)
— public void principal() : boucle principale
Exceptions
Exercice 176. (?) Donner l’affichage exact du programme lors de l’appel : java Main.
import java . util .*;
class SuperException extends Exception {
String message = " " ;
public SuperException () { System . out . println ( " [ SuperException ] :
SuperException () " ) ; }
public SuperException ( String message ) {
System . out . println ( " [ SuperException ] : SuperException (
String ) " ) ;
this . message = message ;
}
public String toString () { return " [ SuperException ] : " + message ;
}
}
interface Humain {
void help () throws SuperException ;
}
class Primate implements Humain {
public Primate () { System . out . println ( " [ Primate ] : Primate () " ) ;
}
public void help () throws SuperException {
System . out . println ( " [ Primate ] : argh ! " ) ;
throw new SuperException ( " lancee par un primate " ) ;
}
public String toString () { return " [ Primate ] : Je suis un
primate " ;}
}
class Daltonien extends Primate {
public Daltonien () { System . out . println ( " [ Daltonien ] : Daltonien
() " ) ; }
CHAPITRE 14. EXERCICES SUPPLÉMENTAIRES
186
static void hipipip () { System . out . println ( " [ Daltonien ] : Hourra !
" ) ;}
public void help () throws SuperException { System . out . println ( " [
Daltonien ] : SOS " ) ; }
public String toString () { return " [ Daltonien ] : Je suis un
daltonien " ;}
}
class Main {
public static void main ( String [] args ) {
ArrayList < Humain > humains = new ArrayList < Humain >() ;
Daltonien a = new Daltonien () ;
Primate b = new Primate () ;
Humain c = new Primate () ;
humains . add ( a ) ; humains . add ( b ) ; humains . add ( c ) ;
System . out . println ( " Premier parcours : " ) ;
for ( Humain h : humains ) {
System . out . println ( h ) ;
}
try {
System . out . println ( " Second parcours : " ) ;
for ( Humain h : humains )
h . help () ;
}
catch ( Exception e ) {
System . out . println ( " [ Main ] : Oups - Exception " + e + "
capturee !!!! " ) ;
}
Daltonien . hipipip () ;
}
}
Exercice 177. (??)
Donner l’affichage obtenu en exécutant le code ci-après, où MaCollection est une solution de l’exercice 173.
import java . util .*;
class SuperException extends Exception {
String s = " " ;
SuperException ( String text ) { s = text ; }
public String toString () { return " Ma super exception : " + s ; }
}
class Humain {
int nbJambes = 2;
public void help () { System . out . println ( " Quelle heure est - il ? " ) ;}
}
class Primate extends Humain implements Chasseur {
public void chasser () {};
14.1. EXERCICES INDÉPENDANTS
187
void test () throws SuperException {
throw new SuperException ( " lancee par " + this . getClass () + " ! " ) ;
}
public String toString () { return " Je suis un primate " ;}
}
interface Chasseur {
abstract void chasser () ;
abstract void help () ;
}
class Daltonien extends Primate {
static boolean whiteIsRed = true ;
static boolean redIsWhite = true ;
private boolean jeVoisLaVieEnRose = true ;
static void hipipip () { System . out . println ( " Hourra ! " ) ;}
public void help () { System . out . println ( " SOS " ) ; }
void help ( int nb ) {
for ( int i =1; i <= nb ; i ++) System . out . print ( " SOS ... " ) ;
System . out . println ( " Aaaaaaargh " ) ;
}
void deprime () { jeVoisLaVieEnRose = false ;}
public String toString () { return " Je suis un daltonien " ;}
}
class Main {
public static void main ( String [] args ) {
try {
MaCollection mc = new MaCollection () ;
Daltonien a = new Daltonien () ;
Primate b = new Primate () ;
Humain c = new Primate () ;
mc . add ( a ) ; mc . add ( b ) ; mc . add ( c ) ;
System . out . println ( " premier parcours " ) ;
Iterator i = mc . iterator () ;
while ( i . hasNext () ) { System . out . println ( i . next () ) ;}
System . out . println ( " once again " ) ;
i = mc . iterator () ;
Primate p = ( Primate ) i . next () ;
System . out . println ( p ) ; p . test () ;
}
catch ( Exception e ) { System . out . println ( " Oups !!! Exception " + e +
" écapture !!!! " ) ;
}
}
}
Exercice 178. (??) - (Collections et exceptions)
Soit une classe Personne possédant un constructeur Personne( String nom) ainsi qu’une méthode
String getNom() retournant la valeur de l’attribut nom.
Ecrire une classe Groupe qui permet de créer des groupes de personnes, dont les membres ont des noms tous
CHAPITRE 14. EXERCICES SUPPLÉMENTAIRES
188
différents, contenant les 3 méthodes void add(Personne p) (ajoutant la personne p au groupe : si la personne p
a un nom déjà présent dans le groupe, une exception de type Exception doit être levée), int size() (retournant
le nombre de personnes dans le groupe) et String toString() (retournant une chaine de caractères formée de
la liste des noms des personnes du groupe)
Multi-processus légers & SWING
L’objectif est de programmer une barre de progression qui simule l’avancement de la projection d’un film
en utilisant des processus légers. La partie graphique est réalisée en SWING : le développement d’une interface
graphique avec la bibliothèque AWT/SWING est décrit dans le chapitre 11.
La
page
http://docs.oracle.com/javase/tutorial/uiswing/examples/components/
ProgressBarDemoProject/src/components/ProgressBarDemo.java contient un exemple de code
pour une barre de progression fourni par Oracle.
On rappelle le code de la classe Film2 :
public class Film2 implements Runnable {
public Thread monProcessus = null ;
private String titre = null ;
public int nb = 6; public int pos = 0;
public Film2 ( String titre , int nb ) {
this . titre = titre ; this . nb = nb ;
monProcessus = new Thread ( this ) ;
}
public void go () { monProcessus . start () ;}
public void run () { // joue le film
try {
for ( pos =1; pos <= nb ; pos ++) {
Thread . sleep (1000) ;
System . out . println ( " [ " + titre + " ] Quart d \ ’ heure numero " + pos
);
}
}
catch ( In te rru pt edE xce pt ion e )
{ System . out . println ( " Projection du film interrompue ! " ) ; }
}}
Exercice 179. (? ? ?) Ecrire une classe ProgressBarDemo implémentant une barre de progression qui surveille
la projection d’un film (variable pos qui varie de 0 à nb de la classe Film2).
Il s’agira d’un composant comportant également un bouton start qui lance la projection du film (méthode go)
et la barre de progression.
Une solution :
import java . awt .*;
import java . awt . event .*;
import javax . swing .*;
public class ProgressBarDemo extends JPanel {
public final static int ONE_SECOND = 1000;
private JProgressBar progressBar ;
private Timer timer ;
private JButton startButton ;
14.1. EXERCICES INDÉPENDANTS
189
private Film2 film = null ;
public ProgressBarDemo () {
film = new Film2 ( " Maya " ,12) ;
// Create the demo ’s UI .
startButton = new JButton ( " Start " ) ;
startButton . addActionListener ( new ActionListener () {
public void actionPerformed ( ActionEvent evt ) {
startButton . setEnabled ( false ) ;
setCursor ( Cursor . ge tPredefi nedCurs or ( Cursor . WAIT_CURSOR
));
film . go () ;
timer . start () ;
// lancement du timer ( et donc de l ’ actualisation de la
barre de progression )
}
}
);
progressBar = new JProgressBar (0 , film . nb ) ;
progressBar . setValue (0) ;
progressBar . setStringPainted ( true ) ;
JPanel panel = new JPanel () ;
panel . add ( startButton ) ;
panel . add ( progressBar ) ;
add ( panel ) ;
// Create a timer .
timer = new Timer ( ONE_SECOND , new ActionListener () {
public void actionPerformed ( ActionEvent evt ) {
progressBar . setValue ( film . pos ) ;
// mise a jour toutes les secondes de la valeur de la
barre de progression
}
}) ;
}
public static void main ( String [] args ) {
// Create and set up the window .
JFrame frame = new JFrame ( " ProgressBarDemo " ) ;
frame . s e t D e f a u l t C l o s e O p e r a t i o n ( JFrame . EXIT_ON_CLOSE ) ;
// Create and set up the content pane .
JComponent newContentPane = new ProgressBarDemo () ;
newContentPane . setOpaque ( true ) ;
frame . setContentPane ( newContentPane ) ;
// Display the window .
frame . pack () ;
frame . setVisible ( true ) ;
}
CHAPITRE 14. EXERCICES SUPPLÉMENTAIRES
190
}
14.2
Problèmes
c
Exercice 180. (? ? ?) Irène
Charon, Télécom ParisTech - Paris 2011
http: // www. infres. enst. fr/ people/ charon/ coursJava/ exercices/ index. html
Modéliser un élève Un élève est modélisé par la classe Eleve d’un paquetage nommé gestionEleves, de la
façon suivante. La classe Eleve possède trois attributs privés :
— son nom, nommé nom, de type String,
— un ensemble de notes, nommé listeNotes, qui sont des entiers rangés dans un ArrayList<Integer>,
— une moyenne de type double, nommée moyenne, qui doit toujours être égale à la moyenne des notes contenues dans l’attribut listeNotes. Un élève sans aucune note sera considéré comme ayant une moyenne
nulle.
La classe Eleve possède un constructeur permettant uniquement d’initialiser le nom de l’élève.
La classe Eleve possède aussi cinq méthodes publiques :
— Un getter pour la moyenne de l’élève c’est-à-dire une méthode d’entête public double getMoyenne()
renvoie la valeur de l’attribut moyenne ;
— Un getter pour le nom de l’élève c’est-à-dire une méthode d’entête public String getNom() renvoie le
nom de l’élève ;
— Un getter pour la liste des notes de l’élève c’est-à-dire une méthode d’entête public
ArrayList<Integer> getListeNotes() renvoie la liste des notes de l’élève ;
— La méthode d’entête public void ajouterNote(int note) ajoute la note reçue en paramètre à
listeNotes ; si la note reçue en paramètre est négative, la note introduite est 0 ; si la note reçue en
paramètre est supérieure à 20, la note introduite est 20 ; la méthode actualise en conséquence l’attribut
moyenne ; l’actualisation est faite à temps constant, et non pas en un temps proportionnel au nombre de
notes déjà enregistrées.
— la méthode d’entête public String toString() qui retourne une description de l’élève considéré (par
exemple : "Sophie (12.25)").
Modéliser un groupe d’élèves Un groupe d’élèves (instances de la classe Eleves.Eleve précédemment
définie) sera ici modélisé par la classe GroupeEleves du paquetage gestionEleves de la façon suivante.
La classe GroupeEleves posséde un attribut privé : une collection d’élèves nommée listeEleves, de type
ArrayList<Eleve>.
La classe GroupeEleves ne possède pas de constructeur explicite.
La classe GroupeEleves possède aussi cinq méthodes publiques :
— la méthode d’entête public int nombre() renvoie le nombre d’élèves contenus dans listeEleves ;
— la méthode d’entête public ArrayList<Eleve> getListe() renvoie listeEleves ;
— la méthode d’entête public void ajouterEleve(Eleve eleve) ajoute l’élève reçu en paramètre à
listeEleves ;
— la méthode d’entête public Eleve chercher(String nom) renvoie l’éleve dont le nom est indiqué
par le paramètre ; si plusieurs élèves ont même nom, la méthode renvoie le premier élève ayant ce nom
contenu dans listeEleves ; si aucun élève n’a le nom indiqué, la méthode retournenull. On pourra
utiliser la méthode equals de la classe String pour comparer une chaîne de caractères à une autre.
— la méthode d’entête public void lister() écrit à l’écran la liste des élèves. Elle utilise une ligne par
Eleve ; elle utilise la méthode toString de la classe Eleve.
14.2. PROBLÈMES
191
Modéliser un élève comparable à un autre Il s’agit ici de compléter la classe Eleve pour faire en
sorte d’avoir une classe qui implémente l’interface java.lang.Comparable<T>. C’est une interface générique,
comme l’indique le <T> mais il n’y a pas grand chose à savoir sur le sujet pour l’utiliser. Il faudra, en implémentant
l’interface, indiquer à la place du T le type des objets qu’on veut comparer entre eux.
L’interface Comparable<T> déclare une seule méthode : public int compareTo(T o);
Quand cette méthode est implémentée, elle doit retourner une valeur strictement négative, nulle ou strictement
positive selon que l’objet concerné (celui qui correspond à la référence this) est plus petit que l’objet o, égal à
l’objet o ou plus grand que l’objet o.
On comparera les élèves selon leur moyenne.
Pour cela, vous allez reprendre la classe Eleve écrite précédemment pour la transformer en une classe qui
implémente l’interface java.lang.Comparable<T>. Il faudra pour cela compléter l’entête de la classe Eleve
par "implements Comparable<Eleve>". Il faudra de plus définir la méthode compareTo déclarée par l’interface
Comparable. Cette méthode est à nouveau générique ; si la classe implémente Comparable<Eleve>, le paramètre
de la méthode doit être de type Eleve, l’entête est donc : public int compareTo(Eleve autreEleve)
Le code de la méthode doit respecter la fonctionnalité décrite plus haut.
Modéliser un groupe d’élèves comparables Il s’agit essentiellement de modéliser un groupe d’élèves comparables entre eux selon leurs moyennes. On souhaite précisément ajouter à la classe GroupeEleves :
— une méthode, nommée meilleurEleve, qui retourne l’élève de meilleure moyenne de la liste
listeEleves ;
— une méthode, nommée trierEleves, qui trie la liste listeEleves selon l’ordre croissant des moyennes
des élèves.
Le code sera très court. On utilisera deux méthodes statiques de la classe java.util.Collections. Plus
précisément :
— l’instruction Collections.max(listeEleves) renvoie l’élève qui est "le plus grand" par rapport à la
relation définie par la méthode compareTo dans la classe Eleve ; cette méthode peut être utilisée pour tout
ArrayList d’objets implémentant l’interface Comparable.
— Collections.sort(listeEleves) trie listeEleve par ordre croissant selon la relation définie par la
méthode compareTo dans la classe Eleve ; cette méthode peut-être utilisée pour tout ArrayList d’objets
implémentant l’interface Comparable.
Des élèves avec un résultat en crédits ECTS Il faut d’abord reprendre la classe Eleve et définir une classe
abstraite nommée EleveECTS qui hérite de la classe Eleve et qui possède de plus une méthode abstraite : public
abstract int resultat();
Cette méthode, quand elle sera implémentée, devra faire un calcul pour retourner le nombre de crédits ECTS
obtenus par l’élève concerné. La classe EleveECTS fera partie du paquetage gestionEleves.
On définira alors deux classes héritant de la classe EleveECTS :
— une classe Eleve1A qui définit la méthode resultat héritée de la classe eleveECTS, en faisant en sorte
que la méthode resultat retourne 60 si la moyenne de l’élève est au moins égal à 12 et 0 sinon ;
— une classe EleveMaster qui définit la méthode resultat héritée de la classe EleveECTS ; cette méthode
compte 6 pour chaque note supérieure ou égale à 10 et 3 pour chaque note supérieure ou égale à 8 et
inférieure à 10 et renvoie le résultat.
Exercice 181. (? ? ?) - (Ail)
Ce problème est consacré à l’étude d’un cas fondamental : modéliser l’évolution d’une population en présence
de vampires.
Préambule : le Vampire Dracula a convié une centaine de convives pour une fête débutant à 20h du soir. Parmi
les convives se cachent un Hero, Van Helsing.
CHAPITRE 14. EXERCICES SUPPLÉMENTAIRES
192
Règles
—
—
—
—
—
—
un Hero, un Humain, un Vampire sont des Individu (la classe Individu est fournie ci-après) ;
un Vampire est endormi de 8h du matin à 20h du soir, réveillé sinon ;
un Hero est éveillé 24h/24 ;
un Humain est endormi de 6h du matin à midi ;
il y a un et un seul héro (Van Helsing) ;
chaque heure, chaque vampire éveillé tente de vampiriser deux victimes :
— si la victime est humaine et endormie, elle est transformée en Vampire ;
— si la victime est humaine et éveillée, celle-ci a 50% de chance d’être vampirisée ;
— si la victime est Van Helsing, celui-ci a 5% de chance d’être vampirisé ;
— chaque heure, Van Helsing détruit jusqu’à deux vampires endormis.
Question
Proposer une implémentation. Prévoir une classe Jeu avec une méthode jouer() permettant d’afficher les statistiques pour chaque heure selon ci-dessous :
Début du jeu
20h:
Population [ 1 Hero, 99 Humains, 1 Vampire].
21h:
Population [ 1 Hero, 97 Humains, 3 Vampires].
22h:
Population [ 1 Hero, 92 Humains, 8 Vampires].
...
Annexe : code de la classe Individu
abstract class Individu {
boolean endormi = false ;
public abstract void setEndormi ( int heure ) ;
public abstract Individu attaqueParVampire () ;
}
Exercice 182. (??) - (Graphes)
On considère les 3 classes Graphe, Sommet et Arete ci-dessous :
public class Arete {
public Sommet a ;
public Sommet b ;
public Arete ( Sommet a , Sommet b ) {
this . a = a ; this . b = b ;
}
public String toString () {
return " Arete : " + a + " - " + b ;
}
}
public class Graphe {
public int n =0;
14.2. PROBLÈMES
193
public ArrayList < Sommet > sommets = new ArrayList () ;
public ArrayList < Arete > aretes = new ArrayList () ;
public boolean estAdjacent ( Sommet u , Sommet v ) {
for ( Arete arete : aretes ) {
if (( arete . a == u ) &&( arete . b == v ) ) return true ;
if (( arete . a == v ) &&( arete . b == u ) ) return true ;
}
return false ;
}
}
public class Sommet {
public int id ;
public Sommet () {}
public Sommet ( int id ) {
this . id = id ;
}
public String toString () {
return " Sommet : " + id ;
}
}
Cycles et chemins
Ecrire :
1. une classe Chemin héritant de Graphe et dont le constructeur Chemin(int n) construit un chemin à n
sommets ;
2. une classe Cycle héritant de Graphe et dont le constructeur Cycle(int n) construit un cycle (élémentaire) à n sommets.
Degrés
Ecrire
1. une méthode int degre(Sommet s) dans la classe Graphe qui retourne le degré du sommet passé en
paramètre ;
2. une méthode int degreMaximum() qui retourne le degré maximum du graphe.
Complementaire
Ecrire une méthode Graphe complementaire() dans la classe Graphe qui retourne le complémentaire du
graphe.
Enumération des cliques
Ecrire une méthode int tailleMaximumClique() dans la classe Graphe qui retourne la taille maximum
d’une clique (rappel : une clique est un ensemble de sommets 2-à-2 adjacents).
194
CHAPITRE 14. EXERCICES SUPPLÉMENTAIRES
Chapitre 15
Travaux pratiques : l’application Labyrinthe
Ce chapitre est consacré à une série de travaux pratiques, qui s’enchainent, dont le thème commun est la réalisation d’une application labyrinthe, par ajouts de fonctionnalités mettant en œuvre progressivement les différentes
notions de ce cours.
Sommaire
15.1 Affichage du plan d’un labyrinthe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
15.2 Modélisation du labyrinthe, déplacement fluide d’un personnage et éclairage . . . . . . . .
15.3 Restructuration de la modélisation du labyrinthe . . . . . . . . . . . . . . . . . . . . . . . .
15.4 Implémentation d’un labyrinthe basée sur une grille . . . . . . . . . . . . . . . . . . . . . .
15.5 Polymorphisme : implémentation alternative d’un labyrinthe . . . . . . . . . . . . . . . . .
15.6 Collections : modélisation de graphes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
15.7 Modélisation d’un labyrinthe par un graphe . . . . . . . . . . . . . . . . . . . . . . . . . .
15.8 Affichage du plus court chemin dans le labyrinthe, via une bibliothèque externe (JGraphT)
15.9 Découverte des principales commandes du JDK et des bibliothèques de Java . . . . . . . .
15.10Débogueur, tests unitaires, exception . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
15.11Eclairage et monstres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
15.12Sauvegarde/restauration d’un labyrinthe et de ses salles . . . . . . . . . . . . . . . . . . . .
15.13Serveur de labyrinthes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
195
196
199
202
204
206
207
209
214
219
220
225
234
239
CHAPITRE 15. TRAVAUX PRATIQUES : L’APPLICATION LABYRINTHE
196
15.1
Affichage du plan d’un labyrinthe
L’objet de ces travaux pratiques est de dessiner le plan d’un labyrinthe dont les salles sont carrées.
Le format utilisé pour stocker un labyrinthe dans un fichier texte est le suivant :
37 37 // nombre de lignes et de colonnes de la grille
1 1 // coordonnees de l’entree
11 35 // coordonnees de la sortie
1 3 // coordonnees des autres salles
...
L’archive TP1.zip contient le code de départ sous la forme d’un projet pour NetBeans. Il s’agit d’une application Java "classique" exploitant les bibliothèques de Processing pour la partie graphique. Ce code ne fait
pas grand chose : il se contente d’afficher une fenêtre avec une image de fond. Le fichier laby.txt inclus est un
exemple de labyrinthe type.
Exercice 183. (?) Pour vous familiariser avec le format ci-dessus, ouvrez un fichier de labyrinthe dans un éditeur
de texte et dessinez sur une feuille la partie du labyrinthe formée des salles dont les coordonnées sont inférieures
ou égales à 7.
On souhaite afficher le plan de ce labyrinthe dans une fenêtre graphique programmée avec Processing.
Chaque case du labyrinthe sera de taille taille par taille, où taille est un paramètre de l’application. L’application possèdera également un paramètre laby qui stocke le nom du fichier du labyrinthe à afficher.
Lors de la lecture du fichier, vous allez récupérer des chaines de caractères représentant un nombre. Pour
convertir une chaine s en un nombre n, vous pouvez faire :
int n = Integer . parseInt ( s ) ; // conversion de s ( String ) dans le nombre
n ( int )
Exercice 184. (??) Dans la méthode setup de la classe Dessin, faites en sorte que la fenêtre soit d’une taille
adaptée au labyrinthe, en prévoyant une marge de 10 pixels tout autour.
Besoin d’aide ?
// recuperation des donnees du fichier
lignes = loadStrings ( " laby . txt " ) ;
// lignes [0] contient les dimensions du labyrinthe
String [] dimension = split ( lignes [0] , " " ) ;
// dimension [0] est la largeur du labyrinthe et dimension [1] la hauteur
int x = Integer . parseInt ( dimension [0]) ;
int y = Integer . parseInt ( dimension [1]) ;
// fenetre graphique de taille adaptee au labyrinthe
int largeur = ( taille * x ) + (2 * 10) ;
int hauteur = ( taille * y ) + (2 * 10) ;
size ( largeur , hauteur ) ;
Exercice 185. (??) Créer une classe Salle : sachant qu’une salle possède des coordonnées x et y, une couleur
(de type int) et peut se dessiner.
Besoin d’aide ?
public class Salle {
int x ;
int y ;
int couleur ;
15.1. AFFICHAGE DU PLAN D’UN LABYRINTHE
197
Salle ( int x , int y , int c ) {
this . x = x ;
this . y = y ;
this . couleur = c ;
}
void dessiner ( Dessin dessin ) {
dessin . fill ( couleur ) ;
dessin . rect (10+(( x -1) * dessin . taille ) , 10+(( y -1) * dessin . taille ) ,
dessin . taille , dessin . taille ) ;
}
}
Exercice 186. (? ? ?) Ecrire une classe Labyrinthe : un labyrinthe contient une collection de salles, peut être
créé à partir d’un fichier, et peut se dessiner.
Besoin d’aide ?
public class Labyrinthe {
Dessin dessin ;
ArrayList < Salle > salles = new ArrayList < >() ; // stockage des salles
Salle entree ;
Salle sortie ;
public Labyrinthe ( Dessin dessin ) {
this . dessin = dessin ;
}
public void load () {
// entree et sortie
String [] coordonnees = dessin . split ( dessin . lignes [1] , " " ) ;
int x = Integer . parseInt ( coordonnees [0]) ;
int y = Integer . parseInt ( coordonnees [1]) ;
entree = new Salle (x , y , 127) ; // 127 : couleur grise
salles . add ( entree ) ;
coordonnees = dessin . split ( dessin . lignes [2] , " " ) ;
x = Integer . parseInt ( coordonnees [0]) ;
y = Integer . parseInt ( coordonnees [1]) ;
sortie = new Salle (x , y , 0) ; // 0 : couleur noire
salles . add ( sortie ) ;
// autres salles
for ( int i = 3; i < dessin . lignes . length ; i ++) { // parcourt de
toutes les lignes sauf la premiere
coordonnees = dessin . split ( dessin . lignes [ i ] , " " ) ;
x = Integer . parseInt ( coordonnees [0]) ;
y = Integer . parseInt ( coordonnees [1]) ;
salles . add ( new Salle (x , y , 255) ) ; // creation de la salle
correspondante et ajout dans salles
}
}
198
CHAPITRE 15. TRAVAUX PRATIQUES : L’APPLICATION LABYRINTHE
public void dessiner () {
for ( Salle s : salles ) {
s . dessiner ( dessin ) ;
}
}
}
Exercice 187. (?) Modifier la méthode draw de la classe Dessin pour qu’elle dessine le labyrinthe.
Exercice 188. (??) Modifier les méthodes de dessin pour qu’elles utilisent des images (de mur, de sol) pour
dessiner le labyrinthe.
Voir http: // www. lemog. fr/ lemog_ textures/ index. php pour récupérer des images adaptées.
15.2. MODÉLISATION DU LABYRINTHE, DÉPLACEMENT FLUIDE D’UN PERSONNAGE ET ÉCLAIRAGE 199
15.2
Modélisation du labyrinthe, déplacement fluide d’un personnage et éclairage
Avant de commencer, récupérez l’archive TP02.zip (pour NetBeans) qui contient une solution du TP précédent.
Dans ce TP, nous allons rajouter la possibilité de déplacer un héros dans le labyrinthe, ainsi qu’un éclairage
autour de sa position.
Dans l’application de départ, le plan du labyrinthe se dessine, mais on ne peut pas l’utiliser pour se déplacer :
la structure du labyrinthe n’est pas modélisée.
Exercice 189. (??) Modifier la classe Salle : une salle peut indiquer si elle est adjacente à une autre.
Besoin d’aide ?
class Salle {
...
boolean adjacente ( Salle s ) {
int distance = Math . abs (x - s . x ) + Math . abs (y - s . y ) ;
return ( distance ==1) ;
}
...
}
Exercice 190. (?) Ajouter une classe Personnage : un personnage a une salle courante, peut se dessiner et se
déplacer dans une autre salle.
Besoin d’aide ?
public class Personnage {
Salle salleCourante ;
int xpix ; int ypix ; // coordonnees graphiques
Dessin dessin ;
public Personnage ( Dessin dessin ) {
this . dessin = dessin ;
}
public void aller ( Salle s ) {
salleCourante = s ;
}
public void dessiner () {
// dessin
dessin . fill (0 ,0 ,255) ;
dessin . rect ( xpix , ypix , dessin . taille , dessin . taille ) ;
}
}
Exercice 191. (??) Rajouter la gestion des déplacements du personnage, avec les touches z,q,s et d.
Besoin d’aide ?
CHAPITRE 15. TRAVAUX PRATIQUES : L’APPLICATION LABYRINTHE
200
void controleur () { // methode gerant les deplacements du heros en
fonction de la touche pressee
// on recupere les coordonnees de la position du heros
Salle position = heros . salleCourante ;
int x = position . x ;
int y = position . y ;
// gestion du deplacement
for ( Salle s : laby . salles ) { // pour toutes les salles s du
labyrinthe
if ( s . adjacente ( position ) ) {
// s est une salle adjacente a la position
if ( s . y ==( y +1) && key == ’s ’) {
heros . aller ( s ) ;
}
if ( s . y ==( y -1) && key == ’z ’) {
heros . aller ( s ) ;
}
if ( s . x ==( x +1) && key == ’d ’) {
heros . aller ( s ) ;
}
if ( s . x ==( x -1) && key == ’q ’) {
heros . aller ( s ) ;
}
}
}
}
void draw () {
laby . dessiner () ;
heros . dessiner () ;
controleur () ;
}
Exercice 192. (?) Afficher un message lorsque le héros est sur la sortie pour l’informer qu’il a gagné.
Le héros se déplace dans le labyrinthe mais son déplacement n’est pas fluide d’un point de vue graphique.
Exercice 193. (? ? ?) Modifier la gestion du déplacement pour que le déplacement soit fluide. Pour cela, il faut
découpler le déplacement dans le labyrinthe (salle par salle) du déplacement graphique, pour que ce dernier se
fasse pixel par pixel. Indication : avant que le héros puisse à nouveau se déplacer dans le labyrinthe, il faut que
le déplacement graphique vers sa position courante soit achevée.
Besoin d’aide ?
public class Personnage {
...
int xpixCible ; int ypixCible ; // coordonnees graphiques cibles
public void aller ( Salle s ) {
...
xpixCible =10+(( salleCourante .x -1) * dessin . taille ) ;
ypixCible =10+(( salleCourante .y -1) * dessin . taille ) ;
}
15.2. MODÉLISATION DU LABYRINTHE, DÉPLACEMENT FLUIDE D’UN PERSONNAGE ET ÉCLAIRAGE 201
public void dessiner () {
// maj de xpix et ypix
if ( xpix < xpixCible ) xpix ++;
if ( xpix > xpixCible ) xpix - -;
if ( ypix < ypixCible ) ypix ++;
if ( ypix > ypixCible ) ypix - -;
...
}
}
Nous allons maintenant mettre en place un éclairage autour du héros : l’affichage des salles sera de plus en
plus sombre en fonction de leur distance (euclidienne) du héros.
Exercice 194. (?) Ecrire une méthode float distance(Salle t) dans la classe Salle qui retourne sa distance à une autre salle t.
Exercice 195. (??) Ecrire une méthode dessinEclairee(float distance) de la classe Salle qui dessine
la salle en fonction de la valeur de distance, avec un rayon maximal de valeur 10. Modifier ensuite le moteur
d’affichage pour qu’il exploite cette nouvelle méthode à la place de l’ancienne.
Besoin d’aide ?
void dessinerEclairee ( float distance ) {
int transparence =( int ) (255 -(( min ( distance ,10) /10) *255) ) ;
fill ( couleur , couleur , couleur , transparence ) ;
if ( transparence >0)
rect ( bordure +(( x -1) * taille ) , bordure +(( y -1) * taille ) , taille ,
taille ) ;
}
CHAPITRE 15. TRAVAUX PRATIQUES : L’APPLICATION LABYRINTHE
202
15.3
Restructuration de la modélisation du labyrinthe
Récupérez l’archive TP03.zip qui contient une solution du TP précédent sous la forme d’un projet pour
netbeans.
Exercice 196. (??) Importez cette archive dans netbeans, testez son fonctionnement puis faites la liste (sur papier) des différentes classes et de leurs méthodes. Etablissez ensuite la liste des différentes phases de l’application,
en suivant son déroulement au sein du code.
Le diagramme des classes de cette solution est donné dans la figure 15.3.
F IGURE 15.1 – Diagramme des classes du code initial
Exercice 197. (??) Sur le papier, proposez une architecture du code en paquets qui permette de séparer ce qui
relève de la vue (dessin d’un labyrinthe), du modèle (du labyrinthe), du contrôleur (partie de l’application gérant
les interactions entre la vue et le modèle), et enfin les outils (par exemple : pour la lecture de données dans un
fichier).
Mettez ensuite en œuvre votre architecture dans netbeans.
Exercice 198. (?) Dans cette modélisation, c’est le personnage qui gère son propre déplacement. Ce n’est pas
très satisfaisant, car un personnage pourrait avoir une implémentation lui permettant de tricher, en traversant par
exemple des murs ... Proposer une modification du modèle pour remédier à cela.
Exercice 199. (?) Le choix du nom x comme attribut de Salle n’est pas très judicieux. Changez-le en numColonne : le code ne compile plus - il y a des erreurs dans plusieurs classes. Faites les modifications.
Les problèmes qui sont survenus montrent les limites des accès directs et publiques à un attribut. Mettez les
deux attributs numColonne et y en visibilité privée. Créez les accesseurs getX/setX et getY/setY. Modifiez le
reste du code en fonction, y compris dans Salle.
Changez maintenant le nom de l’attribut y en numLigne : la seule correction à apporter concerne les getY/setY.
Exercice 200. (?) Réglez la visibilité de tous les attributs de toutes les classes au niveau privé et créez tous les
accesseurs nécessaires, via les assistants de netbeans.
Exercice 201. (??) Implémentez des constructeurs de copie pour Salle et Labyrinthe.
La figure 15.2 présente le diagramme final des classes de l’application.
15.3. RESTRUCTURATION DE LA MODÉLISATION DU LABYRINTHE
F IGURE 15.2 – Diagramme des classes du code final de l’application
203
CHAPITRE 15. TRAVAUX PRATIQUES : L’APPLICATION LABYRINTHE
204
15.4
Implémentation d’un labyrinthe basée sur une grille
Nous allons maintenant faire évoluer notre application de labyrinthe pour qu’elle implémente la modélisation
générique définie dans le chapitre 4. Par la suite, nous proposerons d’autres implémentations plus performantes,
qui seront interchangeables avec cette première version.
Le code de départ est le code final du TP précédent : récupérez l’archive TP04.zip et importez la dans
netbeans.
Exercice 202. (?) Consultez la figure 15.2 qui présente le diagramme des classes du code de départ et son
organisation en paquets, puis consultez la figure 15.3 qui présente le diagramme des classes du code final de
l’application sous forme simplifiée : cela vous aidera à comprendre les objectifs des questions suivantes.
Exercice 203. (?) Créez un sous-paquet labyrinthe.grille du paquet labyrinthe puis déplacez les classes
de ce dernier dans ce sous-paquet.
Exercice 204. (?) Pour éviter un conflit de nom avec les interfaces que nous allons mettre en place, renommez
les classes Labyrinthe, Personnage et Salle en LabyrintheGrille, PersonnageClavier et CaseGrille
respectivement.
Exercice 205. (??) Créez les interfaces Labyrinthe et Salle dans le paquet labyrinthe, définies dans la
section 4.3. Créez l’interface Personnage dans le paquet personnages.
Exercice 206. (?) Modifiez CaseGrille pour qu’elle implémente Salle.
Exercice 207. (??) Modifiez PersonnageClavier pour qu’elle implémente Personnage : pour ce faire, vous
pouvez indiquer au compilateur que les salles en paramètre sont aussi de type CaseGrille, en rajoutant (CaseGrille) devant la variable de la salle. Supprimez le constructeur de la classe Personnage car il n’est plus utile.
Modifiez CaseGrille pour qu’elle implémente Salle.
Exercice 208. (??) Modifiez la classe LabyrintheGrille pour qu’elle implémente l’interface Labyrinthe.
Exercice 209. (??) Modifiez la boucle principale pour qu’elle exploite la nouvelle architecture.
Un peu d’aide ?
// boucle principale
while (! labyrinthe . sortir ( bob ) ) {
// rafraichissement de la vue
labyrinthe . dessiner ( g ) ;
bob . dessiner ( g ) ;
// choix et deplacement
Collection < Salle > sallesAccessibles = labyrinthe . sallesAccessibles (
bob ) ;
Salle destination = bob . faitSonChoix ( sallesAccessibles ) ; // on
demande au heros de faire son choix de salle
if ( destination != bob . getPosition () ) destination . recevoir ( bob ) ; //
deplacement
}
La figure 15.3 donne une version simplifiée du diagramme des classes de l’application implémentant les spécifications du labyrinthe.
15.4. IMPLÉMENTATION D’UN LABYRINTHE BASÉE SUR UNE GRILLE
F IGURE 15.3 – Diagramme des classes du code final de l’application
205
CHAPITRE 15. TRAVAUX PRATIQUES : L’APPLICATION LABYRINTHE
206
15.5
Polymorphisme : implémentation alternative d’un labyrinthe
Récupérez dans netbeans l’archive TP05.zip : cette archive contient le code d’une application qui est proche
de celle développée dans les travaux pratiques de la section précédente. La principale différence est l’utilisation
d’un moteur graphique basé sur bibliothèque SWING (la bibliothèque standard en Java) au lieu de celle de Processing.
En outre, il y a une légère restructuration :
— la classe PersonnageClavier a été déplacée dans le paquet personnages.v1 ;
— les classes JFDessin, JPDessin et Grille de la nouvelle vue sont dans le paquet vue2D.v1 ;
— le paquet vue2D contient une interface IVue
— cette nouvelle interface est implémentée par la (nouvelle) classe Vue du paquet vue2D.v1
Exercice 210. (?) Consultez le contenu de l’interface IVue puis décrivez le fonctionnement de l’implémentation
fournie par la classe Vue du paquet vue2D.v1.
Le code source contient également deux autres nouveaux sous-paquets : personnages.v2 et vue2D.v2. Ces
paquets contiennent une autre implémentation des interfaces du labyrinthe, similaire sur le principe à la version
v1, mais offrant des fonctionnalités un peu plus avancées, comme le déplacement du personnage par le biais des
touches fléchées (sans avoir à appuyer sur entrée) et une animation pixel par pixel de ce déplacement. L’interface
graphique propose également de se déplacer à la souris en cliquant sur des boutons.
Exercice 211. (??) Activez cette version v2 en tirant partie du polymorphisme.
1. sélection du personnage en v2 : mettre la ligne
Personnage bob = new personnages.v2.PersonnageClavier();
dans la classe Main au lieu de la ligne :
Personnage bob = new personnages.v1.PersonnageClavier();.
(a) pourquoi le code compile-t-il toujours ?
(b) observez le code de l’implémentation de cette version v2 : pourquoi la classe PersonnageDefaut
est-elle abstraite ? Comment fonctionne PersonnageClavier ?
(c) en quoi tirons-nous partie du polymorphisme ici ?
2. sélection de la nouvelle vue : mettre la ligne
IVue vue = new vue2D.v2.Vue(labyrinthe, bob);
dans la classe Main au lieu de la ligne :
IVue vue = new vue2D.v1.Vue(labyrinthe, bob);
puis testez.
3. testez les deux autres combinaisons possibles pour le couple vue/personnage.
Au cours de ces petits exercices, vous avez donc mis en valeur la souplesse apportée par la programmation
objet via le mécanisme de l’héritage et du polymorphisme, pour intégrer des développements de développeurs
tiers.
Un grand nombre d’applications fonctionnent sur ce principe, via le mécanisme de plugins par exemple. Les
pilotes d’accès à des bases de données constituent également une bonne illustration : l’API JDBC (Java DataBase
Connectivity) spécifie via des interfaces les méthodes nécessaires pour communiquer avec des bases de données ;
les éditeurs de bases de données, comme Oracle, écrivent et distribuent les pilotes implémentant ces interfaces.
15.6. COLLECTIONS : MODÉLISATION DE GRAPHES
15.6
207
Collections : modélisation de graphes
Un graphe est une structure mathématique permettant de modéliser le réseau routier par exemple : il est formé
d’un ensemble de sommets (un sommet=une ville dans l’exemple) et d’arcs (un arc=un tronçon routier d’une ville
a vers un ville b). On dit que deux sommets a et b sont adjacents s’il existe un arc de a à b, ou de b à a. Pour plus
d’informations sur les graphes, consulter le cours en ligne de Christophe Rapine : http://idmme06.inpg.fr/
~rapinec/Graphe/default.html
Considérons les 3 classes Sommet, Arc et Graphe qui proposent une implémentation très simpliste de la
structure de graphes.
public class Sommet {}
public class Arc {
public Sommet a ;
public Sommet b ;
public Arc ( Sommet a , Sommet b ) {
this . a = a ; this . b = b ;
}
}
public class Graphe {
public ArrayList < Sommet > sommets = new ArrayList () ;
public ArrayList < Arc > arcs = new ArrayList () ;
public boolean estAdjacent ( Sommet u , Sommet v ) {
for ( Arc arc : arcs ) {
if (( arc . a == u ) &&( arc . b == v ) ) return true ;
if (( arc . a == v ) &&( arc . b == u ) ) return true ;
}
return false ;
}
}
Exercice 212. (?) Recopiez-ces trois classes dans un paquet graphes et testez le bon fonctionnement du code
ci-dessous, qui crée un graphe formé de trois sommets a, b, c et des trois arcs ab, bc et ac.
Graphe g = new Graphe () ;
// initialisation des sommets du graphe
Sommet a = new Sommet () ;
Sommet b = new Sommet () ;
Sommet c = new Sommet () ;
g . sommets . add ( a ) ;
g . sommets . add ( b ) ;
g . sommets . add ( c ) ;
// initiation des arcs du graphe
Arc ab = new Arc (a , b ) ;
Arc bc = new Arc (b , c ) ;
Arc ca = new Arc (c , a ) ;
g . arcs . add ( ab ) ;
g . arcs . add ( bc ) ;
g . arcs . add ( ca ) ;
208
CHAPITRE 15. TRAVAUX PRATIQUES : L’APPLICATION LABYRINTHE
Exercice 213. (?) Ecrire une méthode main créant le graphe C5 : ce graphe est un graphe à 5 sommets
{a, b, c, d, e} et les 5 arcs ab, bc, cd, de, ea.
Exercice 214. (?) Rajouter un identifiant de type int à la classe Sommet.
Exercice 215. (??) Ecrire une méthode String toString() permettant de retourner une chaîne de caractères
représentant un graphe, sous la forme du nombre de ses sommets, suivi de la liste de ses arcs.
Exercice 216. (?) Rajouter une méthode ajouter permettant de rajouter un sommet au graphe et une autre pour
une arête.
Exercice 217. (? ? ?) Plus dur : écrire une méthode complementaire qui retourne le complémentaire du graphe
(le complémentaire d’un graphe est le graphe possédant le même ensemble de sommets mais dont les arcs sont
tous les arcs que le graphe initial ne possède pas).
Exercice 218. (? ? ?) Ecrire une méthode tri() de la classe Graphe qui effectue un tri de ses arêtes selon l’ordre
suivant : une arc i j est inférieur ou égale à un arc i0 j0 si et seulement si i ≤ i0 et j ≤ j0 . Prévoir dans la méthode
main une exécution qui montre sur un exemple le bon fonctionnement de votre méthode, en affichant les arcs d’un
graphe avant et après le tri.
15.7. MODÉLISATION D’UN LABYRINTHE PAR UN GRAPHE
15.7
209
Modélisation d’un labyrinthe par un graphe
Dans la première application, nous avons vu comment mettre en place deux implémentations différentes (bien
que similaires d’un point de vue des structures de données utilisées) pour modéliser le labyrinthe. Nous allons
maintenant tirer partie ce de mécanisme, pour mettre en place une modélisation d’un labyrinthe basée sur un
graphe.
Dans un premier temps, nous allons le faire en reprenant la modélisation d’un graphe effectuée dans la section précédente. Dans un second temps (Application 4), nous exploiterons une bibliothèque de graphes par des
développeurs tiers.
Le code de départ est le même que pour la première application du chapitre : TP05.zip.
Exercice 219. (?) Recopiez le paquet graphes de l’application précédente.
Exercice 220. (?) Créez les paquets labyrinthe.graphe et labyrinthe.graphe.v1. Ce dernier paquet va
contenir l’implémentation du labyrinthe en utilisant notre modélisation d’un graphe.
Exercice 221. (?) Dans le paquet labyrinthe.graphe, écrire l’interface ci-après. Cette interface donne les
spécifications nécessaires pour nos (fututes) modélisation du labyrinthe avec un graphe. On demande simplement
de pouvoir construire un graphe (avec des salles identifiées à des sommets du graphe) et tester si deux salles sont
adjacentes.
public interface ILabyrintheGraphe {
public void ajouterSommet ( Salle s ) ;
public void ajouterArc ( Salle u , Salle v ) ;
public boolean estAdjacent ( Salle u , Salle v ) ;
}
Exercice 222. (??) Ecrire une classe abstraite ALabyrintheGraphe implémentant les interfaces Labyrinthe
et ILabyrintheGraphe à l’exception des méthodes ajouterSommet, ajouterArc, estAdjacent, creerLabyrinthe et getSalles.
Besoin d’un peu d’aide ?
package labyrinthe . graphe ;
public abstract class ALabyrintheGraphe implements ILabyrintheGraphe ,
Labyrinthe {
protected Salle entree ;
protected Salle sortie ;
@Override
public abstract void ajouterSommet ( Salle s ) ;
@Override
public abstract void ajouterArc ( Salle u , Salle v ) ;
@Override
public abstract boolean estAdjacent ( Salle u , Salle v ) ;
@Override
public abstract void creerLabyrinthe ( String file ) ;
// to be done : a file format is needed ...
CHAPITRE 15. TRAVAUX PRATIQUES : L’APPLICATION LABYRINTHE
210
public void entrer ( Personnage bob ) {
entree . recevoir ( bob ) ;
}
public boolean sortir ( Personnage bob ) {
return ( bob . getPosition () == sortie ) ;
}
public Collection < Salle > sallesAccessibles ( Personnage bob ) {
Collection < Salle > sa = new ArrayList () ;
Salle s = ( Salle ) bob . getPosition () ;
for ( Salle u : getSalles () )
if ( estAdjacent (u , s ) )
sa . add (( Salle ) u ) ;
return sa ;
}
public abstract Collection < Salle > getSalles () ;
public Salle getEntree ()
{
return entree ;
}
public Salle getSortie ()
{
return sortie ;
}
}
Exercice 223. (??) L’application est à ce stade mal écrite, car il y a beaucoup de dépendance dans le code sur
la classe CaseGrille, notamment dans les vues et pour les personnages. Pour s’affranchir de cette dépendance,
il est donc nécessaire d’exposer les fonctionnalités de cette classe via une interface ICase. Pensez ensuite à bien
remplacer tous les types CaseGrille par ICase dans les classes de la vue et les personnages.
Un peu d’aide ?
package labyrinthe . grille ;
public interface ICase {
public int getX () ; // abcisse
public int getY () ; // ordonnee
public Color getC () ; // couleur
public void setC ( Color c ) ; // modifier couleur
}
Exercice 224. (?) De même, il y a des dépendances avec la classe LabyrintheGrille dans les vues : introduisez
l’interface ILabyrintheGrille ci-dessous et faites les modifications adéquates dans les vues.
package labyrinthe . grille ;
public interface ILabyrintheGrille extends Labyrinthe {
int getLargeur () ;
int getHauteur () ;
}
15.7. MODÉLISATION D’UN LABYRINTHE PAR UN GRAPHE
211
On peut maintenant faire la liaison avec notre paquet graphes, via deux classes SalleSommet et
LabyrintheGrapheGrille.
Exercice 225. (? ? ?) Ecrire une classe SalleSommet héritant de Sommet et implémentant Salle et ICase. Ainsi
une SalleSommet est conceptuellement à la fois un Sommet (du graphe), une Salle (du labyrinthe) et une Case (de
la grille).
Un peu d’aide ?
package labyrinthe . graphe . v1 ;
public class SalleSommet extends Sommet implements Salle , ICase {
public int x ;
public int y ;
public Color c ;
public SalleSommet ( int x , int y , Color c ) {
this . x = x ;
this . y = y ;
this . c = c ;
}
@Override
public boolean recevoir ( Personnage heros ) {
heros . setPosition ( this ) ;
return true ;
}
@Override
public int getX () {
return x ;
}
@Override
public int getY () {
return y ;
}
@Override
public Color getC () {
return c ;
}
@Override
public void setC ( Color c ) {
this . c = c ;
}
}
Exercice 226. (? ? ?) Ecrire une classe LabyrintheGraphe, qui implémente les méthodes abstraites de ALabyrintheGraphe et celles de l’interface ILabyrintheGrille.
Un peu d’aide ?
212
CHAPITRE 15. TRAVAUX PRATIQUES : L’APPLICATION LABYRINTHE
public class LabyrintheGraphe extends ALabyrintheGraphe implements
ILabyrintheGrille {
Graphe g ;
ArrayList < Salle > sommets ;
private int largeur ;
private int hauteur ;
@Override
public void ajouterSommet ( Salle s ) {
g . ajouter (( Sommet ) s ) ;
}
@Override
public void ajouterArc ( Salle u , Salle v ) {
g . ajouter ( new Arc (( Sommet ) u , ( Sommet ) v ) ) ;
}
@Override
public boolean estAdjacent ( Salle u , Salle v ) {
return g . estAdjacent (( Sommet ) u , ( Sommet ) v ) ;
}
@Override
public void creerLabyrinthe ( String file ) {
g = new Graphe () ;
sommets = new ArrayList < Salle >() ;
Scanner sc = null ;
try {
sc = new Scanner ( new File ( file ) ) ;
}
catch ( Exception e ) { System . out . println ( e ) ;}
// les deux premiers entiers
// hauteur largeur
hauteur = sc . nextInt () ;
largeur = sc . nextInt () ;
// coordonnees entree
int ligneEntree = sc . nextInt () ;
int colonneEntree = sc . nextInt () ;
entree = new SalleSommet ( ligneEntree , colonneEntree , Color . blue )
;
// coordonnees sortie
int ligneSortie = sc . nextInt () ;
int colonneSortie = sc . nextInt () ;
sortie = new SalleSommet ( ligneSortie , colonneSortie , Color . red ) ;
// ajout des entree et sortie
sommets . add ( entree ) ;
ajouterSommet ( entree ) ;
sommets . add ( sortie ) ;
ajouterSommet ( sortie ) ;
15.7. MODÉLISATION D’UN LABYRINTHE PAR UN GRAPHE
213
// ajout des autres salles
while ( sc . hasNext () ) {
int ligne = sc . nextInt () ;
int colonne = sc . nextInt () ;
SalleSommet s = new SalleSommet ( ligne , colonne , Color . white )
;
sommets . add ( s ) ;
ajouterSommet ( s ) ;
}
// creation des arcs
for ( Salle u : sommets )
for ( Salle v : sommets ) {
int d = Math . abs ((( SalleSommet ) u ) . getX ()
-(( SalleSommet ) v ) . getX () ) ;
d += Math . abs ((( SalleSommet ) u ) . getY ()
-(( SalleSommet ) v ) . getY () ) ;
if ( d ==1) {
ajouterArc (u , v ) ;
}
}
}
@Override
public Collection < Salle > getSalles () {
return sommets ;
}
@Override
public int getLargeur () {
return largeur ;
}
@Override
public int getHauteur () {
return hauteur ;
}
}
Exercice 227. (?) Il ne reste plus qu’à tester : remplacez dans la méthode main la ligne
ILabyrintheGrille labyrinthe = new LabyrintheGrille();
par la ligne : ILabyrintheGrille labyrinthe = new LabyrintheGraphe();
Exercice 228. (?) Faites une analyse de l’application obtenue : liste des interfaces et de leurs méthodes, ainsi
que des classes qui les implémentent.
CHAPITRE 15. TRAVAUX PRATIQUES : L’APPLICATION LABYRINTHE
214
15.8
Affichage du plus court chemin dans le labyrinthe, via une bibliothèque externe (JGraphT)
Le code de départ est celui réalisé lors de la section précédente : TP05Application4.zip. Cette version
comporte également une troisième version de la vue (paquet vue2D.v3) qui exploite des images pour dessiner le
labyrinthe.
Nous allons utiliser une bibliothèque dédiée à la modélisation des graphes et à leurs algorithmes : JGraphT.
Le site officiel de cette bibliothèque est http://jgrapht.org/. Il existe d’autres bibliothèques pour manipuler
des graphes. JGraphT n’est pas forcément la plus simple à utiliser ou la plus performante.
L’objectif de cette application est de tirer partie de l’abstraction offerte par les interfaces pour inclure du
code développé par des tiers, offrant des fonctionnalités avancées, que nous n’aurions pas pu développer dans
un temps raisonnable et/ou avec un niveau de performance satisfaisant. L’apport d’un tel code tiers ouvre de
nouvelles perspectives à notre application de labyrinthe : pour illustrer ceci, nous exploiterons dans un premier
temps JGraphT pour aider notre héros en lui affichant un plus court chemin entre sa position et la sortie du
labyrinthe.
Exercice 229. (??) Consultez le site de JGraphT. En particulier, analysez le code de démonstration pour un
graphe dirigé : https: // github. com/ jgrapht/ jgrapht/ wiki/ DirectedGraphDemo . Puis, parcourez la
Javadoc pour vous faire une idée des classes et des méthodes disponibles. Quelles sont les méthodes dédiées au
calcul de plus courts chemins ?
Exercice 230. (?) Téléchargez la bibliothèque JGraphT et attachez l’archive jar à votre projet sous netbeans
(clic droit sur "Libraries" puis "Add JAR/Folder").
Pour utiliser la bibliothèque JGraphT dans le cadre de notre application de labyrinthe, nous devons implémenter les deux interfaces Salle et ICase, ainsi que les interfaces ILabyrintheGraphe et ILabyrintheGrille.
De manière similaire à l’application 3, nous allons le faire dans un paquet labyrinthe.graphe.v2 via deux
nouvelles classes SalleSommet et LabyrintheGraphe. Il s’agit donc de retranscrire les deux classe de la v1 en
utilisant la bibliothèque JGraphT.
Exercice 231. (??) Créez le paquet labyrinthe.graphe.v2 et implémentez les interfaces Salle et ICase dans
une classe SalleSommet. Nota : les graphes que nous créerons par la suite auront des ensembles de sommets de
type SalleSommet.
Un peu d’aide ?
public class SalleSommet implements Salle , ICase {
private int x ;
private int y ;
private Color c ;
public SalleSommet ( int x , int y , Color c ) {
this . x = x ;
this . y = y ;
this . c = c ;
}
@Override
public boolean recevoir ( Personnage heros ) {
heros . setPosition ( this ) ;
return true ;
}
@Override
15.8. AFFICHAGE DU PLUS COURT CHEMIN DANS LE LABYRINTHE, VIA UNE BIBLIOTHÈQUE EXTERNE (JGRAPHT)215
public int getX () {
return x ;
}
@Override
public int getY () {
return y ;
}
@Override
public Color getC () {
return c ;
}
@Override
public void setC ( Color c ) {
this . c = c ;
}
}
Exercice 232. (? ? ?) Implémentez maintenant les interfaces ILabyrintheGraphe, ILabyrintheGrille dans
une classe LabyrintheGraphe.
Un peu d’aide ?
public class LabyrintheGraphe extends ALabyrintheGraphe implements
ILabyrintheGrille {
DirectedGraph < SalleSommet , DefaultEdge > g ;
private int largeur ;
private int hauteur ;
@Override
public void ajouterSommet ( Salle s ) {
g . addVertex (( SalleSommet ) s ) ;
}
@Override
public void ajouterArc ( Salle u , Salle v ) {
g . addEdge (( SalleSommet ) u , ( SalleSommet ) v ) ;
}
@Override
public boolean estAdjacent ( Salle u , Salle v ) {
return g . containsEdge (( SalleSommet ) u , ( SalleSommet ) v ) ;
}
@Override
public void creerLabyrinthe ( String file ) {
g = new De fau lt Dir ec ted Gr aph ( DefaultEdge . class ) ;
Scanner sc = null ;
try {
sc = new Scanner ( new File ( file ) ) ;
}
CHAPITRE 15. TRAVAUX PRATIQUES : L’APPLICATION LABYRINTHE
216
catch ( Exception e ) { System . out . println ( e ) ;}
// les deux premiers entiers
// hauteur largeur
hauteur = sc . nextInt () ;
largeur = sc . nextInt () ;
// coordonnees entree
int ligneEntree = sc . nextInt () ;
int colonneEntree = sc . nextInt () ;
entree = new SalleSommet ( ligneEntree , colonneEntree , Color . blue )
;
// coordonnees sortie
int ligneSortie = sc . nextInt () ;
int colonneSortie = sc . nextInt () ;
sortie = new SalleSommet ( ligneSortie , colonneSortie , Color . red ) ;
// ajout des entree et sortie
ajouterSommet ( entree ) ;
ajouterSommet ( sortie ) ;
// ajout des autres salles
while ( sc . hasNext () ) {
int ligne = sc . nextInt () ;
int colonne = sc . nextInt () ;
SalleSommet s = new SalleSommet ( ligne , colonne , Color . white )
;
ajouterSommet ( s ) ;
}
// creation des arcs
for ( Salle u : g . vertexSet () )
for ( Salle v : g . vertexSet () ) {
int d = Math . abs ((( SalleSommet ) u ) . getX ()
-(( SalleSommet ) v ) . getX () ) ;
d += Math . abs ((( SalleSommet ) u ) . getY ()
-(( SalleSommet ) v ) . getY () ) ;
if ( d ==1) {
ajouterArc (u , v ) ;
}
}
}
@Override
public Collection < Salle > getSalles () {
return ( new ArrayList < Salle >( g . vertexSet () ) ) ; // conversion de
Set < SalleSommet > vers Collection < Salle >
}
@Override
public int getLargeur () {
return largeur ;
}
15.8. AFFICHAGE DU PLUS COURT CHEMIN DANS LE LABYRINTHE, VIA UNE BIBLIOTHÈQUE EXTERNE (JGRAPHT)217
@Override
public int getHauteur () {
return hauteur ;
}
}
Testez le bon fonctionnement de cette nouvelle version, en mettant dans le main :
ILabyrintheGrille labyrinthe = new labyrinthe . graphe . v2 .
LabyrintheGraphe () ;
Maintenant que nous utilisons la bibliothèque JGraphT, nous pouvons exploiter des fonctionnalités plus avancées de celle-ci. Ainsi, on peut calculer un plus court chemin entre deux sommets i et c avec
Di jk str aShor tes tP ath dsp = new Di jk str aS hor tes tP ath (g , i , c ) ;
List path = Graphs . getPathVertexList ( dsp . getPath () ) ;
Exercice 233. (? ? ?) Dans la méthode creerLabyrinthe, calculer un plus court chemin entre l’entrée et la
sortie, qui sera dessiné en gris.
Un peu d’aide ?
Di jk str aShor tes tP ath dsp = new Di jk str aS hor tes tP ath (g , ( SalleSommet )
entree , ( SalleSommet ) sortie ) ;
for ( Object s : Graphs . getPathVertexList ( dsp . getPath () ) )
(( SalleSommet ) s ) . setC ( Color . gray ) ;
Impressionnant, non ? Soyons plus fou encore ... Notre héros est particulièrement maladroit : il n’est pas
capable de suivre le plus court chemin en gris ... Pour l’aider, nous allons lui indiquer en permanence un plus court
chemin en jaune entre sa position et la sortie.
Exercice 234. (??) Pour cela, la vue a besoin de pouvoir connaitre un plus court chemin entre la position du héros
et la sortie. Il faut donc rajouter cette fonctionnalité dans une interface. Déclarez la méthode public Collection<Salle> chemin(Salle u, Salle v); dans l’interface Labyrinthe. Donnez une implémentation dans
la classe LabyrintheGraphe du paquet graphe.v2 (pour les autres classes devant fournir une implémentation,
renvoyer simplement null).
Un peu d’aide ?
public Collection < Salle > chemin ( Salle u , Salle v ) {
Di jk str aSh or tes tP ath dsp = new Di jk str aSh or tes tP ath (g , ( SalleSommet
) u , ( SalleSommet ) v ) ;
return Graphs . getPathVertexList ( dsp . getPath () ) ;
}
Exercice 235. (? ? ?) Ecrire une méthode dessinPlusCourtChemin(Graphics g) dans la classe Dessin qui
dessine un plus court chemin en jaune entre la position du héros et la sortie du labyrinthe.
Un peu d’aide ?
public void d es s in Pl u sC o ur tC h em in ( Graphics g ) {
Collection < Salle > chemin = labyrinthe . chemin ( bob . personnage .
getPosition () , labyrinthe . getSortie () ) ;
for ( Salle s : chemin ) {
int i = (( ICase ) s ) . getY () ;
int j = (( ICase ) s ) . getX () ;
CHAPITRE 15. TRAVAUX PRATIQUES : L’APPLICATION LABYRINTHE
218
g . setColor ( Color . yellow ) ;
g . fillRect ( j * unite , i * unite , unite , unite ) ;
}
}
Un problème de performances potentiel est que la classe Dessin redessine très fréquemment le labyrinthe (il
faut 20 images par seconde pour qu’une animation soit fluide ...). Par conséquent, il est important de limiter les
calculs dans les méthodes de dessin. Or dans l’exercice précédent, on demande un plus court chemin systématiquement.
Exercice 236. (??) Pour éviter des calculs inutiles lorsque le héros ne bouge pas, modifier l’implémentation de
la méthode chemin pour qu’elle ne recalcule un plus court chemin vers la sortie que lorsque le héros a bougé.
Un peu d’aide ?
public Collection < Salle > chemin ( Salle u , Salle v ) {
// cette methode retourne un plus court chemin entre u et v
// en appliquant l ’ algorithme de Dijkstra
// cas general
if ( v != sortie ) {
Di jk str aS hor tes tP ath dsp = new Di jk str aS hor tes tP ath (g , (
SalleSommet ) u , ( SalleSommet ) v ) ;
return Graphs . getPathVertexList ( dsp . getPath () ) ;
}
// cas vers la sortie
if ( u != lastPosition ) { // maj necessaire
Di jk str aSh or tes tP ath dsp = new Di jk str aSh or tes tP ath (g , (
SalleSommet ) u , ( SalleSommet ) v ) ;
u n P l u s C o u r t C h e m i n V e r s S o r t i e = Graphs . getPathVertexList ( dsp . getPath
() ) ;
lastPosition = u ;
}
return u n P l u s C o u r t C h e m i n V e r s S o r t i e ;
}
15.9. DÉCOUVERTE DES PRINCIPALES COMMANDES DU JDK ET DES BIBLIOTHÈQUES DE JAVA
15.9
219
Découverte des principales commandes du JDK et des bibliothèques de Java
Cette partie contient une série de petits exercices pour tester vos connaissances. Elle se base sur un code, qui
permet de modéliser un labyrinthe et de se déplacer dedans, dont il est inutile, pour ces travaux pratiques, de se
pencher sur son fonctionnement. L’objectif est de tester sur cette application les différents outils du JDK.
L’archive TP06.zip contient le code de l’application réparti dans les répertoires application, labyrinthe,
personnage et vue2D. La classe principale est la classe LabyrintheApp.
Exercice 237. (?) Recompilez l’ensemble des classes et faites la liste des fichiers d’extension .class qui ont été
générés.
Exercice 238. (?) Sans regarder le code source (les fichiers d’extension .java, utiliser l’utilitaire javap pour
établir par écrit la liste des méthodes de chacune des classes.
Exercice 239. (?) Exécutez le code après avoir localisé la méthode main, le point de départ du programme. Testez
différents labyrinthes.
Exercice 240. (?) Documentez le code des classes LabyrintheGrilleDefaut et Dessin. Générez ensuite la
javadoc.
Exercice 241. (?) Créez une archive TP06.jar au format jar de l’ensemble de l’application, javadoc comprise.
CHAPITRE 15. TRAVAUX PRATIQUES : L’APPLICATION LABYRINTHE
220
15.10
Débogueur, tests unitaires, exception
Le code de départ est fourni par l’archive TP07Application1.zip à ouvrir dans Eclipse.
Utilisation du débogueur
Exercice 242. (?) Dans la vue v3, la classe PersonnageGraphique a deux attributs xpix et ypix pour stocker
les coordonées graphiques (en pixels) du personnage. La méthode majCoordonnees() de la classe Dessin est
chargée d’actualiser ces coordonnées graphiques. Comment faire pour surveiller son fonctionnement avec le
débogueur ?
Indications :
1. placer un point d’arrêt au début dans la méthode majCoordonnees ;
2. modifier manuellement les coordonnées (dans la grille) du personnage bob pour provoquer un déplacement ;
3. surveiller la modification des attributs xpix et ypix.
Exercice 243. (??) Modifiez maintenant la classe Main pour utiliser un LabyrintheGrille chargeant le labyrinthe labys/levelInvalide1.txt (cf extrait de code ci-dessous). Profitez-en pour prendre un moment de
détente et emmener le héros jusqu’à la sortie.
Mettez le labyrinthe en mode labyrinthe.graphe.v2.LabyrintheGraphe (v2). Cela ne marche plus !
Choisissez maintenant le mode labyrinthe.graphe.v1.LabyrintheGraphe (v1). Cela remarche ...
Utilisez le débogueur pour vous rendre jusqu’au point qui pose problème. Proposez une correction.
// String fic = " labys / level3 . txt ";
String fic = " labys / levelInvalide1 . txt " ;
ILabyrintheGrille labyrinthe = new LabyrintheGrille () ;
// ILabyrintheGrille labyrinthe = new labyrinthe . graphe . v2 .
LabyrintheGraphe () ;
Exercice 244. (??) Les fichiers levelInvalide2.txt et levelInvalide3.txt posent, eux aussi, des soucis :
identifiez les problèmes.
Tests unitaires sur les formats de fichiers Le paragraphe précédent a mis en évidence des problèmes posés par des fichiers de labyrinthe non-conforme. Nous allons exploiter les tests unitaires pour pouvoir garantir
facilement que l’ensemble des fichiers de labyrinthe de l’application sont valides.
L’ensemble des tests unitaires de cette partie devront être réalisés dans un paquet tests dédié.
Exercice 245. (??) Ecrire un test unitaire testCoordonneesSalles qui teste, pour chacun des fichiers contenus dans le répertoire labys/, que chacune des salles a des coordonnées valides (i.e. abcisse (resp. ordonnée)
comprise entre 0 et la largeur-1 (resp. hauteur-1) du labyrinthe ).
Un peu d’aide ?
public static boolean t es t Co o rd on n ee sS a ll e s ( File fichier ) {
Fichier f = new Fichier ( fichier . toString () ) ;
// dimensions
int largeur = f . lireNombre () ;
int hauteur = f . lireNombre () ;
int i =0 , j =0;
// test des salles
while (( i != -1) &&( j != -1) ) {
15.10. DÉBOGUEUR, TESTS UNITAIRES, EXCEPTION
221
if (i <0) return false ;
if (j <0) return false ;
if (i >= largeur ) return false ;
if (j >= hauteur ) return false ;
i = f . lireNombre () ;
j = f . lireNombre () ;
}
return true ;
}
@Test
public void t es t Co or d on n ee sS a ll es () {
File repertoire = new File ( " labys / " ) ;
File [] fichiers = getFiles ( repertoire ) ;
// test de chacun des fichiers
boolean succes = true ;
String fichiersInvalides = " " ;
for ( File f : fichiers ) {
if (! t e st Co o rd o nn ee s Sa l le s ( f ) ) {
succes = false ;
fichiersInvalides += f + " " ;
}
}
if (! succes )
fail ( " te s tC oo r do n ne es S al le s - " + fichiersInvalides ) ;
}
Exercice 246. (??) Ecrire un test unitaire testPasDeDoublon qui teste, pour chacun des fichiers contenus dans
le répertoire labys/, que chacune des salles n’est présente qu’une fois (i.e. il n’y a pas deux salles avec les mêmes
coordonnées).
Un peu d’aide ?
public static boolean testPasDeDoublon ( File fichier ) {
Fichier f = new Fichier ( fichier . toString () ) ;
HashSet < String > salles = new HashSet < String >() ;
// dimensions
int largeur = f . lireNombre () ;
int hauteur = f . lireNombre () ;
int i = -2 , j = -2;
// test des salles
while (( i != -1) &&( j != -1) ) {
if ( salles . contains ( i + " " + j ) )
return false ;
salles . add ( i + " " + j ) ;
i = f . lireNombre () ;
j = f . lireNombre () ;
}
return true ;
}
@Test
public void testPasDeDoublon () {
File repertoire = new File ( " labys / " ) ;
CHAPITRE 15. TRAVAUX PRATIQUES : L’APPLICATION LABYRINTHE
222
File [] fichiers = getFiles ( repertoire ) ;
// test de chacun des fichiers
boolean succes = true ;
String fichiersInvalides = " " ;
for ( File f : fichiers ) {
if (! testPasDeDoublon ( f ) ) {
succes = false ;
fichiersInvalides += f + " " ;
}
}
if (! succes )
fail ( " testPasDeDoublon - " + fichiersInvalides ) ;
}
Exercice 247. (? ? ?) Ecrire un test unitaire testChemin qui teste, pour chacun des fichiers contenus dans le
répertoire labys/, qu’il existe bien un chemin entre l’entrée et la sortie.
Un peu d’aide ?
public static boolean testChemin ( File fichier ) { labyrinthe . graphe . v2 .
LabyrintheGraphe labyrinthe = new labyrinthe . graphe . v2 .
LabyrintheGraphe () ;
try {
labyrinthe . creerLabyrinthe ( fichier . toString () ) ;
} catch ( E xc ept ion In val id Fil e eif ) {};
Di jk str aS hor tes tP ath dsp = new Di jk str aS hor tes tP ath ( labyrinthe .
getG () , labyrinthe . getEntree () , labyrinthe . getSortie () ) ;
if ( dsp . getPath () != null ) return true ; // il y a bien un chemin
entre l ’ entree et la sortie
return false ;
}
@Test
public void testChemin () {
File repertoire = new File ( " labys / " ) ;
File [] fichiers = getFiles ( repertoire ) ;
// test de chacun des fichiers
boolean succes = true ;
String fichiersInvalides = " " ;
for ( File f : fichiers ) {
if (! testChemin ( f ) ) {
succes = false ;
fichiersInvalides += f + " " ;
}
}
if (! succes )
fail ( " testChemin - " + fichiersInvalides ) ;
}
Exception ExceptionInvalidFile (labyrinthe de secours) On désire maintenant protéger l’application
contre un fichier de labyrinthe invalide (ce qui permettra par exemple de laisser un utilisateur concevoir ses propres
labyrinthes et les utiliser dans l’application).
Le principe est lorsqu’un fichier est détecté comme non-valide, de passer l’application dans un mode de secours, consistant à charger un fichier de labyrinthe donné, à la place du fichier fautif.
15.10. DÉBOGUEUR, TESTS UNITAIRES, EXCEPTION
223
Exercice 248. (??) Ecrire une méthode statique boolean testValide(String nomFichier) dans la classe
Fichier qui effectue les tests décrits dans le paragraphe précédent sur les tests unitaires, sur un nom de fichier
donné.
Un peu d’aide ?
public static boolean t es t Co o rd on n ee sS a ll e s ( File fichier ) {
Fichier f = new Fichier ( fichier . toString () ) ;
// dimensions
int largeur = f . lireNombre () ;
int hauteur = f . lireNombre () ;
int i =0 , j =0;
// test des salles
while (( i != -1) &&( j != -1) ) {
if (i <0) return false ;
if (j <0) return false ;
if (i >= largeur ) return false ;
if (j >= hauteur ) return false ;
i = f . lireNombre () ;
j = f . lireNombre () ;
}
return true ;
}
public static boolean testPasDeDoublon ( File fichier ) {
Fichier f = new Fichier ( fichier . toString () ) ;
HashSet < String > salles = new HashSet < String >() ;
// dimensions
int largeur = f . lireNombre () ;
int hauteur = f . lireNombre () ;
int i = -2 , j = -2;
// test des salles
while (( i != -1) &&( j != -1) ) {
if ( salles . contains ( i + " " + j ) )
return false ;
salles . add ( i + " " + j ) ;
i = f . lireNombre () ;
j = f . lireNombre () ;
}
return true ;
}
public static boolean testValide ( String nomFichier ) {
File fichier = new File ( nomFichier ) ;
if (! t e st Co o rd o nn ee s Sa ll e s ( fichier ) )
return false ;
if (! testPasDeDoublon ( fichier ) )
return false ;
return true ;
}
Exercice 249. (? ? ?) Dans le cas d’une tentative de chargement d’un fichier invalide (via la méthode creerLabyrinthe), levez une exception ExceptionInvalidFile. A quel endroit doit-on déclarer la classe ExceptionInvalidFile ? Quelle(s) interface(s) faut-il modifier ?
CHAPITRE 15. TRAVAUX PRATIQUES : L’APPLICATION LABYRINTHE
224
Capturez cette exception dans le programme principal Main et traitez la en créant le labyrinthe à partir du
fichier labys/level7.txt.
Si le fichier de secours est lui même invalide, traitez l’exception en mettant fin au programme.
Un peu d’aide ?
Méthode creerLabyrinthe :
@Override
public void creerLabyrinthe ( String file ) throws Exc ep tio nIn va lid Fi le {
if (! outils . Fichier . testValide ( file ) )
throw new Exc ep tio nI nva lid Fi le () ;
...
Classe Main :
String fic = " labys / levelInvalide1 . txt " ;
ILabyrintheGrille labyrinthe = new labyrinthe . graphe . v2 .
LabyrintheGraphe () ;
try {
labyrinthe . creerLabyrinthe ( fic ) ;
}
catch ( E xc ept io nIn val id Fil e eif ) {
System . out . println ( " Fichier de labyrinthe non - valide " ) ;
System . out . println ( " Chargement du fichier de secours ... " ) ;
try {
labyrinthe . creerLabyrinthe ( " labys / level7 . txt " ) ;
} catch ( E xce pt ion In val id Fil e e ) {
System . out . println ( " Fichier de secours invalide ! " ) ;
System . out . println ( " Erreur de l ’ application " ) ;
System . exit (1) ;
};
}
L’archive JavaTD06ApplicationFinal.zip contient la version finale de l’application.
15.11. ECLAIRAGE ET MONSTRES
15.11
225
Eclairage et monstres
Le code de départ est le même que celui de l’application précédente, fournie par l’archive
TP07Application1.zip .
Recopier le paquet vue2D.v3 dans un paquet vue2D.v4 : cette nouvelle vue va mettre en place un éclairage
autour du héros, basé sur la distance dans la grille dans un premier temps. Dans un second temps, un éclairage
plus réaliste basé sur la distance dans le graphe sera mis en place.
Eclairage
Exercice 250. (?) Modifier l’image de fond pour qu’elle soit constituée de pyramide.jpg au lieu de
marbre.jpg, puis modifier dessinFond, pour que le fond ne soit formé que de l’image de fond.
On voudrait maintenant que l’éclairage ne traverse plus les murs : pour cela, nous allons nous baser sur la
distance entre deux salles dans le graphe.
Exercice 251. (??) Ecrire une méthode private int distanceGraphe(Salle s, Salle t) qui retourne la
distance entre deux sommets dans le graphe, puis adapter la méthode dessinSalles en fonction. Comment faire
pour optimiser les performances en exploitant aussi la distance dans la grille ?
Un peu d’aide ?
private int distanceGraphe ( ISalle s , ISalle t ) {
Collection < ISalle > chemin = labyrinthe . chemin (s , t ) ;
if ( chemin != null ) return chemin . size () ;
return 0;
}
public void d es s in Sa l le s Ec la i re es ( Graphics g , IPersonnage p ) {
Collection < ISalle > salles = labyrinthe . getSalles () ;
for ( ISalle s : salles ) {
if ( distanceGrille (s , p . getPosition () ) <10) {
int d = distanceGraphe (s , p . getPosition () ) ;
if (d <=10) {
Color c = new Color (0 ,0 ,(255*(10 - d ) )
/10) ;
dessinSalle (s ,g , c ) ;
}
}
}
}
public void dessinSalle ( ISalle s , Graphics g , Color c ) {
int i = (( ICase ) s ) . getY () ;
int j = (( ICase ) s ) . getX () ;
g . setColor ( c ) ;
g . fillRect ( j * unite , i * unite , unite , unite ) ;
}
Monstres Notre héros peut maintenant parcourir moult labyrinthes, mais il se sent un peu seul. Nous allons
maintenant rajouter des monstres, qui se déplaceront de manière aléatoire, i.e. pour se déplacer leur méthode
faitSonChoix renverra une salle au hasard parmi les salles accessibles. Notre application contiendra donc une
CHAPITRE 15. TRAVAUX PRATIQUES : L’APPLICATION LABYRINTHE
226
collection de personnages, l’un d’entre eux étant le héros, les autres étant des monstres. C’est une occasion rêvée
d’exploiter le polymorphisme ... Un monstre ne pourra pas sortir d’un labyrinthe.
Les monstres ne seront pas dessinés de la même manière que le personnage du joueur. Pour tirer partie du
polymorphisme, il faut donc autoriser à chaque personnage de décider de la manière dont il souhaite se dessiner.
Cela n’a pas de sens de prévoir cela dans l’interface IPersonnage car celle-ci n’est pas concernée par la vue. Le
plus simple est donc d’enrichir cette interface par une nouvelle interface spécifiant les particularités d’un personage
graphique (pour la vue) :
package vue2D ;
public interface IP er son nag eG rap hi que extends IPersonnage {
public void dessiner ( Graphics g ) ;
public void setCoordonnees ( int xpix , int ypix ) ;
public void setSalleChoisie ( ISalle s ) ;
public KeyListener getKeyListener () ;
}
La méthode getKeyListener permet de savoir si un personnage est contrôlé par le clavier, auquel cas la vue
fera des traitements spécifiques pour celui-ci.
Exercice 252. (??) Ecrire une classe abstraite APersonnageGraphique qui propose une implémentation de
toutes les méthodes à l’exception de dessiner.
Un peu d’aide ?
package vue2D ;
public abstract class AP er son nag eG rap hi que implements IPersonnage ,
IP er son na geG rap hi que {
private IPersonnage p ;
public int xpix , ypix ; // coordonnees graphiques du personnage
static public final int unite = 15;
public AP ers on nag eG rap hi que ( IPersonnage p ) {
this . p = p ;
}
@Override
public void setCoordonnees ( int xpix , int ypix ) {
this . xpix = xpix ; this . ypix = ypix ;
}
@Override
public void setSalleChoisie ( ISalle s ) {
if ( p instanceof PersonnageDefaut )
(( PersonnageDefaut ) p ) . salleChoisie = s ;
}
public void setPeutSeDeplacer ( boolean val ) {
if ( p instanceof PersonnageDefaut )
(( PersonnageDefaut ) p ) . peutSeDeplacer = val ;
}
protected void majCoordonnees () {
ISalle s = getPosition () ;
if ( s == null ) return ;
15.11. ECLAIRAGE ET MONSTRES
227
int xpixCible = (( ICase ) s ) . getX () * unite ;
int ypixCible = (( ICase ) s ) . getY () * unite ;
if ( xpix < xpixCible ) xpix ++;
if ( xpix > xpixCible ) xpix - -;
if ( ypix < ypixCible ) ypix ++;
if ( ypix > ypixCible ) ypix - -;
setPeutSeDeplacer ((( xpix == xpixCible ) &&( ypix == ypixCible ) ) ) ;
}
@Override
public ISalle faitSonChoix ( Collection < ISalle > sallesAccessibles
) {
return p . faitSonChoix ( sallesAccessibles ) ;
}
@Override
public ISalle getPosition () {
return p . getPosition () ;
}
@Override
public boolean peutSeDeplacer () {
return p . peutSeDeplacer () ;
}
@Override
public void setPosition ( ISalle s ) {
p . setPosition ( s ) ;
}
@Override
public abstract void dessiner ( Graphics g ) ;
@Override
public KeyListener getKeyListener () {
if ( p instanceof KeyListener )
return (( KeyListener ) p ) ;
return null ;
}
}
Notez le constructeur paramétré prenant un paramètre de type IPersonnage. On peut donc créer un personnage graphique à partir de n’importe quelle implémentation de IPersonnage.
Exercice 253. (??) Ecrire une classe PersonnageGraphique pour les personnages gérés par un joueur.
Un peu d’aide ?
package vue2D ;
public class Pers onnageG raphiqu e extends A Per son na geG ra phi que {
private ImageIcon herosImage = null ;
private int tailleLinkH = 12;
private int tailleLinkL = 4;
CHAPITRE 15. TRAVAUX PRATIQUES : L’APPLICATION LABYRINTHE
228
private ISalle derniereSalle ;
public Personn ageGrap hique ( IPersonnage p ) {
super ( p ) ;
herosImage = new ImageIcon ( " icons / link / LinkRunShieldL1 .
gif " ) ;
}
@Override
public void dessiner ( Graphics g ) {
majCoordonnees () ;
ISalle s = getPosition () ;
int xposImage = xpix -( tailleLinkL /2) ;
int yposImage = ypix - tailleLinkH ;
int l = unite + tailleLinkL ;
int h = unite + tailleLinkH ;
g . drawImage ( herosImage . getImage () , xposImage , yposImage ,l ,h , null )
;
}
}
Exercice 254. Créer une classe Monstre qui se déplace de manière aléatoire et la classe graphique associée
Un peu d’aide ?
public class Monstre extends PersonnageDefaut {
static Random generator = new Random () ;
public Monstre ( ISalle s ) {
super ( s ) ;
}
public ISalle faitSonChoix ( Collection < ISalle > sallesAccessibles ) {
// choix aleatoire
return ( ISalle ) ( sallesAccessibles . toArray () ) [ generator . nextInt (
sallesAccessibles . size () ) ];
}
}
public class MonstreGraphique extends A Pe rso nn age Gr aph iqu e {
private ImageIcon monstreImage = null ;
private int tailleLinkH = 6;
private int tailleLinkL = 2;
public MonstreGraphique ( IPersonnage p ) {
super ( p ) ;
xpix =(( ICase ) p . getPosition () ) . getX () *15;
ypix = (( ICase ) p . getPosition () ) . getY () *15;
monstreImage = new ImageIcon ( " icons / monstre1 . gif " ) ;
}
@Override
public void dessiner ( Graphics g ) {
15.11. ECLAIRAGE ET MONSTRES
229
majCoordonnees () ;
ISalle s = getPosition () ;
int xposImage = xpix -( tailleLinkL /2) ;
int yposImage = ypix - tailleLinkH ;
int l = unite + tailleLinkL ;
int h = unite + tailleLinkH ;
g . drawImage ( monstreImage . getImage () , xposImage , yposImage ,l ,h ,
null ) ;
}
}
Il reste à modifier la vue pour qu’elle prenne en charge une collection de personnages : enrichissons donc
l’interface IVue de deux méthodes pour gérer l’ajout et le retrait de personnages graphiques à la vue.
public interface IVue {
public void dessiner ( ILabyrinthe l ) ;
public void dessiner ( IPe rs onn age Gr aph iq ue p ) ;
public void add ( IPe rs onn age Gr aph iq ue p ) ;
public void remove ( IP ers on nag eGr ap hiq ue p ) ;
}
Il faut prendre garde à une difficulté algorithmique : un processus séparé prend en charge l’affichage de la vue
(méthode paintComponent du composant graphique Dessin). Or dans cette méthode, il est nécessaire de parcourir la collection des personnages pour tous les redessiner. Si on modifie simultanément (dans la classe Main) cette
collection en enlevant des personnages (par exemple s’ils ont perdu), alors cela pose un problème d’accès concurrent. Pour gérer ce problème, il faut prendre un type de collection qui est protégé contre les accès concurrents,
comme CopyOnWriteArrayList. La problématique de la programmation multi-processus est abordée dans le
chapitre 8.
Exercice 255. (? ? ?) Modifier la vue (version 4) pour qu’elle implémente l’interface IVue. Adapter ensuite de
manière similaire les autres versions de la vue.
Un peu d’aide ?
package vue2D . v4 ;
public class Vue implements IVue {
Dessin dessin ;
PanneauDirections directions ;
ILabyrintheGrille labyrinthe ;
JFrame frame = new JFrame ( " Labyrinthe " ) ;
public Vue ( ILabyrintheGrille labyrinthe ) {
...
}
@Override
public void add ( IPe rs onn age Gr aph iq ue pg ) {
ICase s = ( ICase ) pg . getPosition () ;
pg . setCoordonnees ( s . getX () * dessin . unite , s . getY () * dessin . unite ) ;
dessin . add ( pg ) ;
if ( pg . getKeyListener () != null ) {
directions = new PanneauDirections ( labyrinthe , pg ) ;
frame . add ( directions ) ; // panneau des directions
- pour controler heros
CHAPITRE 15. TRAVAUX PRATIQUES : L’APPLICATION LABYRINTHE
230
frame . pack () ;
}
}
@Override
public void remove ( IP ers on nag eGr ap hiq ue pg ) {
dessin . remove ( pg ) ;
}
@Override
public void dessiner ( ILabyrinthe l ) {
dessin . repaint () ;
}
@Override
public void dessiner ( IPe rs onn age Gr aph iq ue p ) {
p . dessiner ( dessin . getGraphics () ) ;
}
}
public class Dessin extends JComponent {
private ILabyrintheGrille labyrinthe ;
...
private CopyOnWriteArrayList < IPersonnageGraphique > personnages = new
CopyOnWriteArrayList < IPersonnageGraphique >() ; //
Co py OnW ri teA rra yL ist pour pouvoir supprimer des elements tout en
faisant des parcours de la liste en parallele ( dans la methode
paintComponent )
public Graphics tampon ; // tampon memoire
Image image ; // image memoire correspondante au tampon memoire
public Dessin ( ILabyrintheGrille labyrinthe )
{
...
}
public void add ( IPe rso nn age Gr aph iq ue p ) {
// on ajoute un ecouteur sur le clavier au dessin du labyrinthe
personnages . add ( p ) ;
if ( p . getKeyListener () != null ) {
addKeyListener ( p . getKeyListener () ) ;
}
}
public void remove ( IPersonnage p ) {
// on ajoute un ecouteur sur le clavier au dessin du labyrinthe
personnages . remove ( p ) ;
}
public void dessinFond () {
...
15.11. ECLAIRAGE ET MONSTRES
}
public void dessinMurs ( Graphics g ) {
...
}
public void de ssi nS all es Vis it ees ( Graphics g , IPersonnage p ) {
...
}
public void dessinEntreeSortie () {
...
}
public void d es s in Pl u sC o ur tC h em in ( Graphics g , IPersonnage p ) {
...
}
public void d es s in Sa l le s Ec la i re es ( Graphics g , IPersonnage p ) {
...
}
public void paintComponent ( Graphics g )
{
// recopie du fond ( image ) ; murs + salles
g . drawImage ( image , 0 , 0 , this ) ;
// dessin des salles eclairees
for ( I Pe rso nna ge Gra ph iqu e p : personnages )
d es si n Sa l le sE c la ir e es (g , p ) ;
// dessin des murs eclaires
dessinMurs ( g ) ;
// dessin des salles visitees
for ( I Pe rso nna ge Gra ph iqu e p : personnages )
de ss inS al les Vis it ees (g , p ) ;
// dessin des plus courts chemins a la sortie pour un
PersonnageClavier ( controle par un joueur )
for ( I Pe rso nna ge Gra ph iqu e p : personnages )
if ( p . getKeyListener () != null )
d es si n Pl u sC ou r tC he m in (g , p ) ;
// dessin des personnages
for ( I Pe rso nna ge Gra ph iqu e p : personnages )
p . dessiner ( g ) ;
}
}
Exercice 256. (??) Créer une bonne dizaine de monstres et les placer à la sortie du labyrinthe.
Un peu d’aide ?
public class Main {
ArrayList < IPersonnageGraphique > personnages = new ArrayList <
IPersonnageGraphique >() ;
231
CHAPITRE 15. TRAVAUX PRATIQUES : L’APPLICATION LABYRINTHE
232
public Main () {
...
// creation du heros
IP er son na geG rap hi que heros = new Personn ageGrap hique ( new
personnages . v2 . PersonnageClavier ( labyrinthe . getEntree () ) ) ;
personnages . add ( heros ) ;
IVue vue = new vue2D . v4 . Vue ( labyrinthe ) ;
vue . add ( heros ) ;
// creation des autres personnages ( monstres )
for ( int i =0; i <=10; i ++) {
IP er son na geG rap hi que mg = new MonstreGraphique ( new
Monstre ( labyrinthe . getSortie () ) ) ;
personnages . add ( mg ) ;
vue . add ( mg ) ;
}
Exercice 257. (? ? ?) Modifier la boucle principale pour qu’elle gère l’ensemble des personnages. AJouter une
détection de collision (à partir de 10 secondes après le lancement du jeu pour laisser un peu de temps aux monstres
pour s’éloigner les uns des autres) : lors d’une collision entre deux personnages, les deux sont retirés du jeu.
Un peu d’aide ?
// boucle principale
ISalle destination = null ;
long horloge = System . currentTimeMillis () ;
while (! labyrinthe . sortir ( heros ) ) {
// rafraichissement de la vue
vue . dessiner ( labyrinthe ) ; // dessin des salles
// dessin des personnages
for ( I Pe rso nna ge Gra ph iqu e p : personnages )
vue . dessiner ( p ) ;
// choix et deplacement
for ( IPersonnage p : personnages ) {
Collection < ISalle > sallesAccessibles = labyrinthe .
sallesAccessibles ( p ) ;
if ( p . peutSeDeplacer () )
destination = p . faitSonChoix ( sallesAccessibles )
; // on demande au heros de faire son choix
de salle
if ( destination != p . getPosition () ) destination . recevoir (
p ) ; // deplacement
}
// detection des collisions
if ( System . currentTimeMillis () >( horloge +10000) ) // on attend 10
secondes avant de tester les collisions
{
boolean collision = false ; I Pe rso nna ge Gra ph iqu e
accidente1 = null ; IPe rso nn age Gr aph iqu e accidente2 =
null ;
for ( I Pe rso nna ge Gra ph iqu e p : personnages )
for ( I Pe rso nna ge Gra ph iqu e q : personnages )
if ( p != q )
if ( p . getPosition () == q .
getPosition () ) {
15.11. ECLAIRAGE ET MONSTRES
233
System . out . println ( "
Collision !! " ) ;
collision = true ;
accidente1 = p ;
accidente2 = q ;
}
if ( collision ) {
personnages . remove ( accidente1 ) ; vue . remove (
accidente1 ) ;
personnages . remove ( accidente2 ) ; vue . remove (
accidente2 ) ;
System . out . println ( " Plus que " + personnages . size
() + " personnages ... " ) ;
}
}
temporisation (10) ;
}
System . out . println ( " Gagne ! " ) ;
Exercice 258. (? ? ?) Ajouter un terrible dragon qui poursuit par un chemin le plus court le héros.
Affichage des murs
Exercice 259. (??) Ajouter l’affichage des murs : un mur ne devient visible que lorsqu’il a été contigu avec une
salle visitee.
L’archive TP07Application2Final.zip contient la version finale de l’application.
CHAPITRE 15. TRAVAUX PRATIQUES : L’APPLICATION LABYRINTHE
234
15.12
Sauvegarde/restauration d’un labyrinthe et de ses salles
Le code de départ est fourni dans l’archive TP08Application1.zip.
Exercice 260. (?) La classe Fichier utilise la classe File, qui va probablement devenir obsolète dans les
prochaines versions de Java. Réécrivez celle-ci en utilisant les fonctionnalités des classes Paths et Files vues
dans ce cours.
Correction de l’exercice 260
Voici les parties modifiées de la classe Fichier :
public class Fichier {
Scanner sc = null ;
public Fichier ( String nomFichier ) {
Path p = Paths . get ( nomFichier ) ;
try {
InputStream is = Files . newInputStream ( p ) ;
sc = new Scanner ( is ) ;
}
catch ( IOException e ) { System . out . println ( e ) ;}
}
public static boolean t es t Co o rd on n ee sS a ll e s ( String nomFichier ) {
Fichier f = new Fichier ( nomFichier ) ;
...
}
public static boolean testPasDeDoublon ( String nomFichier ) {
Fichier f = new Fichier ( nomFichier ) ;
...
}
public static boolean testValide ( String nomFichier ) {
if (! t e st Co o rd o nn ee s Sa ll e s ( nomFichier ) )
return false ;
if (! testPasDeDoublon ( nomFichier ) )
return false ;
return true ;
}
...
}
Exercice 261. (??) L’application possède plusieurs implémentations concurrentes des principales interfaces. Le
choix des implémentations à utiliser est effectué dans le constructeur Main. Ainsi tout nouveau choix oblige à
recompiler le code pour qu’il soit pris en compte.
Utiliser la classe Properties initialisée à partir d’un fichier de paramètres config.txt dont un exemple de
syntaxe est donné ci-dessous, pour que les choix des implémentations soient effectués dans une méthode init de
la classe Main.
# choix du labyrinthe - labys / levelx . txt
laby = labys / level3 . txt
# choix du moteur graphique vue2D . vx - 1 ,2 ,3 ou 4
vue2D =4
15.12. SAUVEGARDE/RESTAURATION D’UN LABYRINTHE ET DE SES SALLES
235
# choix du Personnage pour le heros Personnage . vy . PersonnageClavier - 1
ou 2
heros =2
# choix de la structure de donnees - grille ou grapheV1 ou grapheV2
labyrinthe = grapheV2
Correction de l’exercice 261
public class Main {
ArrayList < IPersonnageGraphique > personnages = new ArrayList <
IPersonnageGraphique >() ;
Properties params = new Properties () ;
ILabyrintheGrille labyrinthe ;
IVue vue ;
IP er son na geG rap hi que heros ;
public void init () {
Charset UTF8 = Charset . forName ( " UTF -8 " ) ;
Path fichier = Paths . get ( " config . txt " ) ;
try {
InputStream is = Files . newInputStream ( fichier ) ;
params . load ( is ) ;
} catch ( IOException ioe ) { System . out . println ( " Echec
lecture : " + ioe ) ;}
// creation du labyrinthe
switch ( params . getProperty ( " labyrinthe " ) ) {
case " grille " :
labyrinthe = new labyrinthe . grille .
LabyrintheGrille () ;
break ;
case " grapheV1 " :
labyrinthe = new labyrinthe . graphe . v1 .
LabyrintheGraphe () ;
break ;
case " grapheV2 " :
labyrinthe = new labyrinthe . graphe . v2 .
LabyrintheGraphe () ;
break ;
default :
labyrinthe = new labyrinthe . grille .
LabyrintheGrille () ;
break ;
}
// chargement du labyrinthe
try {
labyrinthe . creerLabyrinthe ( params . getProperty ( " laby "
));
}
catch ( E xc ept ion In val id Fil e eif ) {
System . out . println ( " Fichier de labyrinthe non - valide
");
System . out . println ( " Chargement du fichier de secours
... " ) ;
try {
CHAPITRE 15. TRAVAUX PRATIQUES : L’APPLICATION LABYRINTHE
236
labyrinthe . creerLabyrinthe ( " labys / level7 . txt
");
} catch ( E xc ept ion In val id Fil e e ) {
System . out . println ( " Fichier de secours
invalide ! " ) ;
System . out . println ( " Erreur de l ’ application "
);
System . exit (1) ;
};
}
// creation du heros
IP er son na geG rap hi que heros ;
switch ( params . getProperty ( " heros " ) ) {
case " 1 " :
heros = new Personn ageGraph ique ( new
personnages . v1 . PersonnageClavier ( labyrinthe .
getEntree () ) ) ;
break ;
case " 2 " :
heros = new Personn ageGraph ique ( new
personnages . v2 . PersonnageClavier ( labyrinthe .
getEntree () ) ) ;
break ;
default :
heros = new Personn ageGraph ique ( new
personnages . v2 . PersonnageClavier ( labyrinthe .
getEntree () ) ) ;
break ;
}
personnages . add ( heros ) ;
// creation de la vue
switch ( params . getProperty ( " vue2D " ) ) {
case " 1 " :
vue = new vue2D . v1 . Vue ( labyrinthe , heros ) ;
break ;
case " 2 " :
vue = new vue2D . v2 . Vue ( labyrinthe ) ;
break ;
case " 3 " :
vue = new vue2D . v3 . Vue ( labyrinthe ) ;
break ;
case " 4 " :
vue = new vue2D . v4 . Vue ( labyrinthe ) ;
break ;
default :
vue = new vue2D . v4 . Vue ( labyrinthe ) ;
break ;
}
vue . add ( heros ) ;
}
public Main () {
init () ; // chargement des parametres et initialisations en
15.12. SAUVEGARDE/RESTAURATION D’UN LABYRINTHE ET DE SES SALLES
237
fonction de ceux - ci
// creation des autres personnages ( monstres )
...
Nous allons mettre en pratique la sérialisation pour pouvoir sauvegarder/restaurer un labyrinthe. Un labyrinthe
étant une implémentation de l’interface ILabyrinthe et contenant un ensemble de salles implémentant l’interface
ISalle, il suffit de déclarer ces deux interfaces comme sérialisables :
public interface ILabyrinthe extends Serializable { ...
public interface ISalle extends Serializable { ...
Normalement, cela devrait être suffisant. Un problème potentiel se pose dans l’implémentation
LabyrintheGraphe qui exploite JGraphT. Le début de cette implémentation est :
public class LabyrintheGraphe extends ALabyrintheGraphe implements
ILabyrintheGrille {
private DirectedGraph < SalleSommet , DefaultEdge > g ;
private int largeur ;
private int hauteur ;
private Collection < ISalle > u n P l u s C o u r t C h e m i n V e r s S o r t i e ;
private ISalle lastPosition ;
...
Exercice 262. (??) Quel est ce problème éventuel ? Comment savoir si le problème se pose vraiment ?
Exercice 263. (??) Ecrire les méthode de sauvegarde et de rechargement d’un labyrinthe dans la classe Main et
tester.
Remarquez que la sauvegarde d’un labyrinthe entraîne de facto la sérialisation de toutes ses salles : nous
n’avons pas à prendre en charge la sauvegarde de chacune de ses salles, puisque l’état d’un labyrinthe englobe
l’état de toutes ses salles.
Correction de l’exercice 263
public void save () {
try {
Path p = Paths . get ( " lab . ser " ) ;
ObjectOutputStream oos = new ObjectOutputStream ( Files .
newOutputStream ( p ) ) ;
oos . writeObject ( labyrinthe ) ;
oos . flush () ;
oos . close () ;
}
catch ( Fi le N ot F ou nd E xc e pt io n e ) { System . out . println ( e ) ;}
catch ( IOException e ) { System . out . println ( e ) ;}
}
public void load () {
try {
Path p = Paths . get ( " lab . ser " ) ;
ObjectInputStream ois = new ObjectInputStream ( Files . newInputStream
(p));
labyrinthe = ( ILabyrintheGrille ) ois . readObject () ;
ois . close () ;
}
catch ( Fi le N ot F ou nd E xc ep t io n e ) { System . out . println ( e ) ;}
catch ( IOException e ) { System . out . println ( e ) ;}
238
CHAPITRE 15. TRAVAUX PRATIQUES : L’APPLICATION LABYRINTHE
catch ( C la s s N o tF o u n d Ex c e p t io n e ) { System . out . println ( e ) ;}
}
15.13. SERVEUR DE LABYRINTHES
15.13
239
Serveur de labyrinthes
L’intérêt d’avoir rendu nos labyrinthes sérialisable dans l’application précédente est que ceux-ci peuvent notamment être transmis sur le réseau. Combiné avec les fonctionnalités de RMI, il est donc facile de mettre en place
un serveur centralisant un ensemble de labyrinthes.
Exercice 264. (??) Ecrire une implémentation d’un serveur de labyrinthes, tel que défini par l’interface RemoteLabyrinthe vue dans le cours. Donner une méthode main contenant le code pour créer une instance de l’objet
sur le serveur et le mettre à disposition.
Correction de l’exercice 264
public class ServeurRMI implements RemoteLabyrinthe {
String repertoire = " la byrinth esDistan ts " ;
public String [] listeLabys () throws RemoteException {
File f = new File ( repertoire ) ;
if ( f . isDirectory () ) {
return f . list () ;
}
return null ;
}
public Labyrinthe getLabyrinthe ( String nom ) throws RemoteException {
Labyrinthe labyrinthe ;
try {
FileInputStream fichier = new FileInputStream (
repertoire + " / " + nom ) ;
ObjectInputStream ois = new ObjectInputStream ( fichier ) ;
labyrinthe = ( LabyrintheGrille ) ois . readObject () ;
ois . close () ;
return labyrinthe ;
}
catch ( Fi le N ot F ou nd E xc e pt io n e ) { System . out . println ( e ) ;}
catch ( IOException e ) { System . out . println ( e ) ;}
catch ( C l as s N o tF o u n d Ex c e p t io n e ) { System . out . println ( e ) ;}
return null ;
}
public static void main ( String [] args ) {
int port = 12345;
ServeurRMI sr = new ServeurRMI () ;
try {
RemoteLabyrinthe rl = ( RemoteLabyrinthe ) Unicast RemoteO bject .
exportObject ( sr ) ;
LocateRegistry . createRegistry ( port ) ;
Registry r = LocateRegistry . getRegistry ( port ) ;
r . bind ( " serveurLabyrinthe " , rl ) ;
System . out . println ( " serveur de labyrinthes distants pret " ) ;
}
catch ( Exception e ) { System . out . println ( e ) ;}
}
}
Exercice 265. (??) Tester le client du cours en ajoutant la méthode loadDistant dans la classe Main.
240
CHAPITRE 15. TRAVAUX PRATIQUES : L’APPLICATION LABYRINTHE
Chapitre 16
Corrigés
Ce chapitre donne des corrections pour les principaux exercices.
Correction de l’exercice 1
size (500 ,300) ; // taille de la fenetre : 500 x300
background (0) ; // couleur de fond : noir
textSize (32) ; // taille de la police : 32
text ( " Bonjour " ,50 ,50) ; // affichage de " Bonjour " , coordonnees : 50 ,50
text ( " tout le monde . " ,50 ,100) ;
text ( " Tout va bien ? " ,50 ,150) ;
Correction de l’exercice 2
int i =3; // variables :
int j = i ; // variables :
j ++; // variables : i =3
i =7; // variables : i =7
print ( i ) ; // affiche 7
i =3
i =3 j =3
j =4
j =4
Correction de l’exercice 3
Pour construire la chaine de caractères à afficher, il faut utiliser la concaténation (opérateur : +). Ce qui donne
par exemple :
size (800 ,100) ; // taille de la fenetre : 800 x100
background (0) ; // couleur de fond : noir
textSize (32) ; // taille de la police : 32
text ( " Les dimensions de la fenetre sont : " + width + " x " + height ,50 ,50) ;
Correction de l’exercice 4
On obtient la liste suivante :
width
height
mouseX
mouseY
pmouseX
pmouseY
key
keyCode
keyPressed
focused
241
CHAPITRE 16. CORRIGÉS
242
online
frameRate
frameCount
Correction de l’exercice 5
size (800 ,100) ; // taille de la fenetre
background (0) ; // couleur de fond : noir
textSize (16) ; // taille de la police
if (( displayWidth >=1024) &&( displayHeight >=768) ) {
text ( " Les dimensions de votre ecran sont suffisantes pour le bon
deroulement de l ’ application " ,50 ,50) ;
}
else {
text ( " Les dimensions de l ’ ecran sont insuffisantes pour un bon
deroulement de l ’ application " ,50 ,50) ;
}
Correction de l’exercice 6
String nom = " " ;
int num =3; // numero de la chaine
switch ( num ) {
case 1:
nom = " TF1 " ;
break ;
case 2:
nom = " France 2 " ;
break ;
case 3:
nom = " France 3 " ;
break ;
case 4:
nom = " Canal + " ;
break ;
case 5:
nom = " France 5 " ;
break ;
default :
nom = " inconnu " ;
}
// affichage
size (800 ,100) ; // taille de la fenetre
background (0) ; // couleur de fond : noir
textSize (16) ; // taille de la police
text ( " Le nom de la chaine " + num + " est : " + nom ,50 ,50) ;
Correction de l’exercice 7
boolean condition = true ;
while ( condition ) { // boucle principale infinie
while ( second () !=0) {}; // on attend que second () retourne 0
println ( " Changement de minute " ) ;
while ( second () ==0) {}; // on attend que second () ne retourne plus 0 fd
}
243
Correction de l’exercice 8
int decompte =60 - second () ; // point de depart du decompte
while ( decompte >0) { // tant que le decompte n ’ est pas fini
// affichage
println ( " Il reste " + decompte + " secondes ... " ) ;
while ( decompte ==60 - second () ) ; // on attend la seconde suivante
decompte =60 - second () ; // mise a jour de decompte
}
Correction de l’exercice 9
for ( int i =0; i <=10; i ++)
println (10 - i ) ;
Correction de l’exercice 10
Le programme affiche les valeurs du tableau une par une :
0
1
2
3
Pour afficher les nombres premiers, on peut les stocker dans un tableau, soit :
int [] tab = {1 ,2 ,3 ,5 ,7 ,11 ,13 ,17 ,19};
for ( int i : tab )
println ( i ) ;
Correction de l’exercice 11
au revoir
bonjour
Correction de l’exercice 12
Pour afficher, le message deux fois, il suffit d’appeler deux fois la méthode fenetreBonjour avec des paramètres différents.
void fenetreBonjour ( int x , int y ) {
textSize (32) ; // taille de la police : 32
text ( " Bonjour " ,x , y ) ; // affichage de " Bonjour " , coordonnees : x , y
}
void setup () {
size (300 ,100) ; // taille de la fenetre : 300 x100
background (0) ; // couleur de fond : noir
fenetreBonjour (50 ,50) ;
fenetreBonjour (60 ,60) ;
}
Correction de l’exercice 13
La méthode draw est appelée régulièrement par le moteur graphique : il va y avoir plein de messages bou dans
le terminal.
Correction de l’exercice 14
CHAPITRE 16. CORRIGÉS
244
int depx = 1;
int x = 50;
void setup () {
size (600 , 300) ; // taille de la fenetre
background (0) ; // couleur de fond : noir
textSize (50) ; // taille de la police
frameRate (60) ; // 60 fps
}
void draw () {
background (0) ; // fond noir
x = x + depx ; // mise -a - jour de x
// gestion du rebond
if ( x + textWidth ( " Bonjour " ) > width ) depx = -1* depx ;
if (x <0) depx = -1* depx ;
text ( " Bonjour " , x , height /2) ; // affichage de " Bonjour " , coordonnees :
x ,50
}
int depx = 1;
int x = 50;
Correction de l’exercice 15
int depx = 1;
int x = 50;
void setup () {
size (600 , 300) ; // taille de la fenetre
background (0) ; // couleur de fond : noir
textSize (50) ; // taille de la police
frameRate (60) ; // 60 fps
}
void draw () {
background (0) ; // fond noir
if ( mousePressed )
x = x + depx ; // mise -a - jour de x
// gestion du rebond
if ( x + textWidth ( " Bonjour " ) > width ) depx = -1* depx ;
if (x <0) depx = -1* depx ;
text ( " Bonjour " , x , height /2) ; // affichage de " Bonjour " , coordonnees :
x ,50
}
Correction de l’exercice 16
L’utilisation de la fonction sin permet de s’affranchir de la gestion du sens du déplacement.
double r = 0;
int xmax = 0;
void setup () {
size (600 , 300) ; // taille de la fenetre
background (0) ; // couleur de fond : noir
textSize (50) ; // taille de la police
frameRate (60) ; // 60 fps
245
xmax = ( int ) ( width - textWidth ( " Bonjour " ) ) ;
}
void draw () {
background (0) ; // fond noir
int x = ( xmax /2) +(( int ) (( xmax /2) * Math . sin ( r ) ) ) ;
r = r +0.05;
text ( " Bonjour " , x , height /2) ; // affichage de " Bonjour " , coordonnees :
x ,50
}
Correction de l’exercice 17
On peut stocker les lettres du texte dans un tableau et réutiliser la solution précédente en l’appliquant à chacune
des lettres via une boucle.
double r = 0;
double pas = 0.05;
int xmax = 0;
char [] lettre = { ’B ’ , ’o ’ , ’n ’ , ’j ’ , ’o ’ , ’u ’ , ’r ’ };
int [] x = new int [7];
void setup () {
size (600 , 300) ; // taille de la fenetre
background (0) ; // couleur de fond : noir
textSize (50) ; // taille de la police
frameRate (60) ; // 60 fps
xmax = ( int ) ( width - textWidth ( " B " ) ) ;
}
void draw () {
background (0) ; // fond noir
for ( int i =0; i <7; i ++)
x [ i ] = ( xmax /2) +(( int ) (( xmax /2) * Math . sin ( r +( i *2* pas ) ) ) ) ;
r = r + pas ;
for ( int i =0; i <7; i ++)
text ( lettre [ i ] , x [ i ] , height /2) ; // affichage de " Bonjour " ,
coordonnees : x ,50
}
Correction de l’exercice 18
void afficherMots ( String
String [] mots = split (
// affichage de chaque
for ( String mot : mots
println ( mot ) ;
}
phrase ) {
phrase , " " ) ; // decoupage en mots
mot
)
void setup () {
afficherMots ( " Il etait un petit navire . " ) ;
}
Correction de l’exercice 19
Le programme affiche :
bonjour
A vous deux , vous avez 37 ans
CHAPITRE 16. CORRIGÉS
246
La première ligne est l’affichage produit par la méthode bonjour. La seconde ligne est le résultat de
la ligne println(message(27));. En effet, la méthode message ajoute (via l’appel à la méthode
operationMysterieuse) la valeur 10 de la variable locale bob à la valeur 27 passée en paramètre et retourne
une chaine de caractères englobant ce résultat.
Correction de l’exercice 20
// phase 1 ( creation du fichier )
String mots = " il etait un petit navire " ;
String [] liste = split ( mots , " " ) ;
// Ecriture des mots dans un fichier , un par ligne
saveStrings ( " mots . txt " , liste ) ;
// phase 2 lecture du fichier - ce code affiche toutes les lignes ’dun
fichier texte
String [] lignes =
loadStrings ( " mots . txt " ) ;
println ( lignes ) ; // affichage des lignes
Correction de l’exercice 21
String [] lignes = loadStrings ( " entree . txt " ) ;
lignes = sort ( lignes ) ;
saveStrings ( " sortie . txt " , lignes ) ;
Correction de l’exercice 22
Il suffit de reprendre l’extrait de code précédent l’exercice et d’ajuster la valeur de la variable chemin pour
qu’elle désigne successivement un fichier texte valide, puis un fichier absent.
Correction de l’exercice 23&24
float inverse ( float x ) {
assert ( x !=0) ;
return 1/ x ;
}
void testInverse () {
assert ( inverse (2) ==0.5) ;
assert ( inverse (4) ==0.25) ;
assert ( inverse (0.25) ==4) ;
assert (( inverse (3) *3) ==1) ;
}
void setup () {
testInverse () ;
}
Correction de l’exercice 25
public class Exercices {
public static void main ( String [] args ) {
politesse () ;
}
static void politesse () {
System . out . println ( " Quel est votre nom ? " ) ;
247
Scanner in = new Scanner ( System . in ) ;
String nom = in . nextLine () ;
System . out . println ( " Bonjour " + nom ) ;
}
}
Correction de l’exercice 26
Il faut utiliser System.out.format pour faire un affichage avec mise en forme.
System . out . println ( " + - - - - - -+ - - - - - -+ - - - - - -+ " ) ;
for ( int i =1; i <=5; i ++) {
System . out . print ( " | " ) ;
System . out . format ( " %4 d | %4 d | %4 d |\ n " ,i , i *i , i * i * i ) ;
}
println ( " + - - - - - -+ - - - - - -+ - - - - - -+ " ) ;
Correction de l’exercice 27
R.A.S.
Correction de l’exercice 28
Les principales modifications qui ont été apportées pour convertir le code au format Java sont détaillées peu
après dans le cours.
Correction de l’exercice 29
R.A.S.
Correction de l’exercice 30
import processing . core .*;
public class Application extends PApplet {
float x = 100;
float y = 100;
float angle1 = 0.0 f ;
float segLength = 50;
public void setup () {
size (640 , 360) ;
strokeWeight (20.0 f ) ;
stroke (255 , 100) ;
}
public void draw () {
background (0) ;
float dx = mouseX - x ;
float dy = mouseY - y ;
angle1 = atan2 ( dy , dx ) ;
x = mouseX - ( cos ( angle1 ) * segLength ) ;
y = mouseY - ( sin ( angle1 ) * segLength ) ;
segment (x , y , angle1 ) ;
ellipse (x , y , 20 , 20) ;
}
CHAPITRE 16. CORRIGÉS
248
void segment ( float x , float y , float a ) {
pushMatrix () ;
translate (x , y ) ;
rotate ( a ) ;
line (0 , 0 , segLength , 0) ;
popMatrix () ;
}
public static void main ( String [] args ) {
PApplet . main ( " Application " ) ;
}
}
Correction de l’exercice 31
public class BouncyBubbles extends PApplet {
/* *
* Bouncy Bubbles
* based on code from Keith Peters .
*
* Multiple - object collision .
*/
int numBalls = 12;
float spring = 0.05 f ;
float gravity = 0.03 f ;
float friction = -0.9 f ;
Ball [] balls = new Ball [ numBalls ];
public void setup () {
size (640 , 360) ;
for ( int i = 0; i < numBalls ; i ++) {
balls [ i ] = new Ball ( random ( width ) , random ( height ) , random (30 , 70) ,
i , balls ) ;
}
noStroke () ;
fill (255 , 204) ;
}
public void draw () {
background (0) ;
for ( int i = 0; i < numBalls ; i ++) {
balls [ i ]. collide () ;
balls [ i ]. move () ;
balls [ i ]. display () ;
}
}
class Ball {
float x , y ;
float diameter ;
249
float vx = 0;
float vy = 0;
int id ;
Ball [] others ;
Ball ( float xin , float yin , float din , int idin , Ball [] oin ) {
x = xin ;
y = yin ;
diameter = din ;
id = idin ;
others = oin ;
}
public void collide () {
for ( int i = id + 1; i < numBalls ; i ++) {
float dx = others [ i ]. x - x ;
float dy = others [ i ]. y - y ;
float distance = sqrt ( dx * dx + dy * dy ) ;
float minDist = others [ i ]. diameter /2 + diameter /2;
if ( distance < minDist ) {
float angle = atan2 ( dy , dx ) ;
float targetX = x + cos ( angle ) * minDist ;
float targetY = y + sin ( angle ) * minDist ;
float ax = ( targetX - others [ i ]. x ) * spring ;
float ay = ( targetY - others [ i ]. y ) * spring ;
vx -= ax ;
vy -= ay ;
others [ i ]. vx += ax ;
others [ i ]. vy += ay ;
}
}
}
public void move () {
vy += gravity ;
x += vx ;
y += vy ;
if ( x + diameter /2 > width ) {
x = width - diameter /2;
vx *= friction ;
}
else if ( x - diameter /2 < 0) {
x = diameter /2;
vx *= friction ;
}
if ( y + diameter /2 > height ) {
y = height - diameter /2;
vy *= friction ;
}
else if ( y - diameter /2 < 0) {
y = diameter /2;
vy *= friction ;
}
CHAPITRE 16. CORRIGÉS
250
}
public void display () {
ellipse (x , y , diameter , diameter ) ;
}
}
static public void main ( String [] passedArgs ) {
PApplet . main ( " BouncyBubbles " )
}
}
Correction de l’exercice 32
On considère que l’occupation mémoire d’une instance est celle prise par ses attributs. Ici l’occupation mémoire des 2 instances d’étudiants est donc égale à 7 caractères (3 pour a, 4 pour b), soit environ 7 octets.
Correction de l’exercice 33
Le programme affiche Je suis bob et je connais bien greg.
Correction de l’exercice 34
Le programme affiche :
Bonjour
Moi , je
Bonjour
Moi , je
Bonjour
Moi , je
Bonjour
Moi , je
Basile
m ’ appelle
Cloclo
m ’ appelle
Adele
m ’ appelle
Eglantine
m ’ appelle
Adele
Basile
Cloclo
null
En effet, les deux lignes :
Etudiant e = new Etudiant () ;
d . nom = " Eglantine " ; // ce n ’ est pas e . nom =" Eglantine " ...
crée un objet e de type Etudiant, puis modifie le nom de l’objet d (et non pas celui de d). L’attribut nom de l’objet
e est donc non initialisé : sa valeur est "null".
Correction de l’exercice 35
Pour l’instance p du projet, il faut veiller à bien affecter tous ses attributs. En particulier, il faut donc avoir créé
auparavant une instance du binôme qui elle-même nécessite deux instances d’étudiant.
L’occupation mémoire d’un projet est principalement la somme de celle de la chaine de caractères stockant
son titre (x octets où x est le nombre de caractères), de la note du projet (4 octets) et du binôme (2 étudiants soit la
mémoire stockant leurs deux noms).
Exemple de code appelant la méthode mystere :
class Etudiant {
String nom ;
}
class Binome {
Etudiant etu1 ;
Etudiant etu2 ;
}
class Projet {
251
Binome b ;
String titre ;
float note ;
}
public float mystere ( Projet p ) {
System . out . println ( " Titre du projet : " + p . titre ) ;
System . out . println ( " Premier membre : " + p . b . etu1 . nom ) ;
System . out . println ( " Second membre : " + p . b . etu2 . nom ) ;
return p . note ;
}
public void void main ( String [] args ) () {
// etudiants
Etudiant b = new Etudiant () ;
b . nom = " bob " ;
Etudiant g = new Etudiant () ;
g . nom = " greg " ;
// creation du binome forme de b et g
Binome bi = new Binome () ;
bi . etu1 = b ; bi . etu2 = g ;
// creation d ’ un projet du binome
Projet p = new Projet () ;
p . b = bi ;
p . titre = " Un super projet " ;
p . note =19.0;
// appel de mystere
System . out . println ( " La note du projet est " + mystere ( p ) ) ;
}
Correction de l’exercice 36
Extrait de code pour créer deux étudiants puis les afficher :
// creation de deux etudiants a et b
Etudiant a = new Etudiant () ;
a . nom = " Bob " ;
Etudiant b = new Etudiant () ;
b . nom = " Greg " ;
System . out . println ( a + " / " + b ) ;
La méthode toString est disponible pour tous les objets et retourne, par défaut, le type de l’objet suivi de
son adresse mémoire.
Correction de l’exercice 37
public String toString () {
String res ;
res = " Titre du projet : " + titre + " \ n " ;
res += " Premier membre : " + b . etu1 . nom + " \ n " ;
res += " Second membre : " + b . etu2 . nom + " \ n " ;
return res ;
}
Correction de l’exercice 38
Le programme affiche :
CHAPITRE 16. CORRIGÉS
252
[ Binome ]: [ Etudiant ] bobby / [ Etudiant ] bob
Correction de l’exercice 39
public String getTitre () {
return titre ;
}
Correction de l’exercice 40
Indication en commentaires de l’ordre de passage lors de l’exécution dans les lignes du code :
class Etudiant { // 4 // 8
String nom ; // 5 // 9
public String toString () { // 21 // 24
return " [ Etudiant ] " + nom ; // 22 // 25
}
}
class Binome { // 13
Etudiant etu1 ; // 14
Etudiant etu2 ; // 15
public String toString () { // 19
return " [ Binome ]: " + etu1 + " / " + etu2 ; // 20 // 23 // 26
}
}
public static void main ( String [] args ) { // 1
// creation de deux etudiants a et b // 2
Etudiant a = new Etudiant () ; // 3
a . nom = " Bob " ; // 6
Etudiant b = new Etudiant () ; // 7
b . nom = " Bobby " ; // 10
// creation d ’ un binome bi // 11
Binome bi = new Binome () ; // 12
bi . etu1 = a ; // 16
bi . etu2 = b ; // 17
System . out . println ( bi ) ; // 18 // 27
} // 28
Correction de l’exercice 41
Le débogueur permet de vérifier le comportement de l’attribut nom de la classe Etudiant.
Correction de l’exercice 42
Le programme affiche :
[ Binome ]: null / null
En effet, le binôme contient deux instances d’étudiants qui n’ont pas été créées : elles sont donc dans l’état null.
Si on décommente la ligne // System.out.println(bi.etu1.nom);, le programme indique une erreur à
l’exécution : en effet, on essaie d’accéder à un attribut d’une instance qui n’a pas été créée.
Correction de l’exercice 43
Le programme affiche :
Saurion Bilbo
253
En effet, la méthode nomsEtudiants renvoie la concaténation des deux noms des étudiants passés en paramètre
(avec un espace de séparation).
Les instances crées en début de la méthode main durent tout le programme puisque celui-ci se termine à la fin
de la méthode main. La variable String de la classe nomsEtudiants est locale à celle-ci : elle est donc créée à
chaque appel de celle-ci et détruite lorsque l’appel se termine.
Correction de l’exercice 44
System . out . println ( " Appel de la methode somme avec deux parametres " ) ;
System . out . println ( " somme (567 ,255) = " + somme (567 ,255) ) ;
System . out . println ( " Appel de la methode somme avec trois parametres " ) ;
System . out . println ( " somme (567 ,255 ,256) = " + somme (567 ,255 ,256) ) ;
Correction de l’exercice 45
Constructeur surchargé pour la classe Binome :
Binome ( Etudiant a , Etudiant b ) {
this . etu1 = a ;
this . etu2 = b ;
}
Constructeur surchargé pour la classe Projet :
Projet ( Binome bi , String titre , float note ) {
this . b = bi ;
this . titre = titre ;
this . note = note ;
}
Correction de l’exercice 46
Le programme affiche :
127 // println ( i )
128 // println ( z )
128 // println ( j )
200 // println ( k )
( erreur ) // println ( l ) - erreur pour convertir la chaine "127+73" qui n
’ est pas convertible en nombre
Correction de l’exercice 48 & 47
public static void main ( String [] args ) {
// creation d ’ un tableau de 5 etudiants
Etudiant [] tab = new Etudiant [5]; // le tableau est cree ... mais pas
les etudiants !
tab [0]= new Etudiant ( " Albert " ) ; // creation et affectation du premier
etudiant
Etudiant b = new Etudiant ( " Bobby " ) ; // autre solution - on cree d ’
abord un etudiant
tab [1]= b ; // puis on l ’ affecte
tab [2]= new Etudiant ( " Charly " ) ;
tab [3]= new Etudiant ( " Dominique " ) ;
tab [4]= new Etudiant ( " Edouard " ) ;
// affichage des etudiants du tableau
for ( Etudiant e : tab )
System . out . println ( e ) ;
// version collection ( ArrayList )
ArrayList < Etudiant > tab2 = new ArrayList < Etudiant >() ;
CHAPITRE 16. CORRIGÉS
254
tab2 . add ( new Etudiant ( " Albert2 " ) ) ;
tab2 . add ( new Etudiant ( " Bobby2 " ) ) ;
tab2 . add ( new Etudiant ( " Charly2 " ) ) ;
tab2 . add ( new Etudiant ( " Dominique2 " ) ) ;
tab2 . add ( new Etudiant ( " Edouard2 " ) ) ;
// affichage des etudiants de la collection
for ( Etudiant e : tab2 )
System . out . println ( e ) ;
}
Correction de l’exercice 49
public class Etudiant {
String nom ;
static String IUT = " IUT de Bordeaux " ;
Etudiant ( String nom ) {
this . nom = nom ;
}
public static void main ( String [] args ) {
// aucune instance de la classe Etudiant ...
System . out . println ( Etudiant . IUT ) ;
// acces a Etudiant . IUT via des instances
Etudiant bob = new Etudiant ( " Bob " ) ;
Etudiant greg = new Etudiant ( " Greg " ) ;
System . out . println ( " IUT de bob : " + bob . IUT ) ;
System . out . println ( " IUT de greg : " + greg . IUT ) ;
}
}
La place mémoire occupée par le programme est formé de la mémoire prise par la chaine "IUT de Bordeaux" ainsi
que les noms des deux étudiants : "Greg" et "Bob".
Correction de l’exercice 50 & 51
Comme l’attribut bob n’est pas statique, on ne peut pas l’affecter dans la méthode (statique) main. Il suffit
donc de déplacer l’affectation dans le constructeur de MainAppli.
public class MainAppli {
Etudiant bob ;
public MainAppli () {
System . out . println ( " Bienvenue " ) ;
bob = new Etudiant ( " Bob " ) ;
}
public static void main ( String [] args ) {
new MainAppli () ;
}
}
Correction de l’exercice 52
static void afficherIUT () {
System . out . println ( IUT ) ;
}
// exemples appels
255
Etudiant . afficherIUT () ; // premiere methode ( preferable )
( new Etudiant () ) . afficherIUT () ; // seconde methode ( maladroit , mais
correct )
Correction de l’exercice 53
Pour comptabiliser le nombre d’instances, on peut ajouter un attribut statique dans la classe Etudiant chargé
de stocker cette valeur, en l’incrémentant naturellement à chaque création d’une instance, i.e. à chaque appel d’un
constructeur.
public class Etudiant {
String nom ;
static String IUT = " IUT de Bordeaux " ;
static int nbTotalEtudiants =0;
Etudiant ( String nom ) {
this . nom = nom ;
nbTotalEtudiants ++;
}
}
public class MainAppli {
/* *
* @param args the command line arguments
*/
public static void main ( String [] args ) {
Etudiant bob = new Etudiant ( " bob " ) ;
Etudiant greg = new Etudiant ( " Greg " ) ;
Etudiant Ralf = new Etudiant ( " Ralf " ) ;
System . out . println ( Etudiant . IUT ) ;
System . out . println ( Etudiant . nbTotalEtudiants ) ;
}
}
Correction de l’exercice 54
Toutes les explications sont dans le cours. Bien observer la différence de fonctionnement entre le passage d’un
paramètre d’un type primitif et celui d’une instance.
Correction de l’exercice 55
La classe Etudiant possède un attribut de type Professeur. Dans le constructeur de copie d’Etudiant, celui-ci
n’est pas copié et reste donc dans l’état null. Ainsi lorsque la méthode passageEtudiant est appelée sur une
copie d’un étudiant, celle-ci provoque l’erreur lors de l’accès à l’attribut nom du professeur de la copie.
Pour corriger cela, il faut veiller lors de la redéfinition du constructeur de la copie, à bien copier tous les
attributs de l’objet.
Correction de l’exercice 56
Le déplacement des classes dans des paquets ne présente pas de difficulté. La manipulation est de plus facilitée
par eclipse/NetBeans qui prennent en charge les modifications à apporter aux autres classes.
Correction de l’exercice 57
La génération des accesseurs est une tâche simple mais fastidieurse : elle est grandement facilitée par les outils
de refactoring d’ eclipse/Netbeans ...
Correction de l’exercice 58
Voici une solution (version simplifiée du patron de conception "Singleton") :
CHAPITRE 16. CORRIGÉS
256
public class Anneau {
private static boolean existe = false ;
private Anneau () {};
public static Anneau getAnneau () {
if ( existe == false ) {
existe = true ;
return new Anneau () ;
}
return null ;
}
}
Le principe consiste à mettre les constructeurs en accès privés, pour que la création d’un anneau ne puisse être
réalisée directement, et de créer une méthode que l’on ne pourra invoquer qu’une seule fois en vue de récupérer
un anneau (qui sera donc unique).
Correction de l’exercice 59
La classe Chat possède un attribut (nom hérité d’Animal), un constructeur sans argument, la méthode
recevoirNom() (héritée d’Animal) et une méthode propre miauler().
Correction de l’exercice 60
public class Personne {
private String nom ;
/* *
* @return the nom
*/
public String getNom () {
return nom ;
}
/* *
* @param nom the nom to set
*/
public void setNom ( String nom ) {
this . nom = nom ;
}
}
public class Etudiant extends Personne {
private static int nbTotalEtudiants =0;
Etudiant ( String nom ) {
setNom ( nom ) ;
nbTotalEtudiants ++;
}
/* *
* @return the nbTotalEtudiants
*/
public static int get NbTotalE tudiant s () {
257
return nbTotalEtudiants ;
}
}
Correction de l’exercice 61
Le code affiche :
Constructeur Personne ()
Constructeur Etudiant ( String )
Correction de l’exercice 62
public
class Personne {
private String nom ;
private static int nbTotalPersonnes =0;
public Personne () {
nbTotalPersonnes ++;
}
/* *
* @return the nom
*/
public String getNom () {
return nom ;
}
/* *
* @param nom the nom to set
*/
public void setNom ( String nom ) {
this . nom = nom ;
}
/* *
* @return the nbTotalPersonnes
*/
public static int getNbTotal () {
return nbTotalPersonnes ;
}
}
public class Professeur extends Personne {
private static int nbTotalProfesseurs =0;
public Professeur ( String nom ) {
setNom ( nom ) ;
nbTotalProfesseurs ++;
}
/* *
CHAPITRE 16. CORRIGÉS
258
* @return the nbTotalProfesseurs
*/
public static int getNbTotal () {
return nbTotalProfesseurs ;
}
}
Correction de l’exercice 63
Le code affiche :
[ Etudiant ] Je suis Arsene Lupin , tres studieux
[ Personne ] Je suis Bugs bunny , une grande personne
Correction de l’exercice 64
Lorsqu’elle est redéfinie dans une classe fille, la méthode getNbTotal appelée dépend de classe stipulée lors
de l’appel car elle est statique.
Correction de l’exercice 65
public String toString () {
return super . toString () + " et je suis un etudiant tres studieux " ;
}
Correction de l’exercice 66
Lorsqu’on enlève le constructeur sans argument, le compilateur indique une erreur :
Implicit super constructor Personne() is undefined. Must explicitly invoke another constructor.
Correction de l’exercice 67
Il n’est pas possible de déclarer la méthode toString de la classe Personne comme final car celle-ci est
redéfinie dans la classe Etudiant. Par contre, on peut le faire pour la classe Etudiant.
On ne peut pas déclarer non plus un constructeur comme final.
Correction de l’exercice 68
Il suffit de redéfinir la méthode equals de la classe Personne :
public boolean equals ( Object o ) {
if ( this == o ) return true ; // si les references sont identiques on
retourne vrai
if ( o instanceof Personne ) // si o est une Personne aussi , on compare
les noms
return ( getNom () . equals ((( Personne ) o ) . getNom () ) ) ;
return false ; // autres cas : on retourne faux
}
Correction de l’exercice 69
public void finalize () {
System . out . println ( " Bye bye de la part de " + getNom () ) ;
}
Correction de l’exercice 70
public abstract class Personne {
private String nom ;
private static int nbTotalPersonnes =0;
public abstract boolean aLeBac () ;
259
...
}
public class Professeur extends Personne {
...
public boolean aLeBac () {
return true ;
}
}
Correction de l’exercice 71
Attention : la méthode getNbTotal ne peut plus être statique.
public interface Individu {
public int getNbTotal () ;
}
public class Personne implements Individu {
...
public int getNbTotal () {
return nbTotalPersonnes ;
}
}
Correction de l’exercice 72
Avec l’interface Labyrinthe, on peut :
— placer un héros à l’entrée ou l’informer s’il est à la sortie avec les méthodes entrer et sortir ;
— déterminer l’ensemble des salles dans lesquelles le héros peut se déplacer à partir de sa position courante
via la méthode sallesAccessibles (car l’interface Personnage permet de connaître sa position)
Les autres méthodes sont des accesseurs, qui sont spécifiés par souci de commodité.
Correction de l’exercice 73
public class Etudiant extends Personne {
...
public void jamesBond () {
System . out . println ( " [ Etudiant ] Je m ’ appelle " + getNom () + " , " +
getNom () + " Bond " ) ;
}
}
public class Professeur extends Personne {
...
public void jamesBond () {
CHAPITRE 16. CORRIGÉS
260
System . out . println ( " [ Professeur ] How do you do , Miss Moneypenny
?");
}
}
Correction de l’exercice 74
public
class Personne {
...
public void jamesBond () {
System . out . println ( " [ Personne ] Miss Moneypenny , je vous saurais
gre de ne pas dire \" le vieux \" en parlant de moi . " ) ;
}
}
Correction de l’exercice 76
Le code :
Etudiant a = new Etudiant ( " Arsene Lupin " ) ;
Etudiant b = new Etudiant ( " Dark Vador " ) ;
Etudiant c = new Etudiant ( " Saurion " ) ;
Professeur d = new Professeur ( " Jojo Lapin " ) ;
Professeur e = new Professeur ( " Barbapapa " ) ;
ArrayList < Personne > personnes = new ArrayList < >() ;
personnes . add ( a ) ; personnes . add ( b ) ; personnes . add ( c ) ; personnes . add
( d ) ; personnes . add ( e ) ;
for ( Personne p : personnes )
p . jamesBond () ;
affiche
[ Etudiant ] Je m ’ appelle
[ Etudiant ] Je m ’ appelle
[ Etudiant ] Je m ’ appelle
[ Professeur ] How do you
[ Professeur ] How do you
Arsene Lupin , Arsene Lupin Bond
Dark Vador , Dark Vador Bond
Saurion , Saurion Bond
do , Miss Moneypenny ?
do , Miss Moneypenny ?
ce qui montre bien que la méthode jamesBond appelée varie en fonction du "type" de la personne.
Correction de l’exercice 75
Etudiant a = new Etudiant ( " Arsene Lupin " ) ;
Personne z = a ; // conversion ascendante : z est de type statique
Personne , de type dynamique Etudiant
System . out . println ( z ) ; // la methode toString appelee est celle d ’
Etudiant ( a )
Etudiant w = ( Etudiant ) z ; // conversion descendante de z ( possible car
z est de type dynamique Etudiant )
Personne x = new Personne ( " x " ) ;
Etudiant h = ( Etudiant ) x ; // interdit : compilation ok , mais erreur a l
’ execution car x est de type dynamique Personne
Correction de l’exercice 77
261
public void affichage ( Collection < Etudiant > etudiants ) {
System . out . println ( " *********** " ) ;
for ( Etudiant e : etudiants )
System . out . println ( e ) ;
}
Main () {
Etudiant
Etudiant
Etudiant
Etudiant
Etudiant
a
b
c
d
e
=
=
=
=
=
new
new
new
new
new
Etudiant ( " a " ) ;
Etudiant ( " b " ) ;
Etudiant ( " c " ) ;
Etudiant ( " d " ) ;
Etudiant ( " e " ) ;
LinkedList < Etudiant > etudiants = new LinkedList < >() ;
etudiants . add ( a ) ;
etudiants . add ( b ) ;
affichage ( etudiants ) ; // affiche a b
ListIterator li = etudiants . listIterator () ; // iterateur en
position debut de liste
li . next () ; // positionnement entre a et b
li . add ( c ) ; // insertion d ’ un element ( ne pas faire etudiants . add ( c )
!)
affichage ( etudiants ) ; // affiche a c b
li . previous () ; // positionnement entre a et c
li . add ( d ) ;
affichage ( etudiants ) ; // affiche a d c b
li . next () ; // positinnement entre c et b
li . remove () ; // suppresion de c
affichage ( etudiants ) ; // affiche a d b
}
Correction de l’exercice 78
void afficherInverse ( LinkedList ll ) {
ListIterator li = ll . listIterator ( ll . size () ) ;
while ( li . hasPrevious () ) {
System . out . println ( li . previous () ) ;
}
}
Une autre solution, plus simple, consiste à exploiter la méthode descendingIterator qui retourne un itérateur parcourant les éléments en sens inverse :
void afficherInverse ( LinkedList ll ) {
ListIterator li = ll . descendingIterator () ;
while ( li . hasNext () ) {
System . out . println ( li . next () ) ;
}
}
Correction de l’exercice 79
public void affichage ( Etudiant [] etudiants ) {
CHAPITRE 16. CORRIGÉS
262
System . out . println ( " *********** " ) ;
for ( Etudiant e : etudiants )
System . out . println ( e ) ;
}
Main () {
Etudiant
Etudiant
Etudiant
Etudiant
Etudiant
a
b
c
d
e
=
=
=
=
=
new
new
new
new
new
Etudiant ( " a " ) ;
Etudiant ( " b " ) ;
Etudiant ( " c " ) ;
Etudiant ( " d " ) ;
Etudiant ( " e " ) ;
Etudiant [] etudiants = new Etudiant [5];
etudiants [0]= a ;
etudiants [1]= b ;
affichage ( etudiants ) ; // affiche a b
etudiants [2]= b ;
etudiants [1]= c ;
affichage ( etudiants ) ; // affiche a c b
etudiants [3]= b ;
etudiants [2]= c ;
etudiants [1]= d ;
affichage ( etudiants ) ; // affiche a d c b
etudiants [3]= null ;
etudiants [2]= b ;
etudiants [1]= d ;
affichage ( etudiants ) ; // affiche a d b
Correction de l’exercice 80
@Override
public boolean equals ( Object o ) {
if ( o == this ) return true ; // je suis égal à moi -^
e mme ...
if ( o instanceof Personne ) // si o est une Personne
return ( getNom () . compareTo ((( Personne ) o ) . getNom () ) ==0) ;
// si o n ’ est pas une Personne , on retourne faux
return false ;
}
@Override
public int hashCode () {
// return 0; // version simple
// version un peu plus compliquee
char c = getNom () . charAt (0) ;
return c ;
}
Correction de l’exercice 81
Etudiant a = new Etudiant ( " Arsene " ) ;
Etudiant b = new Etudiant ( " Bobby " ) ;
HashMap < Etudiant , String > remarques = new HashMap < Etudiant , String >() ;
remarques . put (a , " Etudiant denomme Arsene " ) ;
263
remarques . put (b , " Etudiant denomme Bobby " ) ;
System . out . println ( " Infos sur etudiant a : " + remarques . get ( a ) ) ;
Correction de l’exercice 82
// Collection d ’ etudiants
Etudiant a = new Etudiant ( " a " ) ;
Etudiant b = new Etudiant ( " a " ) ;
Etudiant c = new Etudiant ( " c " ) ;
Etudiant d = new Etudiant ( " d " ) ;
HashSet etudiants = new HashSet () ;
etudiants . add ( a ) ; etudiants . add ( b ) ; etudiants . add ( c ) ; etudiants . add ( d ) ;
// Collection de professeurs
Professeur e = new Professeur ( " e " ) ;
Professeur f = new Professeur ( " f " ) ;
Professeur g = new Professeur ( " g " ) ;
ArrayList profs = new ArrayList () ;
profs . add ( e ) ; profs . add ( f ) ; profs . add ( g ) ;
// Reunion des deux collections
ArrayList tous = new ArrayList () ;
tous . addAll ( etudiants ) ;
tous . addAll ( profs ) ;
Correction de l’exercice 83
Pour trier la collection alphabétiquement, il suffit d’utiliser une implémentation de Comparator adéquate :
public class Comparateur implements Comparator {
@Override
public int compare ( Object a , Object b ) {
// comparaison de deux personnes en fonction de leurs noms
int val = (( Personne ) a ) . getNom () . compareTo ((( Personne ) b ) . getNom ()
);
return val ;
}
}
Correction de l’exercice ??
En cas d’erreur, vérifier que la commande javac a bien généré le fichier HelloWorldApp.class (i.e. qu’il
n’y a pas d’erreur de syntaxe dans la saisie du fichier HelloWorldApp.java).
Correction de l’exercice ??
Pour compiler et placer les exécutables dans le répertoire bin :
javac -d bin - classpath src src / scolarite /* . java
Correction de l’exercice ??
Pour compiler et placer les exécutables dans le répertoire bin :
javac - classpath source : lib / core . jar -d bin source /* . java
Correction de l’exercice ??
Une commande simple pour lancer les exécutables générés par la solution précédente :
java - cp bin : lib / core . jar classeMain
CHAPITRE 16. CORRIGÉS
264
Correction de l’exercice ??
Dans le répertoire src, on utilise la commande javadoc :
javadoc scolarite/*.java -author -d doc
Ceci génère un répertoire doc contenant la documentation :
> ls doc /
allclasses - frame . html constant - values . html help - doc . html index . html
package - list / scolarite / allclasses - noframe . html deprecated - list .
html index - all . html overview - tree . html resources / stylesheet . css
> ls doc / scolarite /
Etudiant . html Main . html package - frame . html package - summary . html
package - tree . html Personne . html Professeur . html
Correction de l’exercice ??
Dans le répertoire bin :
jar cvf scolarite . jar scolarite /* . class
Pour exécuter l’archive :
java - classpath scolarite . jar scolarite . Main
Correction de l’exercice 84
Il suffit d’adapter la session de débogage décrite pas-à-pas dans le cours.
Correction de l’exercice 85
Dans la vue Variables, faire view Menu (petite icone) puis Java pour pouvoir cocher l’affichage des attributs statiques.
Correction de l’exercice 86
Rien à signaler : les tests unitaires à écrire pour les classes Etudiant et Personne sont des retranscriptions
directes du test unitaire décrit dans le cours pour la classe Professeur.
Correction de l’exercice 87
public class Groupe {
ArrayList < Personne > membres = new ArrayList < Personne >() ;
HashSet < String > noms = new HashSet < String >() ;
public void add ( Personne p ) {
if ( noms . contains ( p . getNom () ) )
return ;
membres . add ( p ) ;
noms . add ( p . getNom () ) ;
}
public int size () {
return membres . size () ;
}
public String toString () {
String res = " Groupe : " ;
for ( Personne p : membres )
res += p . getNom () + " , " ;
265
return res ;
}
}
public class GroupeTest {
@Test
public void testSize () {
int resultat = 2;
Groupe g = new Groupe () ;
g . add ( new Personne ( " A " ) ) ;
g . add ( new Personne ( " B " ) ) ;
assertEquals ( g . size () , resultat ) ;
}
@Test
public void testToString () {
Groupe g = new Groupe () ;
g . add ( new Personne ( " A " ) ) ;
g . add ( new Personne ( " B " ) ) ;
assertEquals ( g . toString () ," Groupe : A , B , " ) ;
}
@Test
public void testAdd () {
int resultat = 2;
Groupe g = new Groupe () ;
g . add ( new Personne ( " A " ) ) ;
g . add ( new Personne ( " B " ) ) ;
g . add ( new Personne ( " A " ) ) ; // test detection doublon
assertEquals ( g . size () , resultat ) ;
}
}
Correction de l’exercice 88
Rien à signaler : toutes les explications sont dans le cours.
Correction de l’exercice 89
Toutes les explications sont dans le cours.
Correction de l’exercice 90
public class ExceptionTropGrand extends Exception {}
public class ExceptionNegatif extends Exception {
int valeur ;
ExceptionNegatif ( int val )
{
valeur = val ;
}
public String toString ()
{
CHAPITRE 16. CORRIGÉS
266
return valeur + " est
negatif " ;
}
}
public class Factorielle {
public static int calcul ( int n ) throws ExceptionNegatif ,
ExceptionTropGrand {
int res = 1;
if ( n < 0)
throw new ExceptionNegatif ( n ) ;
for ( int i = 2; i <= n ; i ++) {
if ( res > ( Integer . MAX_VALUE / i ) )
throw new ExceptionTropGrand () ;
res *= i ;
}
return res ;
}
public static void main ( String [] arg ) {
int n ;
try {
n = Integer . parseInt ( arg [0]) ;
System . out . println ( " Factorielle de " + n + " :
" + calcul ( n ) ) ;
}
// gestion erreur : pas d ’ argument
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 . err . println ( " Nombre entier positif
manquant " ) ;
System . exit (1) ;
}
// gestion erreur : argument non entier
catch ( Nu m be rF o rm at E xc e pt io n e ) {
System . err . println ( " L ’ argument doit etre entier
");
System . exit (1) ;
}
catch ( ExceptionNegatif e ) {
System . err . println ( " Le parametre est negatif " ) ;
System . exit (1) ;
}
catch ( ExceptionTropGrand e ) {
System . err . println ( " Le parametre est trop grand
pour ce programme " ) ;
System . exit (1) ;
}
}
}
Correction de l’exercice 91
267
import static org . junit . Assert .*;
import org . junit . Test ;
public class FactorielleTest {
@Test
public void t e s t C a l c u l E x c e p t i o n N e g a t i f () {
boolean succes = false ;
try {
Factorielle . calcul ( -1) ;
} catch ( ExceptionNegatif e ) {
succes = true ;
} catch ( ExceptionTropGrand e ) {
succes = false ;
}
finally {
assertEquals ( succes , true ) ;
}
}
@Test
public void t e s t C a l c u l E x c e p t i o n T r o p G r a n d () {
boolean succes = false ;
try {
Factorielle . calcul (20) ;
} catch ( ExceptionNegatif e ) {
succes = false ;
} catch ( ExceptionTropGrand e ) {
succes = true ;
}
finally {
assertEquals ( succes , true ) ;
}
}
}
Correction de l’exercice 92
Exercice immédiat.
Correction de l’exercice 93
Exercice immédiat.
Correction de l’exercice 94
Exercice immédiat.
Correction de l’exercice 95
Si on indique un chemin incorrect, une exception de type IOException est levée lorsqu’on initalise un flux
en lecture à partir de ce chemin (objet br).
Correction de l’exercice 96
Le programme affiche tous les nombres et la lecture s’interrompt lorsqu’il rencontre le mot "happy" (fin de la
boucle while).
CHAPITRE 16. CORRIGÉS
268
Correction de l’exercice 97
Exercice immédiat.
Correction de l’exercice 98
public void save ( String fichier ) {
Charset UTF8 = Charset . forName ( " UTF -8 " ) ;
Path p = Paths . get ( fichier ) ;
try {
BufferedWriter bw = Files . newBufferedWriter (p , UTF8 ) ;
bw . write ( getNom () + " \ n " ) ;
bw . flush () ; // ecrire toutes les donnees en attente (
vider le tampon )
} catch ( IOException ioe ) { System . err . println ( " Erreur d ’
ecriture : " + ioe ) ;}
}
Correction de l’exercice 99
public static Personne load ( String fichier ) {
Charset UTF8 = Charset . forName ( " UTF -8 " ) ;
Path p = Paths . get ( fichier ) ;
String nom = null ;
try {
BufferedReader br = Files . newBufferedReader (p , UTF8 ) ;
nom = br . readLine () ;
} catch ( IOException ioe ) { System . err . println ( " Erreur de
lecture : " + ioe ) ;}
if ( nom != null )
return new Personne ( nom ) ;
return null ; // si nom est null , on ne cree pas une personne
sans nom
}
Correction de l’exercice 100
Création de l’ensemble des paramètres et enregistrement dans un fichier :
Properties p = new Properties () ;
p . setProperty ( " age " , " 23 " ) ;
p . setProperty ( " nom " ," Cloclo " ) ;
p . setProperty ( " prenom " ," Claudette " ) ;
Charset UTF8 = Charset . forName ( " UTF -8 " ) ;
Path fichier = Paths . get ( " / home / gothmog / test . txt " ) ;
try {
OutputStream os = Files . newOutputStream ( fichier ) ;
p . store ( os , " Fichier de parametres " ) ;
os . flush () ;
} catch ( IOException ioe ) { System . err . println ( " Echec ecriture : " + ioe ) ;}
Le fichier test.txt est créé et son contenu est :
# Fichier de parametres
# Tue Jan 01 14:33:10 CET 2013
nom = Cloclo
age =23
prenom = Claudette
269
Réciproquement, pour récupérer les valeurs des paramètres :
Properties q = new Properties () ;
Charset UTF8 = Charset . forName ( " UTF -8 " ) ;
Path fichier = Paths . get ( " / home / gothmog / test . txt " ) ;
try {
InputStream is = Files . newInputStream ( fichier ) ;
q . load ( is ) ;
} catch ( IOException ioe ) { System . err . println ( " Echec lecture : " + ioe ) ;}
System . out . println ( " Valeur du parametre age : " + q . getProperty ( " age " ) ) ;
System . out . println ( " Valeur du parametre nom : " + q . getProperty ( " nom " ) ) ;
System . out . println ( " Valeur du parametre prenom : " + q . getProperty ( "
prenom " ) ) ;
Correction de l’exercice 101
Exercice immédiat.
Correction de l’exercice 102
// resolution du nom
InetAddress ip = null ;
try {
ip = InetAddress . getLocalHost () ;
} catch ( U nk now nHo st Exc ep tio n e2 ) {
e2 . printStackTrace () ;
}
String nom = ip . getHostName () ;
System . out . println ( " Nom de la machine : " + nom ) ;
// recuperation des adresses IP de la machine
InetAddress [] adresses = null ;
try {
adresses = InetAddress . getAllByName ( nom ) ;
} catch ( U nk now nHo st Exc ep tio n e1 ) {
e1 . printStackTrace () ;
}
// affichage des adresses IP
for ( InetAddress adresse : adresses )
System . out . println ( adresse ) ;
Correction de l’exercice 103
Classe ClientUDP :
import java . net .*;
import java . io .*;
public class ClientUDP {
public static void main ( String [] args ) throws Exception {
InetAddress address = InetAddress . getByName ( " localhost " ) ;
int port = 4321;
String ch = " Hello Bob , ici Greg , tout va bien ? " ; int chl = ch .
length () ;
byte [] message = new byte [1];
DatagramSocket ds = new DatagramSocket () ;
CHAPITRE 16. CORRIGÉS
270
for ( int j =0; j <1000; j ++) {
long start = System . currentTimeMillis () ;
for ( int i =0; i < chl ; i ++) {
message [0]=( byte ) ch . charAt ( i ) ;
DatagramPacket dp = new DatagramPacket ( message
,1 , address , port ) ;
ds . send ( dp ) ;
}
long end = System . currentTimeMillis () ;
System . out . println ( " UDP : Message envoye : temps d ’
emission = " +( end - start ) + " ms " ) ;
}
}
}
Classe ServeurUDP :
import java . net .*;
import java . io .*;
public class ServeurUDP {
public static void main ( String [] args ) throws Exception {
byte [] buffer = new byte [1024]; String ch ;
DatagramPacket dp = new DatagramPacket ( buffer , buffer . length ) ;
DatagramSocket ds = new DatagramSocket (4321) ;
System . out . println ( " Serveur UDP en attente " ) ;
while ( true ) {
ds . receive ( dp ) ;
ch = new String ( buffer , 0 , 0 , dp . getLength () ) ;
System . out . print ( ch ) ;
}
}
}
On observe que la transmission n’est pas fiable (perte de messages et ordre non-préservé).
Correction de l’exercice 104
Classe CLientTCP :
import java . net .*; import java . io .*;
public class ClientTCP {
public static void main ( String [] args ) throws Exception {
String message = " Hello Bob , ici Greg , tout va bien ? " ;
for ( int j =0; j <1000; j ++) {
long start = System . currentTimeMillis () ;
for ( int i =0; i < message . length () ; i ++)
{
Socket s = new Socket ( " localhost " ,5432) ;
OutputStream os = s . getOutputStream () ;
os . write ( message . charAt ( i ) ) ;
s . close () ;
}
long end = System . currentTimeMillis () ;
271
System . out . println ( " TCP : Message envoye : temps d ’ emission = " +(
end - start ) + " ms " ) ;
}
}
}
Classe ServeurTCP :
import java . net .*;
import java . io .*;
public class ServeurTCP {
public static void main ( String [] args ) throws Exception {
ServerSocket ecoute = new ServerSocket (5432 ,5) ;
Socket service = ( Socket ) null ;
System . out . println ( " Serveur TCP en attente " ) ;
while ( true ) {
service = ecoute . accept () ;
InputStream is = service . getInputStream () ;
System . out . print (( char ) is . read () ) ;
service . close () ;
}
}}
On observe que la transmission est fiable, mais beaucoup plus lente qu’avec le protocole UDP.
Correction de l’exercice 105&107
public class Personne implements Serializable { // pour rendre l ’ objet
serialisable
...
public void save2 ( String fichier ) {
Path p = Paths . get ( fichier ) ;
try {
OutputStream os = Files . newOutputStream ( p ) ;
ObjectOutputStream oos = new ObjectOutputStream ( os ) ;
oos . writeObject ( this ) ;
oos . flush () ;
oos . close () ;
}
catch ( IOException e ) { System . out . println ( e ) ;}
}
public static Personne load2 ( String fichier ) {
Path p = Paths . get ( fichier ) ;
Personne res = null ;
try {
InputStream is = Files . newInputStream ( p ) ;
ObjectInputStream ois = new ObjectInputStream ( is ) ;
res =( Personne ) ois . readObject () ;
ois . close () ;
}
catch ( IOException e ) { System . out . println ( e ) ;}
catch ( C l as s N o tF o u n d Ex c e p t io n e ) { System . out . println ( e ) ;}
return res ;
}
CHAPITRE 16. CORRIGÉS
272
Dans la classe Main :
Etudiant a = new Etudiant ( " Arsene Lupin " ) ;
System . out . println ( " serialisation : " ) ;
a . save2 ( " personne . ser " ) ;
Personne p = Personne . load2 ( " personne . ser " ) ;
System . out . println ( p ) ;
Notez que l’objet récupéré par Personne.load2("personne.ser") est de type Etudiant (celui de a) : la
méthode toSrring appelée dans le dernier affichage est donc celle de la classe Etudiant (polymorphisme).
Correction de l’exercice 106
Le code du corrigé est similaire à celui de l’exercice 105.
Correction de l’exercice 108
Le code du corrigé est similaire à celui de la méthode load2 de l’exercice 105.
Correction de l’exercice 109
public static String getName ( XMLEvent event ) {
String res = " " ;
if ( event . isStartElement () ) {
StartElement startElement = event . asStartElement () ;
// balise project ?
if ( startElement . getName () . getLocalPart () . equals ( " project " ) ) {
// parcours des attributs
Iterator < Attribute > attributes = startElement . getAttributes () ;
while ( attributes . hasNext () ) {
Attribute attribute = attributes . next () ;
if ( attribute . getName () . toString () . equals ( " name " ) ) {
res = attribute . getValue () ;
}
}
}
}
return res ;
}
Correction de l’exercice 110
public static String getName () {
String res = " " ;
try {
// creation d ’ un flux en lecture sur le document XML
XMLInputFactory inputFactory = XMLInputFactory . newInstance () ;
InputStream in = new FileInputStream ( " build . xml " ) ;
XMLEventReader eventReader = inputFactory . cr ea teX ML Eve ntR ea der ( in ) ;
// lecture du document XML
while ( eventReader . hasNext () ) {
// nouvel evenement
XMLEvent event = eventReader . nextEvent () ;
// balise ouvrante ?
if ( event . isStartElement () ) {
StartElement startElement = event . asStartElement () ;
// balise project ?
if ( startElement . getName () . getLocalPart () . equals ( " project " ) )
{
273
// parcours des attributs
Iterator < Attribute > attributes = startElement .
getAttributes () ;
while ( attributes . hasNext () ) {
Attribute attribute = attributes . next () ;
if ( attribute . getName () . toString () . equals ( "
name " ) ) {
res = attribute . getValue () ;
}
}
}
}
}
}
catch ( Fi le N ot F ou nd E xc ep t io n e ) { e . printStackTrace () ; }
catch ( XMLStreamException e ) { e . printStackTrace () ; }
return res ;
}
Correction de l’exercice 111
public void save3 ( String fichier ) {
// Create a XMLOutputFactory
XMLOutputFactory outputFactory = XMLOutputFactory . newInstance () ;
// Create XMLEventWriter
XMLEventWriter eventWriter ;
try {
eventWriter = outputFactory .
cr ea teX MLE ve ntW ri ter ( new FileOutputStream (
fichier ) ) ;
// Create a EventFactory
XMLEventFactory eventFactory = XMLEventFactory .
newInstance () ;
XMLEvent eol = eventFactory . createDTD ( " \ n " ) ; //
retour a la ligne
// Create and write Start Tag
StartDocument startDocument = eventFactory .
crea teStart Documen t () ;
eventWriter . add ( startDocument ) ;
eventWriter . add ( eol ) ;
// Balise < groupe >
StartElement se = eventFactory .
createStartElement ( " " ," " , " groupe " ) ;
eventWriter . add ( se ) ;
eventWriter . add ( eol ) ;
// Balise pour les personnes
for ( Personne p : membres ) {
// Balise < personne >
StartElement se2 = eventFactory .
createStartElement ( " " ," " , " personne "
);
eventWriter . add ( se2 ) ;
eventWriter . add ( eol ) ;
// Balises d ’ une personne
CHAPITRE 16. CORRIGÉS
274
createNode ( eventWriter , " nom " , p . getNom
() ) ;
// Balise </ personne >
eventWriter . add ( eventFactory .
createEndElement ( " " , " " , " personne " )
);
eventWriter . add ( eol ) ;
}
// Balise </ groupe >
eventWriter . add ( eventFactory . createEndElement ( "
" , " " , " groupe " ) ) ;
eventWriter . add ( eol ) ;
// fin du document
eventWriter . add ( eventFactory . createEndDocument
() ) ;
eventWriter . close () ;
} catch ( Fi le N ot F ou nd E xc ep t io n e ) {
e . printStackTrace () ;
} catch ( XMLStreamException e ) {
e . printStackTrace () ;
}
}
private void createNode ( XMLEventWriter eventWriter , String name ,
String value ) throws XMLStreamException {
XMLEventFactory eventFactory = XMLEventFactory . newInstance () ;
XMLEvent end = eventFactory . createDTD ( " \ n " ) ;
XMLEvent tab = eventFactory . createDTD ( " \ t " ) ;
// Balise ouvrante
StartElement sElement = eventFactory . createStartElement ( " " , " " ,
name ) ;
eventWriter . add ( tab ) ;
eventWriter . add ( sElement ) ;
// Contenu
Characters characters = eventFactory . createCharacters ( value ) ;
eventWriter . add ( characters ) ;
// Balise fermante
EndElement eElement = eventFactory . createEndElement ( " " , " " ,
name ) ;
eventWriter . add ( eElement ) ;
eventWriter . add ( end ) ;
}
Correction de l’exercice 113
public class Film implements Runnable {
private String titre = null ;
public Film ( String titre ) { this . titre = titre ;}
public void run () { // joue le film
for ( int i =1; i <=6; i ++) {
System . out . println ( " [ " + titre + " ] " + " Quart d \ ’ heure
275
numero " + i ) ;
}}
public static void main ( String [] args ) {
Film film = new Film ( " Les aventures de Maya l \ ’
abeille au pays du Mordor " ) ;
Thread monProcessus = new Thread ( film ) ;
monProcessus . start () ;
}
}
Correction de l’exercice 114
public class Film2 implements Runnable {
public Thread monProcessus = null ;
private String titre = null ;
public int nb = 6; public int pos = 0;
public Film2 ( String titre , int nb ) {
this . titre = titre ; this . nb = nb ;
monProcessus = new Thread ( this ) ;
}
public void go () { monProcessus . start () ;}
public void run () { // joue le film
try {
for ( pos =1; pos <= nb ; pos ++) {
Thread . sleep (1000) ;
System . out . println ( " [ " + titre + " ] Quart d \ ’ heure numero " + pos
);
}
}
catch ( In te rru pt edE xce pt ion e )
{ System . out . println ( " Projection du film interrompue ! " ) ; }
}}
Correction de l’exercice 115
Solution (1)
public class Film3 implements Runnable {
public Thread monProcessus1 = null ;
public Thread monProcessus2 = null ;
private String titre = null ;
private Object verrou = new Object () ;
public int nb = 6;
public int pos = 0;
public Film3 ( String titre , int nb ) {
this . titre = titre ; this . nb = nb ;
monProcessus1 = new Thread ( this ) ;
monProcessus2 = new Thread ( this ) ;
}
public void go () {
CHAPITRE 16. CORRIGÉS
276
monProcessus1 . start () ;
monProcessus2 . start () ;
}
public void run () { // joue le film
System . out . println ( " Debut du film " + titre ) ;
try { for ( int pos =1; pos <= nb ; pos ++) {
// si pos n ’ est pas une variable du
bloc , la boucle s ’ execute une fois (
mais les processus peuvent alterner )
synchronized ( verrou ) {
System . out . println ( " [ " + titre + " ] " + " Quart d \ ’
heure numero " + pos ) ;
System . out . println ( " Processus 1 en vie : " +
monProcessus1 . isAlive () ) ;
System . out . println ( " Processus 2 en vie : " +
monProcessus2 . isAlive () ) ;
}
Thread . sleep (500) ; // tempo
}}
catch ( In te rru pt edE xce pt ion e ) {
System . out . println ( " Projection du film
interrompue ! " ) ; }
}
public static void main ( String [] args ) {( new Film3 ( " Bob " ,6) ) . go
() ;}}
Solution (2)
public class Film3 implements Runnable {
public Thread monProcessus1 = null ;
public Thread monProcessus2 = null ;
private String titre = null ;
private Object verrou = new Object () ;
public int nb = 6;
public int pos = 0;
public Film3 ( String titre , int nb ) {
this . titre = titre ; this . nb = nb ;
monProcessus1 = new Thread ( this ) ;
monProcessus2 = new Thread ( this ) ;
}
public void go () {
monProcessus1 . start () ;
monProcessus2 . start () ;
}
public void run () {
System . out . println ( " Debut du film " + titre ) ;
synchronized ( verrou ) // avec ce verrou , les
processus executent la boucle l ’ un apres l ’
autre
try { for ( int pos =1; pos <= nb ; pos ++) {
277
// si pos n ’ est pas une variable du bloc , la
boucle s ’ execute une fois ( mais les
processus peuvent alterner )
System . out . println ( " [ " + titre + " ] " + " Quart d \ ’ heure
numero " + pos ) ;
System . out . println ( " Processus 1 en vie : " +
monProcessus1 . isAlive () ) ;
System . out . println ( " Processus 2 en vie : " +
monProcessus2 . isAlive () ) ;
Thread . sleep (500) ; // temporisation
}}
catch ( In ter ru pte dE xce pt ion e ) { System . out . println ( "
Projection du film interrompue ! " ) ; }
}
public static void main ( String [] args ) {
( new Film3 ( " Bob " ,6) ) . go () ;
}
}
Correction de l’exercice 117
import java . sql .*; import java . io .*; import java . util .*;
public class AccesBDD {
static java . sql . Connection c = null ;
public static void etablirConnexion () {
String pilote = " com . mysql . jdbc . Driver " ;
try { Class . forName ( pilote ) . newInstance () ;}
catch ( Exception e ) { System . out . println ( " echec pilote :
" + e ) ;};
Properties prop = new Properties () ;
try {
FileInputStream fis = new FileInputStream ( "
params . txt " ) ;
prop . load ( fis ) ;
} catch ( Exception e ) { System . out . println ( " Fichier non
trouve " ) ;}
String url = prop . getProperty ( " jdbc . url " ) ;
String user = prop . getProperty ( " jdbc . user " ) ;
String password = prop . getProperty ( " jdbc . password " ) ;
try { c = DriverManager . getConnection ( url , user , password )
;}
catch ( SQLException e ) { System . out . println ( " echec
connection a la bdd : " + e ) ;}
System . out . println ( " connexion reussie " ) ;
}
public static void main ( String [] args ) {
AccesBDD . etablirConnexion () ;
}
CHAPITRE 16. CORRIGÉS
278
}
Correction de l’exercice 118
public class AccesBDD {
static java . sql . Connection c = null ;
...
public static void update () {
try {
java . sql . Statement s = c . createStatement () ;
String commande = " UPDATE Etudiant set password = ’1 ’ " ;
int nb = s . executeUpdate ( commande ) ;
System . out . println ( nb + " changements " ) ;
} catch ( SQLException e ) { System . out . println ( " Erreur update : " + e ) ;}
}
}
La mise à jour ne marche pas car les droits en écriture ne sont pas ouverts pour le compte utilisé ...
Correction de l’exercice 119
public class AccesBDD {
static java . sql . Connection c = null ;
...
public static void select () {
try {
java . sql . Statement s = c . createStatement () ;
java . sql . ResultSet etudiants = s . executeQuery ( " SELECT * FROM
Etudiant " ) ;
while ( etudiants . next () ) { // parcours de chaque ligne
System . out . println ( etudiants . getString ( " Prenom " ) + " " + etudiants .
getString ( " Nom " ) ) ;
}
}
catch ( SQLException e ) {
System . out . println ( " Erreur select : " + e ) ;
}
}}
Correction de l’exercice 122
public class AccesBDD {
static java . sql . Connection c = null ;
...
public static void ensembleMAJ () {
try {
java . sql . Statement s = c . createStatement (
java . sql . ResultSet . TYPE_SCROLL_INSENSITIVE ,
java . sql . ResultSet . CONCUR_UPDATABLE ) ;
java . sql . ResultSet rs = s . executeQuery ( " SELECT * from Etudiant " ) ;
while ( rs . next () ) {
String adresse = rs . getString ( " firstname " ) ;
rs . updateString ( " firstname " , adresse + " , le terrible dragon " ) ;
rs . updateRow () ;
}
} catch ( SQLException e ) { System . out . println ( " Erreur ensembleMAJ : " + e )
;}
279
}}
Correction de l’exercice 125
public class ExoKeyAdpater {
/* *
* @param args the command line arguments
*/
public static void main ( String [] args ) {
...
jf . addKeyListener ( new MonEcouteurClavier ( d ) ) ;
jf . setVisible ( true ) ;
}
}
public class MonEcouteurClavier extends KeyAdapter {
Dessin d ;
MonEcouteurClavier ( Dessin d ) {
this . d = d ;
}
@Override
public void keyPressed ( KeyEvent e ) {
char touche = e . getKeyChar () ;
switch ( touche ) {
case ’z ’:
d . setY ( d . getY () - 1) ;
break ;
case ’q ’:
d . setX ( d . getX () - 1) ;
break ;
case ’s ’:
d . setY ( d . getY () + 1) ;
break ;
case ’d ’:
d . setX ( d . getX () + 1) ;
break ;
}
d . repaint () ; // on provoque le rafraichissement du dessin
}
}
Correction de l’exercice 126
// ChangeCouleur . java
import java . awt . event .*;
import java . awt .*;
public class ChangeCouleur implements ActionListener {
private Color couleur ;
CHAPITRE 16. CORRIGÉS
280
private Dessin dessin ;
public ChangeCouleur ( Color couleur , Dessin dessin ) {
this . couleur = couleur ;
this . dessin = dessin ;
}
public void actionPerformed ( ActionEvent e ) {
dessin . setColor ( couleur ) ;
dessin . repaint () ;
}
}
// Dessin . java
import java . awt .*;
public class Dessin extends Canvas {
private Color couleur ;
public void setColor ( Color couleur ) {
this . couleur = couleur ;
}
public void paint ( Graphics g ) {
super . paint ( g ) ;
g . setColor ( couleur ) ;
g . drawOval (10 ,10 ,150 ,100) ;
}
}
// Fenetre . java
import java . awt .*;
import javax . swing .*;
public class Fenetre extends JFrame {
private JButton b1 , b2 ;
private Dessin dessin ;
public Fenetre ( String titre ) {
super ( titre ) ;
setBounds (0 ,0 ,400 ,200) ;
b1 = new JButton ( " B1 " ) ;
b2 = new JButton ( " B2 " ) ;
dessin = new Dessin () ;
b1 . addActionListener ( new ChangeCouleur ( Color . red , dessin ) ) ;
b2 . addActionListener ( new ChangeCouleur ( dessin . getForeground () ,
dessin ) ) ;
add ( b1 , " North " ) ;
add ( dessin , " Center " ) ;
add ( b2 , " South " ) ;
setVisible ( true ) ;
}
}
281
Correction de l’exercice ??
// Grille . java
import java . awt .*;
import javax . swing .*;
public class Grille extends Canvas {
private int nbLignes ;
private int nbColonnes ;
private int abs ;
private int ord ;
final static int largeurColonne = 40;
final static int hauteurLigne
= 40;
public Grille ( int nbLignes , int nbColonnes ,
int abs , int ord ) {
this . nbLignes = nbLignes ;
this . nbColonnes = nbColonnes ;
this . abs = abs ;
this . ord = ord ;
}
// Override paint
public void paint ( Graphics g ) {
super . paint ( g ) ;
g . setColor ( Color . black ) ;
// dessin de la grille
for ( int i =0; i < nbLignes ; i ++) {
for ( int j =0; j < nbColonnes ; j ++) {
g . drawRect ( abs + j * largeurColonne , ord + i *
hauteurLigne ,
largeurColonne , hauteurLigne ) ;
}
}
}
}
// Taquin . java
import java . awt .*;
import javax . swing .*;
public class Taquin extends JFrame {
private Grille grille ;
private int nbLignesTaquin ;
private int nbColonnesTaquin ;
final static int largeurFenetre = 400;
final static int hauteurFenetre = 400;
public Taquin ( String titre ) {
super ( titre ) ;
nbLignesTaquin = 3;
nbColonnesTaquin = 3;
setBounds (0 ,0 , largeurFenetre , hauteurFenetre ) ;
int x_0 = ( largeurFenetre - Grille . getLargeurColonne () *
CHAPITRE 16. CORRIGÉS
282
nbColonnesTaquin ) /2;
int y_0 = ( hauteurFenetre - Grille . getHauteurLigne () *
nbLignesTaquin ) /2;
grille = new Grille ( nbLignesTaquin , nbColonnesTaquin , x_0 , y_0 ) ;
add ( grille , " Center " ) ;
setVisible ( true ) ;
}
public static void main ( String args []) {
new Taquin ( " Jeu de Taquin " ) ;
}
}
Correction de l’exercice ??
// Jeton . java
public class Jeton {
private int abs ;
private int ord ;
private int numero ;
private Color couleur ;
public Jeton ( int abs , int ord , int numero , Color couleur ) {
this . abs = abs ;
this . ord = ord ;
this . numero = numero ;
this . couleur = couleur ;
}
public int getAbs () {
return abs ;
}
public int getOrd () {
return ord ;
}
public int getNumero () {
return numero ;
}
public Color getCouleur () {
return couleur ;
}
}
// Grille . java
import java . awt .*;
import javax . swing .*;
public class Grille extends Canvas {
private int nbLignes ;
private int nbColonnes ;
private int abs ;
private int ord ;
final static int largeurColonne = 40;
final static int hauteurLigne
= 40;
private Jeton jetons [][];
283
private int nbJetons ;
public Grille ( int nbLignes , int nbColonnes ,
int abs , int ord )
{
this . nbLignes = nbLignes ;
this . nbColonnes = nbColonnes ;
this . abs = abs ;
this . ord = ord ;
nbJetons = nbLignes * nbColonnes - 1;
jetons = new Jeton [ nbLignes ][ nbColonnes ];
int pas = 256/( nbJetons +2) ; // pas top ...
int num =0;
for ( int i =0; i < nbLignes ; i ++)
for ( int j =0; j < nbColonnes ; j ++) {
jetons [ i ][ j ]= new Jeton ( abs + j * largeurColonne ,
ord + i * hauteurLigne ,
num ++ ,
new Color (100 , num * pas , num * pas ) ) ;
}
jetons [ nbLignes -1][ nbColonnes -1]= null ;
}
// Override paint
public void paint ( Graphics g ) {
super . paint ( g ) ;
// dessin des jetons
for ( int i =0; i < nbLignes ; i ++)
for ( int j =0; j < nbColonnes ; j ++) {
if ( jetons [ i ][ j ]!= null ) {
g . setColor ( jetons [ i ][ j ]. getCouleur () ) ;
g . fillRect ( jetons [ i ][ j ]. getAbs () , jetons [ i ][
j ]. getOrd () ,
largeurColonne , hauteurLigne ) ;
g . setColor ( Color . BLACK ) ;
g . drawString ( " " + jetons [ i ][ j ]. getNumero () ,
( int ) ( jetons [ i ][ j ]. getAbs () +
largeurColonne /2) ,
( int ) ( jetons [ i ][ j ]. getOrd () +
hauteurLigne /2) ) ;
}
}
// dessin de la grille
g . setColor ( Color . black ) ;
for ( int i =0; i < nbLignes ; i ++)
for ( int j =0; j < nbColonnes ; j ++)
g . drawRect ( abs + j * largeurColonne , ord + i * hauteurLigne ,
largeurColonne , hauteurLigne ) ;
}
}
// Taquin . java
CHAPITRE 16. CORRIGÉS
284
import java . awt .*;
import javax . swing .*;
public class Taquin extends JFrame {
private Grille grille ;
private int nbLignesTaquin ;
private int nbColonnesTaquin ;
final static int largeurFenetre = 400;
final static int hauteurFenetre = 400;
public Taquin ( String titre ) {
super ( titre ) ;
nbLignesTaquin = 4;
nbColonnesTaquin = 4;
setBounds (0 ,0 , largeurFenetre , hauteurFenetre ) ;
int x_0 = ( largeurFenetre - Grille . getLargeurColonne () *
nbColonnesTaquin ) /2;
int y_0 = ( hauteurFenetre - Grille . getHauteurLigne () *
nbLignesTaquin ) /2;
grille = new Grille ( nbLignesTaquin , nbColonnesTaquin , x_0 , y_0 ) ;
add ( grille , " Center " ) ;
setVisible ( true ) ;
}
public static void main ( String args []) {
new Taquin ( " Jeu de Taquin " ) ;
}
}
Correction de l’exercice ??
// Click . java
import javax . swing . event .*;
import java . awt . event .*;
import javax . swing .*;
public class Click extends MouseInputAdapter {
private Grille grille ;
public Click ( Grille g ) {
grille = g ;
}
public void mouseClicked ( MouseEvent e ) {
int absClick = e . getX () ;
int ordClick = e . getY () ;
grille . deplacerJeton ( absClick , ordClick ) ;
grille . validate () ;
grille . repaint () ;
}
}
// Jeton . java
import java . awt .*;
public class Jeton {
private int abs ;
285
private int ord ;
private int numero ;
private Color couleur ;
public Jeton ( int abs , int ord , int numero , Color couleur ) {
this . abs = abs ;
this . ord = ord ;
this . numero = numero ;
this . couleur = couleur ;
}
public Jeton ( Jeton j ) {
abs = j . abs ;
ord = j . ord ;
numero = j . numero ;
couleur = j . couleur ;
}
// Ajouter mutateurs si besoin
}
// Grille . java
import java . awt .*;
import javax . swing .*;
public class Grille extends Canvas {
private int nbLignes ;
private int nbColonnes ;
private int abs ;
private int ord ;
final static int largeurColonne = 40;
final static int hauteurLigne
= 40;
private Jeton jetons [][];
private int nbJetons ;
private int i_null ;
private int j_null ;
public Grille ( int nbLignes , int nbColonnes ,
int abs , int ord ) {
this . nbLignes = nbLignes ;
this . nbColonnes = nbColonnes ;
this . abs = abs ;
this . ord = ord ;
nbJetons = nbLignes * nbColonnes - 1;
jetons = new Jeton [ nbLignes ][ nbColonnes ];
int pas = 256/( nbJetons +2) ; // pas top ...
int num =0;
for ( int i =0; i < nbLignes ; i ++)
for ( int j =0; j < nbColonnes ; j ++) {
jetons [ i ][ j ]= new Jeton ( abs + j * largeurColonne ,
ord + i * hauteurLigne ,
num ++ ,
new Color (100 , num * pas , num *
pas ) ) ;
}
CHAPITRE 16. CORRIGÉS
286
jetons [ nbLignes -1][ nbColonnes -1]= null ;
j_null = nbLignes -1;
i_null = nbColonnes -1;
}
// Ajouter mutateurs si besoin
// Override paint
public void paint ( Graphics g ) {
// super . paint ( g ) ;
// dessin des jetons
for ( int i =0; i < nbLignes ; i ++)
for ( int j =0; j < nbColonnes ; j ++) {
if ( jetons [ i ][ j ]!= null ) {
g . setColor ( jetons [ i ][ j ]. getCouleur () ) ;
g . fillRect ( jetons [ i ][ j ]. getAbs () , jetons [ i ][
j ]. getOrd () ,
largeurColonne , hauteurLigne ) ;
g . setColor ( Color . BLACK ) ;
g . drawString ( " " + jetons [ i ][ j ]. getNumero () ,
( int ) ( jetons [ i ][ j ]. getAbs () +
largeurColonne /2) ,
( int ) ( jetons [ i ][ j ]. getOrd () +
hauteurLigne /2) ) ;
}
}
// dessin de la grille
g . setColor ( Color . black ) ;
for ( int i =0; i < nbLignes ; i ++)
for ( int j =0; j < nbColonnes ; j ++)
g . drawRect ( abs + j * largeurColonne , ord + i *
hauteurLigne ,
largeurColonne , hauteurLigne ) ;
}
public void deplacerJeton ( int x , int y ) {
// trouver le jeton
int i_jeton =x - abs ;
int j_jeton =y - ord ;
if ( i_jeton >= 0 && i_jeton < nbColonnes * largeurColonne &&
j_jeton >= 0 && j_jeton < nbLignes * hauteurLigne ) {
i_jeton /= largeurColonne ;
j_jeton /= hauteurLigne ;
}
else
return ;
// verifier l ’ adjacence et si ok permutter
if (( i_null == i_jeton && j_null == j_jeton +1) ||
( i_null == i_jeton && j_null == j_jeton -1) ||
( i_null == i_jeton +1 && j_null == j_jeton ) ||
( i_null == i_jeton -1 && j_null == j_jeton ) ) {
Jeton tmp = jetons [ j_jeton ][ i_jeton ];
jetons [ j_jeton ][ i_jeton ]= null ;
jetons [ j_null ][ i_null ]= tmp ;
jetons [ j_null ][ i_null ]. setAbs ( abs + i_null * largeurColonne
);
287
jetons [ j_null ][ i_null ]. setOrd ( ord + j_null * hauteurLigne ) ;
i_null = i_jeton ;
j_null = j_jeton ;
}
}
}
// Taquin . java
import java . awt .*;
import javax . swing .*;
public class Taquin extends JFrame {
private Grille grille ;
private int nbLignesTaquin ;
private int nbColonnesTaquin ;
final static int largeurFenetre = 400;
final static int hauteurFenetre = 400;
private Click click ;
public Taquin ( String titre ) {
super ( titre ) ;
nbLignesTaquin = 5;
nbColonnesTaquin = 5;
setBounds (0 ,0 , largeurFenetre , hauteurFenetre ) ;
int x_0 = ( largeurFenetre - Grille . getLargeurColonne () *
nbColonnesTaquin ) /2;
int y_0 = ( hauteurFenetre - Grille . getHauteurLigne () *
nbLignesTaquin ) /2;
grille = new Grille ( nbLignesTaquin , nbColonnesTaquin , x_0 , y_0 ) ;
click = new Click ( grille ) ;
grille . addMouseListener ( click ) ;
add ( grille , " Center " ) ;
setVisible ( true ) ;
}
public static void main ( String args []) {
new Taquin ( " Jeu de Taquin " ) ;
}
}
Correction de l’exercice ??
// Taquin . java
import java . awt .*;
import javax . swing .*;
import java . awt . event .*;
public class Taquin extends JFrame {
private Grille grille ;
private int nbLignesTaquin ;
private int nbColonnesTaquin ;
final static int largeurFenetre = 600;
final static int hauteurFenetre = 400;
private Click click ;
private JPanel controle ;
288
CHAPITRE 16. CORRIGÉS
private JButton bLPlus , bLMoins , bCPlus , bCMoins , bReInit ;
class bRClick implements ActionListener {
public void actionPerformed ( ActionEvent e ) {
reInit () ;
}
}
class bLClick implements ActionListener {
private int valIncr ;
public bLClick ( int valIncr ) {
this . valIncr = valIncr ;
}
public void actionPerformed ( ActionEvent e ) {
int tmp = nbLignesTaquin + valIncr ;
if ( tmp > 1 && tmp < 9)
nbLignesTaquin = tmp ;
reInit () ;
}
}
class bCClick implements ActionListener {
private int valIncr ;
public bCClick ( int valIncr ) {
this . valIncr = valIncr ;
}
public void actionPerformed ( ActionEvent e ) {
int tmp = nbColonnesTaquin + valIncr ;
if ( tmp > 1 && tmp < 14)
nbColonnesTaquin = tmp ;
reInit () ;
}
}
public Taquin ( String titre ) {
super ( titre ) ;
nbLignesTaquin = 5;
nbColonnesTaquin = 5;
setBounds (0 ,0 , largeurFenetre , hauteurFenetre ) ;
int x_0 = ( largeurFenetre - Grille . getLargeurColonne () *
nbColonnesTaquin ) /2;
int y_0 = ( hauteurFenetre - Grille . getHauteurLigne () *
nbLignesTaquin ) /2 - 40;
grille = new Grille ( nbLignesTaquin , nbColonnesTaquin , x_0 , y_0 ) ;
click = new Click ( grille ) ;
controle = new JPanel () ;
bLPlus = new JButton ( " nb . lig . + " ) ;
bLMoins = new JButton ( " nb . lig . -" ) ;
bCPlus = new JButton ( " nb . col . + " ) ;
bCMoins = new JButton ( " nb . col . -" ) ;
bReInit = new JButton ( " reinitialiser " ) ;
bReInit . addActionListener ( new bRClick () ) ;
bLPlus . addActionListener ( new bLClick (+1) ) ;
bLMoins . addActionListener ( new bLClick ( -1) ) ;
bCPlus . addActionListener ( new bCClick (1) ) ;
bCMoins . addActionListener ( new bCClick ( -1) ) ;
289
controle . add ( bLMoins ) ;
controle . add ( bLPlus ) ;
controle . add ( bCMoins ) ;
controle . add ( bCPlus ) ;
controle . add ( bReInit ) ;
grille . addMouseListener ( click ) ;
add ( controle , " North " ) ;
add ( grille , " Center " ) ;
setVisible ( true ) ;
}
public void reInit () {
grille . removeM ouseLis tener ( click ) ;
remove ( grille ) ;
repaint () ;
int x_0 = ( largeurFenetre - Grille . getLargeurColonne () *
nbColonnesTaquin ) /2;
int y_0 = ( hauteurFenetre - Grille . getHauteurLigne () *
nbLignesTaquin ) /2 - 40;
grille = new Grille ( nbLignesTaquin , nbColonnesTaquin , x_0 , y_0 ) ;
click = new Click ( grille ) ;
grille . addMouseListener ( click ) ;
add ( grille , " Center " ) ;
setVisible ( true ) ;
}
public static void main ( String args []) {
new Taquin ( " Jeu de Taquin " ) ;
}
}
Correction de l’exercice 133
La couleur de fond va alterner à chaque fois que le composant se redessine (par exemple lorsqu’on déplace la
fenêtre), d’où un effet de "scintillement".
Correction de l’exercice 141
public static void main ( String [] args ) {
EntityManager em = MonEntityManager . getInstance () .
getEntityManager () ;
String req = " SELECT p FROM Personne p " ;
Query query = em . createQuery ( req ) ;
List < Personne > personnes = query . getResultList () ;
for ( Personne p : personnes ) {
System . out . println ( " Id : " + p . getId () + " - " + p . getPrenom
() + " " + p . getNom () + " - " + p . getDateNaissance () ) ;
}
}
Correction de l’exercice 142
EntityManager em = MonEntityManager . getInstance () . getEntityManager () ;
// selection
String req = " SELECT p FROM Personne p " ;
Query query = em . createQuery ( req ) ;
List < Personne > personnes = query . getResultList () ;
// mise a jour
CHAPITRE 16. CORRIGÉS
290
em . getTransaction () . begin () ; // debut de la prise en compte des
modifications
for ( Personne p : personnes ) {
p . setPrenom ( p . getPrenom () + " Junior " ) ;
}
em . getTransaction () . commit () ;
Correction de l’exercice 143
String req = " SELECT p FROM Personne p WHERE p . nom LIKE ’ Dragonair ’" ;
Query query = em . createQuery ( req ) ;
List < Personne > selection = query . getResultList () ;
if ( selection . size () !=0) {
Personne p = selection . get (0) ;
em . getTransaction () . begin () ;
em . remove ( p ) ;
em . getTransaction () . commit () ;
}
Correction de l’exercice 144
// creation d ’ un nouvel objet qui sera insere ensuite dans la base
Personne p = new Personne () ;
p . setNom ( " Scoubidou " ) ;
p . setPrenom ( " Bidou " ) ;
p . setDateNaissance ( new Date () ) ;
// on insere p dans la base ( persistance )
em . getTransaction () . begin () ;
em . persist ( p ) ;
em . getTransaction () . commit () ;
Correction de l’exercice 147
@WebServlet ( " / ServletHello " )
public class ServletHello extends HttpServlet {
private static final long serialVersionUID = 1 L ;
...
/* *
* @see HttpServlet # doGet ( HttpServletRequest request ,
Http ServletR esponse response )
*/
protected void doGet ( HttpServletRequest request ,
Http Servlet Response response ) throws ServletException ,
IOException {
System . out . println ( " [ ServletHello ] methode get appelee "
);
PrintWriter out = response . getWriter () ;
out . println ( " Hello !!! " ) ;
}
...
}
291
Correction de l’exercice 148
<! DOCTYPE html >
< html >
< head >
< meta charset = " UTF -8 " >
< title > Bienvenue </ title >
</ head >
< body >
Pour initier le processus de salutations , veuillez cliquer <a href = " ./
ServletHello " > ici </ a >.
</ body >
</ html >
Correction de l’exercice 149
@WebServlet ( " / S e r v l e t T r a i t e m e n t F o r m u l a i r e " )
public class S e r v l e t T r a i t e m e n t F o r m u l a i r e extends HttpServlet {
...
/* *
* @see HttpServlet # doPost ( HttpServletRequest request ,
Http ServletR esponse response )
*/
protected void doPost ( HttpServletRequest request ,
Http Servlet Response response ) throws ServletException ,
IOException {
String pseudo = request . getParameter ( " pseudo " ) ;
System . out . println ( " [ S e r v l e t T r a i t e m e n t F o r m u l a i r e ]
methode post appelee " ) ;
PrintWriter out = response . getWriter () ;
out . println ( " Votre pseudo est " + pseudo ) ;
}
}
Correction de l’exercice 152
import java . net .*; import java . io .*; import java . util .*;
class SoapRawClient {
public static void main ( String [] args ) throws Exception {
Socket s = new Socket ( " 64.124.140.30 " ,80) ;
String requeteSoap = " <? xml version =\"1.0\" encoding =\" UTF
-8\"? >\ r \n < SOAP - ENV : Envelope xmlns : SOAP - ENV =\" http :// schemas
. xmlsoap . org / soap / envelope /\" xmlns : ns1 =\" urn : xmethods CurrencyExchange \" xmlns : xsd =\" http :// www . w3 . org /2001/
XMLSchema \" xmlns : xsi =\" http :// www . w3 . org /2001/ XMLSchema instance \" xmlns : SOAP - ENC =\" http :// schemas . xmlsoap . org / soap /
encoding /\" SOAP - ENV : encodingStyle =\" http :// schemas . xmlsoap .
org / soap / encoding /\" > < SOAP - ENV : Body > < ns1 : getRate > < country1 >
EURO </ country1 > < country2 > US </ country2 > </ ns1 : getRate > </ SOAP ENV : Body > </ SOAP - ENV : Envelope > " ;
CHAPITRE 16. CORRIGÉS
292
String enteteHTTP = " POST / soap HTTP /1.1\ r \ nHost : services .
xmethod . net \ r \ nConnection : Keep - Alive \ r \ nUser - Agent : java
raw \ r \ nContent - Type : text / xml ; charset utf -8\ r \ nSOAPAction :
\"\"\ r \ nContent - Length : " + requeteSoap . length () + " \ r \ n " ;
System . out . println ( " Envoi de la requete : " ) ;
PrintWriter pw = new PrintWriter ( s . getOutputStream () ) ;
pw . println ( enteteHTTP + " \ r \ n " + requeteSoap ) ;
pw . flush () ;
LineNumberReader lnr = new LineNumberReader ( new
InputStreamReader ( s . getInputStream () ) ) ;
String ligne = " " ;
double val = 0;
while (( ligne = lnr . readLine () ) != null ) {
StringTokenizer st = new StringTokenizer ( ligne , " <>" ) ;
while ( st . hasMoreTokens () ) {
String mot = st . nextToken () ;
if ( mot . startsWith ( " 1. " ) )
val = new Double ( mot ) . doubleValue () ;
}
}
System . out . println ( " 1 euro vaut " + val + " dollars us " ) ;
}
}
Correction de l’exercice 158
Dragon.java
package serveur ;
import javax . jws . WebService ;
import javax . xml . ws . Endpoint ;
@WebService
public class Gardien {
// privee donc ne fait pas partie du service
private boolean autorisation ( String login , String password ) {
System . out . println ( " serveur du service web Gardien :
demande autorisation " ) ;
return ( login . equals ( " Tetouillou " ) && password . equals ( " Le
terrible drago " ) ) ;
}
public String getTresor ( String login , String password ) {
if ( autorisation ( login , password ) ) return " le tresor des
Dragons Maudits " ;
return " une fleche empoisonnee " ;
}
public static void main ( String [] args ) {
293
Endpoint . publish ( " http :// localhost :4040/ Gardien " , new Gardien () )
;
System . out . println ( " Service Gardien pret " ) ;
while ( true ) ;
}
}
Client.java
import serveur .*;
package client ;
public class Client {
public static void main ( String [] args ) {
Gardien mi = new GardienService () . getGardienPort () ;
String reponse = mi . getTresor ( " Tetouillou " ," Le terrible
dragon " ) ;
System . out . println ( " Resultat : " + reponse ) ;
}
}
Correction de l’exercice 159
Réponse (3) : compilé (en bytecode) et interprété (par la JVM)
Correction de l’exercice 160
—
—
—
—
public : accès sans restriction ;
par défaut : accès sans restriction au sein d’un même package ;
protected : accès autorisé aux classes filles ;
private : accès autorisé aux méthodes de l’objet. L’accés est interdit entre 2 instances de la même classe !
Correction de l’exercice 161
CHAPITRE 16. CORRIGÉS
294
Questions
1. La commande javac permet :
Réponses
de compiler le code source en bytecode
de compiler le code source en langage machine
d’exécuter un programme java
2. Pour pouvoir exécuter un
programme Java, il est nécessaire
de disposer du code source
d’avoir une machine virtuelle Java
d’avoir le bon plugin Eclipse
3. Si Daltonien est une classe du
paquet superHeros,
Daltonien.java doit être dans le répertoire superHeros
Daltonien.class doit être dans le répertoire superHeros
ni l’un, ni l’autre
4. Dans le code suivant :
whiteIsRed est partagée par toutes les instances
static boolean whiteIsRed = true ;
jeVoisLaVieEnRose est partagée par toutes les instances
boolean jeVoisLaVieEnRose = true ;
ni l’un, ni l’autre
5. Dans le code suivant :
Humain h = new Daltonien() ;
h.help() ;
h est de type Daltonien
La méthode help de Daltonien est exécutée (si elle existe)
ni l’un, ni l’autre
6. Une classe abstraite
possède toutes ses méthodes abstraites
implémente une interface
ne peut pas être instanciée
7. Une interface
a au moins une méthode abstraite
a toutes ses méthodes abstraites
n’a pas de méthode abstraite
peut hériter de plusieurs classes
8. Une classe
peut implémenter plusieurs interfaces
ni l’un, ni l’autre
Correction de l’exercice 162
1. La surcharge de méthode consiste à définir des méthodes de même nom, mais d’arguments différents ;
2. La composition d’une classe A avec une classe B est la déclaration d’un objet de type B comme variable
de A ;
3. Une méthode d’une classe fille masque une méthode de sa classe mère si elle possède la même signature ;
4. Le polymorphisme est l’exécution de la méthode de même signature la plus spécialisée (i.e. la plus basse
dans la hiérarchie des types de l’objet) ;
5. JUnit est une librairie dédiée au test d’une application Java, qui permet de
— créer un ensemble de classes pour tester les classes propres au projet ;
— séparer le code dédié aux tests du code de l’application ;
6. exemple de code capturant une exception générée par une erreur d’entrée-sortie
try {
Properties props = new Properties () ;
FileInputStream fis = new FileInputStream ( " params . txt " ) ;
295
props . load ( fis ) ;
}
catch ( IOException e ) { System . out . println ( " echec lecture du fichier
: " + e ) ;}
Correction de l’exercice 165
Réponse : 2 (une référencée par x et y, l’autre par z)
Correction de l’exercice 166
Réponse : CiaoBonjour 2003
Correction de l’exercice 167
Réponse : “2 et 1”
Après C x = new C();, nous avons x.i = 1 et x. j = 1 ; Après C y = new C();, nous avons x.i = 2, x. j = 1,
y.i = 2 et y. j = 2 ; Après C z = x;, nous avons donc z.i = 2 et z. j = 1.
Correction de l’exercice 168
Réponse : “1 1 1”
Correction de l’exercice 169
c
Irène
Charon, Télécom ParisTech - Paris 2011
http://perso.telecom-paristech.fr/~charon/coursJava/exercices/corriges/
reponseConstructeur.txt
bonjour de A
constructeur B ( boolean )
B : ( true , 0)
bonjour de A
constructeur B ()
constructeur B ( int )
constructeur B ( boolean , int )
B : ( false , 5)
Correction de l’exercice 170
Marseille est une ville de france , elle comporte : 123456 habitant ( s )
= > elle est donc de categorie : D
Lille est une ville de france , elle comporte : 78456 habitant ( s ) = >
elle est donc de categorie : C
Caen est une ville de france , elle comporte : 654987 habitant ( s ) = >
elle est donc de categorie : E
Lyon est une ville de france , elle comporte : 75832165 habitant ( s ) = >
elle est donc de categorie : H
== > > la tour Eiffel en est un monument
Paris est une ville de france , elle comporte : 1594 habitant ( s ) = > elle
est donc de categorie : B
== > > la tour Eiffel en est un monument
Nantes est une ville de france , elle comporte : 213 habitant ( s ) = > elle
est donc de categorie : A
== > > la tour Eiffel en est un monument
CHAPITRE 16. CORRIGÉS
296
Correction de l’exercice 171
class A {
protected int x ;
A () {}
A ( int _x ) {
x = _x ;
}
int triple () {
return (3* x ) ;
}
}
class B extends A {
private int y ;
B ( int _x , int _y ) {
x = _x ; y = _y ;
}
double divise () {
int triple = 3* x ;
if ( triple ==0) return 0;
return (( double ) y / ( double ) triple ) ; }
int triple () { return (3* y ) ;}
}
class C {
public static void main ( String [] args ) {
int x = 2; int y = 7;
System . out . println ( " Execution avec
A a = new A ( x ) ;
B b = new B (x , y ) ;
System . out . println ( " Methode triple
;
System . out . println ( " Methode triple
;
System . out . println ( " Methode divise
;
b = new B (0 , y ) ;
System . out . println ( " Methode divise
divise () ) ;
}
}
Correction de l’exercice 172
abstract class Animal {
/* *
* La couleur de l ’ animal
*/
protected String couleur ;
x = " + x + " et y = " + y ) ;
de A : " + a . triple () )
de B : " + b . triple () )
de B : " + b . divise () )
de B avec x =0 : " + b .
297
/* *
* Le poids
*/
protected int poids ;
/* *
* La methode manger
*/
protected void manger () {
System . out . println ( " Je mange de la viande " ) ;
}
/* *
* La methode boire
*/
protected void boire () {
System . out . println ( " Je bois de l ’ eau ! " ) ;
}
/* *
* La methode
*/
abstract void
/* *
* La methode
*/
abstract void
de deplacement
deplacement () ;
de cri
crier () ;
public String toString () {
String str = " Je suis un objet de la " + this . getClass
() + " , je suis " + this . couleur + " , je pese " +
this . poids ;
return str ;
}
}
public abstract class Felin extends Animal {
@Override
void deplacement () {
System . out . println ( " Je me deplace seul ! " ) ;
}
}
public abstract class Canin extends Animal {
@Override
void deplacement () {
System . out . println ( " Je me deplace en meute ! " ) ;
}
CHAPITRE 16. CORRIGÉS
298
}
public class Loup extends Canin {
public Loup () {
}
public Loup ( String couleur , int poids ) {
this . couleur = couleur ;
this . poids = poids ;
}
void crier () {
System . out . println ( " J ’ hurle a la lune en faisant
ouhouh ! ! " ) ;
}
}
Correction de l’exercice 173
import java . util .*;
class MaCollection extends AbstractCollection {
public int max = 10; public int taille =0;
Object [] tampon = new Object [ max ];
public Iterator iterator () { return new MonIterateur ( this ) ;}
public int size () { return taille ;}
public boolean add ( Object o ) {
if ( size () < max ) { tampon [ taille ] = o ; taille ++; return
true ;};
return false ;
}
}
class MonIterateur implements Iterator {
private int pos =0;
MaCollection mc = null ;
public MonIterateur ( MaCollection mc ) {
this . mc = mc ;
}
e
public void remove () {
for ( int j = pos ; j <( mc . taille -1) ; j ++) mc . tampon [ j ]= mc .
tampon [ j +1];
mc . taille - -;
}
public Object next () {
if ( pos < mc . taille ) return mc . tampon [ pos ++];
return null ;
}
public boolean hasNext () { return ( pos < mc . taille ) ;}
}
Correction de l’exercice 174
299
c
adapté de : Irène
Charon, Télécom ParisTech - Paris 2011
http://www.infres.enst.fr/people/charon/coursJava/exercices/corriges/oiseau/Oiseau.
java
abstract class Oiseau
{
abstract void decrire () ;
public String toString ()
{
return " Famille des oiseaux : " ;
}
}
class Merle extends Oiseau
{
void decrire ()
{
System . out . println ( this + " je suis un merle " ) ;
}
}
class Pie extends Oiseau
{
void decrire ()
{
System . out . println ( this + " je suis une pie " ) ;
}
}
class Main
{
public static void main ( String [] arg ) throws Exception
{
ArrayList < Oiseau > oiseaux = new ArrayList < Oiseau >() ;
oiseaux . add ( new Merle () ) ;
oiseaux . add ( new Pie () ) ;
oiseaux . add ( new Pie () ) ;
oiseaux . add ( new Merle () ) ;
oiseaux . add ( new Pie () ) ;
for ( Oiseau o : oiseaux )
o . decrire () ;
}
}
Le polymorphisme est exploité dans la boucle finale : la méthode decrire appelée est celle de Merle ou de
Pie (et non celle de Oiseau), selon le type dynamique de l’objet o.
Correction de l’exercice 175
public interface IClient {
public boolean isVIP () ; // indique si un Client est un VIP
}
public class Client implements IClient {
CHAPITRE 16. CORRIGÉS
300
@Override
public boolean isVIP () {
return false ;
}
}
public class ClientVIP implements IClient {
@Override
public boolean isVIP () {
return true ;
}
}
public interface IFileAttente extends Deque < IClient > {
public int tempsAttenteVIP () ; // retourne le temps d ’ attente du
dernier client VIP de la file d ’ attente
public Collection < IClient > clientsVIP () ; // retourne et retire les
clients VIP de la file d ’ attente (! couteux )
}
public class FileAttente extends LinkedList < IClient > implements
IFileAttente {
private final int tempsUnite = 1; // temps de traitement d ’ un
client
@Override
public int tempsAttenteVIP () {
ListIterator < IClient > li = this . listIterator ( this . size () ) ;
// en esperant que cela ne necessite pas de parcourir toute la
liste ;
int index = this . size () ;
boolean trouve = false ;
while ( li . hasPrevious () && ! trouve ) {
index - -;
if ( li . previous () . isVIP () ) {
trouve = true ;
}
}
return tempsUnite * index ;
}
@Override
public Collection < IClient > clientsVIP () {
Collection < IClient > VIPs = new LinkedList < >() ;
for ( IClient client : this ) {
if ( client . isVIP () ) {
VIPs . add ( client ) ;
301
}
}
this . removeAll ( VIPs ) ;
return VIPs ;
}
}
public class AirMoon {
private static final int tempsMax = 5;
private IFileAttente filePrincipale = new FileAttente () ;
private Deque fileVIP = null ;
private Random generateur = new Random () ;
private int tailleMax = 0;
public Collection < IClient > arrivee ( int nbClients , int
pourcentageVIP ) {
// retourne de 0 à nbClients nouveaux clients , x % sont VIP
Collection < IClient > clients = new ArrayList < >() ;
int nb = generateur . nextInt ( nbClients +1) ;
for ( int i =0; i < nb ; i ++) {
int tirage = generateur . nextInt (100) ;
if ( tirage < pourcentageVIP ) {
clients . add ( new ClientVIP () ) ;
}
else
clients . add ( new Client () ) ;
}
System . out . println ( clients . size () + " nouveaux clients en
approche ... " ) ;
return clients ;
}
public void traitement () {
// traitement des files attentes
// si pas de fileVIP : debit liste principale = 2
if ( fileVIP == null ) {
if ( filePrincipale . isEmpty () == false )
filePrincipale . pop () ;
if ( filePrincipale . isEmpty () == false )
filePrincipale . pop () ;
} else {
if ( filePrincipale . isEmpty () == false )
filePrincipale . pop () ;
if ( fileVIP . isEmpty () == false )
fileVIP . pop () ;
if ( fileVIP . isEmpty () )
fileVIP = null ;
}
}
public void principal () {
// boucle principale
while ( true ) {
CHAPITRE 16. CORRIGÉS
302
System . out . println ( " * * * * * * * * * * * * * * * * * * * * * * * * * * * * " ) ;
// ajout de clients
filePrincipale . addAll ( arrivee (5 ,33) ) ;
// affichage statistiques
int attente = filePrincipale . tempsAttenteVIP () ;
System . out . println ( " file principale - taille : " +
filePrincipale . size () ) ;
if ( fileVIP != null )
System . out . println ( " file VIP - taille : " + fileVIP . size
() + " - tailleMax : " + tailleMax ) ;
if ( fileVIP != null )
if ( fileVIP . size () > tailleMax )
tailleMax = fileVIP . size () ;
System . out . println ( " file principale - temps attente VIP : "
+ attente ) ;
// basculement des clients VIP vers la file attente dediee
if ( attente > tempsMax ) {
if ( fileVIP == null ) {
fileVIP = new LinkedList < IClient >() ;
}
fileVIP . addAll ( filePrincipale . clientsVIP () ) ;
}
// traitement
System . out . println ( " Prise en charge des clients " ) ;
traitement () ;
// stats apres traitement
System . out . println ( " file principale - taille : " +
filePrincipale . size () ) ;
if ( fileVIP != null )
System . out . println ( " file VIP - taille : " + fileVIP . size
() + " - tailleMax : " + tailleMax ) ;
}
}
/* *
* @param args the command line arguments
*/
public static void main ( String [] args ) {
new AirMoon () . principal () ;
}
}
Correction de l’exercice 176
1.
2.
3.
4.
5.
6.
7.
8.
9.
[Primate] : Primate()
[Daltonien] : Daltonien()
[Primate] : Primate()
[Primate] : Primate()
Premier parcours:
[Daltonien] : Je suis un daltonien
[Primate] : Je suis un primate
[Primate] : Je suis un primate
Second parcours:
303
10.
11.
12.
13.
14.
[Daltonien] : SOS
[Primate] : argh !
[SuperException] : SuperException(String)
[Main] : Oups - Exception [SuperException] : lancee par un primate
[Daltonien] : Hourra!
capturee !!!!
Correction de l’exercice 177
premier parcours
Je suis un daltonien
Je suis un primate
Je suis un primate
once again
Je suis un daltonien
Oups !!! Exception Ma super exception : lancee par class Daltonien !
capturee !!!!
Correction de l’exercice 178
class Groupe {
ArrayList < Personne > membres = new ArrayList < Personne >() ;
HashSet < String > noms = new HashSet < String >() ;
public void add ( Personne p ) throws Exception {
if ( noms . contains ( p . getNom () ) )
throw new Exception ( " Echec ajout personne " ) ;
membres . add ( p ) ;
noms . add ( p . getNom () ) ;
}
public int size () {
return membres . size () ;
}
public String toString () {
String res = " Groupe : " ;
for ( Personne p : membres )
res += p . getNom () + " , " ;
return res ;
}
public static void main ( String [] args ) {
Groupe g = new Groupe () ;
ArrayList < Personne > population = new ArrayList < Personne
>() ;
for ( int i =0; i <1000; i ++)
population . add ( new Personne ( " Robert Bidochon
numero : " +(( int ) (100* Math . sin ( i ) ) ) ) ) ;
for ( Personne p : population ) {
try { // ! ne pas mettre la boucle a l ’
interieur du try { ...}
g . add ( p ) ;
}
catch ( Exception e ) {
CHAPITRE 16. CORRIGÉS
304
System . out . println ( e ) ;
}
}
System . out . println ( g ) ;
}
}
Correction de l’exercice 180
c
Irène
Charon, Télécom ParisTech - Paris 2011
http://www.infres.enst.fr/people/charon/coursJava/exercices/corriges/oiseau/Oiseau.
java
Eleve
package gestionEleves ;
import java . util . ArrayList ;
public class Eleve implements Comparable < Eleve >{
private String nom ;
private ArrayList < Integer > listeNotes = new ArrayList < Integer >() ;
private double moyenne ;
public Eleve ( String nom ) {
this . nom = nom ;
}
public double getMoyenne () {
return moyenne ;
}
public String getNom () {
return nom ;
}
public ArrayList < Integer > getListeNotes () {
return listeNotes ;
}
/* Une note inferieure a 0 sera considere comme egale a 0 ;
une note superieure a 20 sera considere comme egale a 20 */
public void ajouterNote ( int note ) {
int nbNotes = this . listeNotes . size () ;
if ( note < 0) note = 0;
else if ( note > 20) note = 20;
this . moyenne = ( this . moyenne * nbNotes + note ) / ( nbNotes + 1) ;
listeNotes . add ( note ) ;
}
public String toString () {
return nom + " ( " + ( int ) (100 * moyenne ) /100.0 + " ) " ;
}
public int compareTo ( Eleve autreEleve ) {
if ( this . moyenne < autreEleve . moyenne ) return -1;
305
if ( this . moyenne > autreEleve . moyenne ) return 1;
return 0;
}
}
GroupeEleves
package gestionEleves ;
import java . util . ArrayList ;
public class GroupeEleves {
private ArrayList < Eleve > listeEleves = new ArrayList < Eleve >() ;
public GroupeEleves () {
}
public int nombre () {
return listeEleves . size () ;
}
public ArrayList < Eleve > getListe () {
return listeEleves ;
}
public void ajouterEleve ( Eleve eleve ) {
listeEleves . add ( eleve ) ;
}
public Eleve chercher ( String nom ) {
for ( Eleve eleve : listeEleves )
if ( eleve . getNom () . equals ( nom ) ) return eleve ;
return null ;
}
public void lister () {
System . out . println ( " Liste des eleves : " ) ;
for ( Eleve eleve : listeEleves ) {
System . out . println ( eleve ) ;
}
}
public Eleve meilleurEleve () {
return Collections . max ( listeEleves ) ;
}
public void trierEleves () {
Collections . sort ( listeEleves ) ;
}
}
EleveECTS
package gestionEleves ;
CHAPITRE 16. CORRIGÉS
306
public abstract class EleveECTS extends Eleve {
public EleveECTS ( String nom ) {
super ( nom ) ;
}
public abstract int resultat () ;
}
Eleve1A
package gestionEleves ;
public class Eleve1A extends EleveECTS {
public Eleve1A ( String nom ) {
super ( nom ) ;
}
public int resultat () {
if ( this . getMoyenne () >= 12) return 60;
return 0;
}
}
EleveMaster
package gestionEleves ;
public class EleveMaster extends EleveECTS {
public EleveMaster ( String nom ) {
super ( nom ) ;
}
public int resultat () {
int somme = 0;
for ( int note : getListeNotes () )
if ( note >= 10) somme += 6;
else if ( note >= 8) somme += 3;
return somme ;
}
}
Correction de l’exercice 181
import java . util .*;
abstract class Individu {
boolean endormi = false ;
public abstract void setEndormi ( int heure ) ;
public abstract Individu attaqueParVampire () ;
}
307
class Humain extends Individu {
static Random r = new Random () ;
public void setEndormi ( int heure ) {
endormi = false ;
if ((6 <= heure ) &&( heure <12) )
endormi = true ;
}
public Individu attaqueParVampire () {
if ( endormi ) {
System . out . println ( " [ D ] un individu endormi
vampirise ! " ) ;
return new Vampire () ;
} else {
// 50% de chance
if ( r . nextInt (2) ==0) {
System . out . println ( " [ D ] un individu
eveille vampirise ! " ) ;
return new Vampire () ;
}
}
return this ;
}
}
class Hero extends Humain {
static Random r = new Random () ;
public void setEndormi ( int heure ) {
endormi = false ;
}
public Individu attaqueParVampire () {
// 5% de chance de se faire vampirise
if ( r . nextInt (100) <6) {
System . out . println ( " [ D ] !!!!!!!!!! Van Helsing
est devenu un vampire ! !!! !! !!! !! !!! !! !!! ! " )
;
return new Vampire () ;
}
return this ;
};
}
class Vampire extends Individu {
public void setEndormi ( int heure ) {
endormi = false ;
if ((8 <= heure ) &&( heure <20) )
endormi = true ;
}
public Individu attaqueParVampire () {
}
class Population extends ArrayList {
public String toString () {
return this ; };
CHAPITRE 16. CORRIGÉS
308
return " Population [ " + nb ( " Hero " ) + " Hero , " + nb ( " Humain " )
+ " Humains , " + nb ( " Vampire " ) + " Vampires ] " ;
}
public int nb ( String type ) {
int nb =0;
Iterator i = iterator () ;
while ( i . hasNext () ) {
Object v = i . next () ;
if ( type . equals ( " Hero " ) ) if ( v instanceof Hero )
nb ++;
if ( type . equals ( " Humain " ) ) if ( v instanceof
Humain ) nb ++;
if ( type . equals ( " Vampire " ) ) if ( v instanceof
Vampire ) nb ++;
}
return nb ;
}
}
class Jeu {
private int heure = 0;
private Population population = new Population () ;
private Random r = new Random () ;
public Jeu () {
// initialisation de la population
population . add ( new Hero () ) ;
population . add ( new Vampire () ) ;
for ( int i =1; i <=99; i ++) population . add ( new Humain () ) ;
}
public void jouer () {
System . out . println ( " Debut du jeu " ) ;
// deroulement de 24 heures
for ( int i =0; i <=23; i ++) {
heure = (19 + i ) %24 +1;
System . out . println ( heure + " h : " ) ;
System . out . println ( population ) ;
tour () ;
}
}
public void tour () {
Iterator i = population . iterator () ;
// on recupere les vampires
Vector vampires = new Vector () ;
while ( i . hasNext () ) {
Individu v = ( Individu ) i . next () ;
if ( v instanceof Vampire )
vampires . add ( v ) ;
309
}
// Van Helsing attaque
if ( population . nb ( " Hero " ) >=1) { // on verifie que Van Helsing n ’
a pas ete vampirise
int nbDetruit =0;
for ( int j =0; j < vampires . size () ; j ++) {
Vampire v = ( Vampire ) vampires . elementAt ( j ) ;
v . setEndormi ( heure ) ; // on met a jour l ’ etat
endormi
if ( v . endormi && nbDetruit <2) {
population . remove ( v ) ; vampires . remove ( v
);
System . out . println ( " [ D ] un vampire
detruit ! " ) ; nbDetruit ++;
}
}
}
// les vampires attaquent
for ( int j =0; j < vampires . size () ; j ++) {
Vampire v = ( Vampire ) vampires . elementAt ( j ) ;
if (!( v . endormi ) ) {
int nb = population . size () ;
// tirage aleatoire des 2 victimes
int a = Math . abs (( r . nextInt () ) % nb ) ;
int b = Math . abs (( r . nextInt () ) % nb ) ;
vampiriser ( a ) ; vampiriser ( b ) ;
}
}
}
private void vampiriser ( int i ) {
Individu in = ( Individu ) population . get ( i ) ;
in . setEndormi ( heure ) ; // on met a jour l ’ etat endormi
population . set (i , in . attaqueParVampire () ) ;
}
public static void main ( String args [] ) {
( new Jeu () ) . jouer () ;
}
}
Correction de l’exercice 182
public class Chemin extends Graphe {
public Chemin ( int n ) {
this . n = n ;
// creation des sommets numerotes de 1 a n
for ( int i =1; i <= n ; i ++)
sommets . add ( new Sommet ( i ) ) ;
// creation des aretes : 1 -2 , 2 -3 , ... , n -1 n
for ( int i =0; i <n -1; i ++) // les indices dans le
vecteur vont de 0 a n -1
CHAPITRE 16. CORRIGÉS
310
aretes . add ( new Arete ( sommets . get ( i ) , sommets .
get ( i +1) ) ) ;
}
}
public class Cycle extends Graphe {
public Cycle ( int n ) {
this . n = n ;
// creation des sommets numerotes de 1 a n
for ( int i =1; i <= n ; i ++)
sommets . add ( new Sommet ( i ) ) ;
// creation des aretes : 1 -2 , 2 -3 , ... , n -1 n , +
1-n
for ( int i =0; i <n -1; i ++) { // les indices dans
le vecteur vont de 0 e n -1
aretes . add ( new Arete ( sommets . get ( i ) ,
sommets . get ( i +1) ) ) ;
aretes . add ( new Arete ( sommets . get (0) , sommets .
get (n -1) ) ) ;
}
}
public class Graphe {
...
public int degre ( Sommet s ) {
int res =0;
for ( Sommet u : sommets ) // boucle sur tous les sommets
if ( estAdjacent (s , u ) ) // si s est un voisin
res ++;
// on incremente le
compteur
return res ;
}
public int degreMaximum () {
int res =0;
for ( Sommet u : sommets ) // boucle sur tous les sommets
if ( degre ( u ) > res )
// si le degre de s est
superieur a s
res = degre ( u ) ;
// res prend cette
valeur
return res ;
}
public Graphe complementaire () {
Graphe res = new Graphe () ;
res . n = n ;
res . sommets = sommets ; // ! le graphe res partagera les
311
memes sommets que le graphe courant
// creation des aretes de res
for ( Sommet u : sommets )
for ( Sommet v : sommets )
if ( u != v ) // on ne met pas les boucles
dans le graphe complementaire
if ( estAdjacent (u , v ) == false )
// si u n ’ est pas adjacent
a v
res . aretes . add ( new
Arete (u , v ) ) ; // on
cree l ’ arete uv et
on l ’ ajoute
return res ;
}
private int omega =0;
public void recursion ( ArrayList < Sommet > clique ) {
// enumeration recursive de toutes les cliques
// si la taille de la clique passee en parametre est
plus grande qu ’ omega , on met a jour omega
if ( clique . size () > omega )
omega = clique . size () ;
// appels recursifs
// pour tous les sommets qui ne sont pas dans la clique
for ( Sommet s : sommets )
if ( clique . contains ( s ) == false ) {
// on regarde si s est voisin de tous
les sommets de la clique , i . e . si
en ajoutant s , on a toujours une
clique
boolean onPeutAjouter = true ;
for ( Sommet u : clique )
if ( estAdjacent (s , u ) == false )
{
onPeutAjouter =
false ;
break ;
};
if ( onPeutAjouter ) // si c ’ est le cas ,
on fait l ’ appel recursif en
ajoutant s
{
clique . add ( s ) ;
recursion ( clique ) ;
clique . remove ( s ) ; // il
faut penser a
enlever s , car on n ’
a pas fait de copie
de clique
}
}
}
CHAPITRE 16. CORRIGÉS
312
public int ta illeMax imumCliq ue () {
omega =0;
// on lance les recursions a partir de chaque sommet
for ( Sommet s : sommets ) {
ArrayList < Sommet > clique = new ArrayList < Sommet
>() ;
clique . add ( s ) ;
recursion ( clique ) ;
}
return omega ;
}
}
Correction de l’exercice 266
import java . net .*;
import java . io .*;
import java . util .*;
public class MonServeurWeb {
public static void main ( String [] args ) throws Exception {
ServerSocket ss = new ServerSocket (80) ;
System . out . println ( " Mon serveur web en ecoute " ) ;
Socket s = ss . accept () ;
System . out . println ( " ******* Connexion etablie
************* " ) ;
InputStream is = s . getInputStream () ;
LineNumberReader lnr = new LineNumberReader ( new
InputStreamReader ( is ) ) ;
// lecture ligne par ligne de la requete
String ligne = " " ;
// analyse premiere ligne
ligne = lnr . readLine () ;
if ( ligne . toUpperCase () . indexOf ( " GET " ) != -1) {
// ligne contient GET
System . out . println ( " Serveur : methode GET
utilisee " ) ;
}
}
// autres lignes
while ( ligne . compareTo ( " " ) !=0) {
ligne = lnr . readLine () ;
System . out . println ( " Client : " + ligne ) ;
}
System . out . println ( " envoi de la reponse " ) ;
// reponse
String page = " < HTML > < BODY > <H1 > Hey Man ! </ H1 > </ BODY
> </ HTML > " ;
String entete = " HTTP /1.1 200 voici la page !!\ nDate :
Tue , 25 Oct 2005 19:57:48 GMT \ nServer : MonServeurWeb
\ nAccept - Ranges : bytes \ nContent - Length : " + page .
313
length () + " \ nContent - Type : text / html \ n \ n " ;
OutputStream os = s . getOutputStream () ;
PrintWriter pw = new PrintWriter ( os ) ;
pw . println ( entete ) ;
pw . println () ; // ligne blanche
pw . println ( page ) ;
pw . flush () ;
s . close () ;
}
}
314
CHAPITRE 16. CORRIGÉS
Cinquième partie
Annexes
315
Annexe A
Présentation du protocole HTTP
La compréhension du protocole HTTP est fondamentale pour la programmation web ... Dans ce chapitre, nous
décrivons les principales caractéristiques de ce protocole.
Préambule
Le protocole HTTP (HyperText Transfer Protocol) a été conçu pour être employé sur le Web, mais de façon
suffisamment générique pour être employé dans un cadre moins restrictif.
L’utilisation de TCP comme protocole de transport sous-jacent garantit la fiabilité de la connexion (ré-émission
des trames perdues, élimination des doublons, gestion des acquittements etc ...)
URL
Définition A.1 (URL). Une URL sert à désigner de manière universelle une page. Elle est formée de trois partie :
— le protocole de transport ;
— le nom DNS de la machine hébergeant la page ;
— la page et sa localisation (chemin relatif) sur le serveur.
Exemple d’URL :
https://miage.emi.u-bordeaux1.fr/espaceEtudiants/index.php
Dans cette URL,
— le protocole est https (protocole http sécurisé) ;
— le serveur est miage.emi.u-bordeaux1.fr ;
— la page demandée est la page index.php située dans le répertoire espaceEtudiants.
Principaux protocoles d’Internet
—
—
—
—
—
—
—
HTTP : utilisé pour la navigation web ;
HTTPS : version sécurisée ;
FTP : File Transfer Protocol - transfert de fichiers ;
file : fichier local ;
news : newsgroups ;
mailto : envoi de courrier électronique ;
webdav : accès en lecture/écriture sur un espace d’un serveur web.
317
ANNEXE A. PRÉSENTATION DU PROTOCOLE HTTP
318
Lorsque l’on clique sur l’hyperlien http://www.u-bordeaux.fr dans un navigateur web :
1. le navigateur interroge un serveur DNS pour connaitre l’adresse IP du serveur www.u-bordeaux.fr ;
2. le serveur DNS répond x.x.x.x ;
3. le navigateur initie une connexion TCP sur le port HTTP (80) de la machine x.x.x.x ;
4. il envoie sur la connexion une requête via le protocole HTTP demandant le fichier par défaut : index.html ;
5. le serveur www.u-bordeaux1.fr retourne le fichier demandé via le protocole HTTP (ex.
/var/www/html/index.html) ;
6. la connexion TCP est coupée ;
7. le navigateur affiche tout le texte contenu dans la page ;
8. le navigateur récupère et affiche toutes les images.
Aperçu de la syntaxe
Voici un exemple de requête HTTP émise vers le serveur web example.com.
Requête HTTP
GET / page . html HTTP /1.0
Host : example . com
Dans cette requête, le navigateur la page page.html située à la racine du serveur web example.com, en
précisant qu’il utilise la version 1.0 du protocole HTTP.
Voici la réponse obtenue.
Réponse HTTP
HTTP /1.0 404 Not Found
Accept - Ranges : bytes
Cache - Control : max - age =604800
Content - Type : text / html
Date : Mon , 03 Mar 2014 17:09:43 GMT
Etag : " 359670651 "
Expires : Mon , 10 Mar 2014 17:09:43 GMT
Last - Modified : Fri , 09 Aug 2013 23:54:35 GMT
Server : ECS ( lax /4514)
X - Cache : HIT
x - ec - custom - error : 1
Content - Length : 1270
Connection : close
<! doctype html >
< html >
< head >
< title > Example Domain </ title >
< meta charset = " utf -8 " / >
< meta http - equiv = " Content - type " content = " text / html ; charset = utf -8 "
/>
< meta name = " viewport " content = " width = device - width , initial - scale =1 "
/>
< style type = " text / css " >
body {
background - color : # f0f0f2 ;
319
margin : 0;
padding : 0;
font - family : " Open Sans " , " Helvetica Neue " , Helvetica , Arial ,
sans - serif ;
}
div {
width : 600 px ;
margin : 5 em auto ;
padding : 50 px ;
background - color : # fff ;
border - radius : 1 em ;
}
a : link , a : visited {
color : #38488 f ;
text - decoration : none ;
}
@media ( max - width : 700 px ) {
body {
background - color : # fff ;
}
div {
width : auto ;
margin : 0 auto ;
border - radius : 0;
padding : 1 em ;
}
}
</ style >
</ head >
< body >
< div >
< h1 > Example Domain </ h1 >
<p > This domain is established to be used for illustrative examples
in documents . You may use this
domain in examples without prior coordination or asking for
permission . </ p >
<p > <a href = " http :// www . iana . org / domains / example " > More information
... </ a > </ p >
</ div >
</ body >
</ html >
La réponse est formée de l’entête HTTP (toute la partie avant la première ligne vide) avec un code retour de
valeur 404 (page non-trouvée), suivi du contenu d’une page HTML.
Exercice 266. (??) Ecrire une classe MonServeurWeb qui retourne systématiquement une même page HTML
fixée.
Types de dialogue HTTP
— récupération d’un document : méthode GET ;
— soumission d’un formulaire : méthode GET ou POST ;
ANNEXE A. PRÉSENTATION DU PROTOCOLE HTTP
320
— envoi de document et gestion de site : méthodes PUT, DELETE, LINK, UNLINK ;
— gestion de proxy/cache : méthode HEAD (récupération des informations sur le document)
Paramètres d’une requête
—
—
—
—
—
—
—
User-Agent : informations sur le navigateur et sa plateforme ;
Accept : le type de pages que le client peut traiter ;
Accept-Charset : les jeux de caractères acceptés par le client ;
Accept-Encoding : les codage de page acceptés par le client ;
Host : le nom DNS du serveur ;
Authorization : informations sur l’identité du client ;
Cookie : retourne un cookie préalablement placé par le serveur.
Paramètres requête & réponse
— Date : horodatage du message ;
— Upgrade : le protocole sur lequel l’émetteur veut basculer ;
Paramètres d’une réponse
—
—
—
—
—
Content-Encoding : type de codage du contenu ;
Content-Length : la longueur de la page en octets ;
Last-Modified : heure et date dernière modification de la page ;
Location : demande au client d’envoyer sa requête ailleurs ;
Set-Cookie : demande au client d’enregistrer un cookie.
Groupes de codes de réponse
Chaque requête est suivie d’une réponse, contenant un code d’état à 3 chiffres :
—
—
—
—
—
1xx : Information ;
2xx : Succès - exemple : 200 = la requête a réussi ;
3xx : Redirection - exemples : 301 = page déplacée, 304 = page en cache toujours valide ;
4xx : Erreur client - exemples : 403 = page interdite, 404 = page inexistante ;
5xx : Erreur serveur - exemples : 500 = erreur interne, 503 = recommencer plus tard.
Echange de données
GET : passage de valeurs dans l’URL
Pour transmettre des valeurs au serveur, le client peut les passer dans l’URL, comme dans les deux captures
d’écran ci-après :
321
La syntaxe est la suivante :
/ chemin / page ? var1 = valeur1 & var2 = valeur2 & var3 = valeur3 ...
POST : passage de valeurs dans les données de la trame HTTP
La méthode POST d’HTTP permet d’envoyer des données contenues dans la partie données de la trame HTTP.
Cette méthode est plus transparente pour l’utilisateur qui ne les voit pas passer dans les URL utilisées par son
navigateur. Elle n’est cependant pas plus sûre car toutes les données transitent sans cryptage.
Dans la capture d’écran ci-dessous, les valeurs des attributs url, login, passwd et Envoyer ont transité par
ce biais.
Le suivi de session
La notion de session est importante dans de nombreuses applications (ex. commerce électronique - gestion
d’un panier ). Cependant avec HTTP, le serveur ne maintient pas d’ informations liées aux requêtes précédentes
d’ un même client : les enchainements Requête/Réponse sont indépendants les uns des autres.
ANNEXE A. PRÉSENTATION DU PROTOCOLE HTTP
322
Pour pouvoir réaliser la notion de session sur plusieurs requêtes HTTP,
— le serveur génère un identificateur de session et associe un état (et une date limite de validité) à une session ;
— le client renvoie l ’identificateur de session à chaque requête HTTP vers le serveur ;
L’identificateur de session peut être stocké ou transmis dans les champs cachés d’un formulaire, par réécriture
des URLs, dans des cookies, ou en réutilisant l’identificateur de session SSL (Secure Socket Layer).
Considérons par exemple l’URL suivante :
http://www.bob.fr/servlet/cart?PROD_ID=383
Après réécriture pour injecter un identifiant de session, elle aura la forme :
http://www.bob.fr/servlet/cart;jsessionid=To1128mC33718557521577075At?PROD_ID=383
Le principal inconvénient est que l’URL doit être générée par un script, ce qui nécessite de la programmation
côté serveur.
On peut stocker dans un cookie, une chaîne décrivant l’état d’une session. Les cookies sont communiquée
dans les entêtes de requêtes et/ou des réponses HTTP .
Les limites sont de 300 cookies simultanées par client, 20 cookies par serveur ou domaine et de 4Ko par
cookie. Un client peut refuser des cookies.
Les extensions d’HTTP
Pour terminer cette courte présentation d’HTTP, mentionons deux extensions populaires de celui-ci.
La première est webdav (Web Distributed Authoring and Versioning Protocol), une extension de HTTP pour
la mise à jour de site. Cette version ajoute de nouvelles commandes HTTP
— PROPFIND : retourne les propriétés ;
— PROPPATCH : modifie les propriétés ;
— MKCOL : crée une nouvelle collection ;
— COPY & MOVE : copie ou déplace une ressource au sein d’un espace de nommage ;
— LOCK & UNLOCK : verrouille et déverouille un
La seconde est HTTPS (S comme Secure). Cette extension permet de sécuriser (authentification, confidentialité) l’accès à une ressource web :
— cryptage SSL ;
— Authentification du serveur et/ou du client par PKI ;
— Chiffrage avec une clé (secrète) symétrique de session ;
— Reprise après déconnexion
Table des figures
4.1
Exemple d’héritage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
47
4.2
Exemple d’héritage multiple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
48
5.1
Exemple d’une liste doublement chainée, contenant les éléments 154, 45, 11 et 458. . . . . . . . .
60
5.2
Exemple d’un tableau, contenant les éléments 45, 154, 58, 78, 31, 5 et 74. . . . . . . . . . . . . .
61
c
10.1 Relation n, n entre écouteurs et sources d’événements (Oracle)
. . . . . . . . . . . . . . . . . . 118
c
10.2 Une application avec plusieurs écouteurs (Oracle)
. . . . . . . . . . . . . . . . . . . . . . . . . 119
13.1 Scénario typique des services web (source : http://www.dotnetguru.org/) . . . . . . . . . . 164
13.2 Service web convertisseur de monnaie : les paramètres d’envoi et la réponse . . . . . . . . . . . . 165
13.3 Service web convertisseur de monnaie : données XML envoyées par HTTP . . . . . . . . . . . . 165
13.4 Service web convertisseur de monnaie : données XML retournées par HTTP . . . . . . . . . . . . 166
13.5 Trame HTTP de la requête . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
13.6 Trame HTTP de la réponse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167
15.1 Diagramme des classes du code initial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202
15.2 Diagramme des classes du code final de l’application . . . . . . . . . . . . . . . . . . . . . . . . 203
15.3 Diagramme des classes du code final de l’application . . . . . . . . . . . . . . . . . . . . . . . . 205
323
Téléchargement