Universit´ de la M´diterran´e e e e Facult´ des Sciences de Luminy e

Le langage Java
Master CCI, MINT, I2A, BBSG, etc.

Henri Garreta D´partement d’Informatique – LIF e

2

c H. Garreta, 2000-2006

` TABLE DES MATIERES

` TABLE DES MATIERES

Le langage Java
Henri Garreta
Universit´ de la M´diterran´e, Facult´ des Sciences de Luminy e e e e Table des mati`res e
1 Introduction 1.1 Pratique effective de Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 Consid´rations lexicales e 2.1 Jeu de caract`res . . . e 2.2 Commentaires . . . . . 2.3 Identificateurs . . . . . 2.4 Constantes litt´rales . e 7 7 8 8 8 8 9 10 10 10 11 11 12 12 12 12 13 13 14 15 16 17 18 19 20 20 21

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

3 Types 3.1 Les types de donn´es de Java . . . . . . . . . . . . . . . . . e 3.2 Conversions entre types primitifs . . . . . . . . . . . . . . . 3.3 R´f´rences . . . . . . . . . . . . . . . . . . . . . . . . . . . . ee 3.3.1 S´mantique des valeurs et s´mantique des r´f´rences e e ee 3.3.2 R´f´rence “sur rien” . . . . . . . . . . . . . . . . . . ee 3.3.3 Cas des param`tres des m´thodes . . . . . . . . . . . e e 3.4 Tableaux . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.4.1 D´claration et initialisation . . . . . . . . . . . . . . e 3.4.2 Acc`s aux ´l´ments . . . . . . . . . . . . . . . . . . . e ee 3.4.3 Tableaux ` plusieurs indices . . . . . . . . . . . . . . a 3.4.4 Tableaux initialis´s et tableaux anonymes. . . . . . . e 3.5 Conversions entre types tableaux ou classes . . . . . . . . . 3.6 Copie et comparaison des objets . . . . . . . . . . . . . . . 3.6.1 Copie . . . . . . . . . . . . . . . . . . . . . . . . . . 3.6.2 D´finir la m´thode clone . . . . . . . . . . . . . . . e e 3.6.3 Comparaison . . . . . . . . . . . . . . . . . . . . . . 3.7 Chaˆ ınes de caract`res . . . . . . . . . . . . . . . . . . . . . e 3.7.1 Les classes String et StringBuffer . . . . . . . . . 3.7.2 Copie et comparaison des chaˆ ınes . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . .

4 Expressions et instructions 23 4.1 Rupture ´tiquet´e . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 e e
c H. Garreta, 2000-2006

3

` TABLE DES MATIERES

` TABLE DES MATIERES

5 Classes, paquets, fichiers et r´pertoires e 5.1 Paquets et classes . . . . . . . . . . . . . . . . . . . . . 5.1.1 Les noms des paquets et l’instruction package 5.1.2 Les noms des classes et l’instruction import . . 5.2 Classes, fichiers et r´pertoires . . . . . . . . . . . . . . e 5.2.1 Classes publiques et non publiques . . . . . . . 5.2.2 Point d’entr´e d’un programme. . . . . . . . . . e 5.2.3 O` placer les fichiers des classes ? . . . . . . . . u 5.2.4 Comment distribuer une application Java ? . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

24 24 24 24 25 25 25 26 27 28 28 28 28 29 30 31 32 32 32 33 34 35 36 36 37 37 38 39 39 40 40 41 41 41 42 43 44 44 46 48 49 49 49 50 53 53 54 54 54 56 58 58 58 59 60 61 61

6 Les objets 6.1 ♥ Introduction : les langages orient´s objets . . . . . . . . . . . . . . e 6.2 Classes, variables et m´thodes . . . . . . . . . . . . . . . . . . . . . . e 6.2.1 D´claration des classes et des objets, instanciation des classes e 6.2.2 Instances et membres d’instance . . . . . . . . . . . . . . . . 6.2.3 Membres de classe (membres statiques) . . . . . . . . . . . . 6.3 Surcharge des noms . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.4 Contrˆle de l’accessibilit´ . . . . . . . . . . . . . . . . . . . . . . . . o e 6.4.1 Membres priv´s, prot´g´s, publics . . . . . . . . . . . . . . . e e e 6.4.2 ♥ L’encapsulation . . . . . . . . . . . . . . . . . . . . . . . . 6.5 Initialisation des objets . . . . . . . . . . . . . . . . . . . . . . . . . 6.5.1 Constructeurs . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.5.2 Membres constants (final) . . . . . . . . . . . . . . . . . . . 6.5.3 Blocs d’initialisation statiques . . . . . . . . . . . . . . . . . . 6.5.4 Destruction des objets . . . . . . . . . . . . . . . . . . . . . . 6.6 Classes internes et anonymes . . . . . . . . . . . . . . . . . . . . . . 6.6.1 Classes internes . . . . . . . . . . . . . . . . . . . . . . . . . . 6.6.2 Classes anonymes . . . . . . . . . . . . . . . . . . . . . . . . . 7 H´ritage e 7.1 ♥ Introduction : raffiner, abstraire . . . . . . . 7.2 Sous-classes et super-classes . . . . . . . . . . . 7.2.1 D´finition de sous-classe . . . . . . . . . e 7.2.2 ♥ L’arbre de toutes les classes . . . . . 7.3 Red´finition des m´thodes . . . . . . . . . . . . e e 7.3.1 Surcharge et masquage . . . . . . . . . . 7.3.2 Red´finition des m´thodes . . . . . . . . e e 7.4 H´ritage et constructeurs . . . . . . . . . . . . e 7.5 ♥ Le polymorphisme . . . . . . . . . . . . . . . 7.5.1 G´n´ralisation et particularisation . . . e e 7.5.2 Les m´thodes red´finies sont “virtuelles” e e 7.5.3 M´thodes et classes finales . . . . . . . . e 7.6 ♥ Abstraction . . . . . . . . . . . . . . . . . . . 7.6.1 M´thodes abstraites . . . . . . . . . . . e 7.6.2 Classes abstraites . . . . . . . . . . . . . 7.6.3 Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

8 Exceptions 8.1 Interception des exceptions . . . . . . . . . . . . . . . . . . 8.2 Lancement des exceptions . . . . . . . . . . . . . . . . . . . 8.2.1 Hi´rarchie des exceptions . . . . . . . . . . . . . . . e 8.2.2 D´claration des exceptions lanc´es par une m´thode e e e 8.3 Assert . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 Quelques classes utiles 9.1 Nombres et outils math´matiques . . . . . . . e 9.1.1 Classes-enveloppes des types primitifs 9.1.2 Fonctions math´matiques . . . . . . . e 9.1.3 Nombres en pr´cision infinie . . . . . . e 9.2 Collections et algorithmes . . . . . . . . . . . 9.2.1 Collections et tables associatives . . . 4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

c H. Garreta, 2000-2006

` TABLE DES MATIERES

` TABLE DES MATIERES

9.3 9.4

9.5

9.2.2 It´rateurs . . . . . . . . . . . . . . e 9.2.3 Quelques m´thodes des collections e 9.2.4 Algorithmes pour les collections . . 9.2.5 Algorithmes pour les tableaux . . . R´flexion et manipulation des types . . . e Entr´es-sorties . . . . . . . . . . . . . . . e 9.4.1 Les classes de flux . . . . . . . . . 9.4.2 Exemples . . . . . . . . . . . . . . 9.4.3 Analyse lexicale . . . . . . . . . . . 9.4.4 Mise en forme de donn´es . . . . . e 9.4.5 Repr´sentation des dates . . . . . e 9.4.6 La s´rialisation . . . . . . . . . . . e Expressions r´guli`res . . . . . . . . . . . e e 9.5.1 Principe . . . . . . . . . . . . . . . 9.5.2 Exemples . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

65 66 67 69 70 72 72 75 78 80 83 84 86 86 86 90 90 90 92 92 93 94 96 96 97 99 101 102 103 105 106 110 111 111 112 114 115 116 117 118 118 119 119 121 121 122 122 124 127 129 130 132 133 133 139 139 141 143 5

10 Threads 10.1 Cr´ation et cycle de vie d’un thread . . . . . . . . . e 10.1.1 D´claration, cr´ation et lancement de threads e e 10.1.2 Terminaison d’un thread . . . . . . . . . . . . 10.2 Synchronisation . . . . . . . . . . . . . . . . . . . . . 10.2.1 Sections critiques . . . . . . . . . . . . . . . . 10.2.2 M´thodes synchronis´es . . . . . . . . . . . . e e

11 Interfaces graphiques 11.1 AWT + Swing = JFC . . . . . . . . . . . . . . . . . . . . . . 11.2 Le haut de la hi´rarchie . . . . . . . . . . . . . . . . . . . . . e 11.3 Le point de d´part : JFrame et une application minimale. . . e 11.4 Le thread de gestion des ´v´nements . . . . . . . . . . . . . . e e ´ e 11.5 Ev´nements . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.5.1 Exemple : d´tecter les clics de la souris. . . . . . . . . e 11.5.2 Adaptateurs et classes anonymes . . . . . . . . . . . . 11.5.3 Principaux types d’´v´nements . . . . . . . . . . . . . e e 11.5.4 Exemple : fermeture “prudente” du cadre principal . . 11.6 Peindre et repeindre . . . . . . . . . . . . . . . . . . . . . . . 11.6.1 La m´thode paint . . . . . . . . . . . . . . . . . . . . e 11.6.2 Les classes Graphics et Graphics2D . . . . . . . . . . 11.6.3 Exemple : la mauvaise mani`re de dessiner . . . . . . e 11.6.4 Exemple : la bonne mani`re de dessiner. . . . . . . . . e 11.7 Gestionnaires de disposition . . . . . . . . . . . . . . . . . . . 11.7.1 FlowLayout . . . . . . . . . . . . . . . . . . . . . . . . 11.7.2 BorderLayout . . . . . . . . . . . . . . . . . . . . . . 11.7.3 GridLayout . . . . . . . . . . . . . . . . . . . . . . . . 11.7.4 GridBagLayout . . . . . . . . . . . . . . . . . . . . . . 11.7.5 Exemple : un panneau muni d’un GridBagLayout . . 11.7.6 Exemple : imbrication de gestionnaires de disposition 11.7.7 Bordures . . . . . . . . . . . . . . . . . . . . . . . . . 11.8 Composants pr´d´finis . . . . . . . . . . . . . . . . . . . . . . e e 11.8.1 Exemple : mise en place et emploi de menus . . . . . . 11.8.2 Exemple : construire une boite de dialogue . . . . . . 11.8.3 Exemple : saisie de donn´es . . . . . . . . . . . . . . . e 11.8.4 Exemple : choisir un fichier . . . . . . . . . . . . . . . 11.8.5 Exemple : une table pour afficher des donn´es . . . . . e 11.8.6 Exemple : s´lection et modification dans une table . . e 11.9 Le mod`le MVC (Mod`le-Vue-Controleur) . . . . . . . . . . . e e 11.9.1 Exemple : les vues arborescentes (JTree) . . . . . . . 11.10Images . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.10.1 Exemple : utiliser des icˆnes . . . . . . . . . . . . . . . o 11.10.2 Exemple : charger une image depuis un fichier . . . . 11.10.3 Exemple : construire une image pixel par pixel . . . .
c H. Garreta, 2000-2006

` TABLE DES MATIERES

` TABLE DES MATIERES

12 Java 5 12.1 Types ´num´r´s . . . . . . . . . . . . . . . . . . . . . . . . . . . e ee 12.1.1 Enums . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12.1.2 Dictionnaires et ensembles bas´s sur des types ´num´r´s e e ee 12.2 Emballage et d´ballage automatiques . . . . . . . . . . . . . . . e 12.2.1 Principe . . . . . . . . . . . . . . . . . . . . . . . . . . . 12.2.2 La r´solution de la surcharge en Java 5 . . . . . . . . . e 12.3 Quelques outils pour simplifier la vie du programmeur . . . . . 12.3.1 Importation de membres statiques . . . . . . . . . . . . 12.3.2 Boucle for am´lior´e . . . . . . . . . . . . . . . . . . . . e e 12.3.3 M´thodes avec une liste variable d’arguments . . . . . . e 12.3.4 Entr´es-sorties simplifi´es : printf et Scanner . . . . . e e 12.4 Annotations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12.4.1 Principe . . . . . . . . . . . . . . . . . . . . . . . . . . . 12.4.2 Exploitation des annotations . . . . . . . . . . . . . . . 12.4.3 Annotations pr´d´finies . . . . . . . . . . . . . . . . . . e e 12.4.4 M´ta-annotations . . . . . . . . . . . . . . . . . . . . . . e 12.5 G´n´ricit´ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . e e e 12.5.1 Classes et types param´tr´s . . . . . . . . . . . . . . . . e e 12.5.2 Types bruts . . . . . . . . . . . . . . . . . . . . . . . . . 12.5.3 Types param´tr´s et jokers . . . . . . . . . . . . . . . . e e 12.5.4 Limitations de la g´n´ricit´ . . . . . . . . . . . . . . . . e e e 12.5.5 M´thodes g´n´riques . . . . . . . . . . . . . . . . . . . . e e e Index

. . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . .

145 145 145 147 148 148 150 150 150 152 154 155 157 157 159 160 160 161 162 164 164 166 167 169

Ce poly est en chantier, c’est son ´tat normal e Ceci est la version du 15 juillet 2006 (premi`re ´dition : janvier 2000) e e

Henri.Garreta@UnivMed.fr

La derni`re version de ce document est ` l’adresse e a http://www.dil.univ-mrs.fr/~garreta/Polys/PolyJava.pdf

6

c H. Garreta, 2000-2006

1 INTRODUCTION

1

Introduction

Ce polycopi´ est le support de plusieurs cours de Java donn´s ` la Facult´ des Sciences de Luminy. Tr`s e e a e e succinct, il ne remplace pas la consultation d’ouvrages plus approfondis et illustr´s, parmi lesquels : e pour d´buter : e Patrick Niemeyer & Jonathan Knudsen Introduction ` Java, 2 eme ´dition a e O’Reilly, 2002 deuxi`me lecture : e Ian Darwin Java en action O’Reilly, 2002

Ce texte s’adresse ` des lecteurs connaissant le langage C. De nombreux ´l´ments importants de Java, a ee comme les expressions, les instructions, l’appel des fonctions, etc., ne sont pas repris ici, tout simplement parce qu’ils sont r´alis´s en Java comme en C. En outre, l’expos´ n’est pas progressif : chaque notion est e e e introduite comme si les autres concepts du langage avaient d´j` ´t´ expliqu´s. Cela rend la lecture initiale eaee e de ce document plus ´prouvante, mais la consultation ult´rieure plus efficace. e e Ces notes traitent de la version courante du langage Java et sa plate-forme de d´veloppement, respectie vement appel´s Java 5 et Java 2 Platform Standard Edition 5.0. Cependant, pour la clart´ des id´es, nous e e e avons introduit la plupart des ´l´ments comme il convenait de le faire ` l’´poque de Java 1.4 (par exemple, ee a e l’h´ritage et le polymorphisme sont d’abord expliqu´s sans mentionner ni la g´n´ricit´ ni l’emballage autoe e e e e matique). Les principales nouveaut´s introduites lors du passage de Java 1.4 ` Java 5 sont pr´sent´es ` la e a e e a section 12. Un document est rigoureusement indispensable pour programmer en Java : la documentation en ligne de l’API (Interface du Programmeur d’Applications). C’est un fichier hypertexte qui contient tout ce qu’il faut savoir ` propos de tous les paquets, interfaces, classes, variables et m´thodes de la plate-forme Java. On a e peut le consulter en ligne et le t´l´charger ` l’adresse http://java.sun.com/j2se/1.5.0/docs/api/. ee a Enfin, un excellent document pour apprendre Java est le tutoriel officiel, qu’on peut consulter en ligne ou t´l´charger ` l’adresse http://java.sun.com/tutorial/. ee a

1.1

Pratique effective de Java

Dans cette section nous supposons que vous travaillez sur un syst`me Windows, Linux, Solaris ou e Mac OS X. Certains des produits mentionn´s ici ont ´galement des versions pour des syst`mes plus confie e e dentiels. • Pour pratiquer le langage Java, c’est-`-dire pour saisir, compiler et ex´cuter vos programmes, il vous faut a e un environnement de d´veloppement Java, comportant un compilateur, une machine Java, des biblioth`ques e e – au moins la biblioth`que standard. Vous obtenez tout cela en installant le JDK (Java Development Kit), e que vous pouvez t´l´charger gratuitement depuis le site de Sun, ` l’adresse http://java.sun.com/j2se/1. ee a 5.0/download.jsp. Vous pouvez utiliser n’importe quel ´diteur de textes pour saisir vos programmes. Supposons que vous e ayez tap´ le texte de la c´l`bre classe Bonjour : e ee public class Bonjour { public static void main(String[] args) { System.out.println("Bonjour ` tous!"); a } } Le fichier dans lequel vous avez enregistr´ ce texte doit s’appeler Bonjour.java. Vous en obtenez la e compilation en tapant la commande javac Bonjour.java Si le programme est correct, la compilation se d´roule sans produire aucun message. Vous lancez alors e l’ex´cution de votre programme (pour obtenir l’affichage du texte Bonjour ` tous ! ) en tapant la commande e a java Bonjour Notez bien qu’` la commande javac on donne un nom de fichier (Bonjour.java), tandis qu’` la commande a a java on doit donner un nom de classe (Bonjour ). • Quel que soit le syst`me d’exploitation que vous utilisez, vous pouvez vous constituer un environnement e de travail plus agr´able en installant le JDK puis un petit ´diteur de textes orient´ Java. Vous en trouverez e e e plusieurs sur le web, par exemple Jext, ` l’adresse http://www.jext.org/. a
c H. Garreta, 2000-2006

7

´ 2 CONSIDERATIONS LEXICALES

• Vous obtenez un confort incomparablement sup´rieur si, apr`s JDK, vous installez Eclipse, un puise e sant IDE (environnement de d´veloppement int´gr´) avec de nombreuses fonctionnalit´s pour aider ` la e e e e a programmation en Java, telles que la compl´tion automatique des expressions tap´es, la compilation au fur e e et ` mesure de la frappe, la suggestion de la mani`re de corriger les erreurs, etc. Vous pouvez librement a e t´l´charger Eclipse ` l’adresse http://www.eclipse.org/ ee a • Enfin, si vous voulez programmer des interfaces graphiques rien qu’en jouant de la souris1 vous pouvez gratuitement installer netBeans de Sun (http://www.netbeans.org/) ou JBuilder Foundation de Borland (http://www.borland.fr/jbuilder/).

2
2.1

Consid´rations lexicales e
Jeu de caract`res e

Java utilise le codage des caract`res UTF-16, une impl´mentation de la norme Unicode, dans laquelle e e chaque caract`re est repr´sent´ par un entier de 16 bits, ce qui offre 65 536 possibilit´s au lieu des 128 (ASCII) e e e e ou 256 (Latin-1) impos´s par d’autres langages de programmation. Il y a donc de la place pour la plupart e des alphabets nationaux ; en particulier, tous les caract`res fran¸ais y sont repr´sent´s sans cr´er de conflit e c e e e avec des caract`res d’autres langues. e Cela vaut pour les donn´es manipul´es par les programmes (caract`res et chaˆ e e e ınes) et aussi pour les programmes eux-mˆmes. Il est donc possible en Java de donner aux variables et aux m´thodes des noms e e accentu´s. Mais on ne vous conseille pas d’utiliser cette possibilit´, car vous n’ˆtes jamais ` l’abri de devoir e e e a un jour consulter ou modifier votre programme ` l’aide d’un ´diteur de textes qui ne supporte pas les accents. a e

2.2

Commentaires
int nbLignes; // nombre de variables du syst`me e

Il y a trois sortes de commentaires. D’abord, un texte compris entre // et la fin de la ligne :

Ensuite, un texte compris entre /* et */, qui peut s’´tendre sur plusieurs lignes : e /* Recherche de l’indice ik du "pivot maximum" c.-`-d. tel que k <= ik < nl et a a[ik][k] = max { |a[k][k]|, ... |a[nl - 1][k]| } */ Enfin, cas particulier du pr´c´dent, un texte compris entre /** et */ est appel´ commentaire de documene e e tation et est destin´ ` l’outil javadoc qui l’exploite pour fabriquer la documentation d’une classe ` partir de ea a son texte source2 . Cela ressemble ` ceci (un tel commentaire est associ´ ` l’´l´ment, ici une m´thode, qu’il a ea ee e pr´c`de) : e e /** * R´solution d’un syst`me lin´aire e e e * * @param a Matrice du syst`me e * @param x Vecteur solution du syst`me e * @return <tt>true</tt> si la matrice est inversible, <tt>false</tt> sinon. * @author Henri G. */ public boolean resolution(double a[][], double x[]) { ...

2.3

Identificateurs

Les r`gles de formation des identificateurs sont les mˆmes que dans le langage C, compte tenu des e e remarques du § 2.1. La note Java Code Conventions 3 fait les recommandations suivantes ` propos de la a mani`re de nommer les entit´s des programmes : e e
vous d´butez en Java nous vous d´conseillons de commencer par l`. e e a exemple, la documentation en ligne de l’API, ce gigantesque document hypertexte qui est le compagnon ins´parable e de tout programmeur Java, est automatiquement produit au moyen de javadoc ` partir des fichiers sources de la biblioth`que a e standard. 3 On peut t´l´charger cette note depuis le site http://java.sun.com/docs/codeconv/ ee
2 Par 1 Si

8

c H. Garreta, 2000-2006

´ 2 CONSIDERATIONS LEXICALES

2.4

Constantes litt´rales e

1. Les diff´rents identificateurs, s´par´s par des points, qui forment un nom de paquet sont ´crits princie e e e palement en minuscules, surtout le premier : java.awt.event, monprojet.mesoutils. 2. Le nom d’une classe ou d’une interface est fait d’un mot ou de la concat´nation de plusieurs mots. Chae cun commence par une majuscule, y compris le premier : Point, AbstractListModel, ListeDeNombres, etc. 3. Les noms des m´thodes commencent par une minuscule. Lorsqu’ils sont form´s par concat´nation de e e e mots, chacun sauf le premier commence par une majuscule : toString(), vitesseDuVent(...), etc. 4. La mˆme r`gle vaut pour les noms des variables, aussi bien les variables de classe et d’instance que les e e arguments et les variables locales des m´thodes : i, nombreDeMembres, etc. e a e ee 5. Enfin, les constantes de classe (c’est-`-dire les variables static final) sont ´crites de pr´f´rence en majuscules : Integer.MAX VALUE, TextField.LEFT ALIGNMENT, etc.

2.4

Constantes litt´rales e

Les r`gles pour l’´criture des constantes sont les mˆmes qu’en C, avec les additions suivantes : e e e 1. Caract`res. Les caract`res non imprimables peuvent ˆtre repr´sent´s aussi par la s´quence \uxxxx, o` e e e e e e u xxxx sont les quatre chiffres hexad´cimaux du code num´rique du caract`re. Par exemple, la chaˆ e e e ıne suivante comporte les quatre caract`res A, espace (code 32, ou 20 en hexad´cimal), B et ~ (code 241, e e n ou F1 en hexad´cimal) : e "A\u0020B\u00F1" // la cha^ne de quatre caract`res "A B~" ı e n 2. Entiers. Une constante litt´rale enti`re est suppos´e ˆtre du type int, c’est-`-dire cod´e sur 32 bits, e e e e a e sauf si elle est trop grande pour ˆtre repr´sent´e dans ce type ; elle appartient alors au type long. La e e e lettre L ou l indique que la constante doit ˆtre consid´r´e comme appartenant au type long et cod´e e ee e sur 64 bits, mˆme si sa valeur est petite. Exemple : e 1234 1234L // une valeur du type int // une valeur du type long

Dans l’initialisation d’une variable de type long par une valeur de type int le compilateur ins`re e automatiquement la conversion n´cessaire. On peut alors se demander dans quelle circonstance on est e gˆn´ par le fait qu’une valeur enti`re soit consid´r´e int au lieu de long. Le probl`me r´side le plus e e e ee e e souvent dans l’incapacit´ de repr´senter les r´sultats des op´rations. Par exemple, l’affectation e e e e long u = 4000000 * 6000000; // ERREUR (d´bordement de la muliplication) e est certainement incorrecte, car 4000000 et 6000000 seront consid´r´s de type int, et donc la multiee plication et son r´sultat aussi ; or ce r´sultat ne peut pas ˆtre repr´sent´ dans les 32 bits d’un entier e e e e e (voyez le tableau 1, page 10). Bien entendu, que ce r´sultat soit ensuite affect´ ` une variable de type e ea long ne le rend pas juste. De plus, s’agissant d’un d´bordement en arithm´tique enti`re, aucune erreur e e e ne sera signal´e ni ` la compilation ni ` l’ex´cution (mais le r´sultat sera faux). e a a e e Pour corriger ce probl`me il suffit placer l’op´ration et son r´sultat dans le type long ; ` cause de la e e e a r`gle  du plus fort 4 , il suffit pour cela qu’un des op´randes le soit : e e long u = 4000000L * 6000000; // OK 3. Flottants. Une constante flottante (c’est-`-dire un nombre comportant un point et/ou un exposant, a comme 1.5, 2e-12 ou -0.54321E3) repr´sente une valeur du type double. Les suffixes f ou F peuvent e ˆtre utilis´s pour pr´ciser le type de ce qui est ´crit. e e e e Ainsi, une simple affectation telle que float x = 1.5; // ERREUR (discordance de types) est signal´e comme une erreur. Correction : e float x = 1.5f; On peut aussi laisser la constante ˆtre du type float et en demander explicitement la conversion : e float x = (float) 1.5; 4. Bool´ens. Le type boolean comporte deux valeurs, repr´sent´es par les constantes litt´rales suivantes, e e e e qui sont aussi des mots r´serv´s : e e false // la valeur bool´enne faux e true // la valeur bool´enne vrai e
4 Grosso modo cette r`gle dit que si les deux op´randes d’une op´ration ne sont pas de mˆme type alors le plus  fort  e e e e (c.-`-d. le plus ´tendu, ou le plus pr´cis, ou un flottant face ` un entier, etc.) provoque la conversion vers son type de l’autre a e e a op´rande et le placement dans ce type de l’op´ration et de son r´sultat. Cette r`gle s’applique dans la grande majorit´ des e e e e e op´rations binaires. e

c H. Garreta, 2000-2006

9

3

TYPES

3
3.1

Types
Les types de donn´es de Java e
Les types des donn´es manipul´es par les programmes Java se r´partissent en deux cat´gories : e e e e – les types primitifs, – les objets, c’est-`-dire les tableaux et les classes. a La table 1 r´capitule ce qu’il faut savoir sur les huit types primitifs. e Tab. 1 – Les types de Java Type byte short int long description Octet Entier court Entier Entier long taille ensemble de valeurs Nombres entiers 8 bits de −128 ` 127 a 16 bits de −32 768 ` 32 767 a 32 bits de −2 147 483 648 ` 2 147 483 647 a 64 bits de −263 ` 263 − 1 a Nombres flottants de −3, 4028235×10+38 ` −1, 4×10−45 , a 32 bits 0 et de 1, 4×10−45 ` 3, 4028235×10+38 a de −1, 7976931348623157×10+308 64 bits a ` −4, 9×10−324 , 0 et de 4, 9×10−324 ` 1, 7976931348623157×10+308 a Autres types 16 bits Tous les caract`res Unicode e 1 bit false, true val. init.

0

float

Flottant simple pr´cision e

0.0

double

Flottant double pr´cision e

char boolean

Caract`re e Valeur bool´enne e

’\u0000’ false

On notera que : – tous les types entiers sont  sign´s  (c’est-`-dire repr´sentent des ensembles contenant des nombres e a e positifs et n´gatifs), e – portabilit´ oblige, on n’a pas reproduit en Java la bourde consistant ` laisser la taille des int ` e a a l’appr´ciation de chaque compilateur. e

3.2

Conversions entre types primitifs

La question de la compatibilit´ et de la convertibilit´ entre expressions de types primitifs est r´gl´e en e e e e Java selon un petit nombre de principes simples : 1. Toute valeur d’un type num´rique est convertible vers tout autre type num´rique, selon les modalit´s e e e expliqu´es ci-apr`s. e e 2. Les conversions entre types num´riques sans risque de perte d’information (c’est-`-dire : entier ou e a caract`re vers entier de taille sup´rieure, flottant vers flottant de taille sup´rieure, entier ou caract`re e e e e vers flottant) sont faites, ` la compilation et ` l’ex´cution, sans requ´rir aucune indication particuli`re. a a e e e Par exemple, si les variables qui y figurent ont les types que leurs noms sugg`rent, les affectations e suivantes ne soul`vent le moindre probl`me, ni ` la compilation ni ` l’ex´cution : e e a a e unInt = unChar; unDouble = unFloat; unFloat = unInt; a e 3. Les conversions avec risque de perte d’information (c’est-`-dire : entier vers entier de taille inf´rieure, flottant vers flottant de taille inf´rieure, flottant vers entier) sont refus´es par le compilateur : e e unByte = unInt; // ERREUR

sauf si le programmeur a explicitement utilis´ l’op´rateur de transtypage (ou cast operator ) : e e unByte = (byte) unInt; // Ok

Attention. Une expression comme la pr´c´dente est plac´e sous la responsabilit´ du programmeur, e e e e seul capable de garantir que la valeur de unInt pourra, au moment de l’affectation ci-dessus, ˆtre e repr´sent´e dans une variable de type byte. Cette condition ne peut pas ˆtre v´rifi´e ` la compilation e e e e e a 10
c H. Garreta, 2000-2006

3 TYPES

3.3 R´f´rences ee

et elle ne l’est pas non plus ` l’ex´cution. Cela peut conduire ` des troncations inattendues, non a e a signal´es, et donc ` des r´sultats absurdes. Par exemple, le programme suivant affiche la valeur 1 : e a e int unInt = 257; byte unByte = (byte) unInt; System.out.println(unByte); // DANGER

4. Puisque les caract`res sont repr´sent´s par des nombres, le type char est consid´r´ comme num´rique, e e e ee e pour ce qui nous occupe ici. Cependant, la conversion de n’importe quel type num´rique vers le type e char est consid´r´e comme susceptible d’entraˆ ee ıner une perte de pr´cision ; cela oblige le programmeur e a ` utiliser l’op´rateur de transtypage. e 5. La conversion d’une expression bool´enne vers un type num´rique, ou r´ciproquement, est ill´gale et e e e e rejet´e par le compilateur. Les expressions  ` la mode de C  suivantes sont donc erron´es en Java : e a e if (unInt) ... unInt = (x < y); // ERREUR (n´cessite une conversion int → boolean) e // ERREUR (n´cessite une conversion boolean → int) e

Bien entendu, ces conversions peuvent ˆtre obtenues facilement : e – conversion bool´en → entier : e (unBooleen ? 1 : 0) – conversion nombre → bool´en : (unNombre != 0) e

3.3
3.3.1

R´f´rences ee
S´mantique des valeurs et s´mantique des r´f´rences e e ee

Les expressions de types primitifs ont la s´mantique des valeurs. Cela signifie que si e est une expression e d’un type primitif T, alors ` tout endroit5 o` elle figure e repr´sente une valeur du type T. Par exemple, si a u e unInt est une variable enti`re contenant la valeur 12, alors les trois expressions 12, unInt et unInt/4 + 9 e ont la mˆme signification : la valeur enti`re 12. e e Notez que des trois expressions 12, unInt et unInt/4 + 9, une seule est associ´e ` un emplacement dans e a la m´moire (la seconde). Si une expression a la s´mantique des valeurs elle n’est pas forc´ment un renvoi ` e e e a un objet existant dans la m´moire. e Tous les autres types de Java, c’est-`-dire les tableaux et les classes, ont la s´mantique des r´f´rences. a e ee Cela veut dire qu’une expression d’un type tableau ou classe est, en toute circonstance, une r´f´rence sur un ee objet existant dans la m´moire6 . e
unInt 12 unPoint 10 20

Fig. 1 – S´mantique des valeurs et s´mantique des r´f´rences e e ee On peut voir une r´f´rence comme un pointeur7 g´r´ de mani`re interne par Java. Par exemple, si Point ee ee e est une classe form´e de deux entiers x et y (les coordonn´es d’un point) : e e class Point { int x, y; ... } et si unPoint est une variable de type Point correctement initialis´e : e Point unPoint = new Point(10, 20); alors il convient de se repr´senter la variable unPoint et sa valeur comme le montre la figure 1. e Bien noter qu’en Java aucun signe ne rappelle, lors de l’emploi d’une expression de type r´f´rence, qu’il ee s’agit d’une sorte de pointeur (c’est-`-dire, on n’utilise pas les op´rateurs * ou -> de C). Puisque les objets a e sont toujours acc´d´s par r´f´rence, une expression comme e e ee
5 Comme dans tous les langages qui ont la notion d’affectation, il y a une exception : plac´e au membre gauche d’une e affectation, une expression ne repr´sente pas une valeur mais un emplacement de la m´moire. e e 6 Plus pr´cis´ment, les r´f´rences sont des renvois ` la heap, c’est-`-dire la partie de la m´moire dans laquelle se font les e e ee a a e allocations dynamiques (par new, le moyen de cr´er un tableau ou un objet). e 7 Attention, abus de langage ! Imaginer un pointeur est sans doute une bonne mani`re d’appr´hender une r´f´rence, mais il e e ee ne faut pas oublier qu’en Java on s’interdit de parler de pointeurs, car le programmeur n’est pas cens´ connaˆ e ıtre les d´tails du e proc´d´ par lequel Java d´signe de mani`re interne les objets qu’il cr´e dans la m´moire. e e e e e e

c H. Garreta, 2000-2006

11

3.4 Tableaux

3

TYPES

unPoint.x est sans ambigu¨ e ; elle signifie : le champ x de l’objet r´f´renc´ par unPoint. ıt´ ee e 3.3.2 R´f´rence “sur rien” ee

La valeur particuli`re null indique qu’une variable d’un type r´f´rence n’est pas une r´f´rence ` un objet e ee ee a valide. C’est la valeur qu’il convient de donner ` une telle variable lorsque l’objet r´f´renc´ n’existe pas a ee e encore, ou bien lorsque la variable a cess´ d’ˆtre utile en tant que r´f´rence sur un objet : e e ee Point unPoint = null; // d´claration et initialisation de unPoint e ... // ici, unPoint ne repr´sente pas un objet valide e unPoint = new Point(10, 20); ... // ici on peut travailler avec l’objet unPoint unPoint = null; ... // ` nouveau, unPoint n’est pas un objet valide a Le compilateur se charge d’initialiser ` null les variables d’instance et de classe d’un type objet qui n’ont a pas d’initialisation explicite. Les variables locales ne sont pas initialis´es8 . e 3.3.3 Cas des param`tres des m´thodes e e

Les explications pr´c´dentes sont valables pour toutes sortes d’expressions, quels que soient les contextes e e o` elles apparaissent (sauf les membres gauches des affectations, qui ´chappent ` cette discussion). Cela vaut u e a en particulier pour les param`tres effectifs des appels des m´thodes ; par cons´quent : e e e – un param`tre d’un type primitif est  pass´ par valeur  : lors de l’appel de la m´thode, la valeur du e e e param`tre effectif est copi´e dans le param`tre formel correspondant, qui est une variable locale de la e e e m´thode ; en particulier, si un tel param`tre effectif est une variable, l’appel de la m´thode ne pourra e e e pas en changer la valeur, quoi qu’elle fasse au param`tre formel correspondant ; e – un param`tre d’un type tableau ou classe est  pass´ par r´f´rence9  : lors de l’appel de la m´thode, le e e ee e param`tre formel est initialis´ avec une r´f´rence sur le tableau ou l’objet que repr´sente le param`tre e e ee e e effectif ; il en d´coule que le tableau ou l’objet en question pourra ˆtre r´ellement modifi´ par les actions e e e e que la m´thode fera sur le param`tre formel. e e

3.4
3.4.1

Tableaux
D´claration et initialisation e

Tous les tableaux utilis´s en Java sont des tableaux dynamiques. Cela veut dire que le nombre d’´l´ments : e ee ee ıtre e – n’est pas un ´l´ment constitutif du type du tableau et n’a pas besoin d’apparaˆ dans sa d´claration, – n’est pris en compte qu’au moment de l’ex´cution du programme, mˆme s’il est connu pendant la e e compilation. Il y a deux syntaxes rigoureusement ´quivalentes pour d´clarer un tableau tab dont les ´l´ments sont, e e ee par exemple, des int : int tab[]; et int[] tab; // tab d´signe un tableau de int e Dans l’un et l’autre cas, la variable tab ainsi d´clar´e est une r´f´rence et, pour le moment, elle ne e e ee r´f´rence rien. Si les expressions pr´c´dentes sont la d´claration d’une variable d’instance ou de classe, tab ee e e e aura ´t´ initialis´e avec la valeur null. S’il s’agit de la d´claration d’une variable locale, tab est ind´termin´e ee e e e e et il aurait probablement ´t´ plus sage de la compl´ter par une initialisation explicite, comme : ee e int[] tab = null; // tab d´signe un tableau de int e // tab d´signe un tableau de int e

Le tableau lui-mˆme commence effectivement ` exister lors de l’appel de l’op´rateur new : e a e tab = new int[nombreDeComposantes] ;
8 Les variables locales ne sont pas initialis´es mais toute utilisation d’une variable locale susceptible de ne pas avoir ´t´ e e e initialis´e est d´tect´e par le compilateur et signal´e comme une erreur. e e e e 9 En toute rigueur on peut dire que le passage d’un objet ou d’un tableau comme param`tre d’une m´thode se traduit par e e le passage par valeur de la r´f´rence ` l’objet ou au tableau en question. ee a

12

c H. Garreta, 2000-2006

3 TYPES

3.4 Tableaux

o` nombreDeComposantes est une expression, d’un type entier, qui d´finit la taille du tableau. u e Note. On peut joindre dans la mˆme expression la d´claration de la variable et la cr´ation du tableau e e e correspondant : int[] tab = new int[nombreDeComposantes] ; cela est plus commode, car il y a moins de choses ` ´crire, et plus fiable, car on ´limine la p´riode pendant ae e e laquelle la variable existe sans r´f´rencer un tableau. En g´n´ral c’est faisable, car Java permet d’´crire les ee e e e d´clarations l` o` on veut. e a u 3.4.2 Acc`s aux ´l´ments e ee

Une fois le tableau cr´´, on acc`de ` ses composantes en utilisant la mˆme notation qu’en C : ee e a e tab[i ] o` i est une expression de type entier10 . Pour que l’expression ci-dessus soit l´gitime il faut et il suffit que u e 0 ≤ i < nombreDeComposantes. Autrement dit, les ´l´ments du tableau pr´c´dent sont ee e e t[0], t[1], ... t[nombreDeComposantes−1] Le sch´ma de la figure 2 est la bonne mani`re de se repr´senter l’acc`s tab[i ] : e e e e
+i ... tab tab[0] tab[i] tab[N-1]

Fig. 2 – Un tableau de N ´l´ments ee Taille effective d’un tableau. L’expression unTableau.length permet de connaˆ le nombre d’´l´ıtre ee ments d’un tableau. Cela est bien utile pour ´crire des traitements de tableaux dont la taille est inaccessible e lors de la compilation. Par exemple, le programme suivant affiche les arguments ´crits sur la ligne de come mande (` la suite des deux mots java ArgumentsLigneCommande) quel que soit leur nombre : a class ArgumentsLigneCommande { public static void main(String[] args) { for (int i = 0; i < args.length; i++) System.out.println(args[i]); } } ˆ Controle de l’indice. Lors de l’acc`s ` un ´l´ment d’un tableau, Java contrˆle toujours la validit´ de e a ee o e l’indice. Autrement dit, lors de l’´valuation d’une expression comme tab[i] les deux erreurs possibles les e plus fr´quentes sont d´tect´es et d´clenchent une exception : e e e e – NullPointerException, pour indiquer que tab = null, c’est-`-dire que la variable tab n’est pas a une r´f´rence valide (en clair, le plus souvent : la variable tab a bien ´t´ d´clar´e, mais le tableau ee ee e e correspondant n’a pas encore ´t´ cr´´), e e ee – ArrayIndexOutOfBoundsException, pour indiquer que la condition 0 ≤ i < tab.length n’est pas satisfaite. 3.4.3 Tableaux ` plusieurs indices a

Les tableaux ` plusieurs indices sont dynamiques eux aussi. A titre d’exemple, examinons le cas des a matrices (tableaux ` deux indices). Les d´clarations suivantes sont tout ` fait ´quivalentes : a e a e int mat[][]; int[] mat[]; int[][] mat; // mat d´signe un tableau de int ` deux indices e a // " " " " " " // " " " " " "

Ces trois expressions introduisent mat comme une variable de type tableau ` deux indices de int. On a peut les lire  mat est un tableau-`-un-indice de tableaux-`-un-indice de int . Aucun tableau n’existe pour a a le moment ; selon le contexte, la valeur de mat est soit null, soit ind´termin´e. e e La matrice commence ` exister lorsqu’on appelle l’op´rateur new. Par exemple, de la mani`re suivante : a e e
10 Ne

pas oublier qu’une expression de type char est toujours spontan´ment convertible dans le type int. e

c H. Garreta, 2000-2006

13

3.4 Tableaux

3

TYPES

mat = new int[NL][NC]; o` NL et NC sont des expressions enti`res d´finissant le nombre de lignes et de colonnes de la matrice souhait´e. u e e e L’instruction pr´c´dente a le mˆme effet que le code suivant : e e e mat = new int[NL][]; for (int i = 0; i < NL; i++) mat[i] = new int[NC]; mat[i][j] L’´valuation de cette expression d´clenchera la v´rification successive des conditions : e e e 1 ˚ 2 ˚ 3 ˚ 4 ˚ mat = null 0 ≤ i < mat.length mat[i] = null 0 ≤ j < mat[i].length // un tableau de NL [r´f´rences de] tableaux e e // un tableau de NC int

L’acc`s aux ´l´ments d’un tableau ` deux indices se fait comme en C : e ee a

Ainsi, un tableau ` deux indices dont les ´l´ments sont – par exemple – des int est r´alis´ en Java comme a ee e e un tableau ` un indice dont les ´l´ments sont des [r´f´rences sur des] tableaux ` un indice dont les ´l´ments a ee ee a ee sont des int. La figure 3 repr´sente cette configuration. e
m +i m[0] ... m[i] ... ... m[NL-1][NC-1] m[NL-1]

m[NL-1][0] +j ... m[i][0] ... m[0][0] m[0][NC-1] ... m[i][j]

m[i][NC-1]

Fig. 3 – Acc`s ` m[i][j], m ´tant une matrice ` NL lignes et NC colonnes e a e a Note. Java permet de manipuler, sous la mˆme syntaxe, des tableaux rectangulaires et des tableaux qui e ne le sont pas ; il suffit pour cela de faire soi-mˆme l’initialisation des lignes. Par exemple, le code suivant e cr´e une matrice triangulaire inf´rieure de dimension N dans laquelle chaque ligne a la longueur requise par e e son rang : float[][] mt = new float[N][]; for (int i = 0; i < N; i++) mt[i] = new float[i + 1]; 3.4.4 Tableaux initialis´s et tableaux anonymes. e

Deux syntaxes permettent d’indiquer un ensemble de valeurs devant remplir un tableau. Dans l’une comme dans l’autre, le nombre de valeurs fournies d´termine la taille du tableau. Dans la premi`re forme, e e analogue ` celle qui existe en C, on initialise le tableau au moment de sa d´claration : a e int[] tab = { 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024 }; La deuxi`me forme permet de cr´er un tableau garni de valeurs, mais pas forc´ment ` l’endroit de la e e e a d´claration du tableau : e int[] tab; ... tab = new int[] { 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024 }; 14
c H. Garreta, 2000-2006

3 TYPES

3.5

Conversions entre types tableaux ou classes

En fait, cette deuxi`me syntaxe permet de cr´er des tableaux garnis anonymes, ce qui est bien utile e e lorsqu’un tableau ne sert qu’une seule fois. Par exemple, si la variable tab avait ´t´ d´clar´e et construite ee e e dans l’unique but de figurer dans l’appel d’une m´thode (cela arrive) comme dans : e int tab[] = { 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024 }; traiter(tab); alors il aurait ´t´ plus simple de ne pas d´clarer de variable tab : ee e traiter(new int[] { 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024 }); Cas des matrices. Les mˆmes m´canismes permettent de cr´er des tableaux multidimensionnels garnis, e e e y compris lorsque leurs lignes ne sont pas de mˆme longueur : e int[][] mat = { { 11, 12, 13, 14, 15 }, { 20, 21, 22, 23, 24, 25, 26, 27, 28, 29 }, { }, null, { 51, 52, 53 } }; Notez la diff´rence entre la troisi`me et la quatri`me ligne du tableau pr´c´dent : la troisi`me existe mais e e e e e e est vide (c’est un tableau de 0 ´l´ments), tandis que la quatri`me n’existe pas. ee e Note. Contrairement ` ce qui se passe dans d’autres langages, en Java l’initialisation des tableaux se a fait pendant l’ex´cution du programme, non pendant la compilation. Premi`re cons´quence : initialiser un e e e tableau par une liste de constantes est plus commode, mais pas notablement plus efficace, que la collection d’affectations qui aurait le mˆme effet. Par exemple, la d´claration e e int[] tab = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; a le mˆme effet – mais est plus agr´able ` ´crire et ` lire – que la s´quence e e ae a e int[] tab = new int[10]; tab[0] = 1; tab[1] = 2; ... tab[9] = 10; Deuxi`me cons´quence : les expressions avec lesquelles on initialise des tableaux n’ont pas ` ˆtre n´cessaie e ae e rement des constantes. Des expressions quelconques, ´valu´es pendant l’ex´cution du programme, peuvent y e e e figurer : int[] tab = { x + 1, 2 * x + 1, 4 * x + 1, (int) Math.atan(y) };

3.5

Conversions entre types tableaux ou classes

Attention, section indigeste ! Pour bien comprendre cette section il faut connaˆ les notions de ıtre classe et d’h´ritage. e Le type statique d’une expression E est d´termin´ par les d´clarations des variables apparaissant dans e e e E, ainsi que par les constantes, op´rateurs et m´thodes qui forment E. Par exemple, avec la d´claration e e e Object e = "Bonjour"; le type statique de e est Object. Le type dynamique d’une expression E est celui qui r´sulte des valeurs effectives des variables intervee nant dans E, ainsi que des constantes, op´rateurs et m´thodes constituant E. Par exemple, avec la mˆme e e e d´claration ci-dessus, le type dynamique de e est String. e Dans ces conditions, soit E une expression, Ts son type statique, Td son type dynamique ` un instant a donn´ et Tf un deuxi`me type. Nous nous int´ressons ` la conversion de la valeur de E vers le type Tf , e e e a lorsque Ts et Tf ne sont pas identiques et que ce ne sont pas deux types primitifs. Voici ce qu’il faut savoir a ` ce sujet : e 1. Aucune conversion n’est possible entre un type primitif et un type tableau ou classe, ou r´ciproquement. Dans les points suivants on suppose donc que Ts et Tf sont tous les deux des types tableaux ou classes. e 2. La conversion de la valeur de E vers le type Tf , lorsqu’elle est l´gitime, est une conversion sans travail : elle se r´duit ` consid´rer l’objet d´sign´ par E comme ayant le type Tf , sans que cet objet subisse la e a e e e moindre modification (ne pas oublier que E ne repr´sente qu’une r´f´rence sur un objet). e ee
c H. Garreta, 2000-2006

15

3.6 Copie et comparaison des objets

3

TYPES

3. Sauf le cas particulier examin´ au point 7, la conversion de E vers le type Tf n’est accept´e ` la e e a compilation que si Ts et Tf sont des classes apparent´es, c’est-`-dire des classes dont l’une est souse a classe, directe ou indirecte, de l’autre (autrement dit, Ts et Tf sont sur une mˆme branche de l’arbre e d’h´ritage). e 4. Si Tf est une super-classe, directe ou indirecte, de Ts la conversion de E vers le type Tf est correcte et ne requiert aucune indication particuli`re. On peut appeler cela une g´n´ralisation du type de E. e e e Par exemple, si la classe Article est une super-classe de la classe ArticleSportif, elle-mˆme supere classe de Velo, qui est super-classe de VTT, alors l’affectation suivante est correcte (elle contient la  conversion  d’un objet de type Velo vers le type Article) : Article unArticle = new Velo( ...caract´ristiques d’un v´lo... ); e e L’objet Velo cr´´ ci-dessus n’est en rien modifi´ par son affectation ` la variable unArticle mais, aussi ee e a longtemps qu’il est acc´d´ ` travers cette variable, il est consid´r´ comme un Article, non comme une e ea ee Velo (Article est un concept plus g´n´ral que Velo). e e 5. Si Tf est une sous-classe, directe ou indirecte, de Ts , la conversion de E vers le type Tf est une sorte de particularisation du type de E et : – n’est accept´e ` la compilation que si on utilise explicitement l’op´rateur de transtypage. Exemple : e a e Article unArticle; Velo unVelo; ... unVelo = unArticle; unVelo = (Velo) unArticle;

// Erreur ` la compilation a // Ok (pour la compilation)

– n’est correcte, ` l’ex´cution, que si Td est une sous-classe, directe ou indirecte, de Tf . Exemple : a e unArticle = new VTT( ... caract´ristiques d’un VTT ... ); e ... unVelo = (Velo) unArticle; // Ok, un VTT peut ^tre vu comme une Velo e – d´clenche lorsqu’elle est incorrecte, ` l’ex´cution, l’exception ClassCastException. Exemple : e a e unArticle = new BallonDeBasket( ... caract´ristiques d’un ballon ... ); e ... unVelo = (Velo) unArticle; // d´clenche ClassCastException (un ballon e // ne peut pas ^tre vu comme un v´lo) e e 6. Aucune conversion n’est accept´e entre types tableaux diff´rents (mais n’oubliez pas que le nombre e e d’´l´ments d’un tableau ne fait pas partie de la d´finition de son type : pour Java, un tableau de 10 ee e entiers et un tableau de 20 entiers ont exactement le mˆme type). e 7. Enfin, les seules conversions correctes entre une classe et un tableau concernent n´cessairement la classe e Object, super-classe de toutes les classes et de tous les tableaux. Exemple : int[] uneTable = new int[nombre]; ... Object unObjet = uneTable; // Ok (Object super-classe de tous les objets) ... uneTable = (int[]) unObjet; // Ok (d’apr`s le type dynamique de unObjet) e ´ ´ En resume. L’essentiel ` retenir des r`gles pr´c´dentes : a e e e – ` la compilation, la conversion d’une expression de type objet ou tableau est accept´e sauf si on a e peut pr´dire avec certitude qu’elle ´chouera ` l’ex´cution ; elle requiert parfois l’emploi explicite de e e a e l’op´rateur de transtypage ; e a e oe e – ` l’ex´cution, la conversion du type d’un objet est toujours contrˆl´e ; elle n’est accept´e que si elle correspond ` une g´n´ralisation du type dynamique de l’objet. a e e

3.6

Copie et comparaison des objets

Attention, section indigeste ! On mentionne ici des concepts, concernant notamment les superclasses et les interfaces, qui ne seront expliqu´es qu’` la section 7. e a 16
c H. Garreta, 2000-2006

3 TYPES

3.6

Copie et comparaison des objets

3.6.1

Copie

Une cons´quence du fait que les objets et les tableaux sont toujours manipul´s ` travers des r´f´rences est e e a ee que l’affectation ne fait pas une vraie duplication de la valeur d’une expression, mais uniquement ce qu’on appelle une copie superficielle. Par exemple, consid´rons : e Point p = Point(10, 20); Point q = p; A la suite de l’affectation pr´c´dente on peut penser qu’on a dans q une duplication de p. Il n’en est rien, e e il n’y a pas deux points, mais un seul point r´f´renc´ par deux variables distinctes, comme le montre la figure ee e 4. Dans beaucoup de situations cette mani`re de  copier  est suffisante, mais elle pr´sente un inconv´nient e e e ´vident : chaque modification de la valeur de p se r´percute automatiquement sur celle de q. e e
q p

10 20

Fig. 4 – Copie de la r´f´rence sans duplication de l’objet (copie superficielle) ee Pour obtenir la duplication effective d’un objet, il faut appeler sa m´thode clone. Tous les objets sont e cens´s poss´der une telle m´thode11 car elle est d´clar´e dans la classe Object, la super-classe de toutes les e e e e e classes. Il appartient ` chaque classe de red´finir la m´thode clone, pour en donner une version adapt´e au a e e e rˆle et aux d´tails internes de ses instances. o e La m´thode clone rend un r´sultat de type Object12 , il faut donc l’utiliser comme suit (la d´finition e e e mˆme de la m´thode clone garantit que la conversion du r´sultat de p.clone() vers le type Point est e e e l´gitime) : e Point q = (Point) p.clone();
q p

10 20

10 20

Fig. 5 – Duplication effective d’un objet (copie profonde) Si clone a ´t´ d´finie comme il faut (dans la classe Point) l’expression pr´c´dente affecte ` q une copie ee e e e a profonde de l’objet qui est la valeur de p, comme montr´ sur la figure 5. e Cas des tableaux La m´thode clone fonctionne en particulier dans le cas des tableaux : si t est un tableau, le r´sultat e e de t.clone() est un tableau de mˆme type et mˆme taille que t dont les ´l´ments ont ´t´ initialis´s par e e ee ee e affectation de ceux de t. Par exemple, les instructions suivantes cr´ent deux tableaux disjoints, l’un r´f´renc´ par les variables e ee e tab1 et tab2 et l’autre r´f´renc´ par tab3 : ee e int[] tab1 = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 }; int[] tab2 = tab1; // ceci n’est pas une duplication int[] tab3 = (int[]) tab1.clone(); // ceci est une duplication effective
11 La m´thode clone existe dans tous les objets mais, si le programmeur n’a pas pris certaines pr´cautions (consistent e e soit ` d´finir explicitement la m´thode clone, soit ` d´clarer que la classe impl´mente l’interface Cloneable), une exception a e e a e e CloneNotSupportedException sera lanc´e ` l’ex´cution. e a e 12 C’est regrettable mais n´cessaire, car la d´finition initiale de clone, dans la classe Object, ne peut ˆtre d´clar´e rendant e e e e e autre chose qu’un Object ; ensuite les r´d´finitions de clone dans les sous-classes doivent rendre strictement le mˆme r´sultat, e e e e c’est une contrainte du m´canisme de la red´finition des m´thodes. e e e

c H. Garreta, 2000-2006

17

3.6 Copie et comparaison des objets

3

TYPES

Attention. Le r´sultat rendu par t.clone() est un tableau nouvellement cr´´, de mˆme type et mˆme e ee e e taille que t, dont les ´l´ments sont initialis´s par affectation, non par clonage, de ceux de t. Autrement dit, ee e la m´thode clone pour un tableau n’effectue pas une duplication profonde, mais seulement une duplication e ` un niveau : si les ´l´ments du tableau sont des objets13 le tableau initial et son clone ne sont pas disjoints a ee car ils partagent les mˆmes ´l´ments. Il faut savoir si c’est cela qu’on voulait obtenir. Cette question est e ee reprise dans la section suivante. Note Java 5. La m´thode clone ´tant introduite au niveau de la classe Object, elle est d´clar´e comme e e e e rendant un r´sultat de type Object ce qui, dans le cas des tableaux, obligeait – jusqu’` Java 1.4 – ` l’utiliser e a a munie d’un changement de type : Point[] t = new Point[n]; ... Point[] q = (Point[]) t.clone(); A partir de la version 5 du langage ce n’est plus une obligation. Le traitement sp´cial par le compilateur e de l’appel de clone sur un tableau rend correctes des expressions comme : Point[] t = new Point[n]; ... Point[] q = t.clone(); 3.6.2 D´finir la m´thode clone e e class Point { int x, y; public Point(int a, int b) { x = a; y = b; } public Object clone() { return new Point(x, y); } } On peut ´crire la m´thode clone ` partir de rien, comme ci-dessus, mais on peut aussi all´ger ce travail e e a e et utilisant la m´thode Object.clone() h´rit´e de la classe Object. Pour cela, la classe qu’on ´crit doit e e e e comporter l’´nonc´  implements Cloneable ; 14 sans quoi l’exception CloneNotSupportedException sera e e lanc´e ` l’ex´cution. Exemple : e a e class Rectangle implements Cloneable { Point coinNO, coinSE; public Rectangle(int x1, int y1, int x2, int y2) { coinNO = new Point(x1, y1); coinSE = new Point(x2, y2); } public Object clone() throws CloneNotSupportedException { return super.clone(); // notre version de clone se r´duit ` e a } // la version h´rit´e de la classe Object e e } L’effet de la m´thode Object.clone() est la cr´ation d’un objet de mˆme type que celui qu’on clone, puis e e e l’initialisation de chacune des variables d’instances de l’objet copi´ par affectation de la variable d’instance e correspondante de l’objet original. C’est donc une copie  ` un niveau , moins superficielle que la copie des a r´f´rences, mais ce n’est pas la vraie copie en profondeur : si des membres sont ` leur tour des objets cette ee a sorte de copie ne suffit pas pour faire que l’objet clon´ soit disjoint de l’objet original. e Par exemple, la figure 6 montre ` quoi ressemblerait le couple form´ par un objet de la classe Rectangle, a e d´finie ci-dessus, et son clone, cr´´s par les instructions suivantes : e ee Rectangle p = new Rectangle(10, 20, 30, 40); Rectangle q = (Rectangle) p.clone(); Pour avoir une duplication compl`te la m´thode clone aurait dˆ ˆtre ´crite comme ceci : e e ue e
particulier remarquable : les matrices, qui sont des tableaux de tableaux. Puisque les tableaux sont des objets, il en r´sulte que la m´thode clone ne duplique pas les coefficients des matrices, mˆme lorsque ceux-ci sont d’un type primitif. e e e 14 L’interface Cloneable est enti`rement vide, l’´nonc´ implements Cloneable n’est qu’une marque qui joue le rˆle de  permis e e e o d’utiliser la m´thode Object.clone() . e
13 Cas

La m´thode clone doit ˆtre red´finie dans chaque classe o` cela est utile. Exemple : e e e u

18

c H. Garreta, 2000-2006

3 TYPES

3.6

Copie et comparaison des objets

q

p

coinNO coinSE 10 20 30 40

coinNO coinSE

Fig. 6 – La copie  ` un niveau  que fait Object.clone() a

class Rectangle { Point coinNO, coinSE; ... public Object clone() { return new Rectangle(coinNO.x, coinNO.y, coinSE.x, coinSE.y); } } ou bien, en supposant que la classe Rectangle a un constructeur sans argument : class Rectangle { Point coinNO, coinSE; ... public Object clone() { Rectangle r = new Rectangle(); r.coinNO = (Point) coinNO.clone(); r.coinSE = (Point) coinSE.clone(); return r; } } 3.6.3 Comparaison

Des remarques analogues aux pr´c´dentes peuvent ˆtre faites au sujet de l’op´rateur de comparaison. Si e e e e a et b sont d’un type classe ou tableau, la condition a == b ne traduit pas l’´galit´ des valeurs de a et b, e e mais l’´galit´ des r´f´rences. Autrement dit, cette condition est vraie non pas lorsque a et b sont des objets e e ee ´gaux, mais lorsque a et b sont le mˆme objet. e e Dans beaucoup de situations une telle notion d’´galit´ est trop restrictive. C’est pourquoi tous les objets e e sont cens´s poss´der la m´thode equals qui impl´mente une notion d’´galit´ plus utile. e e e e e e Point p = new Point(10, 20); Point q = new Point(10, 20); ... System.out.println(p == q); System.out.println(p.equals(q)); ...

// ceci affiche false // ceci affiche true (si equals a ´t´ bien d´finie) ee e

La m´thode equals est d´finie une premi`re fois au niveau de la classe Object, o` elle n’est rien de plus e e e u que l’´galit´ des r´f´rences : e e ee class Object { ... public boolean equals(Object o) { return this == o; } } mais chaque classe peut la red´finir pour en donner une version adapt´e au rˆle et aux d´tails internes de e e o e ses instances. Pour notre classe Point voici une premi`re version, pas tr`s juste : e e class Point { ...
c H. Garreta, 2000-2006

19

3.7 Chaˆ ınes de caract`res e

3

TYPES

public boolean equals(Point o) { return x == o.x && y == o.y; } }

// VERSION ERRON´E !!! E

Bien que r´pondant en partie aux besoins, cette version de Point.equals est erron´e car, ` cause du e e a type de l’argument, elle ne constitue pas une red´finition de la m´thode Object.equals(Object o)15 . Voici e e une version plus correcte : class Point { ... public boolean equals(Object o) { return o instanceof Point && x == ((Point) o).x && y == ((Point) o).y; } } Maintenant, la m´thode ci-dessus est une vraie red´finition de Object.equals, mais elle n’est pas encore e e parfaite. Il faut savoir, en effet, que x instanceof K ne signifie pas forc´ment que x est instance de K ( x e est un K ), mais uniquement que x est instance d’une sous-classe de K ( x est une sorte de K ). Ainsi, si on suppose que Pixel est une sous-classe de Point (un Pixel est un Point avec, en plus, l’indication d’une couleur), ` la suite de a Point cePoint = new Point(11, 22); Pixel cePixel = new Pixel(11, 22, "rouge"); la condition  cePoint.equals(cePixel)  sera vraie. Est-ce ce que l’on souhaite ? Probablement non. Voici une nouvelle version de equals qui corrige ce probl`me : e class Point { ... public boolean equals(Object o) { return o != null && o.getClass() == Point.class && ((Point) o).x == x && ((Point) o).y == y; } } Toute surcharge de la m´thode Object.equals(Object o) doit ˆtre une relation d’´quivalence (c’est-`e e e a dire une relation binaire r´flexive, sym´trique et transitive) telle que si x = null alors x.equals(null) est e e faux. De plus, bien que ce ne soit pas une obligation, il semble naturel de faire en sorte que si y = x.clone() alors x == y soit certainement faux et x.equals(y) soit certainement vrai.

3.7
3.7.1

Chaˆ ınes de caract`res e
Les classes String et StringBuffer

Deux classes permettent de repr´senter les chaˆ e ınes de caract`res : e – les instances de la classe String sont invariables ; une fois cr´´es, les caract`res dont elles sont form´es ee e e ne peuvent plus ˆtre modifi´s, e e e a e – les instances de la classe StringBuffer, au contraire, sont destin´es ` subir des op´rations qui modifient les caract`res dont elles sont faites (remplacements, ajouts, suppressions, etc.). e Les d´tails de l’utilisation de ces deux classes, extrˆmement utiles l’une et l’autre, sont d´crits dans e e e la documentation de l’API Java. Limitons-nous ici ` signaler certains privil`ges tout ` fait originaux et a e a int´ressants de la classe String : e e e e e 1. Il existe une notation sp´cifique pour les constantes litt´rales : toute suite de caract`res encadr´e par des guillemets produit une instance de la classe String : String nom = "Gaston Lagaffe"; e e a ınes se note par l’op´rateur16 + : e 2. L’op´ration de concat´nation (mise bout ` bout) de deux chaˆ
15 Il n’est pas ´l´mentaire de comprendre pourquoi cette version simple de equals n’est pas correcte. Il faut imaginer la ee condition p.equals(q) lorsque les valeurs de p et q sont des objets Point alors que la variable q est d´clar´e de type Object e e (une telle situation se produit, par exemple, chaque fois qu’on consid`re une Collection dont les ´l´ments sont des objets e ee Point). Si q est d´clar´e de type Object, p.equals(q) n’utilisera pas notre m´thode Point.equals(Point p), mais e e e Object.equals(Object o) qui n’est pas red´finie dans la classe Point. e 16 Les programmeurs C++ noteront que cet emploi du + est le seul cas de surcharge d’un op´rateur en Java. e

20

c H. Garreta, 2000-2006

3 TYPES

3.7

Chaˆ ınes de caract`res e

String formule = "Monsieur " + nom; // vaut "Monsieur Gaston Lagaffe" 3. Toutes les classes poss`dent la m´thode String toString(), qui renvoie une expression d’un objet e e sous forme de chaˆ de caract`res. La classe Object fournit une version minimale de cette m´thode, ıne e e chaque classe peut en donner une d´finition plus adapt´e. Exemple : e e class Point { int x, y; ... public String toString() { return "(" + x + "," + y + ")"; } } 4. L’op´rateur +, lorsqu’un de ses op´randes est une chaˆ e e ıne, provoque la conversion de l’autre op´rande e vers le type chaˆ Cette conversion est effectu´e ıne. e – dans le cas d’un type primitif, par l’appel d’une des m´thodes statiques valueOf de la classe String, e – dans le cas d’un objet, par l’appel de sa m´thode toString e Par exemple, si p est un objet Point (voir ci-dessus), l’expression "r´sultat: " + p e ´quivaut ` l’expression e a "r´sultat: " + p.toString() e dont la valeur est, par exemple, la chaˆ "r´sultat: (10,20)". ıne e 5. La conversion vers le type chaˆ est pratiqu´e ´galement par certaines m´thodes d’int´rˆt g´n´ral. Par ıne e e e ee e e exemple, les m´thodes print et println de la classe PrintStream (la classe de l’objet System.out) ont e des d´finitions sp´cifiques pour les cas o` leur argument est d’un type primitif ou String et r´cup`rent e e u e e tous les autres cas ` l’aide de la m´thode toString. Par exemple, on peut imaginer print(Object o) a e ainsi ´crite : e public void print(Object o) { print(o.toString()); // print(Object) est ramen´e ` print(String) e a } 3.7.2 Copie et comparaison des chaˆ ınes

1. Clone. La classe String ne comporte pas de m´thode clone : puisque les instances de cette classe e sont immuables, il n’est jamais n´cessaire de les cloner. e 2. Equals. La classe String offre une version de la m´thode equals dont il est facile d’imaginer ce e qu’elle fait : parcourir les deux chaˆ ınes en comparant les caract`res correspondants. Deux chaˆ e ınes construites s´par´ment l’une de l’autre doivent ˆtre compar´es ` l’aide de cette m´thode. Par exemple, si reponse est e e e e a e de type String, l’expression reponse == "oui" vaut souvent false ind´pendamment de la valeur de reponse et n’a donc aucun int´rˆt. Il faut ´crire ` la e ee e a place reponse.equals("oui") ou bien "oui".equals(reponse). 3. Intern. La m´thode equals est utile mais relativement on´reuse, puisqu’elle doit examiner les deux e e chaˆ ınes en comparant les caract`re homologues. Les programmes qui comportent des comparaisons fr´quentes e e entre des chaˆ ınes (en nombre raisonnable) peuvent tirer avantage de la m´thode intern() qui renvoie une e chaˆ ayant les mˆmes caract`res que la chaˆ donn´e, mais appartenant ` un ensemble de chaˆ ıne e e ıne e a ınes sans r´p´tition, au sein duquel deux chaˆ e e ınes ´gales sont forc´ment la mˆme chaˆ Lorsqu’une chaˆ appartient e e e ıne. ıne a ` cet ensemble on dira qu’elle a ´t´ internalis´e. ee e Ainsi, par exemple, ´tant donn´es deux variables s1 et s2 de type String, quelles que soient les valeurs e e des chaˆ ınes t1 et t2, ` la suite de a s1 = t1.intern(); s2 = t2.intern(); les conditions  s1.equals(s2)  et  s1 == s2  sont ´quivalents (mais l’´valuation de la deuxi`me est e e e ´videmment plus rapide). e Note. Les chaˆ ınes de caract`res constantes ´crites dans le programme source sont garanties internalis´es, e e e contrairement aux chaˆ ınes construites pendant l’ex´cution (chaque appel de new doit produire la cr´ation e e d’un nouvel objet). Ainsi, le code suivant affiche certainement  true false true  :
c H. Garreta, 2000-2006

21

3.7 Chaˆ ınes de caract`res e

3

TYPES

... String a = "bonjour"; ... String b = "bonjour"; ... String c = new String("bonjour"); ... String d = c.intern(); ... System.out.println((a == b) + " " + (a == c) + " " + (a == d));

22

c H. Garreta, 2000-2006

4 EXPRESSIONS ET INSTRUCTIONS

4

Expressions et instructions

Cette section ridiculement courte est consacr´e aux diff´rences notables existant entre les expressions (de e e types primitifs) et les instructions de Java et de C. Bonne nouvelle : pour l’essentiel, les expressions et les instructions de Java et celles de C sont les mˆmes. Les expressions sont form´es avec les mˆmes op´rateurs, e e e e les instructions emploient les mˆmes mots-cl´s, les unes et les autres sont soumises aux mˆmes r`gles de e e e e syntaxe, renvoient les mˆmes valeurs et ont les mˆmes effets. e e Deux constructions sont r´alis´es de mani`re originale en Java et m´ritent d’ˆtre expliqu´es dans un e e e e e e polycopi´ succinct comme celui-ci : e e e e – l’instruction de rupture ´tiquet´e, expliqu´e ci-dessous, – la boucle for am´lior´e expliqu´e ` la section 12.3.2. e e e a

4.1

Rupture ´tiquet´e e e

L’instruction goto n’existe pas en Java. Cependant, afin de b´n´ficier du principal service rendu par cette e e instruction, qui est la maˆ ıtrise de l’abandon des boucles imbriqu´es : e – les instructions, et notamment les boucles (for, while, do...while, etc.), peuvent ˆtre ´tiquet´es, e e e – l’instruction break peut ˆtre suivie d’une ´tiquette afin de provoquer l’abandon de plusieurs boucles e e imbriqu´es au lieu d’une seule. e Une ´tiquette est un identificateur qu’il n’est pas n´cessaire de d´clarer ; il suffit de l’´crire, suivi de  : , e e e e devant une instruction, pour qu’il soit connu ` l’int´rieur de celle-ci et inconnu ` l’ext´rieur. a e a e Par exemple, le code (purement d´monstratif) suivant affiche les dates comprises entre le 1er janvier e 2000 et le 30 d´cembre 2005, en supposant que tous les mois ont 30 jours. A chaque affichage, un entier e pseudo-al´atoire de l’intervalle [ 0, 100 [ est tir´ : si c’est un multiple de 3, le mois en cours est abandonn´ e e e (on passe au 1er du mois suivant) ; si c’est un multiple de 13, l’ann´e en cours est abandonn´e (on passe au e e 1er janvier de l’ann´e suivante) ; si le nombre est 0, le travail est abandonn´ : e e ... grandeBoucle: for (int a = 2000; a <= 2005; a++) moyenneBoucle: for (int m = 1; m <= 12; m++) petiteBoucle: for (int j = 1; j <= 30; j++) { System.out.println(j + "/" + m + "/" + a); int x = (int) (Math.random() * 100); if (x == 0) break grandeBoucle; else if (x % 13 == 0) break moyenneBoucle; else if (x % 3 == 0) break petiteBoucle; } ... Ce qui vient d’ˆtre dit ` propos de l’instruction break ´tiquet´e s’applique, mutatis mutandis, ` l’inse a e e a truction continue qui peut, elle aussi, ˆtre ´tiquet´e. Par exemple, voici une r´daction ´quivalente du bout e e e e e de programme pr´c´dent : e e ... grandeBoucle: for (int a = 2000; a <= 2005; a++) moyenneBoucle: for (int m = 1; m <= 12; m++) for (int j = 1; j <= 30; j++) { System.out.println(j + "/" + m + "/" + a); int x = (int) (Math.random() * 100); if (x == 0) break grandeBoucle; else if (x % 13 == 0) continue grandeBoucle; else if (x % 3 == 0) continue moyenneBoucle; } ...

c H. Garreta, 2000-2006

23

5

´ CLASSES, PAQUETS, FICHIERS ET REPERTOIRES

5

Classes, paquets, fichiers et r´pertoires e

L’unit´ de compilation en Java est la classe. Un fichier source se compose n´cessairement de quelques e e classes compl`tes, souvent une seule, et le compilateur produit un fichier compil´ (fichier d’extension .class) e e pour chaque classe rencontr´e dans le fichier source, fˆt-elle une classe interne ou anonyme. e u La syntaxe de la d´finition des classes est expliqu´e ` partir de la section 6.2. Ici nous nous int´ressons e e a e au regroupement des classes en paquets, et au rangement des classes et des paquets dans les fichiers et les r´pertoires. e

5.1

Paquets et classes

Voici la quintessence de la notion de paquet (package) : deux classes peuvent avoir le mˆme nom si elles e appartiennent ` des paquets distincts. a 5.1.1 Les noms des paquets et l’instruction package

Qu’on le veuille on non, chaque classe appartient ` un paquet. Si on ne s’est occup´ de rien, il s’agit du a e paquet sans nom (unnamed package), qui contient toutes les classes pour lesquelles on n’a pas donn´ un nom e de paquet explicite. On peut changer cela ` l’aide de l’instruction package, qui doit ˆtre la premi`re instruction utile (i.e. a e e autre qu’un commentaire) du fichier source : package nomDuPaquet; Toutes les classes d´finies dans un fichier commen¸ant par la ligne pr´c´dente appartiennent ipso facto e c e e au paquet nomm´ nomDuPaquet. D’autres fichiers peuvent comporter cette mˆme instruction ; les classes e e qu’ils d´finissent appartiennent alors ` ce mˆme paquet. e a e Un nom de paquet est un identificateur ou une suite d’identificateurs, chacun s´par´ du suivant par un e e point. Trois exemples : monprojet, java.awt.event et fr.univ mrs.dil.henri.outils. Les noms commen¸ant par java. sont r´serv´s aux paquets contenant les classes de la biblioth`que c e e e standard. Seul Sun Microsystems a le droit de les utiliser. Si vous ´crivez des programmes d’un int´rˆt vraiment g´n´ral, vous devez pouvoir garantir que les noms e ee e e de vos paquets sont uniques dans le monde entier. Le syst`me propos´ par Sun pour nommer ces paquets ` e e a vocation plan´taire consiste ` utiliser les ´l´ments de votre nom de domaine Internet, plac´s ` l’envers. e a ee e a Par exemple, si votre domaine est dil.univ-mrs.fr alors vos paquets pourront ˆtre nomm´s comme e e ceci17 : package fr.univ_mrs.dil.unProjet.outils; 5.1.2 Les noms des classes et l’instruction import

Chaque classe poss`de un nom court, qui est un identificateur, et un nom long form´ en pr´fixant le nom e e e court par le nom du paquet. Par exemple, java.awt.List est le nom long d’une classe du paquet java.awt dont le nom court est List. Une classe peut toujours ˆtre d´sign´e18 par son nom long. Elle peut en outre ˆtre d´sign´e par son nom e e e e e e court : e e – depuis [toute m´thode de] toute classe du mˆme paquet ; e e – depuis [toute m´thode de] toute classe ´crite dans un fichier qui comporte l’instruction : import nomDuPaquet.nomDeLaClasse; ou bien l’instruction (dans cette forme on  importe  d’un coup toutes les classes du paquet indiqu´) : e import nomDuPaquet.*; Note 1. Il ne faut pas donner ` la formule pr´c´dente plus d’effet qu’elle n’en a : unPaquet.* signifie a e e les classes du paquet unPaquet, cela n’inclut pas les classes des ´ventuels  sous-paquets  (les paquets dont e les noms commencent par unPaquet). Ainsi, les deux directives suivantes, d’un usage fr´quent, ne sont pas e redondantes, la premi`re n’implique pas la deuxi`me : e e import java.awt.*; import java.awt.event.*; // les classes du paquet java.awt // les classes du paquet java.awt.event

17 Les ´l´ments d’un nom de paquet doivent ˆtre des identificateurs. C’est la raison pour laquelle les ´ventuels tirets - doivent ee e e ˆtre remplac´s, comme ici, par des blancs soulign´s . e e e 18 Qu’une classe puisse ou doive ˆtre d´sign´e par tel ou tel nom ne pr´juge en rien du fait qu’on ait on non le droit d’acc´der e e e e e ` ses membres ; cela d´pend du contexte et de r`gles expliqu´es ` la section 6.4. a e e e a

24

c H. Garreta, 2000-2006

´ 5 CLASSES, PAQUETS, FICHIERS ET REPERTOIRES

5.2

Classes, fichiers et r´pertoires e

Note 2. Dans certains cas l’utilisation des noms longs des classes reste n´cessaire pour ´viter des ambie e gu¨ es. Par exemple, List est une classe du paquet java.awt et une interface du paquet java.util. Dans ıt´ un fichier o` les deux sont employ´es (ce n’est pas rare), on ne peut pas utiliser le nom court dans les deux u e cas. Note 3. On entend parfois :  il faut ´crire telle instruction import pour avoir le droit d’utiliser telle e classe . Cela n’est jamais vrai : l’instruction import ne sert pas ` rendre visibles des classes (toutes les a classes qui se trouvent dans des fichiers et r´pertoires que la machine Java peut atteindre sont visibles19 ) ni e a ` vous donner le droit d’acc`s ` des classes (cela se pilote ` travers les qualifications public, private, etc., e a a voir la section 6.4). Le seul droit que l’instruction import vous donne est celui d’utiliser un nom court pour nommer une classe que vous auriez pu, sans cela, d´signer par son nom long. e Note 4. Le paquet java.lang est sp´cial : ses classes peuvent ˆtre d´sign´es par leur nom court sans qu’il e e e e soit n´cessaire d’´crire import java.lang.* en tˆte du fichier. Ce paquet est fait de classes fondamentales, e e e qui font partie de la d´finition du langage Java lui-mˆme : Object, String, Math, System, Integer, etc. S’il e e fallait ´crire import java.lang.* pour les utiliser, alors il faudrait l’´crire dans tous les fichiers. e e

5.2
5.2.1

Classes, fichiers et r´pertoires e
Classes publiques et non publiques

La d´claration d’une classe peut ˆtre pr´c´d´e ou non du qualifieur public. Si ce n’est pas le cas, la e e e e e classe est dite  non publique  et elle n’est accessible que depuis les [m´thodes des] autres classes du mˆme e e paquet 20 . Si la d´claration d’une classe est pr´c´d´e du qualifieur public alors la classe est dite publique ; elle est e e e e accessible depuis [les m´thodes de] toutes les classes de tous les paquets. e Si un fichier source contient une classe publique, le nom du fichier doit ˆtre le nom de cette classe, e compl´t´ par l’extension  .java . Par exemple, un fichier contenant le texte ee public class Point { ... d´finition de la classe Point e ... } doit se nommer Point.java. Il en d´coule qu’un fichier source ne peut pas contenir plus d’une classe publique. e En r´sum´, mis ` part les commentaires, un fichier source est donc compos´ des ´l´ments suivants, dans e e a e ee l’ordre indiqu´ : e – ´ventuellement, une instruction package ; e – ´ventuellement, une ou plusieurs instructions import ; e – une ou plusieurs classes, dont au plus une est publique ; elle impose alors son nom au fichier. 5.2.2 Point d’entr´e d’un programme. e

Une classe est dite ex´cutable si, et seulement si elle comporte une m´thode de signature e e public static void main(String[] args) (chaque mot de l’expression pr´c´dente est impos´ strictement, sauf args). Le lancement de l’ex´cution e e e e d’une application Java ´quivaut ` l’appel de la m´thode main d’une classe ex´cutable ; si on travaille ` une e a e e a console, cela se fait par la commande java NomDeLaClasse argum1 . . . argumk L’argument args de main est un tableau dont les ´l´ments sont les chaˆ ee ınes argum1 . . . argumk qui figurent dans la commande qui a lanc´ le programme. Par exemple, voici un programme qui affiche n fois un certain e mot m, n et m ´tant donn´s lors du lancement. Fichier Radotage.java : e e
import n’est donc pas l’homologue en Java de la directive #include du langage C classes non publiques repr´sentent donc des services offerts aux autres classes du mˆme paquet, non des fonctionnalit´s e e e universelles dont n’importe quel utilisateur peut avoir besoin (cela est le rˆle des classes publiques). o
20 Les 19 L’instruction

c H. Garreta, 2000-2006

25

5.2 Classes, fichiers et r´pertoires e

5

´ CLASSES, PAQUETS, FICHIERS ET REPERTOIRES

public class Radotage { public static void main(String[] args) { int n = Integer.parseInt(args[0]); for (int i = 0; i < n; i++) System.out.print(args[1] + " "); } } Compilation et ex´cution de ce programme (dans le cas d’ex´cution pr´sent´ ici, args[0] est la chaˆ e e e e ıne "5" et args[1] la chaˆ "bonsoir") : ıne > javac Radotage.java > java Radotage 5 bonsoir bonsoir bonsoir bonsoir bonsoir bonsoir > 5.2.3 O` placer les fichiers des classes ? u

Un proc´d´, qui d´pend plus ou moins du syst`me d’exploitation sous-jacent, associe ` chaque paquet e e e e a un r´pertoire dans lequel doivent se trouver les fichiers compil´s (fichiers d’extension .class) des classes du e e paquet ; il consiste ` utiliser la structure hi´rarchique du syst`me de fichiers pour reproduire la structure a e e hi´rarchique des noms des paquets. e Ainsi, par exemple, les fichiers des classes des paquets java.awt.event et monprojet.outils doivent ˆtre plac´s, respectivement, dans des r´pertoires nomm´s, sur Unix, java/awt/event/ et monprojet/mese e e e outils/. Sur Windows, ces r´pertoires seraient java\awt\event\ et monprojet\mesoutils\. e Ces chemins sont relatifs 21 aux r´pertoires suivants : e e e e e e – par d´faut, le r´pertoire courant (nomm´ g´n´ralement  . ), – chacun des r´pertoires de la liste d´finie par la valeur de la variable d’environnement CLASSPATH, e e lorsque cette variable est d´finie22 (le cas par d´faut n’est alors pas consid´r´), e e ee – chacun des r´pertoires de la liste d´finie par la valeur de l’argument -cp ou -classpath, si cet argument e e a ´t´ sp´cifi´ lors du lancement de la machine java (l’´ventuelle valeur de la variable CLASSPATH et le ee e e e cas par d´faut sont alors ignor´s). e e Note. Parfois les classes intervenant dans une compilation ou une ex´cution sont prises dans un fichier e “.jar”. Il faut savoir que, fondamentalement, un tel fichier n’est que la forme  zipp´e  d’une arborescence e de r´pertoires et les indications pr´c´dentes s’appliquent pratiquement telles quelles, le fichier “.jar” jouant e e e le rˆle de r´pertoire de base. o e Pour obtenir que le compilateur place les fichiers compil´s dans un certain r´pertoires, en le cr´ant s’il e e e n’existe pas, il faut lancer la compilation avec l’option  -d r´pertoireDeBase . Par exemple, si on souhaite e que les noms des paquets correspondent ` des chemins relatifs au r´pertoire courant (d´sign´ par  . ) il a e e e faut activer le compilateur par la commande : javac -d . MaClasse.java Ainsi, si le fichier MaClasse.java contient une classe nomm´e MaClasse et commence par l’instruction e package monPaquet la commande pr´c´dente produira un fichier appel´ MaClasse.class plac´ dans un e e e e sous-r´pertoire monPaquet du r´pertoire courant . e e Si MaClasse est une classe ex´cutable, on la lancera alors depuis le r´pertoire courant par l’une ou l’autre e e des commandes : java monPaquet/MaClasse (sur UNIX) ou java monPaquet\MaClasse (sur Windows) ou java monPaquet.MaClasse (ind´pendamment du syst`me) e e ` Ou placer les fichiers sources ? Il est conseill´ de suivre la mˆme r`gle pour le placement des fichiers e e e sources (fichiers .java). Si on veut que la compilation d’une classe C produise la compilation des classes
21 Si r est un r´pertoire d´fini par un chemin absolu (i.e. partant de la racine du syst`me de fichiers) A, on dit qu’un chemin e e e B est relatif ` r pour indiquer que B repr´sente le chemin absolu obtenu en concat´nant A et B. a e e Par exemple, si le chemin monProjet/mesOutils/ est relatif au r´pertoire /home/henri/ alors il d´finit le chemin absolu e e /home/henri/monProjet/mesOutils/. 22 Dans les versions r´centes du SDK, sauf besoin sp´cifique il vaut mieux ne pas jouer avec la variable CLASSPATH. S’ils ont e e ´t´ bien install´s, le compilateur et la machine Java acc`dent normalement ` toutes les biblioth`ques de classes dont ils ont e e e e a e besoin dans le cadre d’une utilisation normale de Java.

26

c H. Garreta, 2000-2006

´ 5 CLASSES, PAQUETS, FICHIERS ET REPERTOIRES

5.2

Classes, fichiers et r´pertoires e

dont C d´pend23 , ces derni`res doivent se trouver dans les r´pertoires qui correspondent ` leurs paquets. e e e a Par exemple, si le fichier MaClasse.java commence par l’instruction  package monPaquet ;  alors il vaut mieux placer ce fichier dans le r´pertoire monPaquet. Sa compilation sera alors provoqu´e e e – soit par la compilation d’une classe qui mentionne la classe monPaquet.MaClasse, – soit par la commande  javac -d . monPaquet/MaClasse.java  5.2.4 Comment distribuer une application Java ?

Dans la section pr´c´dente on a expliqu´ o` placer les classes pour que l’ex´cution d’un programme se e e e u e passe bien, du moins lorsqu’elle a lieu sur la machine sur laquelle on a fait le d´veloppement. Mais que se e passe-t-il lorsqu’un programme d´velopp´ sur une machine doit ˆtre ex´cut´ sur une autre ? e e e e e Dans le cas g´n´ral, la compilation d’une application produit de nombreux fichiers classes, ´ventuellement e e e rang´s dans diff´rents r´pertoires (autant de r´pertoires que de paquetages en jeu). Il en r´sulte que, alors e e e e e que pour d’autres langages une application correspond ` un fichier executable, en Java une application se a pr´sente comme une pluralit´ de fichiers et de r´pertoires ; une telle organisation rend complexe et risqu´e e e e e la distribution des applications. C’est pourquoi les ex´cutables Java sont distribu´s sous la forme d’archives e e zip ou, plutˆt, jar 24 , qu’il faut savoir fabriquer. Expliquons cela sur un exemple : o Imaginons qu’une application se compose des classes Principale, Moteur, Fenetres, etc., le point d’entr´e – c’est-`-dire la m´thode par laquelle l’ex´cution doit commencer25 – ´tant dans la classe Principale. e a e e e La compilation de ces classes a produit les fichiers Principale.class,Moteur.class, Fenetres.class, etc. Avec un ´diteur de textes quelconque, composez un fichier nomm´ MANIFEST.MF comportant l’unique e e ligne Main-Class: Principale et tapez la commande : jar -cvfm Appli.jar MANIFEST.MF Principale.class Moteur.class Donnees.class etc. Notez que, si les classes constituant l’application sont les seuls fichiers .class du r´pertoire de travail, e vous pouvez taper la commande pr´c´dente sous la forme e e jar -cvfm Appli.jar MANIFEST.MF *.class Cela produit un fichier nomm´ Appli.jar que, si vous ˆtes curieux, vous pouvez examiner avec n’importe e e quel outil comprenant le format zip : vous y trouverez tous vos fichiers .class et un r´pertoire nomm´ e e META-INF contenant le fichier MANIFEST.MF un peu augment´ : e Manifest-Version: 1.0 Created-By: 1.5.0_05 (Sun Microsystems Inc.) Main-Class: Principale C’est ce fichier Appli.jar que vos distribuerez. Quiconque souhaitera ex´cuter votre application pourra e le faire en tapant la commande26 java -jar Appli.jar ou bien java -classpath Appli.jar Principale Dans ce dernier cas, l’application s’ex´cuterait correctement mˆme si l’archive ne contenait pas de fichier e e manifeste, voire si au lieu de Appli.jar on avait utilis´ une archive Appli.zip tout ` fait ordinaire. e a

23 En principe, la compilation d’une classe C produit la compilation des classes que C mentionne, pour lesquelles cela est utile, c’est-`-dire celles qui ont ´t´ modifi´es depuis leur derni`re compilation. C’est un m´canisme proche de la commande make de a e e e e e Unix mais l’exp´rience montre qu’il y a parfois des rat´s (des classes qui le devraient ne sont pas recompil´es). e e e 24 En r´alit´ une archive jar est la mˆme chose qu’une archive zip, sauf qu’une archive jar est cens´e contenir – sans que la e e e e chose soit certaine – un fichier manifest. 25 Le point d’entr´e est la premi`re instruction d’une m´thode qui doit avoir le prototype public static void main(String[] e e e args), mais que rien n’empˆche que des m´thodes ayant ce prototype existent aussi dans plusieurs autres classes de l’application. e e 26 Sur un syst`me Windows correctement configur´, on pourra mˆme ex´cuter l’application en double-cliquant sur l’icˆne du e e e e o fichier jar. Dans ce cas, il y a int´rˆt ` ce qu’il s’agisse d’une application qui cr´e sa propre interface-utilisateur (par exemple, e e a e graphique) car autrement les entr´es-sorties seront perdues, faute de console. e

c H. Garreta, 2000-2006

27

6

LES OBJETS

6
6.1

Les objets
♥ Introduction : les langages orient´s objets e

Java est un langage orient´ objets. Pour fixer les id´es, voici une pr´sentation tr`s sommaire d’une partie e e e e du jargon et des concepts de base de cette famille de langages27 . Objet. Les entit´s ´l´mentaires qui composent les programmes sont les objets. Un objet est constitu´ par e ee e l’association d’une certaine quantit´ de m´moire, organis´e en champs qu’on appelle des variables d’instance, e e e et d’un ensemble d’op´rations (fonctions, proc´dures...) qu’on appelle des m´thodes. e e e Contrˆle de l’accessibilit´. Les variables d’instance et les m´thodes peuvent ˆtre qualifi´es publiques, c’esto e e e e a `-dire accessibles depuis n’importe quel point du programme, ou priv´es, c’est-`-dire accessibles uniquement e a depuis les m´thodes de l’objet en question. e Encapsulation. Un objet est vu par le reste du programme comme une entit´ opaque : on ne peut modifier e son ´tat, c’est-`-dire les valeurs de ses variables, autrement qu’en faisant faire la modification par une de ses e a m´thodes publiques28 . L’ensemble des m´thodes publiques d’un objet s’appelle son interface 29 . e e L’importante propri´t´ suivante est ainsi garantie : la d´finition d’un objet ne d´pend pas des impl´menee e e e tations (c’est-`-dire les d´tails internes) des autres objets, mais uniquement de leurs interfaces. a e Message. Un message est une requˆte qu’on soumet ` un objet pour qu’il effectue une de ses op´rations. e a e En pratique, cela se traduit par l’appel d’une m´thode de l’objet. Le message sp´cifie quelle op´ration doit e e e ˆtre effectu´e, mais non comment elle doit l’ˆtre : il incombe ` l’objet r´cepteur du message de connaˆ les e e e a e ıtre modalit´s effectives que prend pour lui l’ex´cution de l’op´ration en question. e e e Appellation  orient´ objets . Du point de vue du concepteur, toute proc´dure ou fonction appartient ` e e a un objet ; elle sp´cifie comment un objet donn´ r´agit ` un message donn´. Dans la programmation orient´e e e e a e e objets les entit´s les plus ext´rieures sont les objets, les actions sont d´finies ` l’int´rieur des objets, comme e e e a e attributs de ces derniers. Cela s’oppose ` la programmation traditionnelle, ou  orient´e actions , o` les entit´s les plus ext´rieures a e u e e sont les actions (proc´dures, fonctions, routines, etc.) les objets se d´finissant de mani`re subordonn´e aux e e e e actions (ils en sont les donn´es, les r´sultats, etc.). e e Classe. Une classe est la description des caract´ristiques communes ` tous les objets qui repr´sentent e a e la mˆme sorte de chose. Vue de l’ext´rieur, elle d´finit donc leur interface commune, seul aspect public e e e des objets. Vue de l’int´rieur, c’est-`-dire comme son concepteur la voit, la classe d´finit la structure de la e a e m´moire priv´e de ces objets (les variables d’instance) et les r´ponses aux messages de leurs interfaces (les e e e corps des m´thodes). e Instance. Les objets individuels d´crits par une classe sont ses instances. Chaque objet est instance d’une e classe. Il n’y a pas d’inconv´nient ` identifier la notion de classe avec celle de type : la classe est le type de e a ses instances. L’instanciation est l’op´ration par laquelle les objets sont cr´´s. C’est une notion dynamique (mˆme dans e ee e les langages ` types statiques) : tout objet est cr´´ (a) enti`rement garni et (b) ` un moment bien d´termin´ a ee e a e e de l’ex´cution d’un programme. e

6.2
6.2.1

Classes, variables et m´thodes e
D´claration des classes et des objets, instanciation des classes e

Fondamentalement, une classe est constitu´e par un ensemble de membres, qui repr´sentent e e – des donn´es, on les appelle alors variables, champs, ou encore donn´es membres, e e e – des traitements, on les appelle alors m´thodes ou fonctions membres. La syntaxe de la d´claration d’une classe est la suivante : e visibilit´ class identificateur { e d´claration d’un membre e ... d´claration d’un membre e } ou visibilit´ est soit rien, soit le mot public (la classe est alors dite publique, voyez la section 5.2.1). e
27 Nous 28 En

traitons ici de l’aspect encapsulation des langages orient´s objets. On introduira plus loin les concepts li´s ` l’h´ritage. e e a e Java, la possibilit´ d’avoir des variables d’instance publiques temp`re cette affirmation. e e 29 Attention, en Java le mot interface a un sens technique bien pr´cis, voir la section 7.6.3 e

28

c H. Garreta, 2000-2006

6 LES OBJETS

6.2

Classes, variables et m´thodes e

Les membres des classes se d´clarent comme les variables et les fonctions en C, sauf qu’on peut les pr´fixer e e par certains qualifieurs, private, public, etc., expliqu´s plus loin. Exemple : e class Point { int x, y; // coordonn´es cart´siennes e e void montre() { System.out.println("(" + x + "," + y + ")"); } double distance(Point p) { int dx = x - p.x; int dy = y - p.y; return Math.sqrt(dx * dx + dy * dy); } } Une classe est un type de donn´es : ` la suite d’une d´claration comme la pr´c´dente, on pourra d´clarer e a e e e e des variables de type Point, c’est-`-dire des variables dont les valeurs sont des instances de la classe Point a et ont la structure d´finie par cette derni`re. Exemple : e e Point p; // d´claration d’une variable Point e

Notez bien que la d´claration pr´c´dente introduit une variable p qui pourra r´f´rencer des instances de e e e ee la classe Point, mais qui, pour le moment, ne r´f´rence rien. Selon son contexte, une d´claration comme la ee e pr´c´dente donnera ` p : e e a – soit la valeur null, dans le cas de la d´claration d’une variable membre, e – soit une valeur conventionnelle  variable non initialis´e , dans le cas de la d´claration d’une variable e e locale d’une m´thode. e On obtient que p soit une r´f´rence valide sur un objet soit en lui affectant une instance existante, soit ee en lui affectant une instance nouvellement cr´´e, ce qui s’´crit : ee e p = new Point(); // cr´ation d’une nouvelle instance de la classe Point e

La justification des parenth`ses ci-dessus, et d’autres informations importantes sur l’instanciation et l’initiae lisation des objets, sont donn´es ` la section 6.5.1. e a ´ Note. Etant donn´e une classe C, on dit indiff´remment instance de la classe C ou objet C. e e 6.2.2 Instances et membres d’instance

La d´claration donn´e en exemple ` la section pr´c´dente introduit une classe Point, comportant pour e e a e e le moment deux variables d’instance, x et y, et deux m´thodes d’instance, montre et distance. En disant e que ces ´l´ments sont des variables et des m´thodes d’instance de la classe Point on veut dire que  chaque ee e instance en a sa propre version , c’est-`-dire : a – pour les variables, qu’il en existe un jeu diff´rent dans chaque instance, disjoint de ceux des autres e instances : chaque objet Point a sa propre valeur de x et sa propre valeur de y, – pour les m´thodes, qu’elles sont li´es ` un objet particulier : un appel de la m´thode montre n’a de e e a e sens que s’il est adress´ ` un objet Point (celui qu’il s’agit de montrer). ea Ainsi, sauf dans quelques situations particuli`res que nous expliciterons plus loin, pour acc´der ` un e e a membre d’instance il faut indiquer l’instance concern´e. Cela se fait par la mˆme notation que l’acc`s aux e e e champs des structures en C : p.x p.montre() : un acc`s ` la variable d’instance x de l’objet p e a : un appel de la m´thode montre sur l’objet p e

L’expression p.montre() est la version Java de la notion, fondamentale en programmation orient´e e objets,  envoyer le message montre ` l’objet p . A priori, le mˆme message pourrait ˆtre envoy´ ` des a e e e a objets diff´rents. Il incombe ` chaque destinataire, ici p, de connaˆ les d´tails pr´cis que prend pour lui la e a ıtre e e r´action au message montre. e ` ` Acces a ses propres membres. Ainsi, une m´thode d’instance est n´cessairement appel´e ` travers e e e a un objet, le destinataire du message que la m´thode repr´sente. Dans le corps de la m´thode, cet objet est e e e implicitement r´f´renc´ par toutes les expressions qui mentionnent des membres d’instance sans les associer ee e a ` un objet explicite. Par exemple, dans la m´thode distance de la classe Point : e
c H. Garreta, 2000-2006

29

6.2 Classes, variables et m´thodes e

6

LES OBJETS

... double distance(Point p) { int dx = x - p.x; int dy = y - p.y; return Math.sqrt(dx * dx + dy * dy); } ... les variables x et y qui apparaissent seules d´signent les coordonn´es de l’objet ` travers lequel on aura e e a appel´ la m´thode distance. D’autre part, les expressions p.x et p.y d´signent les coordonn´es de l’objet e e e e qui aura ´t´ mis comme argument de cet appel. ee Ainsi, ` l’occasion d’un appel de la forme (a et b sont des variables de type Point) : a a.distance(b); le corps de la m´thode distance ´quivaut ` la suite d’instructions : e e a { int dx = a.x - b.x; int dy = a.y - b.y; return Math.sqrt(dx * dx + dy * dy); } ` ` ˆ Acces a soi-meme. A l’int´rieur d’une m´thode d’instance, l’identificateur this d´signe l’objet ` travers e e e a lequel la m´thode a ´t´ appel´e. Par exemple, la m´thode distance pr´c´dente peut aussi s’´crire, mais ici e ee e e e e e cela n’a gu`re d’avantages 30 , comme ceci : e ... double distance(Point p) { int dx = this.x - p.x; int dy = this.y - p.y; return Math.sqrt(dx * dx + dy * dy); } ... Pour voir un exemple o` l’emploi de this est n´cessaire, imaginez qu’on nous oblige ` ´crire la m´thode u e ae e d’instance distance en la ramenant ` un appel d’une m´thode de classe : a e static int distanceSymetrique(Point a, Point b); c’est-`-dire une m´thode qui, comme une fonction en C, n’est pas attach´e ` une instance particuli`re et a e e a e calcule la distance entre les deux points donn´s comme arguments. Cela donnerait : e ... int distance(Point p) { return distanceSymetrique(this, p); } ... 6.2.3 Membres de classe (membres statiques)

Les membres de classe sont ceux dont la d´claration est qualifi´e par le mot r´serv´ static : e e e e class Point { int x, y; // static int nombre; // void montre() { // System.out.println("(" + x + "," + y + ")"); } static int distanceSymetrique(Point p, Point q) { // return Math.abs(p.x - q.x) + Math.abs(p.y - q.y); } } variables d’instance variable de classe m´thode d’instance e

m´thode de classe e

Contrairement aux membres d’instance, les membres de classe ne sont pas attach´s aux instances. Ce qui e signifie que :
30 Notez cependant que certains stylistes de la programmation en Java recommandent d’employer syst´matiquement la notation e this.variabledInstance ou this.methodedInstance(...) pour les membres d’instance, et proscrivent cette notation pour les membres statiques (cf. section 6.2.3) ; cela permet ` un lecteur du programme de distinguer plus facilement ces deux sortes de a membres lors de leur utilisation.

30

c H. Garreta, 2000-2006

6 LES OBJETS

6.3

Surcharge des noms

– de chaque variable de classe il existe un unique exemplaire pour toute la classe, auquel toutes les instances acc`dent, e – l’appel d’une m´thode de classe ne se fait pas obligatoirement en association avec une instance partie culi`re (autre que celles qui figurent ´ventuellement ` titre de param`tres) ; un tel appel ne repr´sente e e a e e pas un message envoy´ ` un objet. ea Cons´quence importante : dans une m´thode de classe, l’objet this n’est pas d´fini. e e e Ainsi l’expression suivante, par exemple ´crite dans une m´thode ´trang`re ` la classe Point : e e e e a Point.nombre est un acc`s bien ´crit ` la variable de classe nombre et, p et q ´tant des objets Point, l’expression e e a e Point.distanceSymetrique(p, q) est un appel correct de la m´thode de classe distanceSymetrique. e Remarque 1. On notera que les deux expressions pr´c´dentes peuvent s’´crire aussi e e e p.nombre p.distanceSymetrique(p, q) (le pr´fixe  p.  ne sert ici qu’` indiquer qu’il s’agit des membres nombre et distance de la classe Point ; ces e a expressions ne font aucun usage implicite de l’objet p) mais ces notations sont trompeuses – et d´conseill´es – e e car elles donnent aux acc`s ` des membres de classe l’apparence d’acc`s ` des membres d’instance. e a e a Remarque 2. On l’aura compris, les m´thodes de classe sont ce qui se rapproche le plus, en Java, des e fonctions  autonomes , comme celles du langage C. Par exemple, la m´thode Math.sqrt, mentionn´e plus e e haut, n’est rien d’autre que la fonction racine carr´e qui existe dans toutes les biblioth`ques math´matiques. e e e L’obligation d’enfermer de telles fonctions dans des classes n’est pas une contrainte mais une richesse, puisque cela permet d’avoir, dans des classes diff´rentes, des fonctions qui ont le mˆme nom. e e De mˆme, la notion de variable de classe est voisine de celle de variable globale du langage C. De e telles variables existent d`s que la classe est charg´e, c’est-`-dire la premi`re fois qu’elle intervient dans le e e a e programme, jusqu’` la terminaison de celui-ci. a

6.3

Surcharge des noms

En Java les noms peuvent ˆtre surcharg´s : un mˆme nom peut d´signer plusieurs entit´s distinctes, ` e e e e e a la condition que lors de chaque utilisation du nom le compilateur puisse d´terminer sans erreur quelle est e l’entit´ d´sign´e. e e e En pratique, cela signifie qu’on peut donner le mˆme nom e – ` des entit´s dont les rˆles syntaxiques sont diff´rents, comme une classe et un membre, ou bien une a e o e variable et une m´thode, e – ` des membres de classes distinctes, a – ` des m´thodes de la mˆme classe ayant des signatures31 diff´rentes. a e e e Par exemple, dans la classe Math on trouve, parmi d’autres, les m´thodes int abs(int x) (valeur absolue e d’un entier), float abs(float x) (valeur absolue d’un flottant), double abs(double x), etc. Lorsque le compilateur rencontre une expression comme u = Math.abs(v); la m´thode effectivement appel´e est celle qui correspond au type effectif de v. L’int´rˆt de ce m´canisme e e ee e est facile ` voir : il dispense le programmeur d’avoir ` connaˆ toute une s´rie de noms diff´rents (un pour a a ıtre e e chaque type d’argument) pour nommer les diverses impl´mentations d’un mˆme concept, la valeur absolue e e d’un nombre. On notera que le type du r´sultat d’une m´thode ne fait pas partie de sa signature : deux m´thodes d’une e e e mˆme classe, ayant les mˆmes types d’arguments mais diff´rant par le type du r´sultat qu’elles rendent ne e e e e peuvent pas avoir le mˆme nom. e Remarque. Il ne faut pas confondre la surcharge avec la red´finition des m´thodes, que nous expliquerons e e a ` la section 7.3 : 1. La surcharge des m´thodes consiste dans le fait que deux m´thodes, souvent membres de la mˆme e e e classe, ont le mˆme nom mais des signatures diff´rentes. Sur un appel de m´thode faisant apparaˆ e e e ıtre ce nom, la d´termination de la m´thode qu’il faut effectivement appeler est faite d’apr`s les arguments e e e de l’appel. C’est un probl`me statique, c’est-`-dire r´solu pendant la compilation du programme. e a e
31 La

signature d’une m´thode est la liste des types des ses arguments. e

c H. Garreta, 2000-2006

31

6.4 Contrˆle de l’accessibilit´ o e

6

LES OBJETS

2. La red´finition des m´thodes consiste dans le fait que deux m´thodes ayant le mˆme nom et la mˆme e e e e e signature sont d´finies dans deux classes dont l’une est sous-classe, directe ou indirecte, de l’autre. e Nous verrons que, sur l’appel d’une m´thode ayant ce nom, la d´termination de la m´thode qu’il faut e e e effectivement appeler est faite d’apr`s le type effectif de l’objet destinataire du message. C’est un e m´canisme dynamique, qui intervient au moment de l’ex´cution du programme. e e

6.4
6.4.1

Contrˆle de l’accessibilit´ o e
Membres priv´s, prot´g´s, publics e e e

La d´claration d’un membre d’une classe (variable ou m´thode, d’instance ou de classe) peut ˆtre qualifi´e e e e e par un des mots r´serv´s private, protected ou public, ou bien ne comporter aucun de ces qualifieurs. e e Cela fonctionne de la mani`re suivante : e – membres priv´s : un membre d’une classe C dont la d´claration commence par le qualifieur private e e n’est accessible que depuis [les m´thodes de] la classe C ; c’est le contrˆle d’acc`s le plus restrictif, e o e – un membre d’une classe C dont la d´claration ne commence pas par un des mots private, protected e ou public est dit avoir l’accessibilit´ par d´faut ; il n’est accessible que depuis la classe C et depuis les e e autres classes du paquet auquel C appartient, – membres prot´g´s : un membre d’une classe C dont la d´claration commence par le qualifieur protected e e e n’est accessible que depuis la classe C, les classes du paquet auquel C appartient et les sous-classes, directes ou indirectes, de C, – membres publics : un membre de la classe C dont la d´claration commence par le qualifieur public est e accessible partout o` C est accessible ; c’est le contrˆle d’acc`s le plus permissif. u o e ´ ´ A propos des membres proteges. En r´alit´, la r`gle qui gouverne l’acc`s aux membres prot´g´s est e e e e e e plus complexe que l’explication ci-dessus ne le sugg`re. Consid´rons un acc`s tel que : e e e unObjet.unMembre ou unObjet.unMembre(arguments) dans lequel unMembre (une variable dans le premier cas, une m´thode dans le second) est un membre prot´g´ e e e d’une certaine classe C. Pour ˆtre l´gitime, cette expression e e – doit ˆtre ´crite dans une m´thode de C ou d’une classe du mˆme paquet que C – en cela les membres e e e e proteg´s sont trait´s comme les membres ayant l’accessibilit´ par d´faut –, ou bien e e e e – doit ˆtre ´crite dans une classe S qui est sous-classe de C, mais alors unObjet doit ˆtre instance de S e e e ou d’une sous-classe de S. 6.4.2 ♥ L’encapsulation

Quand on d´bute dans la programmation orient´e objets on peut trouver que le contrˆle de l’accessibilit´ e e o e est une chinoiserie peu utile. Il faut comprendre, au contraire, que cette question est un ´l´ment fondamental ee de la m´thodologie. e En effet, nous cherchons ` ´crire des programmes les plus modulaires possibles, or l’encapsulation est a e l’expression ultime de la modularit´ : faire en sorte que chaque objet du syst`me que nous d´veloppons e e e ne puisse pas d´pendre de d´tails internes des autres objets. Ou, dit autrement, garantir que chaque objet e e s’appuie sur les sp´cifications des autres, non sur leurs impl´mentations. e e Il en d´coule une r`gle de programmation simple : tout membre qui peut ˆtre rendu priv´ doit l’ˆtre32 . e e e e e Pour illustrer cette question, consid´rons notre classe Point, et supposons que nous souhaitions permettre e a ` ses utilisateurs d’acc´der ` l’abscisse et ` l’ordonn´e des points. Premi`re solution, rendre les membres x e a a e e et y publics : class Point { public int x, y; ... } Exemple d’utilisation : si p est un objet Point, pour transformer un point en son sym´trique par rapport ` e a l’axe des abscisses il suffira d’´crire : e p.y = - p.y; Voici une autre d´finition possible de la classe Point : e
32 Certains langages vont plus loin, et font que toutes les variables sont d’office priv´es. Java permet de d´clarer des variables e e d’instance et de classe publiques ; notre propos ici est de d´conseiller de telles pratiques. e

32

c H. Garreta, 2000-2006

6 LES OBJETS

6.5

Initialisation des objets

class Point { private int x, y; public int getX() { return x; } public int getY() { return y; } public void setPos(int a, int b) {  validation de a et b  x = a; y = b; } ... } avec cette d´finition, pour transformer un point p en son sym´trique par rapport ` l’axe des abscisses il faut e e a ´crire : e p.setPos(p.getX(), - p.getY()); Malgr´ les apparences, la deuxi`me mani`re est la meilleure, car elle respecte le principe d’encapsulation, e e e ce que ne fait pas la premi`re. D’abord, cette deuxi`me d´finition permet de garantir la coh´rence interne e e e e des objets cr´´s par les utilisateurs de la classe Point, y compris les plus ´tourdis, puisque le seul moyen ee e de modifier les valeurs des variables d’instance x et y est d’appeler la m´thode setPos, dans laquelle le e concepteur de la classe a ´crit le code qui valide ces valeurs (partie  validation de a et b ). e Ensuite, la deuxi`me d´finition garantit qu’aucun d´veloppement utilisant les objets Point ne fera ine e e tervenir des d´tails de l’impl´mentation de cette classe, puisque les variables d’instance x et y sont priv´es, e e e c’est-`-dire inaccessibles. Par exemple, si un jour il devenait n´cessaire de repr´senter les points par leurs a e e coordonn´es polaires au lieu des coordonn´es cart´siennes, cela pourrait ˆtre fait sans rendre invalide aucun e e e e programme utilisant la classe Point puisque, vu de l’ext´rieur, le comportement de la classe Point serait e maintenu : class Point { private double rho, theta; public int getX() { return (int) (rho * Math.cos(theta)); } public int getY() { return (int) (rho * Math.sin(theta)); } public void setPos(int a, int b) { rho = Math.sqrt(a * a + b * b); theta = Math.atan2(b, a); } ... }

6.5

Initialisation des objets

En Java la cr´ation d’un objet est toujours dynamique : elle consiste en un appel de l’op´rateur new, et e e peut se produire ` n’importe quel endroit d’un programme. Il faut savoir que les variables d’instance des a objets ne restent jamais ind´termin´es, elles ont des valeurs initiales pr´cises. e e e Un ´l´ment de la m´thodologie objet, tr`s important pour la qualit´ des programmes, est que le concepteur ee e e e d’une classe maˆ ıtrise compl`tement les actions ` faire et les valeurs initiales ` donner lors de la cr´ation des e a a e instances ; il peut ainsi garantir que les objets sont, d`s leur cr´ation, toujours coh´rents. e e e Notons pour commencer que, s’il n’y a pas d’autres indications, Java donne une valeur initiale ` chaque a membre d’une classe. Pour les membres de types primitifs, la valeur initiale correspondant ` chaque type a est celle qu’indique le tableau 1 (page 10). Pour les membres d’autres types, c’est-`-dire les tableaux et les a objets, la valeur initiale est null. Ainsi, sans que le programmeur n’ait ` prendre de pr´caution particuli`re, a e e les instances d’une classe d´clar´e comme ceci e e class Point { int x, y; ... } sont des points ayant (0, 0) pour coordonn´es. e Lorsque l’initialisation souhait´e est plus complexe que l’initialisation par d´faut, mais assez simple pour e e s’´crire comme un ensemble d’affectations ind´pendantes de l’instance particuli`re cr´´e, on peut employer e e e ee une notation analogue ` celle de C. Par exemple, voici une classe Point dont les instances sont des points a al´atoirement dispos´s dans le rectangle [0, 600[×[0, 400[ : e e
c H. Garreta, 2000-2006

33

6.5 Initialisation des objets

6

LES OBJETS

class Point { int x = (int) (Math.random() * 600); int y = (int) (Math.random() * 400); ... } Lorsque l’initialisation requise est plus complexe qu’une suite d’affectations, ou lorsqu’elle d´pend d’´l´ments e ee li´s au contexte dans lequel l’objet est cr´´e, la notation pr´c´dente est insuffisante. Il faut alors d´finir un e ee e e e ou plusieurs constructeurs, comme expliqu´ ` la section suivante. ea 6.5.1 Constructeurs

Un constructeur d’une classe est une m´thode qui a le mˆme nom que la classe et pas de type du r´sultat33 . e e e Exemple : class Point { private int x, y; public Point(int a, int b) { validation de a et b x = a; y = b; } ... } Un constructeur est toujours appel´ de la mˆme mani`re : lors de la cr´ation d’un objet, c’est-`-dire e e e e a comme op´rande de l’op´rateur new : e e Point p = new Point(200, 150); Dans un constructeur on ne trouve pas d’instruction return : un constructeur ne rend rien (c’est l’op´rateur e new qui rend quelque chose, ` savoir une r´f´rence sur l’objet nouveau), son rˆle est d’initialiser les variables a ee o d’instance de l’objet en cours de cr´ation, et plus g´n´ralement d’effectuer toutes les op´rations requises par e e e e la cr´ation de l’objet. e Comme les autres m´thodes, les constructeurs peuvent ˆtre qualifi´s public, protected ou private, ou e e e avoir l’accessibilit´ par d´faut. Une situation utile et fr´quente est celle montr´e ci-dessus : un ou plusieurs e e e e constructeurs publics servant surtout ` initialiser un ensemble de variables d’instance priv´es. a e La surcharge des m´thodes vaut pour les constructeurs : une classe peut avoir plusieurs constructeurs, e qui devront alors diff´rer par leurs signatures : e class Point { private int x, y; public Point(int a, int b) { validation de a et b x = a; y = b; } public Point(int a) { validation de a x = a; y = 0; } ... } A l’int´rieur d’un constructeur l’identificateur this utilis´ comme un nom de variable d´signe (comme e e e dans les autres m´thodes d’instance) l’objet en cours de construction. Cela permet parfois de lever certaines e ambigu¨ es ; par exemple, le constructeur ` deux arguments donn´ plus haut peut s’´crire aussi34 : ıt´ a e e
33 Nous l’avons d´j` dit, le type du r´sultat d’une m´thode ne fait pas partie de sa signature. N´anmoins, le fait d’avoir ou ea e e e non un type du r´sultat est un ´l´ment de la signature. Il en d´coule que, par exemple, dans une certaine classe Point peuvent e ee e coexister un constructeur Point() et une m´thode void Point() distincts. e 34 Parfois les noms des arguments des m´thodes ne sont pas indiff´rents, ne serait-ce qu’en vue de la documentation (ne pas e e oublier que l’outil javadoc fabrique la documentation ` partir du code source). a

34

c H. Garreta, 2000-2006

6 LES OBJETS

6.5

Initialisation des objets

class Point { private int x, y; public Point(int x, int y) { validation de x et y this.x = x; this.y = y; } ... } A l’int´rieur d’un constructeur, l’identificateur this, utilis´ comme un nom de m´thode, d´signe un autre e e e e constructeur de la mˆme classe (celui qui correspond aux arguments de l’appel). Par exemple, voici une autre e mani`re d’´crire le second constructeur de la classe Point montr´e plus haut : e e e class Point { private int x, y; public Point(int x, int y) { validation de x et y this.x = x; this.y = y; ... } public Point(int x) { this(x, 0); ... } ... } Note 1. Lorsqu’un tel appel de this(...) apparaˆ il doit ˆtre la premi`re instruction du constructeur. ıt, e e Note 2. L’expression qui cr´e un objet,  new UneClasse() , se pr´sente toujours comme l’appel d’un e e constructeur, mˆme lorsque le programmeur n’a ´crit aucun constructeur pour la classe en question. Tout e e se passe comme si Java avait dans ce cas ajout´ ` la classe un constructeur implicite, r´duit ` ceci : ea e a UneClasse() { super(); } (la signification de l’expression super() est explique ` la section 7.4) a 6.5.2 Membres constants (final)

La d´claration d’un membre d’une classe peut commencer par le qualifieur final. Nous verrons ` la e a section 7.5.3 ce que cela veut dire pour une m´thode ; pour une variable, cela signifie qu’une fois initialis´e, e e elle ne pourra plus changer de valeur ; en particulier, toute apparition de cette variable ` gauche d’une a affectation sera refus´e par le compilateur. Une telle variable est donc plutˆt une constante. e o Par exemple, voici comment d´clarer une classe dont les instances sont des points immuables : e class Point { final int x, y; Point(int a, int b) { validation de a et b x = a; y = b; } ... }

c H. Garreta, 2000-2006

35

6.5 Initialisation des objets

6

LES OBJETS

N.B. Les affectations de x et y qui apparaissent dans le constructeur sont accept´es par le compilateur, e car il les reconnaˆ comme des initialisations ; toute autre affectation de ces variables sera refus´e. ıt e Le programmeur a donc deux mani`res d’assurer que des variables d’instance ne seront jamais modifi´es e e une fois les instances cr´es : qualifier ces variables final ou bien les qualifier private et ne pas d´finir de e e m´thode qui permettrait de les modifier. La premi`re mani`re est pr´f´rable, car e e e ee e e e e – la qualification private n’empˆche pas qu’une variable soit modifi´e depuis une m´thode de la mˆme classe, – la qualification final informe le compilateur du caract`re constant de la variable en question, celui-ci e peut donc effectuer les optimisations que ce caract`re constant permet. e Constantes de classe. Les variables de classe peuvent elles aussi ˆtre qualifi´es final ; elles sont e e alors tr`s proches des  pseudo-constantes  qu’on d´finit en C par la directive #define. La coutume est de e e nommer ces variables par des identificateurs tout en majuscules : public class Point { public static final int XMAX = 600; public static final int YMAX = 400; private int x, y; public Point(int x, int y) throws Exception { if (x < 0 || x >= XMAX || y < 0 || y > YMAX) throw new Exception("Coordonn´es invalides pour un Point"); e this.x = x; this.y = y; } ... } 6.5.3 Blocs d’initialisation statiques

Les constructeurs des classes sont appel´s lors de la cr´ation des objets, ce qui est tout ` fait adapt´ ` e e a ea l’initialisation des variables d’instance. Mais comment obtenir l’initialisation d’une variable de classe, lorsque cette initialisation est plus complexe qu’une simple affectation ? Un bloc d’initialisation statique est une suite d’instructions, encadr´e par une paire d’accolades, pr´c´d´e e e e e du mot static. Ces blocs (et les autres affectations servant ` initialiser des variables de classe) sont ex´cut´s, a e e dans l’ordre o` ils sont ´crits, lors du chargement de la classe, c’est-`-dire avant toute cr´ation d’une instance u e a e et avant tout acc`s ` une variable ou ` une m´thode de classe. Exemple : e a a e public class Point { public static int xMax; public static int yMax; static { Dimension taillEcran = Toolkit.getDefaultToolkit().getScreenSize(); xMax = taillEcran.width; yMax = taillEcran.height; } ... } 6.5.4 Destruction des objets

Dans beaucoup de langages, comme C et C++, l’op´ration de cr´ation des objets (malloc, new, etc.) est e e coupl´e ` une op´ration sym´trique de lib´ration de la m´moire (free, delete, etc.) que le programmeur e a e e e e doit explicitement appeler lorsqu’un objet cesse d’ˆtre utile. Dans ces langages, l’oubli de la restitution e de la m´moire allou´e est une cause d’erreurs sournoises dans les programmes. Or, lorsque les objets sont e e complexes, la lib´ration compl`te et correcte de la m´moire qu’ils occupent, effectu´e par des m´thodes e e e e e appel´es destructeurs, est un probl`me souvent difficile. e e Voici une bonne nouvelle : en Java il n’y a pas d’op´ration de lib´ration de la m´moire ; en particulier, e e e 36
c H. Garreta, 2000-2006

6 LES OBJETS

6.6

Classes internes et anonymes

on n’a pas besoin de munir les classes de destructeurs qui seraient le pendant des constructeurs35 . Quand un objet cesse d’ˆtre utile on l’oublie, tout simplement. Exemple : e Point p = new Point(a, b); ... p = new Point(u, v); ici, le point P(a,b) a ´t´  oubli´  ee e ... p = null; maintenant, le point P(u,v) a ´t´ oubli´ ´galement ee ee ... Si un programme peut tourner longtemps sans jamais s’occuper de la destruction des objets devenus caducs c’est que la machine Java int`gre un m´canisme, appel´ garbage collector, qui r´cup`re la m´moire e e e e e e inutilis´e. A cet effet Java maintient dans chaque objet le d´compte des r´f´rences que l’objet supporte, et e e ee r´cup`re l’espace occup´ par tout objet dont le nombre de r´f´rences devient nul36 . e e e ee Ce m´canisme, qui implique le parcours de structures ´ventuellement tr`s complexes, se met en marche e e e automatiquement lorsque la m´moire libre vient ` manquer, et aussi en tache de fond lorsque la machine est e a oisive ou effectue des op´rations lentes, comme des acc`s r´seau ou des lectures au clavier. e e e

6.6

Classes internes et anonymes

Cette section pointue peut ˆtre ignor´e en premi`re lecture. e e e 6.6.1 Classes internes

Si la seule utilit´ d’une classe I est de satisfaire les besoins d’une autre classe E alors on peut d´finir I e e a ` l’int´rieur de E. On dit que I est une classe interne, et que E est la classe englobante de I. Cela a deux e cons´quences majeures : e – une cons´quence statique, facile ` comprendre : ` l’int´rieur de la classe englobante le nom de la classe e a a e interne peut ˆtre employ´ tel quel, mais ` l’ext´rieur de la classe englobante le nom de la classe interne e e a e n’est utilisable que s’il est pr´fix´ par le nom de la classe englobante ou par celui d’une instance de e e cette classe ; il en r´sulte une diminution, toujours bienvenue, du risque de collisions de noms, e – une cons´quence dynamique, bien plus subtile : sauf si elle est d´clar´e static, la classe interne fait e e e implicitement r´f´rence ` une instance particuli`re de la classe englobante. ee a e Exemple. La classe Automobile est destin´e ` repr´senter les divers mod`les au catalogue d’un construce a e e teur. Un mˆme mod`le est fabriqu´ avec diff´rents moteurs. Il a ´t´ d´cid´ que la classe Moteur n’a d’int´rˆt e e e e ee e e ee qu’` l’int´rieur de la classe Automobile37 : a e class Automobile { String modele; Moteur moteur; Automobile(String m, int c) { modele = m; moteur = new Moteur(c); } class Moteur { int cylindree; Moteur(int c) { cylindree = c; }
35 Attention, ne prenez pas pour un destructeur la m´thode finalize, une notion difficile ` cerner et ` utiliser que nous e a a n’expliquerons pas dans ce cours. 36 On peut expliquer le fonctionnement du garbage collector de la mani`re suivante : un objet est vivant s’il est (a) la valeur e d’une variable de classe d’une classe charg´e, (b) la valeur d’une variable d’instance d’un objet vivant ou (c) la valeur d’une e variable locale d’une m´thode active. e Le travail du garbage collector se compose, au moins logiquement, de trois phases : (1) parcourir tous les objets vivants et  marquer  les cellules m´moire qu’ils occupent, (2) noter comme ´tant r´utilisables toutes les cellules non marqu´es pendant e e e e la phase pr´c´dente et (3) de temps en temps, r´arranger la m´moire pour regrouper les cellules r´utilisables, de mani`re ` e e e e e e a lutter contre l’´miettement de l’espace disponible. e 37 Nous faisons l` un choix de conception tr`s discutable (mˆme s’il paraˆ normal que le moteur soit ` l’int´rieur de l’autoa e e ıt a e mobile !) car cela interdit que deux mod`les d’automobile distincts aient le mˆme moteur. e e

c H. Garreta, 2000-2006

37

6.6 Classes internes et anonymes

6

LES OBJETS

public String toString() { return "Moteur de " + cylindree + " cc du " + modele; } } public static void main(String[] args) { Automobile auto = new Automobile("coup´", 2400); e System.out.println(auto.moteur); } } Regardez bien la m´thode toString ci-dessus : les deux variables cylindree et modele qui y apparaissent e ont des statuts diff´rents, puisque cylindree se r´f`re ` une instance de Moteur, tandis que modele se r´f`re e ee a ee a ` une instance d’Automobile. Puisque la classe Moteur toute enti`re est un membre non statique de la classe Automobile, la premi`re e e ne peut ˆtre acc´d´e qu’` travers une instance particuli`re de la seconde. Ainsi, par exemple, le constructeur e e e a e de Moteur est une m´thode d’instance de Automobile. e Il en r´sulte que dans les m´thodes d’instance de Moteur deux variables this sont d´finies : celle qui e e e identifie un objet Moteur particulier et celle qui identifie l’objet Automobile dans lequel ce Moteur est  n´ . e D’une certaine mani`re, ce deuxi`me this se comporte comme une variable de classe de la classe Moteur. e e A l’int´rieur d’une instance de Moteur, ces deux variables peuvent ˆtre atteintes par les appellations e e respectives this et Automobile.this. 6.6.2 Classes anonymes

Les classes anonymes sont des classes internes cr´´es au moment de leur instanciation, comme les tableaux ee anonymes (cf. section 3.4.4). Cela est particuli`rement utile lorsqu’il faut d´finir une sous-classe d’une classe e e donn´e, ou une classe impl´mentant une interface donn´e, alors qu’on n’envisage de cr´er qu’une seule e e e e instance de la nouvelle classe. Par exemple, l’instruction suivante cr´e dans la foul´e une classe anonyme, sous-classe de la classe Point, e e dans laquelle la m´thode toString est red´finie, et une instance (qui restera unique) de la classe anonyme e e ainsi d´finie : e Point o = new Point(0, 0) { public String toString() { // l’objet o est un Point ordinaire, sauf return "[0rigine]"; // pour ce qui concerne la m´thode toString ! e } }; Si, apr`s la compilation de la classe Point ci-dessus, on examine la liste des fichiers produits par le e compilateur on trouvera les deux fichiers Point.class et Point$1.class. Le second traduit la cr´ation e d’une classe anonyme ` l’occasion de l’instanciation pr´c´dente. a e e Autre exemple, tir´ de la gestion des ´v´nements dans AWT : pour attacher un auditeur d’´v´nements e e e e e  action  ` un composant produisant de tels ´v´nements, comme un bouton ou un menu, il faut ex´cuter a e e e l’instruction : composant.addActionListener(auditeur ); o` auditeur est un objet d’une classe impl´mentant l’interface ActionListener. Or cette interface se compose u e d’une unique m´thode, actionPerformed. On s’en sort donc ` moindres frais avec une classe anonyme : e a composant.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { traitement de l’´v´nement e e } });

38

c H. Garreta, 2000-2006

´ 7 HERITAGE

7
7.1

H´ritage e
♥ Introduction : raffiner, abstraire

Pour approcher la notion d’h´ritage, examinons deux sortes de besoins, l´g`rement diff´rents, qu’il sae e e e tisfait. Le premier, que nous appellerons raffinement, correspond ` la situation suivante : on dispose d’une a classe A achev´e, op´rationnelle, d´finissant des objets satisfaisants, et il nous faut d´finir une classe B qui en e e e e est une extension : les objets B ont tout ce qu’ont les objets A plus quelques variables et m´thodes ajout´es e e et/ou quelques m´thodes  am´lior´es  (on dira red´finies). e e e e Imaginez, par exemple, qu’on dispose d’une classe RectangleGeometrique pour repr´senter des rece tangles en tant qu’entit´s math´matiques, avec des variables d’instance (x, y, largeur, hauteur, etc.) et e e des m´thodes (translation, rotation, etc.) et qu’il nous faille une classe RectangleGraphique dont les e instances sont des entit´s rectangulaires capables de s’afficher sur un ´cran. Elles comportent donc tout ce qui e e fait un RectangleGeometrique et, en plus, quelques variables (couleurFond, couleurBord, ´paisseurBord, e etc.) et m´thodes suppl´mentaires (affichage, transformationCouleur, etc.). e e
membres d'un Rectangle Geometrique

Rectangle Géométrique

membres d'un Rectangle Geometrique

Rectangle Graphique

membres d'un Rectangle Geometrique membres additionnels

Rectangle Géométrique

extends

Rectangle Graphique

membres additionnels

Fig. 7 – Raffiner Si nous ne disposions que des outils expliqu´s dans les chapitres pr´c´dents, nous devrions d´finir la e e e e classe RectangleGraphique en y dupliquant (cf. figure 7, ` gauche) les informations qui se trouvent dans la a d´finition de RectangleGeometrique. e Le m´canisme de l’h´ritage permet d’´viter cette duplication, en nous laissant d´clarer d`s le commene e e e e cement que la classe RectangleGraphique ´tend la classe RectangleGeometrique et en poss`de donc tous e e les attributs (cf. figure 7, ` droite). De cette mani`re, il ne nous reste ` d´finir que les membres sp´cifiques a e a e e que la deuxi`me classe (que nous appellerons la sous-classe) ajoute ` la premi`re (la super-classe). e a e Une deuxi`me sorte de besoins auxquels l’h´ritage s’attaque sont ceux qui se manifestent lorsqu’on a e e a e ` d´velopper plusieurs classes poss´dant un ensemble de membres communs. Sans l’h´ritage, ces derniers e e devraient ˆtre d´clar´s en autant d’exemplaires qu’il y a de classes qui en ont besoin. L’h´ritage va nous e e e e permettre de les d´clarer une seule fois, dans une classe distincte, invent´e pour l’occasion, dont la seule e e justification sera de contenir ces membres communs ` plusieurs autres. a
membres communs

RectangleGraphique membres communs membres spécifiques d'un rectangle

EllipseGraphique membres communs

Objet graphique

extends

extends

membres spécifiques d'une ellipse

membres spécifiques d'un rectangle RectangleGraphique

membres spécifiques d'une ellipse EllipseGraphique

Fig. 8 – Abstraire Imaginons par exemple une application manipulant plusieurs sortes d’objets susceptibles de s’afficher
c H. Garreta, 2000-2006

39

7.2 Sous-classes et super-classes

7

´ HERITAGE

sur un ´cran : des rectangles, des ellipses, des polygones, etc. Il faudra d´finir plusieurs classes distinctes, e e comportant des ´l´ments sp´cifiques – on ne dessine pas un rectangle comme on dessine une ellipse – mais ee e aussi des ´l´ments communs, aussi bien des variables (la couleur du fond, la couleur et l’´paisseur du bord, ee e etc.) que des m´thodes (positionnement, translation, etc.) e Voyez la figure 8 (droite) : l’h´ritage permet de d´finir des classes RectangleGraphique, EllipseGraphie e que, etc., dans lesquelles seuls les ´l´ments sp´cifiques sont sp´cifi´s, car elles sont d´clar´es comme des ee e e e e e extensions d’une classe ObjetGraphique o` sont d´finis les ´l´ments communs ` toutes ces autres classes. u e ee a Il y a une diff´rence tr`s pr´cise entre cette situation et la pr´c´dente : la classe ObjetGraphique ne doit e e e e e pas avoir des instances. Un objet qui serait un ObjetGraphique sans ˆtre quelque chose de plus concret e (un RectangleGraphique, une EllipseGraphique, etc.) n’aurait pas de sens. Pour exprimer cette propri´t´ ee on dit que ObjetGraphique est une classe abstraite. Elle est un moyen commode et puissant pour d´signer e collectivement les diverses sortes d’objets destin´s ` l’affichage, mais pour la cr´ation d’une tel objet il faut e a e toujours utiliser une classe concr`te. e Remarque pointue. Voyant les deux exemples pr´c´dents on peut se demander s’il est possible de e e d´finir la classe RectangleGraphique comme sous-classe en mˆme temps de RectangleGeometrique et e e d’ObjetGraphique. La r´ponse est non, en Java l’h´ritage est simple : pour des raisons aussi bien d’efficacit´ e e e que de simplicit´ conceptuelle, une classe ne peut ˆtre un raffinement que d’une seule autre. e e Cependant nous introduirons, le moment venu, la notion d’interface (une interface est une classe  tr`s  e abstraite) et on verra qu’une classe peut ˆtre sous-classe de plusieurs interfaces, la relation d’h´ritage se e e nommant alors impl´mentation. Ainsi, si notre classe ObjetGraphique peut ˆtre ´crite sous-forme d’interface, e e e on pourra finalement exprimer en Java que la classe RectangleGraphique est une extension de la classe RectangleGeometrique et une impl´mentation de l’interface ObjetGraphique ; cela nous apportera une e grande partie des avantages qu’on aurait pu esp´rer de l’h´ritage multiple, s’il avait ´t´ possible. e e ee

7.2
7.2.1

Sous-classes et super-classes
D´finition de sous-classe e

L’h´ritage est le m´canisme qui consiste ` d´finir une classe nouvelle, dite classe d´riv´e ou sous-classe, e e a e e e par extension d’une classe existante, appel´e sa classe de base ou super-classe. La sous-classe poss`de d’office e e tous les membres de la super-classe, auxquels elle ajoute ses membres sp´cifiques. e L’h´ritage est indiqu´ par l’expression  extends SuperClasse  ´crite au d´but de la d´finition d’une e e e e e classe. Par exemple, d´finissons la classe Pixel comme sous-classe de la classe Point qui nous a d´j` servi e ea d’exemple. Un pixel est un point, ´tendu par l’ajout d’une variable d’instance qui repr´sente une couleur e e (Color est une classe d´finie dans le paquet java.awt) : e class Point { int x, y; ... } class Pixel extends Point { Color couleur; ... } Avec la d´finition pr´c´dente, un Pixel se compose de trois variables d’instance : x et y, h´rit´es de la e e e e e classe Point, et couleur, une variable d’instance sp´cifique. Ces trois variables sont membres de plein droit e de la classe Pixel et, si pix est un objet de cette classe, on peut ´crire – sous r´serve qu’on ait le droit e e d’acc`s – pix.x et pix.y exactement comme on peut ´crire pix.couleur. e e Note. En toute rigueur, si la d´finition d’une classe D commence par e class D extends B ... on devrait dire que D est une sous-classe directe de B ou que B est une super-classe directe de D. Par ailleurs, on dit que D est une sous-classe de B si D est sous-classe directe de B ou d’une sous-classe de B. Dans ce cas, on dit aussi que B est super-classe de D. Une super-classe [resp. une sous-classe] qui n’est pas directe est dite indirecte. Cela ´tant, nous dirons sous-classe [resp. super-classe] pour sous-classe directe [resp. super-classe directe], e sauf dans les (rares) situations o` cela cr´erait une confusion. u e 40
c H. Garreta, 2000-2006

´ 7 HERITAGE

7.3

Red´finition des m´thodes e e

7.2.2

♥ L’arbre de toutes les classes

En Java l’h´ritage est simple : chaque classe poss`de au plus une super-classe directe. En fait, il y a une e e classe particuli`re, nomm´e Object, qui n’a pas de super-classe, et toutes les autres classes ont exactement e e une super-classe directe. Par cons´quent, l’ensemble de toutes les classes, muni de la relation  est sous-classe e directe de , constitue une unique arborescence dont la racine est la classe Object. Ainsi, les variables et m´thodes qui composent un objet ne sont pas toutes d´finies dans la classe de e e celui-ci : il y en a une partie dans cette classe, une partie dans sa super-classe, une autre partie dans la super-classe de la super-classe, etc. Cela introduit la question de la recherche des m´thodes : un message e ayant ´t´ envoy´ ` un objet, la r´ponse pertinente doit ˆtre recherch´e dans la classe de celui-ci ou, en cas ee ea e e e d’´chec, dans la super-classe de celle-l`, puis dans la super-classe de la super-classe, et ainsi de suite, en e a remontant le long de la branche de l’arbre d’h´ritage, jusqu’` trouver une classe dans laquelle on a pr´vu de e a e r´pondre au message en question. e Le proc´d´ de recherche des m´thodes est un probl`me important, dans tous les langages qui pratiquent e e e e l’h´ritage38 . Il peut ˆtre trait´ ` la compilation, on parle alors de liaison statique, ou bien ` l’ex´cution, on dit e e ea a e qu’on pratique alors la liaison dynamique. La liaison statique est plus efficace, puisque les incertitudes sont lev´es avant que l’ex´cution ne commence, mais la liaison dynamique est beaucoup plus souple et puissante. e e Comme la suite le montrera, Java met en œuvre un savant m´lange de liaison statique et dynamique. e

7.3
7.3.1

Red´finition des m´thodes e e
Surcharge et masquage

Que se passe-t-il lorsque des membres sp´cifiques de la sous-classe ont le mˆme nom que des membres e e h´rit´s ? Deux cas : e e 1. Si les entit´s qui ont le mˆme nom ont des rˆles ou des signatures diff´rentes, alors c’est un cas de e e o e surcharge et il est trait´ comme s’il s’agissait de deux membres de la mˆme classe. Par exemple, rien e e ne s’oppose ` ce que cohabitent sans conflit dans la sous-classe une variable d’instance propre f et une a m´thode h´rit´e f, ou deux m´thodes de signatures diff´rentes dont une est h´rit´e et l’autre non. e e e e e e e 2. S’il s’agit au contraire de deux entit´s ayant le mˆme rˆle et, dans le cas des m´thodes, la mˆme e e o e e signature alors il y a masquage : dans la sous-classe, le membre sp´cifique masque le membre h´rit´. e e e Il y a plusieurs mani`res d’acc´der ` un membre masqu´. Imaginons par exemple qu’on a introduit une e e a e mesure de qualit´ dans les points, et aussi une notion de qualit´ dans les pixels, ces deux notions n’ayant e e pas de rapport entre elles39 : class Point { int x, y; int qualite; ... } class Pixel extends Point { Color couleur; int qualite; ... } Dans ces conditions, ` l’int´rieur d’une m´thode de la classe Pixel, les expressions qualite et this.qualite a e e d´signent le membre qualite sp´cifique de la classe Pixel. Mais les expressions e e super.qualite et ((Point) this).qualite d´signent toutes deux le membre qualite h´rit´ de la classe Point. e e e
38 Notez que ce probl`me est partiellement simplifi´ et optimis´ en Java par le fait qu’on n’y pratique que l’h´ritage simple. e e e e Dans les langages ` h´ritage multiple, o` une classe peut avoir plusieurs super-classes directes, le graphe d’h´ritage n’est plus a e u e un arbre. Dans ces langages, la recherche des m´thodes devient une op´ration litigieuse et complexe. e e 39 Si la qualit´ d’un pixel (en tant que pixel) n’a pas de rapport avec la qualit´ d’un point, c’est une maladresse que de donner e e le mˆme nom ` ces variables d’instance ; il vaudrait certainement mieux que le membre Pixel.qualite ait un autre nom. e a

c H. Garreta, 2000-2006

41

7.3 Red´finition des m´thodes e e

7

´ HERITAGE

7.3.2

Red´finition des m´thodes e e

Si la d´finition dans une sous-classe d’une variable ayant le mˆme nom qu’une variable de la super-classe e e apparaˆ souvent comme une maladresse de conception, la d´finition d’une m´thode ayant le mˆme nom et ıt e e e la mˆme signature qu’une m´thode de la super-classe est au contraire une technique justifi´e et tr`s utile. e e e e En effet, la sous-classe ´tant une extension, c’est-`-dire une  am´lioration , de la super-classe, les objets e a e de la sous-classe doivent r´pondre ` tous les messages auxquels r´pondent les objets de la super-classe, mais e a e il est normal qu’il y r´pondent de mani`re  am´lior´e  : beaucoup de m´thodes de la sous-classe font les e e e e e mˆmes choses que des m´thodes correspondantes de la super-classe, mais elles le font  mieux , c’est-`-dire e e a en prenant en compte ce que les objets de la sous-classe ont de plus que ceux de la super-classe. Exemple, courant mais fondamental : la m´thode toString40 : e class Point { int x, y; ... public String toString() { return "(" + x + "," + y + ")"; } } class Pixel extends Point { Color couleur; ... public String toString() { return "[(" + x + "," + y + ");" + couleur + "]"; } } Il est facile de comprendre pourquoi la r´ponse ` un appel comme unPixel.toString() peut ˆtre vue e a e comme une  am´lioration  de la r´ponse ` unPoint.toString() : si unPoint repr´sente un point et e e a e unPixel un pixel, les expressions System.out.println(unPoint); System.out.println(unPixel); affichent, par exemple (10,20) [(30,40);java.awt.Color.red] ´ Note. Ecrite comme elle l’est, la m´thode Pixel.toString pr´c´dente a deux d´fauts : e e e e – le mˆme code (le traitement de x et y) est ´crit dans Point.toString et dans Pixel.toString, ce e e qui constitue une redondance, toujours regrettable, – plus grave, la classe Pixel s’appuie ainsi sur des d´tails internes de la classe Point. e Une mani`re bien pr´f´rable d’´crire les m´thodes red´finies de la sous-classe consiste ` laisser les m´thodes e ee e e e a e de la super-classe se charger du traitement de la partie h´rit´e : e e class Pixel extends Point { Color couleur; ... public String toString() { return "[" + super.toString() + ";" + couleur + "]"; } } Contraintes de la red´finition des m´thodes e e 1. On ne peut pas profiter de la red´finition d’une m´thode pour en restreindre l’accessibilit´ : la e e e red´finition d’une m´thode doit ˆtre au moins  aussi publique  que la d´finition originale. En particue e e e lier, la red´finition d’une m´thode publique41 doit ˆtre qualifi´e public. e e e e 2. La signature de la red´finition d’une m´thode doit ˆtre identique ` celle de la m´thode h´rit´e. La e e e a e e e signature d’une m´thode se compose de trois ´l´ments : e ee
m´thode toString appartient ` tous les objets, puisqu’elle est d´finie dans la classe Object. e a e notera que cela concerne en particulier la red´finition – obligatoire, dans ce cas – des m´thodes des interfaces (cf. section e e 7.6.3) qui, par d´finition, sont toutes implicitement publiques. e
41 On 40 La

42

c H. Garreta, 2000-2006

´ 7 HERITAGE

7.4

H´ritage et constructeurs e

– le nom de la m´thode, e – la suite des types de ses arguments, – le fait que la m´thode soit ordinaire (d’instance) ou static. e Par exemple, si la d´finition d’une m´thode commence par la ligne e e public double moyenne(double[] tableau, int nombre, String titre) { alors sa signature est le triplet : < moyenne, (double[], int, java.lang.String), non statique > 3. Il faut faire tr`s attention ` la contrainte ´voqu´e au point pr´c´dent, car les diff´rences de signature e a e e e e e ne sont pas en tant que telles signal´es par le compilateur. e En effet, imaginons que la m´thode ci-dessus soit d´finie dans une classe A, et que dans une sous-classe e e B de A on ´crive une m´thode (que l’on pense ˆtre une red´finition de la pr´c´dente) commen¸ant par e e e e e e c public double moyenne(double[] tableau, short nombre, String titre) { Les signatures de ces deux m´thodes n’´tant pas identiques, la deuxi`me n’est pas une red´finition e e e e de la premi`re. H´las pour le programmeur, cela ne constitue pas une erreur que le compilateur pourrait e e diagnostiquer, mais un simple cas de surcharge : la classe B poss`de deux m´thodes nomm´es moyenne : la e e e premi`re prend un int pour second argument tandis que la deuxi`me prend un short42 . e e 4. On notera bien que le type du r´sultat renvoy´ par une m´thode ne fait pas partie de la signature de e e e celle-ci. Dans ces conditions : – une d´finition de m´thode est une red´finition si et seulement si la signature est identique ` celle d’une e e e a m´thode h´rit´e, ind´pendamment du type du r´sultat, e e e e e – si une d´finition est une red´finition, alors le type du r´sultat de la red´finition doit ˆtre identique au e e e e e type du r´sultat de la m´thode h´rit´e. e e e e Note Java 5. La pratique montre que la contrainte exprim´e ci-dessus est trop forte. Dans la mesure e o` on peut voir la red´finition d’une m´thode comme un raffinement de celle-ci, on souhaiterait parfois que u e e la m´thode raffin´e puisse rendre un r´sultat qui est lui-mˆme un raffinement du r´sultat renvoy´ par la e e e e e e m´thode originale. e C’est pourquoi en Java 5 la r`gle pr´c´dente est devenue : e e e – une d´finition de m´thode est une red´finition si et seulement si la signature est identique ` celle d’une e e e a m´thode h´rit´e, ind´pendamment du type du r´sultat, e e e e e – si une d´finition est une red´finition, alors le type du r´sultat de la red´finition doit ˆtre soit identique, e e e e e soit une sous-classe du type du r´sultat de la m´thode h´rit´e. e e e e

7.4

H´ritage et constructeurs e

L’initialisation d’un objet d’une sous-classe implique son initialisation en tant qu’objet de la super-classe. Autrement dit, tout constructeur de la sous-classe commence n´cessairement par l’appel d’un constructeur e de la super-classe. Si le programmeur n’a rien pr´vu, ce sera le constructeur sans arguments de la super-classe, qui doit donc e exister. Mais on peut expliciter l’appel d’un constructeur de la super-classe par l’instruction : super(arguments); Si elle est pr´sente, cette instruction doit ˆtre la premi`re du constructeur de la sous-classe. Par exemple, si e e e la classe Point n’a qu’un constructeur, ` deux arguments : a class Point { int x, y; Point(int x, validation this.x = this.y = } ... }

int y) { de x et y x; y;

alors le programmeur est oblig´ d’´crire un constructeur pour la classe Pixel, comme ceci : e e
42 La diff´rence entre un int et un short ´tant minime (la valeur 10 est-elle de type int ou de type short ?), on montre l` e e a une situation qui, si elle ´tait voulue, serait certainement une maladresse source de confusion et d’erreurs ult´rieures. e e

c H. Garreta, 2000-2006

43

7.5 ♥ Le polymorphisme

7

´ HERITAGE

class Pixel extends Point { Color couleur; Pixel(int x, int y, Color couleur) { super(x, y); this.couleur = couleur; } ... }

7.5

♥ Le polymorphisme

Le polymorphisme est un concept particuli`rement riche pr´sent dans beaucoup de langages orient´s e e e objets. L’aspect que nous en ´tudions ici tourne autour des notions de g´n´ralisation et particularisation. e e e La g´n´ralisation est la possibilit´ de tenir un objet pour plus g´n´ral qu’il n’est, le temps d’un traitement. e e e e e Par exemple, pour appliquer une transformation g´om´trique ` un Pixel il suffit de savoir qu’il est une sorte e e a de Point. Par particularisation nous entendons la possibilit´ de retrouver le type particulier d’un objet alors que ce e dernier est acc´d´ ` travers une variable d’un type plus g´n´ral. Par exemple, retrouver le fait que la valeur e ea e e d’une variable de type Point est en r´alit´ un Pixel. e e 7.5.1 G´n´ralisation et particularisation e e

´ ´ Generalisation (conversion sous-classe → super-classe). Si B est une super-classe d’une classe D, la conversion d’un objet D vers le type B est une op´ration naturelle et justifi´e. En effet, ` cause de l’h´ritage, e e a e toutes les variables et m´thodes de la classe B sont dans la classe D ; autrement dit, tout ce qu’un B sait faire, e un D sait le faire aussi bien (et peut-ˆtre mieux, ` cause de la red´finition possible des m´thodes h´rit´es). e a e e e e Par cons´quent, en toute circonstance, l` o` un B est attendu on peut mettre un D. e a u De telles conversions sont implicites et toujours accept´e par le compilateur. Nous les rencontrerons dans e de tr`s nombreuses situations. Par exemple, avec nos classes Point et Pixel : e class Point { int x, y; ... int distance(Point p) { return Math.abs(x - p.x) + Math.abs(y - p.y); } ... } class Pixel extends Point { Color couleur; ... } si les variables unPoint et unPixel ont les types que leurs noms sugg`rent, l’expression e r = unPoint.distance(unPixel); illustre la g´n´ralisation : lors de l’appel de la m´thode distance, la valeur de la variable unPixel est affect´e e e e e a ` l’argument p (de type Point) ; ainsi, durant le calcul de sa distance au point donn´, le pixel est vu comme e un Point, ce qui est suffisant car, dans le calcul d’une distance, seuls interviennent les membres que le pixel h´rite de la classe Point. e Notez que c’est le mˆme genre de consid´rations qui donnent un sens ` l’expression : e e a r = unPixel.distance(unPoint); Particularisation (conversion super-classe → sous-classe). Une cons´quence du fait que les objets e sont d´sign´s par r´f´rence (cf. section 3.3) est qu’ils ne sont pas alt´r´ par leur g´n´ralisation : lorsqu’un e e ee ee e e objet d’une classe D est acc´d´ ` travers une variable d’un type plus g´n´ral B on ne peut pas atteindre les e ea e e membres de la classe D qui ne sont pas dans B, mais ces membres ne cessent pas pour autant d’exister dans l’objet en question. Cela donne un sens ` l’op´ration r´ciproque de la g´n´ralisation : donner ` un objet O un type T plus a e e e e a particulier que celui ` travers lequel il est connu ` un instant donn´. Bien entendu, cela n’est l´gitime que si a a e e T est soit le type effectif de O, soit une g´n´ralisation de ce type. e e 44
c H. Garreta, 2000-2006

´ 7 HERITAGE

7.5 ♥ Le polymorphisme

Une telle conversion doit toujours ˆtre explicit´e par le programmeur, elle n’est jamais implicite : e e Point unPoint; Pixel unPixel = new Pixel(a, b, c); ... unPoint = unPixel; // OK. G´n´ralisation e e ... unPixel = unPoint; ... unPixel = (Pixel) unPoint; // ERREUR // OK. Particularisation

Les contraintes que doit v´rifier une expression de la forme (T ) E, o` T est une classe et E une expression e u d’un type classe, ne sont pas les mˆmes ` la compilation et ` l’ex´cution du programme : e a a e – ` la compilation, il faut que T soit sous-classe ou super-classe, directe ou indirecte, du type effectif a de E (c’est-`-dire que les conversions ne sont accept´es ` la compilation qu’entre deux classes qui se a e a trouvent sur la mˆme branche de l’arbre d’h´ritage), e e – ` l’ex´cution, il faut que la conversion repr´sente une g´n´ralisation du type effectif de E, sinon, une a e e e e exception ClassCastException sera lanc´e. e Ces contraintes sont faciles ` comprendre : il suffit de se rappeler que la g´n´ralisation et la particularia e e sation sont des transformations sans travail (des  jeux de pointeurs ), qui ne peuvent en aucun cas ajouter a ` un objet des membres qu’il n’a pas. Exemple. Une situation dans laquelle la g´n´ralisation et la particularisation apparaissent tr`s naturellee e e ment est l’emploi d’outils g´n´raux de la biblioth`que Java, comme les collections. Par exemple, une mani`re e e e e de repr´senter une ligne polygonale consiste ` se donner une liste (ici, une liste chaˆ ee, LinkedList) de e a ın´ points (ici, tir´s au hasard) : e ... Point unPoint; List lignePolygonale = new LinkedList(); for (int i = 0; i < nbr; i++) { unPoint = new Point((int)(XMAX * Math.random()), (int)(YMAX * Math.random())); lignePolygonale.add(unPoint); } ... Consid´rons l’expression  lignePolygonale.add(unPoint) . Les LinkedList ´tant d´finies ` un niveau e e e a de g´n´ralit´ maximal, l’objet unPoint ne peut ˆtre trait´, dans les m´thodes de la liste, que comme un simple e e e e e e Object, la classe la plus g´n´rale. e e Du coup, lorsque ces points sont extraits de la liste, il faut explicitement leur redonner le type qui est le leur. Par exemple, cherchons ` afficher les sommets d’une telle ligne polygonale : a ... Iterator iter = lignePolygonale.iterator(); while (iter.hasNext()) { unPoint = (Point) iter.next(); application ` unPoint d’un traitement n´cessitant un Point a e } ... La conversion  (Point) iter.next()  est n´cessaire, car le r´sultat rendu par next est un Object. Elle e e est certainement correcte, car dans la collection que iter parcourt nous n’avons mis que des objets Point. Lorsqu’une liste n’est pas homog`ne comme ci-dessus, il faut s’aider de l’op´rateur instanceof pour e e retrouver le type effectif des objets. Par exemple, supposons que figure soit une collection contenant des points et des objets d’autres types, voici comment en traiter uniquement les points : ... Iterator iter = figure.iterator(); while (iter.hasNext()) {

c H. Garreta, 2000-2006

45

7.5 ♥ Le polymorphisme

7

´ HERITAGE

Object unObjet = iter.next(); if (unObjet instanceof Point) { Point unPoint = (Point) unObjet; traitement de unPoint } } ... N.B. Le programme pr´c´dent traitera les points, mais aussi les pixels et les objets de toutes les souse e classes, directes et indirectes, de la classe Point. Telle est la s´mantique de l’op´rateur instanceof (c’est le e e comportement g´n´ralement souhait´). e e e

7.5.2

Les m´thodes red´finies sont “virtuelles” e e

Soit v une variable d’un type classe B et m une m´thode de B ; un appel tel que v.m(arguments) est e l´gitime. Imaginons qu’au moment d’un tel appel, la valeur effective de v est instance de D, une sous-classe e de B, dans laquelle m a ´t´ red´finie. La question est : quelle version de m sera appel´e ? Celle de B, la classe ee e e de la variable v, ou bien celle de D, la classe de la valeur effective de v ? En Java, la r´ponse est : la seconde. Quel que soit le type sous lequel un objet est vu ` la compilation, lorse a qu’` l’ex´cution la machine appelle sur cet objet une m´thode qui a ´t´ red´finie, la d´finition effectivement a e e ee e e employ´e est la plus particuli`re, c’est ` dire la plus proche du type effectif de l’objet. e e a Exemple, trivial mais r´v´lateur, avec la m´thode toStringtoString : e e e Object unObjet = new Point(1, 2); ... String str = unObjet.toString(); La chaˆ affect´e ` str par l’expression ci-dessus est "(1,2)", c’est-`-dire l’expression textuelle d’un objet ıne e a a Point (et non "Point@12cf71", qui serait le r´sultat de la version de toString d´finie dans Object). Noter e e que c’est bien parce que toString est d´finie dans la classe Object que l’expression unObjet.toString() e est accept´e ` la compilation, mais ` l’ex´cution c’est la version d´finie dans la classe Point, la classe de la e a a e e valeur effective de unObjet, qui est employ´e. e Dans certains langages, comme C++, on appelle cela des m´thodes virtuelles ; en Java, toutes les e m´thodes sont donc virtuelles. Cela a d’importantes cons´quences m´thodologiques, et rend le polymore e e phisme extrˆmement int´ressant et puissant. e e Autre exemple (plus instructif). Consid´rons une application manipulant des classes d’objets devant e ˆtre dessin´s dans des fenˆtres graphiques : des segments, des rectangles, des cercles, etc. Dans toutes ces e e e classes il y a une m´thode seDessiner prenant en charge la pr´sentation graphique des objets en question. e e La question est : comment manipuler ensemble ces objets diff´rents, tout en ayant le droit d’utiliser ce e qu’ils ont en commun ? Par exemple, comment avoir une collection de tels objets graphiques diff´rents, et e pouvoir appeler la m´thode seDessiner de chacun ? e On l’aura compris, la solution consiste ` d´finir une super-classe de toutes ces classes, dans laquelle la a e m´thode seDessiner est introduite : e class ObjetGraphique { ... void seDessiner(Graphics g) { } ... } class Segment extends ObjetGraphique { int x0, y0, x1, y1; // extr´mit´s e e void seDessiner(Graphics g) { g.drawLine(x0, y0, x1, y1); } ... } 46
c H. Garreta, 2000-2006

´ 7 HERITAGE

7.5 ♥ Le polymorphisme

class Triangle extends ObjetGraphique { int x0, y0, x1, y1, x2, y2; // sommets void seDessiner(Graphics g) { g.drawLine(x0, y0, x1, y1); g.drawLine(x1, y1, x2, y2); g.drawLine(x2, y2, x0, y0); } ... } class Cercle extends ObjetGraphique { int x, y; // centre int r; // rayon void seDessiner(Graphics g) { g.drawOval(x - r, y - r, x + r, y + r); } ... } Exemple d’utilisation de ces classes : une figure est un tableau d’objets graphiques : ObjetGraphique[] figure = new ObjetGraphique[nombre]; ... figure[i ] = new Segment(arguments); ... figure[j ] = new Triangle(arguments); ... figure[k ] = new Cercle(arguments); ... Les divers objets graphiques subissent une g´n´ralisation lors de leur introduction dans le tableau figure. e e Puisque seDessiner est d´finie dans la classe ObjetGraphique, on peut appeler cette m´thode ` travers e e a les ´l´ments de figure. L’int´rˆt de tout cela r´side dans le fait que c’est la m´thode propre ` chaque objet ee ee e e a graphique particulier qui sera appel´e, non celle de la classe ObjetGraphique : e for (int i = 0; i < figure.length; i++) figure[i].seDessiner(g); // ceci d´pend de la valeur effective de figure[i] e Les m´thodes red´finies sont n´cessairement virtuelles e e e La  virtualit´  des m´thodes peut donc se r´sumer ainsi : la m´thode effectivement appel´e par une e e e e e expression comme unObjet.uneMethode() d´pend de la valeur effective de unObjet au moment de cet appel, e non de la d´claration de unObjet ou de propri´t´s statiques d´coulant du texte du programme. e ee e Bien que la plupart du temps cette propri´t´ soit extrˆmement utile, on voudrait quelquefois pouvoir ee e choisir la m´thode a appeler parmi les diverses red´finitions faites dans les super-classes. Il faut comprendre e e que cela ne se peut pas : le m´canisme des m´thodes virtuelles en Java n’est pas d´brayable. e e e Seule exception : si uneMethode est red´finie dans une classe, l’expression super.uneMethode() permet e d’appeler, depuis une m´thode de la mˆme classe, la version de uneMethode qui est en vigueur dans la e e super-classe. Mais il n’y a aucun autre moyen pour  contourner  une ou plusieurs red´finitions d’une e m´thode. e On notera que les variables ne sont pas trait´es comme les m´thodes (mais la red´finition des variables e e e n’a pas d’int´rˆt, ce n’est qu’un masquage g´n´ralement maladroit). L’exemple suivant illustre la situation : ee e e class A { String v = "variable A"; String m() { return "m´thode A"; } e } class B extends A { String v = "variable B"; String m() { return "m´thode B"; } e }

c H. Garreta, 2000-2006

47

7.5 ♥ Le polymorphisme

7

´ HERITAGE

class C extends B { String v = "variable C"; String m() { return "m´thode C"; } e void test() { System.out.println( v ); System.out.println( super.v ); System.out.println( ((A) this).v ); System.out.println( m() ); System.out.println( super.m() ); System.out.println( ((A) this).m() ); } public static void main(String[] args) { new C().test(); } } Affichage obtenu : variable C variable B variable A m´thode C e m´thode B e m´thode C e 7.5.3

← pour une variable, cela  marche  ← pour une m´thode non e

M´thodes et classes finales e

Les m´thodes virtuelles sont extrˆmement utiles, mais elles ont un coˆt : de l’information cach´e doit e e u e ˆtre ajout´e aux instances de la classe ObjetGraphique pour permettre ` la machine Java de rechercher la e e a m´thode qui doit ˆtre effectivement appel´e ` l’occasion d’une expression comme e e e a unObjetGraphique.seDessiner(g); cette  recherche  prenant d’ailleurs du temps ` l’ex´cution. a e C’est la raison pour laquelle le programmeur peut optimiser son programme en d´clarant qu’une m´thode e e ne sera pas red´finie dans les ´ventuelles sous-classes, ce qui permet au compilateur de lier statiquement les e e appels de cette m´thode, au lieu de les lier dynamiquement comme le veut la r`gle g´n´rale. e e e e Cela se fait par l’emploi du qualifieur final. Par exemple, s’il y a lieu de penser que le dessin d’un cercle sera le mˆme dans la classe Cercle et dans les sous-classes de celle-ci, on peut d´clarer cette m´thode comme e e e ceci : class Cercle extends ObjetGraphique { int x, y; // centre int r; // rayon final void seDessiner(Graphics g) { // cette m´thode ne sera e g.drawOval(x - r, y - r, x + r, y + r); // pas red´finie dans les e } // sous-classes de Cercle ... } Classes finales. Si une classe ne doit pas avoir de sous-classes, on peut effectuer d’un seul coup le travail d’optimisation pr´c´dent sur toutes ses m´thodes en d´clarant final la classe tout enti`re. Par exemple, si e e e e e la classe Triangle n’est pas destin´e ` avoir des sous-classes, on peut l’´crire : e a e final class Triangle extends ObjetGraphique { int x0, y0, x1, y1, x2, y2; // sommets void seDessiner(Graphics g) { g.drawLine(x0, y0, x1, y1); g.drawLine(x1, y1, x2, y2); g.drawLine(x2, y2, x0, y0); } ... }

48

c H. Garreta, 2000-2006

´ 7 HERITAGE

7.6 ♥ Abstraction

7.6
7.6.1

♥ Abstraction
M´thodes abstraites e

Revenons sur l’exemple de la section pr´c´dente : e e class ObjetGraphique { ... void seDessiner(Graphics g) { } ... } Nous observons ceci : la m´thode seDessiner doit ˆtre d´finie dans la classe ObjetGraphique, car le e e e rˆle de cette classe est de rassembler les ´l´ments communs aux divers objets graphiques consid´r´s, or la o ee ee capacit´ de se repr´senter graphiquement est bien un de ces ´l´ments, sans doute le principal. En mˆme temps e e ee e il nous est impossible, au niveau de la classe ObjetGraphique, d’´crire quoi que ce soit dans la m´thode e e seDessiner, car nous n’en savons pas assez sur ce qu’il s’agit de dessiner. On exprime cette situation en disant que seDessiner est une m´thode abstraite. Sa d´claration dans la e e classe ObjetGraphique est une  promesse  : on annonce que tout objet graphique devra avoir une m´thode e seDessiner, qu’il n’est pas possible de fournir pour le moment. En Java on peut d´clarer qu’une m´thode est abstraite ` l’aide du qualifieur abstract. Le corps de la e e a m´thode doit alors ˆtre remplac´ par un point virgule (et, comme on le verra ` la section suivante, la classe e e e a contenant une telle m´thode doit elle aussi ˆtre d´clar´e abstraite) : e e e e abstract class ObjetGraphique { ... abstract void seDessiner(Graphics g); ... } Les m´thodes abstraites d’une classe restent abstraites dans ses sous-classes, sauf si elles y ont une e red´finition qui leur donne un corps. e La magie des fonctions virtuelles, l’exemple canonique. Ce n’est pas parce qu’une m´thode est e abstraite qu’on ne peut pas l’appeler, y compris dans des classes o` elle n’est pas encore d´finie. Par exemple, u e une mani`re (na¨ e ıve) d’effacer un objet graphique consiste ` le dessiner avec une plume qui a pour couleur la a couleur du fond : abstract class ObjetGraphique { ... abstract void seDessiner(Graphics g); ... void sEffacer(Graphics g) { g.setColor(getBackground()); // on prend la couleur du fond seDessiner(g); } ... } Une instance de la classe ObjetGraphique (mais nous verrons qu’il ne peut pas en exister) ne serait pas plus capable de s’effacer qu’elle ne le serait de se dessiner, mais toute sous-classe qui d´finit une version e effective de seDessiner dispose aussitˆt d’une version op´rationnelle de sEffacer. o e 7.6.2 Classes abstraites

Une classe abstraite est une classe qui ne doit pas avoir d’instances ; elle n’est destin´e qu’` avoir des e a sous-classes qui, elles, auront des instances. Cela peut provenir – d’une impossibilit´ technique ; par exemple, une classe qui poss`de des m´thodes abstraites (en propre e e e ou h´rit´es) ne peut pas avoir des instances, car ce seraient des objets dont une partie du comportement e e requis n’aurait pas ´t´ ´crit, eee e a – d’une impossibilit´ conceptuelle, c’est-`-dire un choix du concepteur. Par exemple, les classes XXXAdapter du paquet java.awt.event sont des classes enti`rement faites de m´thodes vides, dont d’´ventuelles e e e instances n’auraient aucun int´rˆt. ee On indique qu’une classe est abstraite par le qualifieur abstract :
c H. Garreta, 2000-2006

49

7.6 ♥ Abstraction

7

´ HERITAGE

abstract class ObjetGraphique { ... abstract void seDessiner(Graphics g); ... } Il est obligatoire d’employer le qualifieur abstract devant toute classe qui poss`de des m´thodes abse e traites, propres ou h´rit´es. e e 7.6.3 Interfaces

Parlant na¨ ıvement, on peut dire que les m´thodes non abstraites d’une classe sont des  services  que e celle-ci rend aux concepteurs de ses ´ventuelles sous-classes, tandis que les m´thodes abstraites sont des e e  corv´es  que la classe transmet aux concepteurs des sous-classes, qui devront en donner des d´finitions. e e Une interface est une classe enti`rement faite de membres publics qui sont e – des m´thodes abstraites, e – variables statiques finales (c’est-`-dire des constantes de classe). a Une interface se d´clare avec le mot-cl´ interface au lieu de class. Il n’y a alors pas besoin d’´crire les e e e qualifieurs public et abstract devant les m´thodes, ni public, static et final devant les variables. e Par exemple, si elle est enti`rement faite de m´thodes abstraites, notre classe ObjetGraphique peut ˆtre e e e d´clar´ comme une interface : e e interface ObjetGraphique { void seDessiner(Graphics g); ... } Autre exemple, extrait de la biblioth`que Java (plus pr´cis´ment du paquet java.util) : e e e interface Collection { boolean isEmpty(); int size(); boolean contains(Object o); boolean add(Object o); Iterator iterator(); ... } Une interface est une sp´cification : elle fixe la liste des m´thodes qu’on est certain de trouver dans toute e e classe qui d´clare ˆtre conforme ` cette sp´cification. e e a e Cela s’appelle une impl´mentation de l’interface, et s’indique avec le qualifieur implements : e class Tas implements Collection { public boolean isEmpty() { impl´mentation de la m´thode isEmpty e e } public int size() { impl´mentation de la m´thode size e e } ... } Notez que les m´thodes des interfaces sont toujours publiques (implicitement) ; par cons´quent, leurs e e d´finitions dans des sous-classes doivent ˆtre explicitement qualifi´es public, sinon une erreur sera signal´e. e e e e Deux interfaces peuvent h´riter l’une de l’autre. Par exemple, toujours dans le paquet java.util on e trouve l’interface SortedSet (ensemble tri´) qui est une sous-interface de Set, elle-mˆme une sous-interface e e de Collection, etc. D’autre part, la relation implements entre une classe et une interface est aussi une sorte d’h´ritage. Cet e h´ritage est multiple : une classe peut impl´menter plusieurs interfaces distincts. Par exemple, on pourrait e e d´finir une classe Figure qui serait une sorte de Collection (d’objets graphiques) et en mˆme temps une e e sorte d’0bjetGraphique (puisque capable de se dessiner) : 50
c H. Garreta, 2000-2006

´ 7 HERITAGE

7.6 ♥ Abstraction

class Figure implements Collection, ObjetGraphique { ... public void seDessiner(Graphics g) { appeler seDessiner sur chaque membre de la figure } public boolean add(Object o) { ajouter o (un objet graphique) ` la figure a } public boolean isEmpty() { la figure est-elle vide ? } ... } L’int´rˆt de cette sorte d’h´ritage multiple apparaˆ clairement : une instance de notre classe Figure ee e ıt pourra ˆtre mise ` tout endroit o` une Collection est attendue, et aussi ` tout endroit o` un ObjetGraphique e a u a u est attendu. Interface pour transmettre une fonction Java ne comportant pas la notion de pointeur, encore moins de pointeur sur une fonction, comment fait-on dans ce langage pour qu’une m´thode soit argument d’une autre ? e La solution consiste ` d´clarer la m´thode comme membre d’une interface d´finie ` cet effet, et ` mettre a e e e a a l’interface comme type de l’argument en question. Voici par exemple comment d´finir, dans une classe e CalculNumerique, une m´thode zero qui recherche par dichotomie une solution approch´e ` ε-pr`s de e e a e l’´quation f (x) = 0, x ∈ [ a, b ] : e class MonCalculNumerique { ... static double zero(double a, double b, double epsilon, Fonction f) { double c; if (f.val(a) > f.val(b)) { c = a; a = b; b = c; } while (Math.abs(b - a) > epsilon) { c = (a + b) / 2; if (f.val(c) < 0) a = c; else b = c; } return (a + b) / 2; } ... } Fonction est une interface d´finie pour cette occasion. Elle se r´duit ` une m´thode val, qui repr´sente la e e a e e fonction en question : interface Fonction { double val(double x); } Notez ` quel point Fonction et zero illustrent la notion d’interface et son utilit´ :  ˆtre un objet a e e Fonction  ce n’est rien d’autre que  poss´der une m´thode nomm´e val qui prend un double et rend un e e e double . Il n’y a pas besoin d’en savoir plus ´crire la m´thode zero ! e e √ A titre d’exemple proposons-nous d’utiliser cette m´thode pour calculer la valeur de 2 approch´e ` e e a 10−8 pr`s. Il s’agit de r´soudre l’´quation x2 − 2 = 0 sur, par exemple, l’intervalle [ 0, 2 ]. La fonction ` e e e a consid´rer est donc f (x) = x2 − 2. e Premi`re m´thode, lourde, d´finir une classe expr`s : e e e e
c H. Garreta, 2000-2006

51

7.6 ♥ Abstraction

7

´ HERITAGE

class X2moins2 implements Fonction { public double val(double x) { return x * x - 2; } } class Essai { public static void main(String[] args) { Fonction f = new X2moins2(); double z = CalculNumerique.zero(0, 2, 1e-8, f); System.out.println(z); } } Remarquant que la classe X2moins2 n’a qu’une instance, on peut all´ger notre programme en utilisant ` e a sa place une classe anonyme : class Essai { public static void main(String[] args) { Fonction f = new Fonction() { public double val(double x) { return x * x - 2; } }; double z = CalculNumerique.zero(0, 2, 1e-8, f); System.out.println(z); } } On peut mˆme faire l’´conomie de f (mais le programme obtenu est-il plus lisible ?) : e e public class Courant { public static void main(String[] args) { double z = CalculNumerique.zero(0, 2, 1e-8, new Fonction() { public double val(double x) { return x * x - 2; } }); System.out.println(z); } }

52

c H. Garreta, 2000-2006

8 EXCEPTIONS

8

Exceptions

On dit qu’une exception se produit lorsque les conditions d’ex´cution sont telles que la poursuite du e programme devient impossible ou incorrecte. Exemple : l’indice d’un tableau se trouve en dehors des bornes, la m´moire manque lors de la cr´ation d’un objet, etc. e e Les exceptions sont g´n´ralement d´tect´es par les fonctions de bas niveau, voire par la machine Java e e e e elle-mˆme, mais la plupart du temps ce n’est que dans les fonctions de haut niveau qu’on peut programmer e la r´action ad´quate ` de telles situations non pr´vues. e e a e Le m´canisme des exceptions de Java est un moyen de communication permettant aux ´l´ments des e ee couches  basses  des programmes de notifier aux couches  hautes  la survenue d’une condition anormale. Par exemple, nous avons d´j` vu une classe Point dont les instances ´taient soumises ` validation. Une ea e a bonne mani`re de traiter l’invalidit´ des arguments de la construction d’un point consiste `  lancer  une e e a exception : class Point { static final int XMAX = 1024, YMAX = 768; int x, y; Point(int a, int b) throws Exception { if (a < 0 || a >= XMAX || b < 0 || b >= YMAX) throw new Exception("Coordonn´es ill´gales"); e e x = a; y = b; } ... } Les exceptions ont diverses sortes de causes : – des causes synchrones43 , c’est-`-dire provoqu´es par l’ex´cution d’une instruction, comme la division a e e par z´ro, l’impossibilit´ d’allouer de la m´moire, l’impossibilit´ de charger une classe, etc., e e e e – cas particulier du pr´c´dent, l’ex´cution de l’instruction throw, e e e – des causes asynchrones ; en Java il n’y en a que deux : l’ex´cution, depuis un autre thread, de la e m´thode Thread.stop et la survenue d’une erreur interne de la machine Java. e Une fois lanc´e, une exception  remonte , c’est-`-dire : e a – l’ex´cution du bloc de code dans lequel l’exception a ´t´ lanc´e est abandonn´, e ee e e – si ce bloc n’est ni un bloc try ni le corps d’une m´thode, alors il est lui-mˆme vu comme une instruction e e ayant lanc´ l’exception, e – si ce bloc est le corps d’une m´thode, alors l’appel de cette derni`re, dans la m´thode appelante, est e e e vu comme une instruction ayant lanc´ l’exception, e – si ce bloc est un bloc try comportant une clause catch compatible avec l’exception, alors cette derni`re e est  attrap´e  : le code associ´ ` cette clause catch est ex´cut´, et l’exception ne va pas plus loin. e ea e e

8.1

Interception des exceptions

Syntaxe : try { bloc0 } catch(type exception1 arg1 ) { bloc1 } catch(type exception2 arg2 ) { bloc2 } ... finally { block }
43 Ces causes synchrones montrent qu’il n’est pas correct de se limiter ` dire qu’une exception est une situation impr´visible. a e Une division par z´ro ou l’utilisation d’une r´f´rence nulle ne sont qu’apparemment impr´visibles, si on avait mieux analys´ e ee e e le programme et ses donn´es on aurait pu les pr´voir avec certitude. Mais, comme on le verra, il est tr`s commode de ranger e e e parmi les exceptions ces ´v´nements, malgr´ tout difficiles ` pr´voir et g´n´ralement aux cons´quences graves. e e e a e e e e

c H. Garreta, 2000-2006

53

8.2 Lancement des exceptions

8

EXCEPTIONS

Les blocs catch sont en nombre quelconque, le bloc finally est optionnel. Cela fonctionne de la mani`re suivante : les instructions qui composent bloc0 sont ex´cut´es. Plusieurs e e e cas sont possibles : e 1. Si l’ex´cution de bloc0 ne provoque le lancement d’aucune exception, les bouts de code composant bloc1 ... block−1 sont ignor´s. e 2. Si une exception E est lanc´e par une des instructions de bloc0 , ce bloc est imm´diatement abandonn´ e e e et on recherche le premier i tel que E soit instance, directe ou indirecte, de type exceptioni . Alors, E est affect´ ` l’argument argi et le bloc correspondant, bloci , est ex´cut´ (d’une certaine mani`re, cette ea e e e partie ressemble donc ` un appel de fonction). a 3. Si le type de E ne correspond ` aucun des type exceptioni alors l’exception continue sa trajectoire : a l’ensemble  try...catch...  est consid´r´ comme une instruction ayant lanc´ E. ee e L’´ventuel code composant le bloc finally est ex´cut´ quelle que soit la mani`re dont le bloc try a ´t´ e e e e ee quitt´, y compris le cas n e ˚3. Autrement dit, ce bloc finally est le moyen d’assurer qu’un code sera ex´cut´ e e a ` la suite de bloc0 mˆme si ce bloc se termine en catastrophe. e

8.2

Lancement des exceptions
throw exception;

Une exception se lance par l’instruction o` exception est une expression dont la valeur doit ˆtre un objet d’une des classes de la hi´rarchie expliqu´e u e e e a ` la section suivante. 8.2.1 Hi´rarchie des exceptions e

Toutes les classes d’exceptions appartiennent ` un sous-arbre de l’arbre des classes dont la racine est la a classe Throwable : Object Throwable Error Exception RuntimeException

IOException

Classe de base de toutes les exceptions. Erreurs graves (en particulier, exceptions asynchrones) qu’il n’est pas raisonnable de chercher ` r´cup´rer. a e e Exceptions m´ritant d’ˆtre d´tect´es et trait´es. e e e e e Exceptions pouvant survenir durant le fonctionnement normal de la machine Java : - indice de tableau hors des bornes - acc`s ` un membre d’une r´f´rence null e a ee - erreur arithm´tique (division par z´ro, etc.) e e Exceptions pouvant survenir pendant les op´rations e d’entr´e/sortie e

Vos propres classes exceptions viennent ici Autres classes 8.2.2 D´claration des exceptions lanc´es par une m´thode e e e

Une m´thode contenant une instruction pouvant lancer une exception d’un certain type E doit n´cessairement e e – soit attraper l’exception, au moyen d’un bloc  try...catch...  ad´quat, e e o e – soit d´clarer qu’elle est susceptible de lancer, ou plutˆt de laisser ´chapper, des exceptions du type E. Cette d´claration se fait par un ´nonc´ de la forme e e e throws listeDExceptions plac´ ` la fin de l’en-tˆte de la m´thode. Par exemple, le constructeur de la classe Point ´tant ´crit ainsi ea e e e e Point(int a, int b) throws Exception { if (a < 0 || a >= XMAX || b < 0 || b >= YMAX) throw new Exception("Coordonn´es ill´gales"); e e x = a; y = b; } 54
c H. Garreta, 2000-2006

8 EXCEPTIONS

8.2

Lancement des exceptions

(notez que l’´nonc´ throws Exception est obligatoire) pour l’essayer, nous devrons ´crire une m´thode main e e e e soit de la forme : public static void main(String[] args) { ... try { Point p = new Point(u, v); ... } catch (Exception e) { System.out.println("Probl`me: " + e.getMessage()); e } ... } soit de la forme : public static void main(String[] args) throws Exception { ... Point p = new Point(u, v); ... } Note. Dans cet exemple nous nous contentons d’un travail assez sommaire, en ne d´finissant pas de e classe sp´cifique pour les exceptions qui int´ressent notre programme. En r`gle g´n´rale on d´finit de telles e e e e e e classes, car cela permet une r´cup´ration des erreurs plus s´lective : e e e class ExceptionCoordonnesIllegales extends Exception { ExceptionCoordonnesIllegales() { super("Coordonn´es ill´gales"); e e } } class Point { Point(int a, int b) throws Exception { if (a < 0 || a >= XMAX || b < 0 || b >= YMAX) throw new ExceptionCoordonnesIllegales(); x = a; y = b; } ... public static void main(String[] args) { ... try { Point p = new Point(u, v); } catch (ExceptionCoordonnesIllegales e) { System.out.println("Probl`me avec les coordonn´es du point"); e e } ... } } ˆ ´ ˆ ´ Exceptions controlees et non controlees. Les exceptions contrˆl´es sont celles qu’on est oblig´ oe e de d´clarer dans l’en-tˆte de toute m´thode susceptible de les lancer ; les autres exceptions sont dites non e e e contrˆl´es. oe Toutes les exceptions sont contrˆl´es, sauf : oe – les instances de Error, car elles expriment des ´v´nements irrattrapable, e e – les instances de RuntimeException, car s’il fallait d´clarer ces exceptions, cela concernerait toutes les e m´thodes. e
c H. Garreta, 2000-2006

55

8.3 Assert

8

EXCEPTIONS

8.3

Assert

Le service rendu par l’instruction assert 44 est double : – assert est un m´canisme simple et concis permettant de v´rifier, ` l’ex´cution, qu’` certains endroits e e a e a d’un programme des conditions qui doivent ˆtre remplies le sont effectivement, e – assert comporte un dispositif global et imm´diat d’activation ou d´sactivation qui permet, au moe e ment de l’ex´cution, de choisir entre plusieurs modes, allant d’un  mode atelier  muni de nombreuses e v´rifications (qui consomment du temps) ` un  mode utilisateur final , plus rapide puisque les oce a currences de assert y sont neutralis´es. e L’instruction assert se pr´sente sous deux formes : e assert expression 1 ; et assert expression 1 : expression 2 ; expression 1 est une expression bool´enne, expression 2 une expression d’un type quelconque. Dans les deux e cas, l’ex´cution de l’instruction assert commence par ´valuer expression 1 . Si cette expression est vraie, e e l’ex´cution continue sans que rien ne se passe. e En revanche, si expression 1 se r´v`le fausse alors une erreur45 de type java.lang.AssertionError est e e lanc´e. Si c’est la deuxi`me forme de assert qui a ´t´ utilis´e, la valeur d’expression 2 figure dans le message e e ee e associ´ ` l’erreur. ea Exemple : void uneMethode(int x) { ... assert MIN < x && x < MAX; ... } Lors d’une ex´cution dans laquelle uneMethode est appel´e avec un argument inf´rieur ` MIN ou sup´rieur ` e e e a e a MAX on obtiendra l’arrˆt du programme – sauf si l’erreur est attrap´e – avec un message du style : e e java.lang.AssertionError at Tests.uneMethode(Tests.java:187) at Tests.main(Tests.java:12) Exception in thread "main" Si l’instruction assert avait ´t´ ´crite sous la forme : eee ... assert MIN < x && x < MAX : "MIN < x && x < MAX, x = " + x; ... alors le message aurait ´t´ ee java.lang.AssertionError: MIN < x && x < MAX, x = -25 at Tests.uneMethode(Tests.java:187) at Tests.main(Tests.java:12) Exception in thread "main" Note. L’instruction assert est un puissant outil pour d´couvrir des erreurs de conception et on ne saurait e trop insister sur l’int´rˆt de son utilisation. Mais elle n’est pas la bonne mani`re de sauver des situations que ee e le programmeur ne peut pas empˆcher, comme des erreurs dans les donn´es : saborder le programme n’est e e en g´n´ral pas la bonne r´action ` une faute de frappe commise par l’utilisateur ! e e e a De mˆme, assert n’est pas la bonne mani`re d’envoyer des messages ` l’utilisateur final : en principe e e a non-informaticien, celui-ci n’est pas cens´ comprendre un diagnostic abscons mentionnant des classes dont il e ignore l’existence et des num´ros de ligne dont il n’a que faire. L’information que assert produit ne s’adresse e qu’` l’auteur du programme. a En r´sum´, assert est tr`s pratique mais il faut le remplacer par une autre construction (comme une e e e instruction if qui produit un dialogue avec l’utilisateur sans tuer le programme) lorsque
44 Notez que, contrairement ` ce qui se pase en C, le m´canisme assert n’est pas r´alis´ en Java par une biblioth`que s´par´e, a e e e e e e mais par une vraie instruction, c’est-`-dire une extension de la syntaxe du langage. a 45 Rappelons que les erreurs (classe java.lang.Error, sous-classe de java.lang.Throwable) sont une sorte d’exceptions non contrˆl´es. Elles sont destin´es principalement – mais non exclusivement – ` repr´senter des probl`mes s´rieux que les applicaoe e a e e e tions ne doivent pas chercher ` rattraper. a

56

c H. Garreta, 2000-2006

8 EXCEPTIONS

8.3 Assert

– il s’agit de d´tecter des erreurs autres que de conception ou de programmation, e – il s’agit de produire des messages adress´s ` l’utilisateur final, e a – il s’agit de v´rifications qui doivent ˆtre faites mˆme lorsque le m´canisme assert n’est pas actif (voir e e e e ci-dessous). Activation et d´sactivation de assert e Par d´faut, assert est d´sactiv´ : quand une application Java est ex´cut´e, cela se passe comme si ces e e e e e instructions avaient ´t´ enlev´es du code. ee e Pour rendre le m´canisme actif, il faut lancer la machine Java avec l’option -enableassertions ou -ea, e qui peut prendre un argument, de la mani`re suivante : e -ea -ea:unPackage... -ea:... -ea:uneClasse -esa activation activation activation activation activation de de de de de assert assert assert assert assert partout dans l’application, dans le paquetage indiqu´ et tous ses sous-paquetages, e dans le paquetage sans nom, dans la classe indiqu´e, e dans toutes les classes du syst`me. e

Exemple. Ex´cution d’une application dont la m´thode principale est dans la classe MaClasse en activant e e assert dans toutes les classes du paquetage courant : java -ea ... MaClasse

c H. Garreta, 2000-2006

57

9

QUELQUES CLASSES UTILES

9
9.1
9.1.1

Quelques classes utiles
Nombres et outils math´matiques e
Classes-enveloppes des types primitifs

Java comporte une unique arborescence de classes, de racine Object, dans laquelle se trouvent les classes de toutes les donn´es manipul´es par les programmes... sauf les valeurs de types primitifs qui, pour d’´videntes e e e raisons d’efficacit´, gardent leur statut de donn´es ´l´mentaires, utilisables par les op´rations basiques cˆbl´es e e ee e a e dans les ordinateurs. Afin que ces donn´es simples puissent ˆtre trait´es comme des objets lorsque cela est n´cessaire, Java e e e e fournit huit classes-enveloppes, une pour chaque type primitif : Byte, Short, Integer, Long, Float, Double, Boolean et Character. Le rˆle principal de chaque instance d’une de ces classes est d’encapsuler une valeur o du type primitif correspondant. Ces classes ont ` peu pr`s les mˆmes m´thodes ; parmi les principales : a e e e – un constructeur prenant pour argument la valeur du type primitif qu’il s’agit d’envelopper, – la r´ciproque : une m´thode XXX value (XXX vaut byte, short, etc.) pour obtenir la valeur de type e e primitif envelopp´e, e – une m´thode nomm´e toString pour obtenir cette valeur sous forme de chaˆ de caract`res, e e ıne e – ´ventuellement, une m´thode statique, appel´e parseXXX, permettant d’obtenir une valeur d’un type e e e primitif ` partir de sa repr´sentation textuelle (cela s’appelle parse car former une valeur ` partir d’un a e a texte c’est faire une analyse lexicale), – ´ventuellement, une m´thode statique valueOf pour construire un tel objet ` partir de sa repr´sentation e e a e sous forme de texte. Par exemple, IntegerValueOf(uneChaine) donne le mˆme r´sultat que e e new Integer(Integer.parseInt(uneChaine)) ; Pour chacune des huit classes, ces m´thodes sont46 : e Classe Byte : Byte(byte b) byte byteValue() String toString() static Byte valueOf(String s) static byte parseByte(String s) Classe Short : Short(short s) short shortValue() String toString() static Short valueOf(String s) static short parseShort(String s) Classe Integer : Integer(int i) int intValue() String toString() static Integer valueOf(String s) static int parseInt(String s) Classe Long : Long(long l) long longValue() String toString() static Long valueOf(String s) static long parseLong(String s) Classe Float : Float(float f) float floatValue() String toString() static Float valueOf(String s) static float parseFloat(String s) conversion conversion conversion conversion conversion float → Float Float → float Float → String String → Float String → float conversion conversion conversion conversion conversion long → Long Long → long Long → String String → Long String → long conversion conversion conversion conversion conversion int → Integer Integer → int Integer → String String → Integer String → int conversion conversion conversion conversion conversion short → Short Short → short Short → String String → Short String → short conversion conversion conversion conversion conversion byte → Byte Byte → byte Byte → String String → Byte String → byte

46 Les deux premi`res m´thodes list´es ici pour chacune de ces huit classes (le constructeur et la m´thode xxx Value()) assurent e e e e deux op´rations qu’on appelle famili`rement l’emballage et le d´ballage d’une valeur primitive. e e e On notera qu’` partir de Java 5 ces op´rations deviennent implicites (cf. section 12.2) : le compilateur se charge de les ins´rer a e e l` o` elles sont n´cessaires. a u e

58

c H. Garreta, 2000-2006

9 QUELQUES CLASSES UTILES

9.1

Nombres et outils math´matiques e

Classe Double : Double(double d) double doubleValue() String toString() static Double valueOf(String s) static double parseDouble(String s) Classe Boolean : Boolean(boolean b) boolean booleanValue() String toString() static Boolean valueOf(String s) Classe Character : Character(char c) char charValue() String toString() conversion char → Character conversion Character → char conversion Character → String conversion conversion conversion conversion boolean → Boolean Boolean → boolean Boolean → String String → Boolean conversion conversion conversion conversion conversion double Double Double String String → → → → → Double double String Double double

A propos de conversions, signalons ´galement l’existence dans la classe String des m´thodes suivantes : e e static static static static static static static 9.1.2 String String String String String String String valueOf(int i) valueOf(long l) valueOf(float f) valueOf(double d) valueOf(boolean b) valueOf(char c) valueOf(Object o) conversion conversion conversion conversion conversion conversion conversion int → String long → String float → String double → String boolean → String char → String Object → String

Fonctions math´matiques e

La classe Math repr´sente la biblioth`que math´matique. Elle n’est pas destin´e ` avoir des instances : e e e e a toutes ses m´thodes sont statiques. On y trouve : e static double E, static double PI Les constantes e (2.718281828459045) et π (3.141592653589793) type abs(type v) Valeur absolue ; type est un des types int, long, float ou double type max(type a, type b) Le plus grand de a et b ; type est un des types int, long, float ou double type min(type a, type b) Le plus petit de a et b ; type est un des types int, long, float ou double double floor(double v) Le plus grand entier inf´rieur ou ´gal ` v. e e a double ceil(double v) Le plus petit entier sup´rieur ou ´gal ` v. e e a double rint(double v) L’entier le plus proche de v ; s’il y en a deux, celui qui est pair. int round(int v) L’entier le plus proche de v. La valeur de round(v) est la mˆme que celle de (int) floor(v + 0,5). e double random() Un nombre pseudo-al´atoire dans [0, 1[. (S’agissant de nombres pseudo-al´atoires, la classe java.e e util.Random permet de faire des choses plus savantes.) double exp(double x), double log(double x)
c H. Garreta, 2000-2006

59

9.1 Nombres et outils math´matiques e

9

QUELQUES CLASSES UTILES

Exponentielle et logarithme nep´rien. e double pow(double a, double b) Elevation de a ` la puissance b. a La documentation ne donne pas d’indication sur l’algorithme employ´ ni sur son efficacit´, mais e e il est raisonnable de penser que si b est entier (c’est-`-dire si b = Math.ceil(b), alors ab est a calcul´ en faisant des multiplications, aussi peu nombreuses que possible. Dans le cas g´n´ral, ab e e e est calcul´ par la formule eb log a . e double sin(double x), double cos(double x), double tan(double x), double asin(double x), double acos(double x), double atan(double x) Fonctions trigonom´triques habituelles. e double atan2(double b, double a) Rend une valeur de l’intervalle ] − π, π] qui est l’argument du complexe a + ib (pour a = 0 c’est b donc la mˆme chose que tan a ). e 9.1.3 Nombres en pr´cision infinie e

Le paquet java.math (` ne pas confondre avec la classe Math du paquet java.lang) offre des classes a permettant de manipuler des nombres avec autant de chiffres que n´cessaire. e Nombres entiers. Les instances de la classe BigInteger repr´sentent des nombres entiers. Parmi les e principaux membres de cette classe : BigInteger ZERO : la constante 0 BigInteger ONE : la constante 1 BigInteger(String s) : conversion String → BigInteger BigInteger(byte[] t) : conversion byte[] → BigInteger BigInteger add(BigInteger b) : addition BigInteger subtract(BigInteger b) : soustraction BigInteger multiply(BigInteger b) : multiplication BigInteger divide(BigInteger b) : quotient BigInteger remainder(BigInteger b) : reste BigInteger[] divideAndRemainder(BigInteger b) : quotient et reste BigInteger gcd(BigInteger b) : P.G.C.D. BigInteger compareTo(BigInteger b) : comparaison BigInteger max(BigInteger b) : max BigInteger min(BigInteger b) : min int intValue() : conversion BigInteger → int long longValue() : conversion BigInteger → long static BigInteger valueOf(long v) : conversion long → BigInteger Exemple. Le programme suivant calcule la factorielle d’un entier donn´ en argument : e class Factorielle { public static void main(String[] args) { if (args.length <= 0) System.out.println("Emploi: java Factorielle <nombre>"); else { int n = Integer.parseInt(args[0]); BigInteger k = BigInteger.ONE; BigInteger f = BigInteger.ONE; for (int i = 0; i < n; i++) { f = f.multiply(k); k = k.add(BigInteger.ONE); } System.out.println(f); } } } 60
c H. Garreta, 2000-2006

9 QUELQUES CLASSES UTILES

9.2

Collections et algorithmes

exemple d’utilisation : >java Factorielle 500 122013682599111006870123878542304692625357434280319284219241358838584537 315388199760549644750220328186301361647714820358416337872207817720048078 etc. 00000000000000000000000000000000000000000000000000000000000000000000000 > ´ Nombres decimaux. Les objets BigDecimal sont des nombres d´cimaux en grande pr´cision. Plus pr´e e e cis´ment, un BigDecimal nombre est repr´sent´ par un couple form´ d’un BigInteger mantisse et d’un int e e e e (32 bits) echelle tels que nombre = Parmi les principaux membres de cette classe : BigDecimal abs() : valeur absolue BigDecimal add(BigDecimal val) : addition BigDecimal subtract(BigDecimal val) : soustraction BigDecimal multiply(BigDecimal val) : multiplication BigDecimal divide(BigDecimal val, int roundingMode) : division int compareTo(BigDecimal val) : comparaison BigDecimal max(BigDecimal val) : max BigDecimal min(BigDecimal val) : min float floatValue() : conversion BigDecimal → float double doubleValue() : conversion BigDecimal → double int intValue() : conversion BigDecimal → int long longValue() : conversion BigDecimal → long BigInteger unscaledValue() : obtention de la partie mantisse int scale() : obtention de la partie ´chelle e BigDecimal setScale(int scale) : d´finition de la partie ´chelle e e e BigDecimal movePointLeft(int n), BigDecimal movePointLeft(int n) : d´placement de la virgule mantisse 10echelle

9.2

Collections et algorithmes

Note Java 5. Les classes et interfaces de la biblioth`que des collections et algorithmes ont ´t´ consid´rae ee e blement modifi´es par l’introduction de la g´n´ricit´ en Java 5. Notez bien que les collections sont expliqu´es e e e e e ici comme en Java 1.4 ; pour les nouveaut´s apparues en Java 5 consultez la section 12.5. e Les programmes que nous r´alisons couramment manipulent trois sortes de donn´es : les valeurs de types e e primitifs, les objets et les collections d’objets. Dans les sections pr´c´dentes nous avons vu ce que Java propose e e pour les types primitifs et les objets. Pour repr´senter les pluralit´s d’objets nous ne connaissons pour l’instant que les tableaux. Ceux-ci ont e e un avantage consid´rable, la possibilit´ d’acc´der directement (i.e. en  temps constant ) ` un ´l´ment ` e e e a ee a partir de son indice. Mais ils ont aussi un important inconv´nient, le manque de souplesse dans la gestion e de leur espace, puisqu’il faut connaˆ le nombre maximum de leurs ´l´ments au moment de leur cr´ation ıtre ee e (d’o` un risque du d´bordement). u e Cette section montre qu’` cˆt´ des tableaux, la biblioth`que Java propose une grande vari´t´ de colleca oe e ee tions, qui sont des structures de donn´es beaucoup plus souples et riches. e 9.2.1 Collections et tables associatives

Le paquet java.util contient les interfaces et les classes pour la manipulation des collections d’objets. Nous donnons ici la liste exhaustive de ces interfaces et classes, avec une explication du principe de chacune47 .
47 Il n’est pas ais´ d’expliquer les collections et, encore moins, les algorithmes qui leur sont associ´s, car on ne peut pas se e e limiter aux sp´cification de ces classes. Par exemple, pour faire comprendre les diff´rences qu’il y a entre une ArrayList et un e e Vector il faut se pencher sur les impl´mentations de ces classes, ce qui va contre le principe d’encapsulation. e Tout au long de cette section on gardera ` l’esprit que les indications sur l’impl´mentation restent des recommandations a e et que, mˆme si cela semble bizarre, une r´alisation particuli`re de la biblioth`que pourrait ne pas les suivre, ` condition de e e e e a respecter les sp´cifications. e

c H. Garreta, 2000-2006

61

9.2 Collections et algorithmes

9

QUELQUES CLASSES UTILES

Pour les d´tails pr´cis, on se r´f´rera avec profit aux pages concernant le paquetage java.util dans la e e ee documentation de l’API (http://java.sun.com/j2se/1.5.0/docs/api/) et dans le tutoriel Java (http: //java.sun.com/tutorial/). Dans les descriptions suivantes la relation d’h´ritage entre les classes est indiqu´e par la marge laiss´e ` e e e a gauche : Interfaces Collection. Cette interface repr´sente la notion de collection d’objets la plus g´n´rale. A ce e e e niveau d’abstraction, les op´rations garanties sont : obtenir le nombre d’´l´ments de la e ee collection, savoir si un objet donn´ s’y trouve, ajouter (sans contrainte sur la position) et e enlever un objet, etc. On notera que les ´l´ments des collections sont d´clar´s avec le type Object. Cela a deux ee e e cons´quences principales48 : e 1. Tout objet particulier peut devenir membre d’une collection, et il ne  perd  rien en le devenant, mais la vue qu’on en a en tant que membre de la collection est la plus g´n´rale qui soit. Par cons´quent, quand un objet est extrait d’une collection il faut e e e lui  redonner  explicitement le type particulier qui est le sien. Au besoin, relisez la section 7.5.1. 2. Les valeurs de types primitifs (boolean, int, float, etc.) ne peuvent pas ˆtre directee ment ajout´es aux collections, il faut auparavant les  emballer  dans des instances des e classes enveloppes correspondantes (Boolean, Integer, Float, etc.). Point important, toutes les collections peuvent ˆtre parcourues. Cela se fait ` l’aide d’un e a it´rateur (interfaces Iterator ou Enumeration, voyez la section 9.2.2), un objet qui assure e que tous les ´l´ments de la collection sont atteints, chacun ` son tour, et qui encapsule les ee a d´tails pratiques du parcours, d´pendant de l’impl´mentation de la collection. e e e List. Il s’agit de la notion de s´quence49 , c’est-`-dire une collection o` chaque ´l´ment a e a u ee un rang. On trouve ici des op´rations li´es ` l’acc`s direct aux ´l´ments (insertion d’un e e a e ee ´l´ment ` un rang donn´, obtention de l’´l´ment qui se trouve ` un rang donn´, etc.) ee a e ee a e mˆme si, ` ce niveau de g´n´ralit´, on ne peut pas donner d’indication sur le coˆt de e a e e e u tels acc`s. e Set. Comme c’est souvent le cas, la notion d’ensemble est celle de collection sans r´p´tition : e e un Set ne peut pas contenir deux ´l´ments e1 et e2 v´rifiant e1 .equals(e2 ). ee e Pour l’essentiel, l’interface Set n’ajoute pas des m´thodes ` l’interface Collection, mais e a uniquement la garantie de l’unicit´ des ´l´ments lors de la construction d’un ensemble e ee ou de l’ajout d’un ´l´ment. ee Aucune indication n’est donn´e a priori sur l’ordre dans lequel les ´l´ments d’un Set e ee sont visit´s lors d’un parcours de l’ensemble. e SortedSet. Un ensemble tri´ garantit que, lors d’un parcours, les ´l´ments sont suce ee cessivement atteints en ordre croissant – soit selon un ordre propre aux ´l´ments (qui doivent alors impl´menter l’interface ee e Comparable), e – soit selon une relation d’ordre (un objet Comparator) donn´e explicitement lors de la cr´ation de l’ensemble. e Map. Cette interface formalise la notion de liste associative ou dictionnaire, c’est-`-dire une cola lection de couples (cl´, valeur ) appel´s associations. e e Les op´rations fondamentales des dictionnaires sont l’ajout et la suppression d’une associae tion et la recherche d’une association ` partir de la valeur d’une cl´. a e
48 Ces deux r`gles sont importantes et il faut s’assurer de bien les comprendre. Nous devons cependant signaler que dans Java e 5 elles sont consid´rablement adoucies par deux ´l´ments nouveaux : e ee – les types param´tr´s (cf. section 12.5.1) permettent de travailler avec des collections dont les ´l´ments sont plus pr´cis que e e ee e de simples Object, – l’emballage et d´ballage automatique (cf. section 12.2) simplifient notablement l’introduction et l’extraction des ´l´ments e ee des collections. 49 Pour exprimer que les ´l´ments d’une s´quence ont un rang, la documentation officielle parle de collections ordonn´es. ee e e Attention, l’expression est trompeuse, cela n’a rien ` voir avec une quelconque relation d’ordre sur les ´l´ments (les collections a ee respectant une relation d’ordre sont appel´es collections tri´es). e e

62

c H. Garreta, 2000-2006

9 QUELQUES CLASSES UTILES

9.2

Collections et algorithmes

Sont fournies ´galement des m´thodes pour obtenir diverses vues du dictionnaire : l’ensemble e e des cl´s, la collection des valeurs, l’ensemble des associations. e L’unicit´ des cl´s est garantie : un objet Map ne peut pas contenir deux associations (c1 , v1 ) e e et (c2 , v2 ) telles que c1 .equals(c2 ). SortedMap. Un dictionnaire dans lequel les associations sont plac´es dans l’ordre croissant e des cl´s, ce qui se manifeste lors des parcours des trois collections associ´es au dictione e naire : l’ensemble des cl´s, la collection des valeurs et l’ensemble des associations. e Classes AbstractCollection. Cette classe abstraite offre un d´but d’impl´mentation de l’interface Cole e lection, destin´e ` all´ger l’effort ` fournir pour disposer d’une impl´mentation compl`te. e a e a e e Pour avoir une collection immuable il suffit de d´finir une sous-classe de AbstractCollection, e dans laquelle seules les m´thodes iterator() et size() sont ` ´crire. Pour avoir une cole ae lection modifiable il faut en outre surcharger la m´thode add(Object o). e AbstractList. Classe abstraite qui est un d´but d’impl´mentation de l’interface List. e e Attention, on suppose ici qu’il s’agit de r´aliser une collection bas´e sur une structure e e de donn´es, comme un tableau, dans laquelle l’acc`s direct est optimis´. Si la liste n’est e e e pas bas´e sur une structure de donn´es ` acc`s direct il vaut mieux ´tendre la classe e e a e e AbstractSequentialList au lieu de celle-ci. Pour disposer d’une liste immuable, les m´thodes get(int index) et size() sont ` e a ´crire. Si on souhaite une liste modifiable, il faut en outre surcharger la m´thode set(int e e index, Object element). AbstractSequentialList. Cette classe abstraite est un d´but d’impl´mentation de e e l’interface List, qu’il faut utiliser – de pr´f´rence ` AbstractList – lorsque la ee a collection ` d´finir est bas´e sur une structure de donn´es ` acc`s (uniquement) a e e e a e s´quentiel. e Pour disposer d’une liste il faut ´tendre cette classe en d´finissant les m´thodes e e e listIterator() et size(). L’it´rateur renvoy´ par la m´thode listIterator est e e e utilis´ par les m´thodes qui impl´mentent l’acc`s direct50 get(int index), set(int e e e e index, Object element), etc. LinkedList. Une impl´mentation compl`te de l’interface List. e e Une LinkedList est r´put´e ˆtre impl´ment´e par une liste chaˆ ee bidirectione e e e e ın´ nelle. En tout cas, toutes les op´rations qui ont un sens pour de telles listes e existent et ont, en principe, le coˆt qu’elles auraient dans ce cas. u Il en d´coule que les LinkedList sont particuli`rement bien adapt´es ` la r´alisation e e e a e de piles (LIFO), de files d’attente (FIFO) et de queues ` deux extr´mit´s. a e e ArrayList. La mˆme chose qu’un Vector, sauf que ce n’est pas synchronis´ (` propos e e a de synchronisation, voyez la section 10.2). Vector. Un Vector est une liste impl´ment´e par un tableau qui prend soin de sa propre e e taille. Lorsqu’on a besoin d’un tableau qui grandit au fur et ` mesure du d´roulement d’un a e programme, les Vector sont une alternative int´ressante aux tableaux ordinaires : e l’acc`s direct aux ´l´ments y est r´alis´ avec autant d’efficacit´ et, en plus, le proe ee e e e grammeur n’a pas besoin d’estimer a priori la taille maximale de la structure, qui se charge de grandir selon les besoins, du moins si les insertions se font ` la fin du a tableau. Deux propri´t´s, dont on peut ´ventuellement fixer la valeur lors de la construction ee e du Vector, en commandent la croissance : – capacity : la taille courante du tableau sous-jacent au Vector, e e e – capacityIncrement : le nombre d’unit´s dont la capacit´ est augment´e chaque fois que cela est n´cessaire. e De plus, un objet Vector est synchronis´51 . e
50 Par cons´quent, l’acc`s au ieme ´l´ment a un coˆ t proportionnel ` i. C’est la raison pour laquelle on doit prendre Abstracte e ee u a SequentialList pour super-classe uniquement lorsque la structure de donn´es sous-jacente ne dispose pas d’acc`s direct. e e 51 Cela veut dire que des dispositions sont prises pour interdire que deux threads acc`dent de mani`re concurrente ` un mˆme e e a e Vector, ce qui donnerait des r´sultats impr´visibles si l’un des deux modifiait le vecteur. A ce propos voyez la section 10.2. e e

c H. Garreta, 2000-2006

63

9.2 Collections et algorithmes

9

QUELQUES CLASSES UTILES

Stack. Un objet Stack est une pile impl´ment´e par un Vector. Cela se manifeste e e par des op´rations sp´cifiques : e e – push(Object o) : empiler un objet au sommet de la pile, – Object pop() : d´piler l’objet au sommet de la pile, e – Object peek() : obtenir la valeur de l’objet au sommet de la pile sans le d´piler, e – boolean empty() : la pile est-elle vide ? – int search(Object o) : rechercher un objet en partant du sommet de la pile. AbstractSet. Cette classe abstraite est un d´but d’impl´mentation de l’interface Set. Le e e proc´d´ est le mˆme que pour AbstractCollection (voir ci-dessus) sauf que le programe e e meur doit prendre soin, en d´finissant les m´thodes manquantes, d’ob´ir aux contraintes e e e suppl´mentaires des ensembles, surtout pour ce qui est de l’unicit´ des ´l´ments. e e ee HashSet. Une impl´mentation (compl`te) de l’interface Set r´alis´e ` l’aide d’une table e e e e a d’adressage dispers´, ou hashcode (c’est-`-dire une instance de la classe HashMap). e a C’est tr`s efficace, car les op´rations de base (add, remove, contains et size) se font e e en temps constant. Le seul inconv´nient52 est que lors des parcours de la structure e les ´l´ments apparaissent dans un ordre impr´visible. ee e LinkedHashSet. Une impl´mentation de l’interface Set qui utilise une table de hase code et, de mani`re redondante, une liste doublement chaˆ ee. e ın´ Cela fonctionne comme un HashSet, et avec pratiquement la mˆme efficacit´, e e mais de plus, lors des parcours de la structure ses ´l´ments sont retrouv´s dans ee e l’ordre dans lequel ils ont ´t´ ins´r´s (c’est la premi`re insertion qui d´termine ee ee e e l’ordre). Un LinkedHashSet est une bonne solution lorsqu’on ne peut pas supporter l’ordre g´n´ralement chaotique dans lequel sont parcourus les ´l´ments d’un HashSet et e e ee que, en mˆme temps, on ne peut pas payer le coˆt additionnel d’un TreeSet, voir e u ci-apr`s. e TreeSet. Une impl´mentation de l’interface Set r´alis´e ` l’aide d’un arbre binaire de e e e a recherche (c’est-`-dire une instance de la classe TreeMap). a Lors des parcours, les ´l´ments de l’ensemble apparaissent en ordre croissant ee – soit relativement ` un ordre qui leur est propre, et dans ce cas ils doivent impl´menter a e l’interface Comparable, – soit relativement ` une relation d’ordre, un objet Comparator, fournie lors de la a cr´ation de la structure. e Les op´rations de base (add, remove et contains) sont garanties prendre un temps e O(log n) (n est le nombre d’´l´ments). ee AbstractMap. Cette classe abstraite offre un d´but d’impl´mentation de l’interface Map, destin´e e e e a e ` all´ger l’effort ` fournir pour disposer d’une impl´mentation compl`te. a e e Pour avoir un dictionnaire immuable il suffit de d´finir une sous classe de AbstractMap et e d’y red´finir la m´thode entrySet. Pour un dictionnaire modifiable il faut en outre red´finir e e e la m´thode put. e e a e HashMap. Impl´mentation de l’interface Map ` l’aide d’une table d’adressage dispers´ ou hashcode. C’est tr`s efficace, car les op´rations de base (get et put) se font en temps e e constant, du moins si on suppose que la fonction de dispersion r´partir uniform´ment e e les valeurs des cl´s. e La table de hashcode est r´alis´e par un tableau qui, ` la mani`re d’un Vector, prend e e a e soin de grandir lorsque la table atteint un certain facteur de remplissage. Le seul inconv´nient est que lors des parcours de la structure les ´l´ments apparaissent e ee avec les cl´s dans un ordre impr´visible. e e
52 Ce n’est qu’un inconv´nient pratique. En th´orie, la notion de  ordre dans lequel les ´l´ments appartiennent ` un ensemble  e e ee a n’a mˆme pas de sens. e

64

c H. Garreta, 2000-2006

9 QUELQUES CLASSES UTILES

9.2

Collections et algorithmes

LinkedHashMap. Impl´mentation de l’interface Map ` l’aide d’une table de hashcode et e a d’une liste doublement chaˆ ee redondante. ın´ Grˆce ` cette liste, lors des parcours de la structure les associations (cl´,valeur ) a a e apparaissent de mani`re ordonn´e, en principe dans l’ordre dans lequel elles ont ´t´ e e ee ajout´es ` la table (dit ordre d’insertion). e a Un argument du constructeur permet de sp´cifier que l’on souhaite, au lieu de l’ordre e d’insertion, un ordre d´fini par les acc`s faits aux ´l´ments une fois qu’ils ont ´t´ e e ee ee ajout´s : cela consiste ` ordonner les ´l´ments en allant du moins r´cemment acc´d´ e a ee e e e vers le plus r´cemment acc´d´ (cela permet d’utiliser des objets LinkedHashMap e e e pour r´aliser des  structures LRU ). e IdentityHashMap. Cette classe impl´mente l’interface Map comme HashMap, sauf qu’elle e utilise la relation d’´quivalence d´finie par l’op´rateur == (´galit´  des r´f´rences ) au e e e e e ee lieu de celle d´finie par le pr´dicat equals (´galit´  des valeurs ). e e e e C’est donc une classe tr`s technique, ` utiliser avec une extrˆme prudence. e a e WeakHashMap. Cette classe impl´mente l’interface Map comme HashMap, sauf que les assoe ciations (cl´,valeur ) sont consid´r´es comme ´tant faiblement li´es ` la table. e ee e e a Cela veut dire que si la table est le seul objet qui r´f´rence une association donn´e, ee e alors cette derni`re pourra ˆtre d´truite par le garbage collector (cf. section 6.5.4) la e e e prochaine fois que la m´moire viendra ` manquer. e a L’int´rˆt pratique est facile ` voir : lorsqu’un objet devient sans utilit´ le programmeur ee a e n’a pas ` se pr´occuper de l’enlever des tables WeakHashMap dans lesquelles il a pu ˆtre a e e enregistr´ et qui, sans cela, le feraient passer pour utile et le maintiendraient en vie. e Cela ´tant, c’est encore une classe tr`s technique, ` utiliser avec prudence. e e a e TreeMap. Cette classe impl´mente l’interface SortedMap en utilisant un arbre binaire rouge et noir 53 . C’est tr`s efficace, puisque les op´rations fondamentales (containsKey, get, e e put et remove) sont garanties en O(log n) (n est le nombre d’associations dans le dictionnaire) et, de plus, lors des parcours de la structure les associations apparaissent avec les cl´s en ordre croissant : e – soit relativement ` un ordre naturel, et dans ce cas les cl´s doivent impl´menter a e e l’interface Comparable, – soit relativement ` une relation d’ordre (un objet Comparator) fournie lors de la a cr´ation du dictionnaire. e Attention. Une relation d’ordre (m´thode compareTo dans le cas de l’interface Comparable, e m´thode compare dans le cas de l’interface Comparator) doit ˆtre compatible avec e e l’´galit´ (cf. section 9.2.4) pour qu’un objet TreeMap qui l’utilise soit correct. e e 9.2.2 It´rateurs e

Un objet qui impl´mente l’interface Iterator repr´sente le parcours d’une collection. Il permet donc, au e e moyen d’op´rations ind´pendantes de la collection particuli`re dont il s’agit, de se positionner sur le premier e e e ´l´ment de la collection et d’obtenir successivement chaque ´l´ment de cette derni`re. ee ee e Ce que  premier ´l´ment  veut dire et l’ordre dans lequel les ´l´ments sont obtenus d´pendent de la ee ee e nature de la collection parcourue, comme il a ´t´ indiqu´ dans la section pr´c´dente. ee e e e Un objet Iterator encapsule une position courante qui repr´sente l’´tat du parcours. Les m´thodes e e e constitutives de l’interface Iterator sont : e e e boolean hasNext() : Pr´dicat vrai si et seulement si le parcours que l’it´rateur repr´sente n’est pas fini (c’est-`-dire si la position courante est valide). a e e Object next() : Renvoie l’objet sur lequel l’it´rateur est positionn´ et fait avancer la position courante. void remove() : Supprime de la collection le dernier ´l´ment pr´c´demment renvoy´ par l’it´rateur. ee e e e e Il faut avoir pr´alablement appel´ next, et un appel de remove au plus est permis pour un e e appel de next.
53 A propos des arbres rouge et noir, voyez Thomas Cormen, Charles Leiserson et Ronald Rivest, Introduction ` l’algoritha mique, Dunod, 1994.

c H. Garreta, 2000-2006

65

9.2 Collections et algorithmes

9

QUELQUES CLASSES UTILES

L’op´ration remove est  facultative . Plus pr´cis´ment, elle est li´e54 au caract`re modie e e e e fiable de la collection qui est en train d’ˆtre parcourue : e – si la collection est modifiable, cette op´ration doit ˆtre op´rationnelle, e e e e a – si la collection est immuable, cette op´ration doit se limiter ` lancer une exception UnsupportedOperationException. L’impl´mentation d’un it´rateur d´pend de la collection ` parcourir ; par cons´quent, on construit un e e e a e it´rateur nouveau, positionn´ au d´but d’une collection, en le demandant ` cette derni`re. Cela fonctionne e e e a e selon le sch´ma suivant : e Collection uneCollection; ... Iterator unIterateur = uneCollection.iterator(); while ( unIterateur .hasNext() ) { Object unObjet = unIterateur .next(); exploitation de unObjet } Exemple. Le programme purement d´monstratif suivant affiche la liste tri´e des nombres distincts 55 e e obtenus en tirant pseudo-al´atoirement N nombres entiers dans l’intervalle [ 0, 100 [ : e ... TreeSet nombres = new TreeSet(); for (int i = 0; i < N; i++) { int r = (int) (Math.random() * 100); nombres.add(new Integer(r)); } Iterator it = nombres.iterator(); while (it.hasNext()) { Object x = it.next(); System.out.print(x + " "); } ... Enumeration. Signalons l’existence de l’interface Enumeration, sensiblement ´quivalente ` Iterator. Sans e a aller jusqu’` dire que Enumeration est d´sapprouv´e (deprecated ), la documentation officielle indique qu’il a e e faut lui pr´f´rer Iterator... ce qui n’est pas toujours possible car un certain nombre de services sont r´serv´s ee e e aux ´num´rations et n’ont pas de contrepartie pour les it´rateurs (par exemple, la m´thode list de la classe e e e e Collections, cf. section 9.2.4). Les homologues des m´thodes hasNext et next, dans Enumeration, sont hasMoreElements et nextElement. e Le sch´ma, dans le cas des ´num´rations, est donc celui-ci : e e e Collection uneCollection; ... Enumeration uneEnumeration = uneCollection.elements(); while ( uneEnumeration.hasMoreElements() ) { Object unObjet = uneEnumeration.nextElement(); exploitation de unObjet } 9.2.3 Quelques m´thodes des collections e

Voici quelques m´thodes des collections parmi les plus importantes (il y en a beaucoup plus que cela) : e Collection. Toutes les collections r´pondent aux messages suivants : e boolean isEmpty() est vrai si et seulement si la collection est vide, int size() renvoie le nombre d’´l´ments de la collection, ee boolean contains(Object unObjet) est vrai si et seulement si la collection contient un ´l´ment ee elt v´rifiant elt.equals(unObjet), e
54 Cette question est cruciale pour les collections dont les op´rations fondamentales sont d´finies ` partir de l’it´rateur e e a e correspondant, comme dans le cas de AbstractSequentialList (cf. section 9.2.1). 55 On a des nombres distincts parce qu’on les range dans un ensemble, et cette liste est tri´e parce qu’il s’agit un TreeSet. e

66

c H. Garreta, 2000-2006

9 QUELQUES CLASSES UTILES

9.2

Collections et algorithmes

void add(Object unObjet) ajoute l’objet indiqu´ ` la collection ; la place ` laquelle l’objet est ea a ajout´ d´pend du type particulier de la collection, e e void remove(Object unObjet) si la collection a des ´l´ments elt v´rifiant elt.equals(unObjet) ee e alors l’un d’entre eux est enlev´ de la collection ; dans le cas o` il y en a plusieurs, savoir e u lequel est ´limin´ d´pend du type de la collection. e e e List. Les listes ont ceci de plus que les simples collections : Object get(int i) renvoie l’´l´ment qui se trouve ` la ieme place, ee a ee e void set(int i, Object unObjet) remplace le ieme ´l´ment par l’objet indiqu´, void add(Object unObjet) ajoute l’objet indiqu´ ` la fin de la liste, ea void add(int i, Object unObjet) ins`re l’objet indiqu´ ` la ieme place (les ´l´ments dont e e a ee l’indice ´tait ≥ i ont, apr`s l’insertion, un indice augment´ de 1). e e e Set. Les ensembles n’ont pas des m´thodes additionnelles, mais un certain comportement est garanti : e boolean add(Object unObjet) si l’ensemble ne contient pas un ´l´ment elt v´rifiant elt.equals(unObjet) alors cette ee e m´thode ajoute cet objet et renvoie true ; sinon, l’ensemble reste inchang´ et cette m´thode e e e renvoie false. Map. Les  dictionnaires  sont des collections de paires (cl´, valeur ) : e Object put(Object cle, Object valeur) ajoute au dictionnaire l’association (cl´, valeur ) indie qu´e ; cette m´thode renvoie la pr´c´dente valeur associ´e ` la cl´ indiqu´, ou null si une e e e e e a e e telle cl´ ne se trouvait pas dans la structure de donn´es, e e Object get(Object cle) renvoie la valeur associ´e ` la cl´ indiqu´e, ou null si une telle cl´ e a e e e n’existe pas. Attention, obtenir null comme r´sultat n’implique pas n´cessairement que la cl´ cherch´e e e e e n’est pas dans la structure : si le couple (cl´, null) existe on obtient le mˆme r´sultat (pour e e e savoir si une cl´ existe il vaut mieux utiliser containsKey), e e e boolean containsKey(Object cle) vrai si et seulement si un couple comportant la cl´ indiqu´e se trouve dans le dictionnaire, boolean containsValue(Object valeur) vrai si et seulement si un ou plusieurs couples comportant la valeur indiqu´e existent dans le dictionnaire. e 9.2.4 Algorithmes pour les collections

La classe Collections (notez le pluriel56 ) est enti`rement faite de m´thodes statiques qui op`rent sur des e e e collections, pour les transformer, y effectuer des recherches, en construire de nouvelles, etc. Nous pr´sentons e ici quelques m´thodes de la classe Collections parmi les plus importantes. e A propos de relation d’ordre. Certaines des op´rations d´crites ci-apr`s cr´ent ou exploitent des cole e e e lections tri´es. Il faut savoir qu’il y a deux mani`res de sp´cifier la relation d’ordre par rapport ` laquelle e e e a une structure est dite tri´e : e – L’interface Comparable. Dire d’une classe qu’elle impl´mente l’interface Comparable c’est dire que ses e instances forment un ensemble muni d’une relation d’ordre, donn´e par l’unique m´thode de cette interface e e int compareTo(Object o) d´finie par : a.compareTo(b) est n´gatif, nul ou positif selon que la valeur de a est inf´rieure, ´gale ou e e e e sup´rieure ` celle de b. e a – L’interface Comparator. Les impl´mentations de cette interface sont des relations57 qu’on passe comme e arguments aux m´thodes qui utilisent ou cr´ent des collections ordonn´es. Cette interface comporte essene e e tiellement la m´thode : e int compare(Object o1, Object o2)
56 Il est rare qu’une classe soit d´sign´e par un substantif pluriel ; une classe  ordinaire  porte plutˆt le nom singulier qui e e o d´crit ses instances. On utilise des pluriels pour d´signer des classes enti`rement faites d’utilitaires se rapportant ` une classe e e e a qui aurait le nom en question, au singulier. Compos´es de variables et m´thodes statiques, ces classes ne sont pas destin´es ` e e e a avoir des instances, elles sont des biblioth`ques de fonctions. e 57 Ne pas confondre ces deux interfaces : un Comparable repr´sente un objet sur lequel est d´finie une relation d’ordre, un e e Comparator repr´sente la relation d’ordre elle-mˆme. e e

c H. Garreta, 2000-2006

67

9.2 Collections et algorithmes

9

QUELQUES CLASSES UTILES

d´finie, comme la pr´c´dente, par : compare(a, b) est n´gatif, nul ou positif selon que la valeur de a est e e e e inf´rieure, ´gale ou sup´rieure ` celle de b. e e e a Dans un cas comme dans l’autre, ces comparaisons supportent quelques contraintes (ce qui est dit ici sur compare s’applique ´galement ` compareTo) : e a – on doit avoir signe(compare(x,y)) == -signe(compare(y,x)) pour tout couple de valeurs x et y ; – la relation est transitive : si compare(x,y) > 0 et compare(y,z) > 0 alors compare(x,z) > 0 ; – si compare(x,y) == 0 alors signe(compare(x,z)) == signe(compare(y,z)) pour tout z ; – en outre, bien que ce ne soit pas strictement requis, on fait g´n´ralement en sorte que e e (compare(x,y) == 0) == x.equals(y) Lorsque cette derni`re contrainte n’est pas satisfaite, la documentation doit comporter l’avertissement e “Note : this comparator imposes orderings that are inconsistent with equals.” Tri d’une collection. static void sort(List uneListe) trie uneListe selon l’ordre naturel de ses ´l´ments, ce qui requiert que ee ces ´l´ments impl´mentent l’interface Comparable et soient comparables deux ` deux. ee e a static void sort(List uneList, Comparator compar) trie uneListe selon l’ordre d´termin´ par compar. e e Dans l’un et l’autre cas le tri est garanti stable : deux ´l´ments ´quivalents (pour la relation d’ordre) ee e se retrouvent plac´s l’un par rapport ` l’autre dans la liste tri´e comme ils ´taient dans la liste avant e a e e le tri. static Comparator reverseOrder() renvoie un comparateur qui repr´sente l’ordre inverse de l’ordre nae turel d’une collection. Par exemple, si l’expression Collections.sort(uneListe) est correcte, alors Collections.sort(uneListe, Collections.reverseOrder()); est correcte aussi et produit le tri de la liste donn´e par  ordre d´croissant . e e static Object max(Collection uneCollection) static Object max(Collection uneCollection, Comparator compar) static Object min(Collection uneCollection) static Object min(Collection uneCollection, Comparator compar) Ces quatre m´thodes recherchent e le maximum ou le minimum d’une collection donn´e, soit par rapport ` l’ordre naturel de la collection e a (dont les ´l´ments doivent alors impl´menter l’interface Comparable) soit par rapport ` la relation ee e a d’ordre repr´sent´e par l’argument compar. e e ´ Recherches dans une collection triee static int binarySearch(List uneListe, Object uneValeur) Recherche la valeur indiqu´e dans la liste e donn´e en utilisant l’algorithme de la recherche binaire (ou dichotomique). La liste doit ˆtre tri´e see e e lon son ordre naturel, ce qui implique que ses ´l´ments impl´mentent l’interface Comparable et sont ee e comparables deux ` deux. a static int binarySearch(List uneListe, Object uneValeur, Comparator compar) Recherche la valeur indiqu´e dans la liste donn´e en utilisant l’algorithme de la recherche binaire (ou dichotomique). e e La liste doit ˆtre tri´e selon l’ordre d´fini par compar. e e e Dans l’un et l’autre cas, le coˆt d’une recherche est de l’ordre de log n si la liste est bas´e sur un vrai u e acc`s direct aux ´l´ments (i.e. le coˆt de la recherche est O(log n) si le coˆt d’un acc`s est O(1)). e ee u u e static int indexOfSubList(List grandeListe, List petiteListe) Recherche le d´but de la premi`re e e occurrence de petiteListe en tant que sous-liste de grandeListe. Plus pr´cis´ment, renvoie la plus e e petite valeur i ≥ 0 telle que grandeListe.subList(i, i + petiteListe.size()).equals(petiteListe) ou -1 si une telle valeur n’existe pas. static int lastIndexOfSubList(List grandeListe, List petiteListe) Recherche le d´but de la derni`re e e occurrence de petiteListe en tant que sous-liste de grandeListe. Plus pr´cis´ment, renvoie la plus e e grande valeur i ≥ 0 telle que grandeListe.subList(i, i + petiteListe.size()).equals(petiteListe) ou -1 si une telle valeur n’existe pas. Pour les deux m´thodes ci-dessus la documentation indique que la technique employ´e est celle de la e e  force brute . Peut-ˆtre ne faut-il pas en attendre une grande efficacit´... e e 68
c H. Garreta, 2000-2006

9 QUELQUES CLASSES UTILES

9.2

Collections et algorithmes

Utilitaires de base static List nCopies(int n, Object uneValeur) Renvoie une liste immuable form´e de n copies de la e valeur uneValeur. Il s’agit de copie superficielle : l’objet uneValeur n’est pas clon´ pour en avoir n e exemplaires. static void copy(List destin, List source) Copie les ´l´ments de la liste source dans les ´l´ments ee ee correspondants de la liste destin, qui doit ˆtre au moins aussi longue que source. Attention, cette e op´ration ne cr´e pas de structure : les deux listes doivent exister. e e static void fill(List uneListe, Object uneValeur) Remplace toutes les valeurs de uneListe par l’unique valeur uneValeur. static boolean replaceAll(List uneListe, Object ancienneValeur, Object nouvelleValeur) Remplace dans uneListe tous les ´l´ments ´gaux ` ancienneValeur par nouvelleValeur. Renvoie ee e a true si au moins un remplacement a eu lieu, false sinon. static ArrayList list(Enumeration uneEnumeration) Construit un objet ArrayList contenant les ´l´ee ments successivement renvoy´s par l’´num´ration indiqu´e, plac´s dans l’ordre dans lequel cette derni`re e e e e e e les a donn´s. e ´ static void swap(List uneListe, int i, int j) Echange les valeurs de uneListe qui se trouvent aux emplacements i et j. static void reverse(List uneListe) Renverse l’ordre des ´l´ments de uneListe. ee static void rotate(List uneListe, int distance) Fait  tourner  les ´l´ments de uneListe : l’´l´ment ee ee qui se trouvait ` l’emplacement i se trouve, apr`s l’appel de cette m´thode, ` l’emplacement a e e a (i + distance) modulo uneListe.size() static void shuffle(List uneListe) R´arrange pseudo-al´atoirement les ´l´ments de uneListe. e e ee static Set singleton(Object unObjet) Renvoie un ensemble immuable constitu´ de l’unique ´l´ment e ee repr´sent´ par unObjet. e e static List singletonList(Object unObjet) Renvoie une liste immuable constitu´e de l’unique ´l´ment e ee unObjet. static Map singletonMap(Object cl´, Object valeur) Renvoie une liste associative immuable constitu´e e e de l’unique association (cle, valeur). La classe Collections contient encore deux autres s´ries de m´thodes, pour lesquelles nous renvoyons ` e e a la documentation officielle : – des m´thodes synchronizedCollection(Collection c), synchronizedList(List l), synchronizede Map(Map m), etc., pour obtenir une version synchronis´e (i.e. pouvant faire face aux acc`s simultan´s e e e faits par plusieurs thread concurrents) d’une collection donn´e, e – des m´thodes unmodifiableCollection unmodifiableList(List l), unmodifiableMap(Map m), etc., e pour obtenir une copie immuable d’une collection donn´e. e 9.2.5 Algorithmes pour les tableaux

La classe Arrays (encore un pluriel) est enti`rement faite de m´thodes statiques qui op`rent sur des e e e tableaux. Voici certaines de ces m´thodes parmi les plus importantes : e static List asList(Object[] unTableau) Construit et renvoie une liste (de taille fixe) bˆtie au-dessus a du tableau donn´. e static int binarySearch(type[] unTableau, type uneValeur) Recherche binaire (ou dichotomique). type est un des mots byte, short, int, long, float, double ou char. Ces m´thodes recherchent uneValeur dans le tableau unTableau, qui doit ˆtre tri´ par rapport ` l’ordre e e e a naturel du type en question. Elles renvoient un entier i qui repr´sente e – si i ≥ 0, l’emplacement dans le tableau de la valeur recherch´e, e – si i < 0, la valeur i = −(j + 1) o` j est l’emplacement dans le tableau dans lequel il faudrait mettre u uneValeur si on voulait l’ins´rer tout en gardant tri´ le tableau. e e

c H. Garreta, 2000-2006

69

9.3 R´flexion et manipulation des types e

9

QUELQUES CLASSES UTILES

static int binarySearch(Object[] unTableau, Object uneValeur) static int binarySearch(Object[] unTableau, Object uneValeur, Comparator compar) Mˆme d´finition que pr´c´demment. Le tableau doit ˆtre tri´, dans le premier cas par rapport ` l’ordre e e e e e e a naturel de ses ´l´ments (qui doivent alors impl´menter l’interface Comparable), dans le second cas par ee e rapport ` la relation d’ordre exprim´e par l’argument compar. a e ´ static boolean equals(type[] unTableau, type[] unAutreTableau) Egalit´ de tableaux. e type est un des mots byte, short, int, long, float, double, boolean, char ou Object (autant dire n’importe quel type). Ce pr´dicat est vrai si et seulement si les deux tableaux pass´ comme arguments sont ´gaux (au sens e e e de equals, dans le cas des tableaux d’objets). static void fill(type[] unTableau, type uneValeur) static void fill(type[] unTableau, int debut, int fin, type uneValeur) Remplissage d’un tableau avec une mˆme valeur. e type est un des mots byte, short, int, long, float, double, boolean, char ou Object. La valeur indiqu´e est affect´e ` tous les ´l´ments du tableau (premier cas) ou aux ´l´ments de rangs e e a ee ee debut, debut+1, ... fin (second cas). static void sort(type [] unTableau) static void sort(type [] unTableau, int debut, int fin) Tri d’un tableau, soit tout entier (premier cas) soit depuis l’´l´ment d’indice debut jusqu’` l’´l´ment d’indice fin (second cas). ee a ee type est un des mots byte, short, int, long, float, double, boolean, char ou Object (autant dire n’importe quel type). Le tri se fait par rapport ` l’ordre naturel des ´l´ments du tableau ce qui, dans le cas o` type est a ee u Object, oblige ces ´l´ments ` ˆtre d’un type qui impl´mente l’interface Comparable. ee ae e La m´thode de tri employ´e est une variante ´labor´e du l’algorithme du tri rapide (quicksort) qui, e e e e nous dit-on, offre un coˆt de n × log n mˆme dans un certain nombre de cas pourris o` le quicksort de u e u base serait quadratique. static void sort(Object[] a, int fromIndex, int toIndex, Comparator compar) Mˆme chose que e ci-dessus, mais relativement ` la relation d’ordre d´finie par l’argument compar. a e

9.3

R´flexion et manipulation des types e

La classe java.lang.Class et les classes du paquet java.lang.reflect (aux noms ´vocateurs, comme e Field, Method, Constructor, Array, etc.) offrent la possibilit´ de pratiquer une certaine introspection : un e objet peut inspecter une classe, ´ventuellement la sienne propre, acc´der ` la liste de ses membres, appeler e e a une m´thode dont le nom n’est connu qu’` l’ex´cution, etc. e a e Une instance de la classe java.lang.Class repr´sente un type (c’est-`-dire un type primitif ou une e a classe). Supposons que nous ayons une variable d´clar´e ainsi : e e Class uneClasse; Nous avons plusieurs mani`res de lui donner une valeur : e e – une classe-enveloppe d’un type primitif (Integer, Double, etc.) poss`de une constante de classe TYPE qui repr´sente le type primitif en question ; de plus, l’expression type.class a le mˆme effet. Ainsi, les e e deux expressions suivantes affectent le type int ` la variable uneClasse : a uneClasse = Integer.TYPE; uneClasse = int.class; – si uneClasse est l’identificateur d’une classe, alors l’expression uneClasse.class a pour valeur la classe en question58 . L’exemple suivant affecte le type java.math.BigInteger ` la variable uneClasse : a uneClasse = java.math.BigInteger.class;
58 Notez la diff´rence entre TYPE et class pour les classes-enveloppes des types primitifs : Integer.TYPE est le type int, tandis e que Integer.class est le type java.lang.Integer.

70

c H. Garreta, 2000-2006

9 QUELQUES CLASSES UTILES

9.3

R´flexion et manipulation des types e

– on peut aussi demander le chargement de la classe, ` partir de son nom compl`tement sp´cifi´. a e e e C’est un proc´d´ plus on´reux que les pr´c´dents, o` la plupart du travail de chargement (dont e e e e e u la d´tection d’´ventuelles erreurs dans les noms des classes) ´tait fait durant la compilation alors e e e que, dans le cas pr´sent, il sera fait pendant l’ex´cution. L’exemple suivant affecte encore le type e e java.math.BigInteger ` la variable uneClasse : a uneClasse = Class.forName("java.math.BigInteger"); a – enfin, le moyen le plus naturel est de demander ` un objet quelle est sa classe. L’exemple suivant affecte encore le type java.math.BigInteger ` la variable uneClasse : a Object unObjet = new BigInteger("92233720368547758079223372036854775807"); ... uneClasse = unObjet.getClass(); Pour survoler cette question assez pointue, voici un exemple qui illustre quelques unes des possibilit´s de e la classe Class et du paquet reflect. La m´thode demoReflexion ci-dessous, purement d´monstrative, prend deux objets quelconques a et b et e e appelle successivement toutes les m´thodes qui peuvent ˆtre appel´es sur a avec b pour argument, c’est-`-dire e e e a les m´thodes d’instance de la classe de a qui ont un unique argument de type la classe de b : e

import java.lang.reflect.Method; import java.math.BigInteger; public class DemoReflexion { static void demoReflexion(Object a, Object b) { try { Class classe = a.getClass(); Method[] methodes = classe.getMethods(); for (int i = 0; i < methodes.length; i++) { Method methode = methodes[i]; Class[] params = methode.getParameterTypes(); if (params.length == 1 && params[0] == b.getClass()) { Object r = methode.invoke(a, new Object[] { b }); System.out.println( a + "." + methode.getName() + "(" + b + ") = " + r); } } } catch (Exception exc) { System.out.println("Probl`me : " + exc); e } } public static void main(String[] args) { demoReflexion(new BigInteger("1001"), new BigInteger("1003")); } } Le programme pr´c´dent affiche la sortie e e 1001.compareTo(1003) = -1 1001.min(1003) = 1001 1001.add(1003) = 2004 ... 1001.gcd(1003) = 1 1001.mod(1003) = 1001 1001.modInverse(1003) = 501

c H. Garreta, 2000-2006

71

9.4 Entr´es-sorties e

9

QUELQUES CLASSES UTILES

9.4

Entr´es-sorties e

Note importante. Java 5 a introduit des outils simples et pratiques pour effectuer la lecture et l’´criture e de donn´es format´es. Ils sont expliqu´e dans la section 12.3.4 (page 155). e e e 9.4.1 Les classes de flux

Java fournit un nombre impressionnant de classes prenant en charge les op´rations d’entr´e sortie. Voici e e une pr´sentation tr`s succincte des principales (la marge laiss´e ` gauche refl`te la relation d’h´ritage). e e e a e e Plusieurs exemples d’utilisation de ces classes sont donn´s dans la section 9.4.2 : e ´ Flux d’octets en entree InputStream Cette classe abstraite est la super-classe de toutes les classes qui repr´sentent des flux d’octets en entr´e. e e Comporte une unique m´thode, int read(), dont chaque e appel renvoie un octet extrait du flux. Un flux qui obtient les donn´es lues, des octets, ` partir e a d’un fichier. Construction ` partir d’un String (le nom du fichier), a d’un File ou d’un FileDescriptor. Un flux qui obtient les donn´es lues, des octets, ` partir e a d’un tableau d’octets indiqu´ lors de la construction. e Un flux qui obtient les donn´es lues, des octets, ` partir e a d’un PipedOutputStream auquel il est connect´. Ce deuxi`me e e flux doit appartenir ` un thread distinct, sous peine de a blocage. Construction ` partir d’un PipedOutputStream ou a ` partir de de rien. a Un flux qui obtient les donn´es lues, des octets, ` partir e a d’un autre InputStream, auquel il ajoute des fonctionnalit´s. e Ajoute aux fonctionnalit´s d’un autre InputStream la mise e en tampon des octets lus. Construction ` partir d’un InputStream. a Ajoute aux fonctionnalit´s d’un autre InputStream la e possibilit´ de  d´lire  un octet (un octet  d´lu  est e e e retrouv´ lors d’une lecture ult´rieure). e e Construction ` partir d’un InputStream. a Un objet de cette classe obtient de l’InputStream sousjacent des donn´es appartenant aux types primitifs de Java. e Voir DataOutputStream, plus loin. Construction ` partir d’un InputStream. a Un flux capable de d´-s´rialiser les objets pr´alablement e e e s´rialis´s dans un ObjectOutputStream. e e Voir les sections  s´rialisation  et ObjectOutputStream. e Construction ` partir d’un InputStream. a Un flux qui obtient les donn´es lues, des octets, ` partir de e a la concat´nation logique d’une suite d’objets InputStream. e Construction ` partir de deux InputStream ou d’une a ´numeration dont les ´l´ments sont des InputStream. e ee

FileInputStream

ByteArrayInputStream PipedInputStream

FilterInputStream

BufferedInputStream

PushbackInputStream

DataInputStream

ObjectInputStream

SequenceInputStream

En r´sum´ : e e – les sous-classes directes de InputStream se distinguent par le type de la source des donn´es lues, e – les sous classes de FilterInputStream sont diverses sortes de couches ajout´es par-dessus un autre e flux. Flux d’octets en sortie OutputStream Classe abstraite, super-classe de toutes les classes qui repr´sentent e des flux (d’octets) en sortie. Un tel flux re¸oit des octets et c
c H. Garreta, 2000-2006

72

9 QUELQUES CLASSES UTILES

9.4

Entr´es-sorties e

les envoie vers un certain puits ( puits  s’oppose `  source ) a Comporte une m´thode abstraite : void write(int b), qui e ´crit un octet. e FileOutputStream Un flux de sortie associ´ ` un fichier du syst`me sous-jacent. ea e Construction ` partir d’un String, d’un File ou d’un a FileDescriptor. Un flux de sortie dans lequel les donn´es sont rang´es dans e e un tableau d’octets. Construction ` partir de rien. a Voir PipedInputStream. Construction ` partir d’un PipedInputStream ou ` partir a a de rien. Un flux qui envoie les donn´es ` un autre flux (donn´ lors e a e de la construction) apr`s leur avoir appliqu´ un certain e e traitement. Construction ` partir d’un OutputStream. a Un flux de sortie qui regroupe les octets logiquement ´crits e avant de provoquer un appel du syst`me sous-jacent en vue e d’une ´criture physique. e Construction ` partir d’un OutputStream. a Un flux de sortie destin´ ` l’´criture de donn´es des types ea e e primitifs de Java. Les donn´es ne sont pas format´es (traduites en textes e e lisibles par un humain) mais elles sont ´crites de mani`re e e portable. Construction ` partir d’un OutputStream. a Un flux destin´ ` l’´criture de toutes sortes de donn´es, sous ea e e une forme lisible par un humain. Ces flux ne gˆn`rent jamais d’exception ; ` la place, ils e e a positionnent une variable priv´e qui peut ˆtre test´e ` l’aide e e e a de la m´thode checkError. e De plus, ces flux g`rent le vidage (flushing) du tampon dans e certaines circonstances : appel d’une m´thode println, e ´criture d’un caract`re ’\n’, etc. e e Construction ` partir d’un OutputStream. a Un flux de sortie pour ´crire des donn´es de types primitifs e e et des objets Java dans un OutputStream. Cela s’appelle la s´rialisation des donn´es. Les objets peuvent ensuite ˆtre e e e lus et reconstitu´s en utilisant un ObjectInputStream. e Construction ` partir d’un OutputStream. a Seuls les objets qui impl´mentent l’interface Serializable e peuvent ˆtre ´crits dans un flux ObjectOutputStream. La e e classe (nom et signature) de chaque objet est cod´e, ainsi e que les valeurs des variables d’instance (sauf les variables d´clar´es transient). e e La s´rialisation d’un objet implique la s´rialisation des e e objets que celui-ci r´f´rence, il s’agit donc de mettre en ee s´quence les nœuds d’un graphe, qui peut ˆtre cyclique. e e C’est pourquoi cette op´ration, assez complexe, est e tr`s utile. e ` ´ Flux de caracteres en entree Les flux de caract`res, en entr´e et en sortie. Ces flux sont comme les flux d’octets, mais l’information e e ´l´mentaire y est le caract`re (Java utilisant le codage Unicode, un caract`re n’est pas la mˆme chose qu’un ee e e e octet). Reader
c H. Garreta, 2000-2006

ByteArrayOutputStream

PipedOutputStream

FilterOutputStream

BufferedOutputStream

DataOutputStream

PrintStream

ObjectOutputStream

Classe abstraite, super-classe de toutes les classes qui lisent 73

9.4 Entr´es-sorties e

9

QUELQUES CLASSES UTILES

des flux de caract`res. e Deux m´thodes abstraites : int read(char[], int, int) e et void close(). BufferedReader Les objets de cette classe am´liorent l’efficacit´ des op´rations e e e d’entr´e en  buff´risant  les lectures sur un flux souse e jacent (un objet Reader donn´ en argument du e constructeur). Construction ` partir d’un Reader. a Un flux d’entr´e buff´ris´ qui garde trace des num´ros des e e e e lignes lues. (Une ligne est une suite de caract`res termin´e par ’\r’, e e ’\n’ ou ’\r\n’). Construction ` partir d’un Reader. a Un flux qui obtient les caract`res lus ` partir d’un tableau e a de caract`res interne, qu’on indique lors de la construction e du flux. Construction ` partir d’un char[]. a Un flux de caract`res qui obtient les donn´es dans un flux e e d’octets (un objet InputStream donn´ en argument du e constructeur). Un tel objet assure donc le travail de conversion des octets en caract`res. e Construction ` partir d’un InputStream. a (Classe de confort) Un flux d’entr´e de caract`res qui e e obtient ses donn´es dans un fichier. e On suppose que le codage des caract`res par d´faut et la e e taille par d´faut du tampon sont ad´quats. (Si tel n’est pas e e le cas il vaut mieux construire un InputStreamReader sur un FileInputStream) Construction ` partir d’un String, d’un File ou d’un a FileDescriptor. Classe abstraite pour la lecture de flux filtr´s ( ?) e Poss`de, ` titre de membre, un objet Reader dont il filtre e a les caract`res lus. e Flux d’entr´e de caract`res ayant la possibilit´ de  d´lire  e e e e un ou plusieurs caract`res. e Construction ` partir d’un Reader. a L’´quivalent, pour des flux de caract`res, d’un PipedInputStream. e e Construction ` partir d’un PipedWriter ou ` partir de rien. a a Un flux de caract`res dont la source est un objet String. e Construction ` partir d’un String. a ` Flux de caracteres en sortie Writer Classe abstraite, super-classe de toutes les classes qui ´crivent e des flux de caract`res. e M´thodes abstraites : void write(char[], int, int), e void flush() et void close(). Un flux de sortie dans lequel les caract`res logiquement e ´crits sont group´s pour diminuer le nombre effectif d’´critures e e e physiques. Construction ` partir d’un Writer. a Un flux qui met les caract`res produits dans un tableau de e caract`res. e Construction ` partir de rien. a
c H. Garreta, 2000-2006

LineNumberReader

CharArrayReader

InputStreamReader

FileReader

FilterReader

PushbackReader

PipedReader StringReader

BufferedWriter

CharArrayWriter

74

9 QUELQUES CLASSES UTILES

9.4

Entr´es-sorties e

OutputStreamWriter

Un flux qui met les caract`res ´crits dans un OutputStream e e pr´alablement construit. e Un tel objet prend donc en charge la conversion des caract`res en des octets. e Construction ` partir d’un OutputStream. a (Classe de confort) Un flux qui envoie les caract`res ´crits e e dans un fichier. Construction ` partir d’un String, d’un File ou d’un a FileDescriptor. Classe abstraite pour la d´finition de flux filtr´s d’´criture e e e de caract`res. e L’´quivalent, pour un flux de caract`res, d’un e e PipedOutputStream. Voir PipedReader. Construction ` partir d’un PipedReader ou ` partir de rien. a a Un flux qui met les caract`res ´crits dans un objet e e StringBuffer, lequel peut ˆtre utilis´ pour construire un e e String. Construction ` partir de rien. a Un flux de caract`res destin´ ` l’envoi de repr´sentations e ea e format´es de donn´es dans un flux pr´alablement construit. e e e Un PrintWriter se construit au-dessus d’un OutputStream ou d’un Writer pr´alablement cr´´. e ee

FileWriter

FilterWriter PipedWriter

StringWriter

PrintWriter

Autres classes File Un objet File est la repr´sentation abstraite d’un fichier, e d’un r´pertoire ou, plus g´n´ralement, d’un chemin du e e e syst`me de fichiers sous-jacent. e Un FileDescriptor est un objet opaque qui repr´sente une e entit´ sp´cifique de la machine sous-jacente : un fichier e e ouvert, un socket ouvert ou toute autre source ou puits d’octets. La principale utilit´ d’un tel objet est la cr´ation d’un e e FileInputStream ou d’un FileOutputStream. Un objet RandomAccessFile repr´sente un fichier e supportant l’acc`s relatif. e Cette classe impl´mente les interfaces DataInput et e DataOutput, c’est-`-dire qu’elle supporte les op´rations de a e lecture et d’´criture, ainsi que des op´rations pour e e positionner et obtenir la valeur d’une certaine position courante dans le fichier Un objet de cette classe est un analyseur qui reconnaˆ des ıt unit´s lexicales form´es d’octets ou de caract`res provenant e e e d’une source de donn´es. e Les unit´s sont de quatre types : TT NUMBER (la valeur est e e un double), TT WORD, TT EOF et (´ventuellement) TT EOL. Dans le mˆme ordre d’id´es, voir aussi StringTokenizer. e e

FileDescriptor

RandomAccessFile

StreamTokenizer

9.4.2

Exemples

1. Ecriture et lecture dans un fichier  binaire . Les deux m´thodes de la classe FichierBinaire e suivante permettent d’enregistrer un tableau de nombres flottants (des double) dans un fichier, ou bien de reconstituer un tel tableau ` partir de ses valeurs pr´alablement enregistr´es. Le fichier en question est form´ a e e e d’un nombre entier n suivi de n nombres flottants en double pr´cision : e
c H. Garreta, 2000-2006

75

9.4 Entr´es-sorties e

9

QUELQUES CLASSES UTILES

class FichierBinaire { public void sauver(double nombres[], String nomFichier) throws IOException { FileOutputStream fichier = new FileOutputStream(nomFichier); DataOutputStream donnees = new DataOutputStream(fichier); int combien = nombres.length; donnees.writeInt(combien); for (int i = 0; i < combien; i++) donnees.writeDouble(nombres[i]); donnees.close(); } public double[] restaurer(String nomFichier) throws IOException { FileInputStream fichier = new FileInputStream(nomFichier); DataInputStream donnees = new DataInputStream(fichier); int combien = donnees.readInt(); double[] result = new double[combien]; for (int i = 0; i < combien; i++) result[i] = donnees.readDouble(); donnees.close(); return result; } } ´ 2. Ecriture et lecture dans un fichier de texte. Mˆme sorte de travail qu’` l’exemple pr´c´dente, e a e e mais le fichier est maintenant de texte. C’est moins efficace, puisque les donn´es sont exprim´es sous une e e forme textuelle (donc du travail suppl´mentaire lors de l’´criture et lors de la lecture), mais cela permet e e l’exploitation ou la modification du fichier par des outils g´n´raux (imprimantes, ´diteurs de texte, etc.). e e e Pour faciliter l’´ventuelle exploitation  humaine  du fichier, devant chaque donn´e ni on ´crit son rang i : e e e class FichierDeTexte { public void sauver(double nombres[], String nomFichier) throws IOException { FileOutputStream fichier = new FileOutputStream(nomFichier); PrintWriter donnees = new PrintWriter(fichier); int combien = nombres.length; donnees.println(combien); for (int i = 0; i < combien; i++) donnees.println(i + " " + nombres[i]); donnees.close(); } public double[] restaurer(String nomFichier) throws IOException { FileInputStream fichier = new FileInputStream(nomFichier); InputStreamReader adaptateur = new InputStreamReader(fichier); LineNumberReader donnees = new LineNumberReader(adaptateur); String ligne = donnees.readLine(); int combien = Integer.parseInt(ligne); double[] result = new double[combien]; for (int i = 0; i < combien; i++) { ligne = donnees.readLine(); StringTokenizer unites = new StringTokenizer(ligne); unites.nextToken(); // on ignore le rang i result[i] = Double.parseDouble(unites.nextToken()); } if (donnees.readLine() != null) throw IOException("Il y a plus de donn´es que pr´vu"); e e donnees.close(); return result; } ´ 3. Lecture sur l’entree standard. La classe Est (comme Entree STandard) offre un petit nombre de m´thodes statiques destin´es ` effectuer simplement des op´rations de lecture ` l’entr´e standard (souvent e e a e a e le clavier) : – int lireInt() : lecture d’un nombre entier, 76
c H. Garreta, 2000-2006

9 QUELQUES CLASSES UTILES

9.4

Entr´es-sorties e

– – – –

double lireDouble() : lecture d’un nombre flottant, char lireChar() : lecture d’un caract`re, e String lireString() : lecture de toute une ligne, void viderTampon() : remise ` z´ro du tampon d’entr´e. a e e

Note Java 5. La classe Est montr´e ici reste un exemple int´ressant d’utilisation de la biblioth`que des e e e entr´es-sorties, mais il faut savoir qu’` partir de la version 5 de Java le service rendu par cette classe est pris e a en charge – de mani`re un peu plus professionnelle – par la classe java.util.Scanner de la biblioth`que e e standard (cf. § 12.3.4, page 155). Exemple d’utilisation de notre classe Est : ... int numero; String denomination; double prix; char encore; ... do { System.out.print("numero? "); numero = Est.lireInt(); System.out.print("d´nomination? "); e Est.viderTampon(); denomination = Est.lireString(); System.out.print("prix? "); prix = Est.lireDouble(); System.out.print("Encore (o/n)? "); Est.viderTampon(); encore = Est.lireChar(); } while (encore == ’o’); ... Voici la classe Est : import java.io.*; import java.util.StringTokenizer; public class Est { public static int lireInt() { return Integer.parseInt(uniteSuivante()); } public static double lireDouble() { return Double.parseDouble(uniteSuivante()); } public static char lireChar() { if (tampon == null) tampon = lireLigne(); if (tampon.length() > 0) { char res = tampon.charAt(0); tampon = tampon.substring(1); return res; } else { tampon = null; return ’\n’; } } public static String lireString() { if (tampon == null) return lireLigne();
c H. Garreta, 2000-2006

// Si tous les caract`res ordinaires e // de la ligne courante ont ´t´ lus, e e // le caract`re renvoy´ est ’\n’ e e

77

9.4 Entr´es-sorties e

9

QUELQUES CLASSES UTILES

else { String res = tampon; tampon = null; return res; } } public static void viderTampon() { tampon = null; } private static String uniteSuivante() { if (tampon == null) tampon = lireLigne(); StringTokenizer unites = new StringTokenizer(tampon, limites); while ( ! unites.hasMoreTokens()) { tampon = lireLigne(); unites = new StringTokenizer(tampon, limites); } String unite = unites.nextToken(); tampon = tampon.substring(tampon.indexOf(unite) + unite.length()); return unite; } private static String lireLigne() { try { // Les IOException sont transform´es e return entree.readLine(); // en RuntimeException (non contr^l´es) o e } catch (IOException ioe) { throw new RuntimeException("Erreur console [" + ioe.getMessage() +"]"); } } private static BufferedReader entree = new BufferedReader(new InputStreamReader(System.in)); private static String tampon = null; private static final String limites = " \t\n,;:/?!%#$()[]{}"; } 9.4.3 Analyse lexicale

L’analyse lexicale est la transformation d’une suite de caract`res en une suite d’unit´s lexicales ou tokens e e – des  mots  – ob´issant ` une grammaire lexicale souvent d´finie par un ensemble d’expressions r´guli`res. e a e e e En Java, l’analyse lexicale la plus basique est prise en charge par les objets StringTokenizer et StreamTokenizer. A partir de la version 1.4, des analyseurs plus sophistiqu´s peuvent ˆtre r´alis´s ` l’aide des classes e e e e a de manipulation d’expressions r´guli`res, java.util.regex.Pattern et java.util.regex.Matcher. A ce e e sujet, voyez la section 9.5. D’autre part, Java 5 introduit la classe java.util.Scanner, plus puissante que StreamTokenizer mais plus simple d’emploi que le couple Pattern et Matcher. Pour illustrer l’emploi des objets StreamTokenizer, ou  analyseurs lexicaux de flux , voici un exemple classique (classique dans certains milieux !) : le programme suivant reconnaˆ et ´value des expressions ıt e arithm´tiques lues ` l’entr´e standard, form´es avec des nombres, les quatre op´rations et les parenth`ses, e a e e e e et termin´es par le caract`re ’=’. e e Il est int´ressant d’observer l’empilement de flux d’entr´e r´alis´ dans le constructeur de la classe Cale e e e culateur : ` la base, il y a System.in, qui est un flux d’octets (un InputStream) ;  autour  de ce flux on a a mis adaptateur, qui est un flux de caract`res (un InputStreamReader, donc un Reader) ;  autour  de e ce dernier, on a construit unites, un flux d’unit´s lexicales (un StreamTokenizer). e

78

c H. Garreta, 2000-2006

9 QUELQUES CLASSES UTILES

9.4

Entr´es-sorties e

class Calculateur { private StreamTokenizer unites; public Calculateur() throws IOException { InputStreamReader adaptateur = new InputStreamReader(System.in); unites = new StreamTokenizer(adaptateur); }

public double unCalcul() throws Exception { unites.nextToken(); double result = expression(); if (unites.ttype != ’=’) throw new ErreurSyntaxe("’=’ attendu ` la fin de l’expression"); a return result; }

private double expression() throws IOException, ErreurSyntaxe { double result = terme(); int oper = unites.ttype; while (oper == ’+’ || oper == ’-’) { unites.nextToken(); double tmp = terme(); if (oper == ’+’) result += tmp; else result -= tmp; oper = unites.ttype; } return result; }

private double terme() throws IOException, ErreurSyntaxe { double result = facteur(); int oper = unites.ttype; while (oper == ’*’ || oper == ’/’) { unites.nextToken(); double tmp = facteur(); if (oper == ’*’) result *= tmp; else result /= tmp; oper = unites.ttype; } return result; }

private double facteur() throws IOException, ErreurSyntaxe { double result = 0;

if (unites.ttype == StreamTokenizer.TT_NUMBER) { result = unites.nval; unites.nextToken(); }
c H. Garreta, 2000-2006

79

9.4 Entr´es-sorties e

9

QUELQUES CLASSES UTILES

else if (unites.ttype == ’(’) { unites.nextToken(); result = expression(); if (unites.ttype != ’)’) throw new ErreurSyntaxe("’)’ attendue " + unites.ttype); unites.nextToken(); } else throw new ErreurSyntaxe("nombre ou ’(’ attendu " + unites.ttype); return result; } } class ErreurSyntaxe extends Exception { ErreurSyntaxe(String message) { super(message); } } 9.4.4 Mise en forme de donn´es e

Le paquet java.text est fait de classes et d’interfaces pour la mise en forme de textes, dates, nombres, etc. Les plus utiles, pour nous : Format tions NumberFormat Classe abstraite concernant la mise en forme des informasensibles aux particularit´s locales (nombres, dates, etc.). e Classe abstraite, super-classe des classes concernant la mise en forme et la reconnaissance des nombres (aussi bien les types primitifs que leurs classes-enveloppes). Classe concr`te, prenant en charge l’´criture en base 10 des e e nombres. Un objet de cette classe repr´sente l’ensemble des symboles e qui interviennent dans le formatage d’un nombre (s´parateur e d´cimal, s´parateur de paquets de chiffres, etc.). e e Le constructeur par d´faut de cette classe initialise un objet e avec les valeurs locales de tous ces symboles.

DecimalFormat DecimalFormatSymbols

Examinons quelques exemples : ´ Exemple 1. Ecriture d’un nombre en utilisant le format local (i.e. le format en vigueur ` l’endroit o` le a u programme est ex´cut´) : e e import java.text.NumberFormat; public class AProposDeFormat { static public void main(String[] args) { NumberFormat formateur = NumberFormat.getInstance(); double x = 1234567.23456789; System.out.println("format \"brut\"

: " + x);

String s = formateur.format(x); System.out.println("format local par d´faut : " + s); e ... Affichage obtenu (en France) : format "brut" : 1234567.23456789 format local par d´faut : 1 234 567,235 e Comme on le voit ci-dessus, il se peut que des nombres format´s en accord avec les particularit´s locales e e ne puissent pas ˆtre donn´s `  relire  aux m´thodes basiques de Java, comme Integer.parseInt ou e e a e 80
c H. Garreta, 2000-2006

9 QUELQUES CLASSES UTILES

9.4

Entr´es-sorties e

Double.parseDouble. Pour reconstruire les nombres que ces textes expriment on doit employer les m´thodes e correspondantes de la classe NumberFormat. Voici une possible continuation de l’exemple pr´c´dent : e e ... try { Number n = formateur.parse(s); x = n.doubleValue(); } catch (ParseException exc) { System.out.println("Conversion impossible : " + exc.getMessage()); } System.out.println("valeur \"relue\" : " + x); } } Affichage obtenu maintenant : format "brut" : 1234567.23456789 format local par d´faut : 1 234 567,235 e valeur "relue" : 1234567.235 Exemple 2. Utilisation de ces classes pour obtenir une mise en forme pr´cise qui ne correspond pas e forc´ment aux particularit´s locales. Dans l’exemple suivant on ´crit les nombres avec exactement deux e e e d´cimales, la virgule d´cimale ´tant repr´sent´e par un point et les chiffres avant la virgule group´s par e e e e e e paquets de trois signal´s par des apostrophes : e public class Formatage { static public void main(String[] args) { DecimalFormatSymbols symboles = new DecimalFormatSymbols(); symboles.setDecimalSeparator(’.’); symboles.setGroupingSeparator(’\’’); DecimalFormat formateur = new DecimalFormat("###,###,###.00", symboles); double d = 1.5; System.out.println(formateur.format(d)); d = 0.00123; System.out.println(formateur.format(d)); int i = 12345678; System.out.println(formateur.format(i)); } } l’affichage est ici : 1.50 .00 12’345’678.00 Variante, avec : DecimalFormat formateur = new DecimalFormat("000,000,000.00", symboles); l’affichage aurait ´t´ : ee 000’000’001.50 000’000’000.00 012’345’678.00 Exemple 3. Des formats sp´cifiques comme les pr´c´dents peuvent aussi ˆtre compos´s ` l’aide de e e e e e a m´thodes  set...  de la classe DecimalFormat : e
c H. Garreta, 2000-2006

81

9.4 Entr´es-sorties e

9

QUELQUES CLASSES UTILES

public class Formatage { public static void main(String[] args) { DecimalFormat formateur = (DecimalFormat) NumberFormat.getInstance(); // Modifier ce format afin qu’il ressemble ` 999.99[99] a DecimalFormatSymbols symboles = new DecimalFormatSymbols(); symboles.setDecimalSeparator(’.’); formateur.setDecimalFormatSymbols(symboles); formateur.setMinimumIntegerDigits(3); formateur.setMinimumFractionDigits(2); formateur.setMaximumFractionDigits(4); double d = 1.5; System.out.println(d + " se formate en " + formateur.format(d)); d = 1.23456789; System.out.println(d + " se formate en " + formateur.format(d)); } } Ex´cution : e

1.5 se formate en 001.50 1.23456789 se formate en 001.2346

Exemple 4. La classe Fomat et ses voisines n’offrent pas un moyen simple d’effectuer un cadrage ` droite a des nombres ou, plus exactement, un cadrage calcul´ ` partir de la position de la virgule. De telles mises en ea forme sont n´cessaires pour constituer des colonnes de nombres correctement align´s. e e Par exemple, proposons-nous d’afficher une table de conversion en degr´s Celsius d’une s´rie de temp´ratures e e e exprim´es en degr´s Fahrenheit : e e public class ConversionTemperature { public static void main(String[] args) { NumberFormat formateur = new DecimalFormat("##.###"); for (int c = -40; c <= 120; c += 20) { double f = (c - 32) / 1.8; System.out.println(c + " " + formateur.format(f)); } } } L’affichage obtenu n’est pas tr`s beau : e -40 -40 -20 -28,889 0 -17,778 20 -6,667 40 4,444 60 15,556 80 26,667 100 37,778 120 48,889 Voici une deuxi`me version de ce programme, dans laquelle un objet FieldPosition est utilis´ pour e e connaˆ ıtre, dans l’expression format´e d’un nombre, l’indice du caract`re sur lequel se termine sa partie e e enti`re (c’est-`-dire, selon le cas, soit la fin du nombre, soit la position de la virgule). Cet indice permet de e a calculer le nombre de blancs qu’il faut ajouter ` la gauche du nombre pour le cadrer proprement : a 82
c H. Garreta, 2000-2006

9 QUELQUES CLASSES UTILES

9.4

Entr´es-sorties e

public class ConversionTemperature { public static void main(String[] args) { NumberFormat formateur = new DecimalFormat("##.###"); FieldPosition pos = new FieldPosition(NumberFormat.INTEGER_FIELD); for (int c = -40; c <= 120; c += 20) { double f = (c - 32) / 1.8; String sc = formateur.format(c, new StringBuffer(), pos).toString(); sc = ajoutEspaces(4 - pos.getEndIndex(), sc); String sf = formateur.format(f, new StringBuffer(), pos).toString(); sf = ajoutEspaces(4 - pos.getEndIndex(), sf); System.out.println(sc + " " + sf); } } private static String ajoutEspaces(int n, String s) { return " ".substring(0, n) + s; } } L’affichage obtenu maintenant est nettement mieux align´ : e -40 -20 0 20 40 60 80 100 120 9.4.5 -40 -28,889 -17,778 -6,667 4,444 15,556 26,667 37,778 48,889

Repr´sentation des dates e

La manipulation et l’affichage des dates sont trait´s en Java avec beaucoup de soin (portabilit´ oblige !), e e mais la question peut sembler un peu embrouill´e car elle est r´partie sur trois classes distinctes de mani`re e e e un peu arbitraire : java.util.Date - Un objet de cette classe encapsule un instant dans le temps (repr´sent´ par le nombre e e de millisecondes ´coul´es entre le 1er janvier 1970, ` 0:00:00 heures GMT, et cet instant). e e a D’autre part, ces objets ont des m´thodes pour le calcul de l’ann´e, du mois, du jour, etc., mais elles e e sont d´sapprouv´es, ce travail doit d´sormais ˆtre donn´ ` faire aux objets Calendar. e e e e ea java.util.GregorianCalendar - Cette classe est une sous-classe de la classe abstraite Calendar. Ses instances repr´sentent des dates, d´compos´es en plusieurs nombres qui expriment l’ann´e, le mois, le jour e e e e dans le mois, dans l’ann´e et dans la semaine, l’heure, les minutes, etc. e java.text.DateFormat - Les instances de cette classe, fortement d´pendante des particularit´s locales, e e s’occupent de l’expression textuelle des dates. Exemple 1. Voici comment mesurer le temps que prend l’ex´cution d’un certain programme (attention, e il s’agit de  temps ´coul´ , qui comprend donc le temps pris par d’´ventuelles autres tˆches qui ont ´t´ e e e a ee entrelac´es avec celle dont on cherche ` mesurer la dur´e) : e a e ... void unCalcul() { Date d0 = new Date(); un calcul prenant du temps... Date d1 = new Date(); System.out.println("Temps ´coul´: " e e + (d1.getTime() - d0.getTime()) / 1000.0 + " secondes");
c H. Garreta, 2000-2006

83

9.4 Entr´es-sorties e

9

QUELQUES CLASSES UTILES

} ... Exemple 2. Un programme pour savoir en quel jour de la semaine tombe une date donn´e : e class Jour { public static void main(String[] args) { if (args.length < 3) System.out.println("Emploi: java Jour <jour> <mois> <annee>"); else { int j = Integer.parseInt(args[0]); int m = Integer.parseInt(args[1]); int a = Integer.parseInt(args[2]); GregorianCalendar c = new GregorianCalendar(a, m - 1, j); int s = c.get(GregorianCalendar.DAY_OF_WEEK); String[] w = { "dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi" }; System.out.println(w[s - 1]); } } } Exemple 3. Affichage personnalis´ de la date. Chaque fois qu’on demande ` un objet Maintenant de se e a convertir en chaˆ de caract`res, il donne une expression de la date et heure courantes : ıne e class Maintenant { private static String[] mois = { "janvier", "f´vrier", "mars", ... "d´cembre" }; e e private static String[] jSem = { "dimanche", "lundi", "mardi", ... "samedi" }; public String toString() { GregorianCalendar c = new GregorianCalendar(); return jSem[c.get(c.DAY_OF_WEEK) - 1] + " " + c.get(c.DAY_OF_MONTH) + " " + mois[c.get(c.MONTH) - 1] + " " + c.get(c.YEAR) + " ` " + c.get(c.HOUR) + " heures " + c.get(c.MINUTE); a } public static void main(String[] args) { System.out.println("Aujourd’hui: " + new Maintenant()); } } Exemple 4. La mˆme chose, ` l’aide d’un objet DateFormat : e a class Maintenant { DateFormat df = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.MEDIUM, Locale.FRANCE); public String toString() { return df.format(new Date()); } public static void main(String[] args) { System.out.println("Aujourd’hui: " + new Maintenant()); } } 9.4.6 La s´rialisation e

La s´rialisation est un m´canisme tr`s puissant, tr`s utile et tr`s simple d’emploi pour sauvegarder des e e e e e objets entiers dans des fichiers et de les restaurer ult´rieurement. Les points cl´s en sont : e e e e e e e – pour que les instances d’une classe puissent ˆtre s´rialis´es il doit ˆtre dit que cette classe impl´mente l’interface Serializable, une interface vide par laquelle le programmeur exprime qu’il n’y a pas d’opposition ` ce que les objets en question soient conserv´s dans un fichier, a e e – la s´rialisation d’un objet implique la sauvegarde des valeurs de toutes ses variables d’instance, sauf celles d´clar´es transient (transitoires), e e – la s´rialisation d’un objet implique en principe celle des objets qu’il r´f´rence (ses objets membres) qui e ee doivent donc ˆtre ´galement s´rialisables, e e e e ee – le couple s´rialisation (sauvegarde dans un fichier) – d´s´rialisation (restauration depuis un fichier) comporte un m´canisme de contrˆle des versions, assurant que les classes servant ` la reconstitution e o a des objets sont coh´rentes avec les classes dont ces objets ´taient instances lors de leur s´rialisation, e e e 84
c H. Garreta, 2000-2006

9 QUELQUES CLASSES UTILES

9.4

Entr´es-sorties e

Le service rendu par Java lors de la s´rialisation d’un objet est important, car la tˆche est complexe. e a En effet, pour sauvegarder un objet il faut sauvegarder aussi les ´ventuels objets qui sont les valeurs de e ses variables d’instance ; ` leur tour, ces objets peuvent en r´f´rencer d’autres, qu’il faut sauver ´galement, a ee e et ainsi de suite. Cela revient ` parcourir un graphe, qui tr`s souvent comporte des cycles, en ´vitant de a e e sauvegarder plusieurs fois un mˆme objet et sans sombrer dans des boucles infinies. e A titre d’exemple, voici un programme purement d´monstratif qui cr´e une liste chaˆ ee circulaire et la e e ın´ sauvegarde dans un fichier. Chaque maillon porte une date (l’instant de sa cr´ation) dont on a suppos´ que e e la sauvegarde ´tait sans int´rˆt ; pour cette raison, cette variable a ´t´ d´clar´e transient : e ee ee e e

class Maillon implements Serializable { String info; transient Date date; Maillon suivant; Maillon(String i, Maillon s) { info = i; suivant = s; date = new Date(); } public String toString() { return info + " " + date; } } class TestSerialisation { static String[] jour = { "Dimanche", "Lundi", "Mardi", ... "Samedi" }; public static void main(String[] args) { Maillon tete, queue; tete = queue = new Maillon(jour[6], null); for (int i = 5; i >= 0; i--) tete = new Maillon(jour[i], tete); queue.suivant = tete; try { ObjectOutputStream sortie = new ObjectOutputStream( new FileOutputStream("Liste.dat")); sortie.writeObject(tete); sortie.close(); } catch (Exception e) { System.out.println("probl`me fichier: " + e.getMessage()); e } } }

Et voici un programme qui reconstruit la liste chaˆ ee (bien entendu, dans la liste reconstitu´e les maillons ın´ e n’ont pas de date) : class TestDeserialisation { public static void main(String[] args) { Maillon tete = null; try { ObjectInputStream entree = new ObjectInputStream( new FileInputStream("Liste.dat")); tete = (Maillon) entree.readObject(); entree.close(); }
c H. Garreta, 2000-2006

85

9.5 Expressions r´guli`res e e

9

QUELQUES CLASSES UTILES

catch (Exception e) { System.out.println("probl`me fichier: " + e.getMessage()); e } // affichage de contr^le o for (int i = 0; i < 7; i++, tete = tete.suivant) System.out.println(tete); } }

9.5
9.5.1

Expressions r´guli`res e e
Principe

Le lecteur est suppos´ connaˆ e ıtre par ailleurs la notion d’expression r´guli`re, dont l’explication, mˆme e e e succincte, d´passerait le cadre de ce cours. Disons simplement que les expressions r´guli`res consid´r´es ici e e e ee sont grosso modo celles du langage Perl59 , et donnons quelques exemples montrant leur utilisation en Java. Le paquetage concern´, java.util.regex, se compose des deux classes Pattern et Matcher : e Pattern - Un objet de cette classe est la repr´sentation  compil´e 60 d’une expression r´guli`re. e e e e Cette classe n’a pas de constructeur. On cr´e un objet Pattern par une expresion de la forme : e Pattern motif = Pattern.compile(texteExpReg [, flags ]) ; o` texteExpReg est une chaˆ de caract`res contenant l’expression r´guli`re. Si cette derni`re est u ıne e e e e incorrecte, l’instruction ci-dessus lance une exception PatternSyntaxException, qui est une sousclasse de RuntimeException61 . Matcher - Un objet de cette classe se charge d’appliquer une expression r´guli`re sur un texte, pour effectuer e e les op´rations de reconnaissance, de recherche ou de remplacement souhait´es. e e On cr´e un objet Matcher a partir d’un objet Pattern, par une expression de la forme : e ` Matcher reconnaisseur = motif .matcher(texteAExaminer ) ; L’objet reconnaisseur ainsi cr´´ dispose des m´thodes : ee e boolean matches() : l’expression r´guli`re reconnaˆ e e ıt-elle la totalit´ du texte ` examiner ? e a boolean lookingAt() : l’expression r´guli`re reconnaˆ e e ıt-elle le d´but du texte ` examiner ? e a boolean find([int start]) : l’expression r´guli`re reconnaˆ e e ıt-elle un morceau du texte ` examia ner ? A la suite d’un appel d’une des trois m´thodes ci-dessus, les m´thodes int start() et int end() de e e l’objet Matcher renvoient le d´but et la fin de la sous-chaˆ reconnue, tandis que la m´thode String e ıne e group() renvoie la chaˆ reconnue elle-mˆme. ıne e On se reportera ` la documentation de l’API pour des explications sur les (nombreuses) autres possia bilit´s de la classe Matcher. e 9.5.2 Exemples

´ 1. Decouper un texte. Pour les op´rations les plus simples il n’y a pas besoin de cr´er des objets e e Matcher, des m´thodes ordinaires (exemple 1) ou statiques (exemple 2) de la classe Pattern suffisent. Par e exemple, le programme suivant prend une chaˆ donn´e en argument et l’affiche ` raison d’un mot par ligne, ıne e a en consid´rant que les s´parateurs de mots sont la virgule ( , ), le point-virgule ( ; ) et les caract`res e e e blancs de toute sorte (collectivement repr´sent´s par  \s ) : e e

59 La syntaxe pr´cise des expressions r´guli`res accept´es par Java est donn´e dans la documentation de l’API, au d´but de e e e e e e l’explication concernant la classe java.util.regex.Pattern 60 On peut penser qu’un objet Pattern consiste essentiellement en l’encapsulation des tables qui d´finissent l’automate d’´tats e e fini correspondant ` ’expression r´guli`re donn´e. a e e e 61 Rappelons que les RuntimeException sont des exceptions non contrˆl´es. Par cons´quent, si une m´thode contient des appels oe e e de Pattern.compile il n’est pas n´cessaire de lui adjoindre une clause  throws PatternSyntaxException . e

86

c H. Garreta, 2000-2006

9 QUELQUES CLASSES UTILES

9.5

Expressions r´guli`res e e

import java.util.regex.Pattern; public class Mots { public static void main(String[] args) { Pattern motif = Pattern.compile("[,;\\s]+"); // d´coupe de la cha^ne args[0] en mots e ı String[] tabMots = motif.split(args[0]); for (int i = 0; i < tabMots.length; i++) System.out.println(tabMots[i]); } }

Exemple d’utilisation : $ java Mots Andr´ e B´atrice e Caroline Emile $ "Andr´, B´atrice e e Caroline, Emile"

´ 2. Verifier la correction d’une chaˆ ıne. Une autre op´ration simple qu’une m´thode statique de la e e classe Pattern peut effectuer sans l’aide d’un objet Matcher : compiler une expression r´guli`re et d´terminer e e e si elle correspond ` la totalit´ d’une chaˆ donn´e. a e ıne e Par exemple, la m´thode suivante d´termine si la chaˆ pass´e en argument est l’´criture correcte d’un e e ıne e e nombre d´cimal en virgule fixe : e boolean bienEcrit(String texte) { final String expr = "[+-]?[0-9]+(\\.[0-9]*)?"; // texte appartient-il ` l’ensemble de mots d´fini par expr ? a e return Pattern.matches(expr, texte); }

Note 1. Cette m´thode donne pour bonnes des chaˆ e ınes comme "12.3", "123." et "123", mais rejette ".123". Pour corriger cela il suffit de d´finir expr comme ceci : e final String expr = "[+-]?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))";

Note 2. Dans le cas o` la m´thode pr´c´dente est susceptible d’ˆtre fr´quemment appel´e, il est mau e e e e e e nifestement maladroit de compiler chaque fois la mˆme expression r´guli`re. L’emploi d’un objet Pattern e e e permet d’´viter cela : e Pattern pattern = Pattern.compile("[+-]?[0-9]+(\\.[0-9]*)?"); boolean bienEcrit(String texte) { return pattern.matcher(texte).matches(); } 3. Recherches multiples. Le programme suivant extrait les liens contenus dans une page html (repr´sent´s par des balises comme <a href="http://www.luminy.univ-mrs.fr" ... >) : e e
c H. Garreta, 2000-2006

87

9.5 Expressions r´guli`res e e

9

QUELQUES CLASSES UTILES

import java.io.*; import java.util.regex.*; public class TrouverURL { public static void main(String[] args) { String texte; try { File fichier = new File(args[0]); char[] tampon = new char[(int) fichier.length()]; Reader lecteur = new FileReader(fichier); lecteur.read(tampon); lecteur.close(); texte = new String(tampon); } catch (IOException e) { e.printStackTrace(); return; } String expReg = "<a[ \\t\\n]+href=\"[^\"]+\""; Pattern motif = Pattern.compile(expReg, Pattern.CASE_INSENSITIVE); Matcher recon = motif.matcher(texte); int pos = 0; while (recon.find(pos)) { String s = texte.substring(recon.start(), recon.end()); System.out.println(s); pos = recon.end(); } } } Ce programme affiche des lignes de la forme <a href="MonSoft.html" <a href="Polys/PolyJava.html" ... <a href="http://www.luminy.univ-mrs.fr/" <a href="mailto:garreta@univmed.fr" On peut extraire des informations plus fines, en utilisant la notion de groupe de capture des expressions r´guli`res. Voici les seules modifications ` faire dans le programme pr´c´dent : e e a e e public class TrouverURL { ... String expReg = "<a[ \\t\\n]+href=\"([^\"]+)\""; ... ↑ ↑ String s = texte.substring(recon.start(1), recon.end(1)); ... ↑ ↑ } L’affichage est maintenant comme ceci (magique, n’est-ce pas ?) : MonSoft.html Polys/PolyJava.html ... http://www.luminy.univ-mrs.fr/ mailto:garreta@univmed.fr 4. Remplacer toutes les occurrences. Le programme suivant remplace les occurrences du mot chat par le mot chien 62 . Appel´ avec l’argument ”un chat, deux chats, trois chats dans mon jardin” il affiche e donc la chaˆ un chien, deux chiens, trois chiens dans mon jardin. ıne
62 Utile,

hein ?

88

c H. Garreta, 2000-2006

9 QUELQUES CLASSES UTILES

9.5

Expressions r´guli`res e e

Notez que c’est recon, l’objet Matcher, qui prend soin de recopier dans la chaˆ sortie les caract`res ıne e qui se trouvent entre les occurrences du motif cherch´ : e public static void main(String[] args) throws Exception { Pattern motif = Pattern.compile("chat"); Matcher recon = motif.matcher(args[0]); StringBuffer sortie = new StringBuffer(); while(recon.find()) recon.appendReplacement(sortie, "chien"); recon.appendTail(sortie); System.out.println(sortie.toString()); } L’exemple ci-dessus a ´t´ r´dig´ comme cela pour illustrer les m´thodes appendReplacement et appendTail. ee e e e On notera cependant que, s’agissant de remplacer toutes les occurrences d’un motif par une chaˆ ıne, il y a un moyen plus simple : public static void main(String[] args) throws Exception { Pattern motif = Pattern.compile("chat"); Matcher recon = motif.matcher(args[0]); String sortie = recon.replaceAll("chien"); System.out.println(sortie); }

c H. Garreta, 2000-2006

89

10 THREADS

10

Threads

L’´tude des threads est ` sa place dans un cours sur la programmation parall`le et sort largement du e a e cadre de ce polycopi´. Nous nous limitons ici ` donner quelques explications sur certaines notions basiques e a auxquelles on est confront´ dans les applications les plus courantes, par exemple lorsqu’on programme des e interfaces utilisateur graphiques. Un thread, ou processus l´ger, est l’entit´ constitu´e par un programme en cours d’ex´cution. Cela se e e e e mat´rialise par un couple (code, donn´es) : le code en cours d’ex´cution et les donn´es que ce code traite. Si e e e e on en parle ici c’est que Java supporte l’existence simultan´e de plusieurs threads : ` tout instant, plusieurs e a programmes peuvent s’ex´cuter en mˆme temps. e e Ce que  en mˆme temps  veut dire exactement d´pend du mat´riel utilis´. Dans un syst`me poss´dant e e e e e e plusieurs processeurs il est possible que divers threads s’ex´cutent chacun sur un processeur distinct et on est e alors en pr´sence d’un parall´lisme vrai. Mais, plus couramment, les syst`mes ne disposent que d’un unique e e e processeur et il faut se contenter d’un parall´lisme simul´ : les divers threads existant ` un instant donn´ e e a e disposent du processeur ` tour de rˆle. a o Diff´rentes raisons peuvent faire qu’un thread qui est en train d’utiliser le processeur le c`de ` un autre e e a thread qui patiente pour l’avoir : 1) le blocage du thread actif par une op´ration ad hoc comme wait (attendre) ou sleep (dormir), e 2) le blocage du thread actif par le fait qu’il entame une op´ration d’entr´e-sortie63 , e e e e e a 3) le d´blocage d’un thread ayant une priorit´ sup´rieure ` celle du thread actif, 4) l’´puisement d’une certaine tranche de temps allou´e au thread actif. e e Si tout thread cesse d’ˆtre actif d`s qu’un thread de priorit´ sup´rieure est prˆt (cas 3, ci-dessus) on dit e e e e e qu’on a affaire ` un parall´lisme pr´emptif. La machine Java proc`de g´n´ralement ainsi. a e e e e e Le fonctionnement par attribution de tranches de temps (cas 4) ne fait pas partie des sp´cifications de e la machine Java. Celle-ci en b´n´ficie ou non, selon les caract´ristiques du mat´riel sous-jacent. e e e e

10.1
10.1.1

Cr´ation et cycle de vie d’un thread e
D´claration, cr´ation et lancement de threads e e

Il y a deux mani`res, tr`s proches, de cr´er un thread : e e e – en d´finissant et en instanciant une sous-classe de la classe Thread d´finie ` cet effet, dans laquelle au e e a moins la m´thode void run() aura ´t´ red´finie, e ee e – en appelant le constructeur de Thread avec pour argument un objet Runnable64 Dans un cas comme dans l’autre, le thread cr´´ est rendu vivant lors de l’appel de sa m´thode start(). ee e Java met alors en place tout le n´cessaire pour g´rer le nouveau thread, puis appelle sa m´thode run(). Au e e e moment de son d´marrage, le thread nouvellement cr´´ : e ee – a pour code la m´thode run(), e e e e ee – a pour donn´es celles du thread dans lequel il a ´t´ cr´´. Exemple. Le thread purement d´monstratif suivant est tr`s simple : dix fois il affiche son nom et un entier e e croissant, puis s’endort pour une p´riode comprise entre 0 et 1000 millisecondes : e public class ThreadSimple extends Thread { public ThreadSimple(String nom) { super(nom); }
63 C’est dans ce type de situations qu’on peut voir pourquoi le parall´lisme fait gagner du temps mˆme lorsqu’il est simul´ : e e e quand un thread se met en attente d’une entr´e-sortie sur disque dur (op´ration beaucoup plus lente que le travail en m´moire e e e centrale) ou, pire, en attente d’un ´v´nement r´seau, d’une frappe au clavier, etc., le contrˆle est pass´ ` un thread non bloqu´ ; e e e o ea e ainsi, l’unit´ centrale travaille plus souvent. e 64 Un objet Runnable – c.-`-d. impl´mentation de l’interface java.lang.Runnable – est tout simplement un objet poss´dant a e e une m´thode run(). e

90

c H. Garreta, 2000-2006

10 THREADS

10.1

Cr´ation et cycle de vie d’un thread e

public void run() { for (int i = 0; i < 10; i++) { System.out.println(getName() + " " + i); try { sleep((long) (Math.random() * 1000)); } catch (InterruptedException e) { } } System.out.println(getName() + " termin´"); e } } Note. La m´thode sleep doit toujours ˆtre appel´e dans un bloc try...catch puisque c’est par le e e e lancement d’une exception InterruptedException que le thread endormi est r´veill´ lorsque le temps indiqu´ e e e est ´coul´. Il en est de mˆme, et pour la mˆme raison, de la m´thode wait. e e e e e Voici un programme qui fait tourner en parall`le deux exemplaires de ce thread : e public class DemoDeuxThreads { public static void main (String[] args) { new ThreadSimple("Seychelles").start(); new ThreadSimple("Maurice").start(); } } Affichage obtenu (c’est un exemple ; lors d’une autre ex´cution, l’imbrication des messages  Seychelles ...  e parmi les messages  Maurice ...  peut ˆtre tout ` fait diff´rente) : e a e Seychelles 0 Seychelles 1 Maurice 0 Seychelles 2 Seychelles 3 Maurice 1 Maurice 2 ... Seychelles termin´ e Maurice 8 Maurice 9 Maurice termin´ e Deuxi`me exemple (un peu pr´matur´, les interfaces graphiques ne sont ´tudi´es qu’` la section suivante). e e e e e a Nous souhaitons disposer d’un cadre dont la barre de titre affiche constamment l’heure courante ` la seconde a pr`s. Il suffira pour cela de cr´er, en mˆme temps que le cadre, un deuxi`me thread qui dort65 la plupart du e e e e temps, se r´veillant chaque seconde pour mettre ` jour le texte affich´ dans la barre de titre : e a e import java.text.DateFormat; import java.util.*; import javax.swing.*; public class CadreAvecHeure extends JFrame { private String titre; public CadreAvecHeure(String titre) { super(titre); this.titre = titre; new Thread(new Runnable() { public void run() { gererLaPendule(); } }).start(); setDefaultCloseOperation(EXIT_ON_CLOSE); setSize(600, 400); setVisible(true); }
65 Quand

un thread dort il ne consomme aucune ressource syst`me. e

c H. Garreta, 2000-2006

91

10.2 Synchronisation

10

THREADS

private void gererLaPendule () { while (true) { try { Thread.sleep(1000); } catch(InterruptedException e) { } Date h = Calendar.getInstance().getTime(); String s = DateFormat.getTimeInstance().format(h); setTitle(titre + " " + s); } } public static void main(String[] args) { new CadreAvecHeure("Test"); } } 10.1.2 Terminaison d’un thread

Lorsqu’un thread atteint la fin de sa m´thode run(), il se termine normalement et lib`re les ressources e e qui lui ont ´t´ allou´es. Mais comment provoque-t-on la terminaison anticip´e66 d’un thread ? ee e e La classe Thread comporte une m´thode stop() dont le nom exprime bien la fonction. Mais elle est e vigoureusement d´sapprouv´e (deprecated ), car elle laisse dans un ´tat ind´termin´ certains des objets qui e e e e e d´pendent du thread stopp´. e e La mani`re propre et fiable de terminer un thread consiste ` modifier la valeur d’une variable que le thread e a consulte r´guli`rement. Par exemple, voici la classe CadreAvecHeure montr´e pr´c´demment, compl´t´e par e e e e e ee une m´thode arreterLaPendule() qui stoppe le rafraˆ e ıchissement de l’heure affich´e : e public class CadreAvecHeure extends JFrame { String titre; boolean go; public CadreAvecHeure(String titre) { comme la version pr´c´dente e e } private void gererLaPendule () { go = true; while (go) { try { Thread.sleep(1000); } catch(InterruptedException e) { } Date d = Calendar.getInstance().getTime(); String s = DateFormat.getTimeInstance().format(d); setTitle(titre + " " + s); } } public void arreterLaPendule() { go = false; } ... }

10.2

Synchronisation

Lorsqu’il d´marre, un thread travaille sur les donn´es du thread dans lequel il a ´t´ cr´´. Par exemple, e e e e ee dans le programme CadreAvecHeure ci-dessus, le thread  fils  qui ex´cute la m´thode gererLaPendule e e acc`de aux donn´es this (atteinte lors de l’appel de setTitle) et go qui appartiennent aussi au thread e e  p`re  qui l’a cr´´. Cela pose le probl`me de l’acc`s concurrent aux donn´es, source potentielle de blocages e ee e e e
66 On notera que si un thread se compose d’une boucle infinie, comme dans la m´thode gererLaPendule, sa terminaison ne e peut ˆtre qu’anticip´e : sans intervention ext´rieure, cette m´thode ne se terminera jamais. e e e e

92

c H. Garreta, 2000-2006

10 THREADS

10.2

Synchronisation

et d’erreurs subtiles, un probl`me d´licat qu’on r`gle souvent ` l’aide de verrous prot´geant des sections e e e a e critiques. 10.2.1 Sections critiques

Imaginons la situation suivante : nous devons parcourir une collection, membre d’UneCertaineClasse, en effectuant une certaine action sur chacun de ses ´l´ments. La collection est repr´sent´e par un objet List, ee e e l’action par un objet Action (une interface express´ment d´finie ` cet effet). e e a interface Action { void agir(Object obj); } public class UneCertaineClasse { List liste; ... void parcourir(Action action) { Iterator iter = liste.iterator(); while (iter.hasNext()) action.agir(iter.next()); } ... } S’il n’y a pas plusieurs threads pouvant acc´der ` la liste, la m´thode pr´c´dente est correcte. Mais e a e e e s’il peut arriver que, pendant qu’un thread parcourt la liste, un autre thread la modifie en ajoutant ou en enlevant des ´l´ments, alors le parcours pr´c´dent a des chances de devenir incoh´rent. Une premi`re mani`re ee e e e e e de r´soudre ce probl`me consiste ` rendre synchronis´e la m´thode parcourir : e e a e e public class UneCertaineClasse { List liste; ... synchronized void parcourir(Action action) { Iterator iter = liste.iterator(); while (iter.hasNext()) action.agir(iter.next()); } ... } L’effet du qualifieur synchronized plac´ devant la m´thode parcourir est le suivant : lors d’un appel e e de cette m´thode, de la forme67 e unObjet.parcourir(uneAction); un verrou sera pos´ sur unObjet de sorte que tout autre thread qui tentera une op´ration synchronis´e sur e e e ce mˆme objet (en appelant une autre m´thode synchronized de cette mˆme classe) sera bloqu´ jusqu’` ce e e e e a que ce verrou soit enlev´. e Cela r`gle le probl`me de l’acc`s concurrent ` la liste, mais peut-ˆtre pas de mani`re optimale. En effet, e e e a e e si le traitement que repr´sente la m´thode agir est long et complexe, alors la liste risque de se trouver e e verrouill´ pendant une longue p´riode et ralentir l’ensemble du programme. e e D’o` une solution bien plus l´g`re : ´tablir une section critique (i.e. prot´g´e par un verrou) dans laquelle u e e e e e on ne fait que cloner la liste puis, le verrou ´tant lev´, effectuer le parcours du clone, lequel ne craint pas les e e modifications que d’autres threads pourraient faire sur la liste originale : synchronized static void parcourir(List liste, Action action) { List copie = new LinkedList(); synchronized(liste) { Iterator iter = liste.iterator(); while (iter.hasNext()) copie.add(iter.next()); }
67 Un

appel de la forme  parcourir(uneAction)  n’est pas diff´rent, car il ´quivaut `  this.parcourir(uneAction) . e e a

c H. Garreta, 2000-2006

93

10.2 Synchronisation

10

THREADS

Iterator iter = copie.iterator(); while (iter.hasNext()) action.agir(iter.next()); } 10.2.2 M´thodes synchronis´es e e

Pour terminer cette pr´sentation des threads voici un grand classique de la programmation parall`le : e e une impl´mentation du mod`le dit  des producteurs et des consommateurs . e e Consid´rons la situation suivante : un certain nombre de fois – par exemple 10 – chaque producteur e  fabrique  un produit (num´rot´), le d´pose dans un entrepˆt qui ne peut en contenir qu’un, puis dort un e e e o temps variable : public class Producteur extends Thread { private Entrepot entrepot; private String nom; public Producteur(Entrepot entrepot, String nom) { this.entrepot = entrepot; this.nom = nom; } public void run() { for (int i = 0; i < 10; i++) { entrepot.deposer(i); System.out.println("Le producteur " + this.nom + " produit " + i); try { sleep((int) (Math.random() * 100)); } catch (InterruptedException e) { } } } } Un certain nombre de fois – par exemple 10 encore – chaque consommateur prend le produit qui se trouve dans l’entrepˆt et le  consomme  : o public class Consommateur extends Thread { private Entrepot entrepot; private String nom; public Consommateur(Entrepot entrepot, String nom) { this.entrepot = entrepot; this.nom = nom; } public void run() { int valeur = 0; for (int i = 0; i < 10; i++) { valeur = entrepot.prendre(); System.out.println("Le consommateur " + this.nom + " consomme " + valeur); } } } Ce qui est remarquable dans ce syst`me c’est que les producteurs et les consommateurs ne se connaissent e pas et ne prennent aucune mesure pour pr´venir les conflits. e Or, lorsque l’entrepˆt est vide les consommateurs ne peuvent pas consommer et lorsqu’il est plein les o producteurs ne peuvent pas produire. C’est l’entrepˆt qui g`re ces conflits. En revanche, il est ind´pendant o e e du nombre de producteurs et de consommateurs qui traitent avec lui :

94

c H. Garreta, 2000-2006

10 THREADS

10.2

Synchronisation

public class Entrepot { private int contenu; private boolean disponible = false; public synchronized int prendre() { while (disponible == false) { try { wait(); } catch (InterruptedException e) { } } disponible = false; notifyAll(); return contenu; } public synchronized void deposer(int valeur) { while (disponible == true) { try { wait(); } catch (InterruptedException e) { } } contenu = valeur; disponible = true; notifyAll(); } } Le fonctionnement de l’entrepˆt est assez savant. Les m´thodes prendre et deposer ´tant synchronized, o e e un seul thread peut s’y trouver ` un instant donn´. a e Lorsqu’un consommateur appelle la m´thode prendre e – si un produit est disponible (i.e. disponible == true) alors disponible devient false et une notification est envoy´e ` tous les threads qui sont en attente d’une notification sur cet entrepˆt ; en mˆme e a o e temps, le consommateur obtient le produit, – s’il n’y a pas de produit disponible (i.e. disponible == false) alors le thread appelle wait et se met donc en attente d’une notification sur cet entrepˆt. o L’appel de notifyAll d´bloque tous les threads en attente. Si parmi eux il y a des producteurs, au plus un e d’eux trouve disponible == false et peut donc produire, tous les autres (producteurs et consommateurs) se rebloquent (` cause du while) et attendent une nouvelle notification. a Le fonctionnement de l’entrepˆt vu dˆ cˆt´ de la m´thode deposer est rigoureusement sym´trique du o u oe e e pr´c´dent. Voici un programme principal qui lance deux de ces threads : e e public class Essai { public static void main(String[] args) { Entrepot entrepot = new Entrepot(); Producteur producteur = new Producteur(entrepot, "A"); Consommateur consommateur = new Consommateur(entrepot, "B"); producteur.start(); consommateur.start(); } } Affichage obtenu : Le producteur A Le consommateur Le producteur A Le consommateur etc. depose 0 B obtient 0 depose 1 B obtient 1

c H. Garreta, 2000-2006

95

11

INTERFACES GRAPHIQUES

11

Interfaces graphiques

Plus encore que pour les sections pr´c´dentes, il faut rappeler que ces notes ne cherchent pas ` ˆtre un e e ae manuel de r´f´rence, mˆme pas succinct. Quels que soient les langages et les syst`mes utilis´s, les biblioth`ques ee e e e e pour r´aliser des interfaces graphiques sont toujours extrˆmement copieuses. Celles de Java ne font pas e e exception ; nous n’avons pas la place ici pour expliquer les tr`s nombreuses classes et m´thodes qui les e e constituent. Nous nous contenterons donc d’expliquer les principes de fonctionnement des ´l´ments d’AWT et de ee Swing les plus importants et d’en montrer le mode d’emploi ` travers des exemples de taille raisonnable. a Outre un certain nombre de livres plus ou moins exhaustifs, dont l’ouvrage (en fran¸ais, ` part le titre) : c a David Flanagan Java Foundation Classes in a Nutshell O’Reilly, 2000 deux r´f´rences absolument indispensables pour programmer des interfaces graphiques en Java sont les deux ee hypertextes d´j` cit´s : ea e – la documentation en ligne de l’API Java : http://java.sun.com/j2se/1.5.0/docs/api/, notamment tout ce qui concerne les paquets dont les noms commencent par java.awt et javax.swing, – le tutoriel en ligne : http://java.sun.com/tutorial/, sp´cialement sa section Creating a GUI with e JFC/Swing.

11.1

AWT + Swing = JFC

Une caract´ristique remarquable du langage Java est de permettre l’´criture de programmes portables e e avec des interfaces-utilisateur graphiques, alors que partout ailleurs de telles interfaces sont r´alis´es au moyen e e de biblioth`ques s´par´es du langage et tr`s d´pendantes du syst`mes d’exploitation sur lequel l’application e e e e e e doit tourner, ou du moins de la couche graphique employ´e. e La premi`re biblioth`que pour r´aliser des interfaces-utilisateur graphiques68 en Java a ´t´ AWT, acroe e e ee nyme de Abstract Windowing Toolkit. Fondamentalement, l’ensemble des composants de AWT est d´fini e comme la partie commune des ensembles des composants graphiques existant sur les diff´rents syst`mes e e sur lesquels la machine Java tourne. Ainsi, chaque composant d’AWT est impl´ment´ par un composant e e homologue (peer ) appartenant au syst`me hˆte, qui en assure le fonctionnement. e o AWT est une biblioth`que graphique originale et pratique, qui a beaucoup contribu´ au succ`s de Java, e e e mais elle a deux inconv´nients : puisque ses composants sont cens´s exister sur tous les environnements e e vis´s, leur nombre est forc´ment r´duit ; d’autre part, mˆme r´put´s identiques, des composants appartenant e e e e e e a ` des syst`mes diff´rents pr´sentent malgr´ tout des diff´rences de fonctionnement qui finissent par limiter e e e e e la portabilit´ des programmes qui les utilisent. e C’est la raison pour laquelle AWT a ´t´ compl´t´e par une biblioth`que plus puissante, Swing. Apparue ee ee e comme une extension (c.-`-d. une biblioth`que optionnelle) dans d’anciennes versions de Java, elle appartient a e a ` la biblioth`que standard depuis l’apparition de  Java 2 . En Swing, un petit nombre de composants de haut e niveau (les cadres et les boˆ de dialogue) sont appari´s ` des composants homologues de l’environnement ıtes e a graphique sous-jacent, mais tous les autres composants – qui, par d´finition, n’existent qu’inclus dans un des e pr´c´dents – sont ´crits en  pur Java 69 et donc ind´pendants du syst`me hˆte. e e e e e o Au final, Swing va plus loin que AWT mais ne la remplace pas ; au contraire, les principes de base et un grand nombre d’´l´ments de Swing sont ceux de AWT. Ensemble, AWT et Swing constituent la biblioth`que ee e officiellement nomm´e JFC (pour Java Foundation Classes 70 ). e L’objet de la description qui va suivre est Swing mais, comme on vient de le dire, cela ne nous dispensera pas d’expliquer beaucoup de concepts de AWT qui jouent un rˆle important dans Swing, comme le syst`me o e des ´v´nements (EventListener ), les gestionnaires de disposition (LayoutManager ), etc. e e Les classes de AWT constituent le paquet java.awt et un ensemble de paquets dont les noms commencent par java.awt : java.awt.event, java.awt.font, java.awt.image, etc. Les classes de Swing forment le paquet javax.swing et un ensemble de paquets dont les noms commencent par javax.swing : javax.swing.border, javax.swing.filechooser, javax.swing.tree, etc.
nommer les interfaces-utilisateur graphiques vous trouverez souvent l’acronyme GUI, pour Graphics User Interface. documentation officielle exprime cela en disant que les composants de AWT, chacun appari´ avec un composant du e syst`me sous-jacent, sont  lourds , tandis que ceux de Swing, ´crits en pur Java, sont  l´gers . Ah, la belle langue technie e e commerciale... ! 70 JFC est une sorte de citation de la c´l`bre MFC ou Microsoft Foundation Classes, une biblioth`que de classes C++ de ee e chez Microsoft pour programmer les interfaces-utilisateur graphiques des applications destin´es ` Windows. e a
69 La 68 Pour

96

c H. Garreta, 2000-2006

11 INTERFACES GRAPHIQUES

11.2

Le haut de la hi´rarchie e

11.2

Le haut de la hi´rarchie e

Commen¸ons par donner quelques indications sur la mani`re dont le travail se partage entre les classes c e qui constituent le haut de la hi´rarchie d’h´ritage, c’est-`-dire les classes dont toutes les autres h´ritent. e e a e Cette section 11.2 est enti`rement  culturelle  (elle ne contient pas d’informations techniques pr´cises), e e mais il vaut mieux avoir compris les concepts qu’elle expose avant de commencer ` utiliser les composants a graphiques. Composants (Component) La classe java.awt.Component est le sommet de la hi´rarchie qui nous int´resse ici, c’est-`-dire la supere e a classe de toutes les classes qui repr´sentent des composants graphiques, aussi bien AWT que Swing. Le come portement d’un objet Component est tr`s riche, mais si on se limite aux fonctionnalit´s les plus repr´sentatives e e e du rˆle d’un composant il faut mentionner : o • Se dessiner. Puisqu’un composant est un objet graphique, la plus pr´visible de ses m´thodes est celle e e qui en produit l’exposition sur un ´cran ou un autre organe d’affichage. Cette m´thode se nomme paint et e e elle est toute prˆte pour les composants pr´d´finis, qui prennent soin de leur apparence graphique, mais on e e e doit la red´finir lorsqu’on cr´e des composants au dessin sp´cifique. e e e Comme on le verra, cette m´thode obligatoire n’est jamais appel´e par les programmes ; au lieu de cela, e e c’est la machine Java qui l’appelle, chaque fois que l’apparence graphique d’un composant a ´t´ endommag´e ee e et doit ˆtre r´par´e. e e e Davantage d’informations au sujet de la m´thode paint et des op´rations graphiques sont donn´es dans e e e la section 11.6. • Obtenir la taille et la position du composant. Une autre propri´t´ fondamentale de tout composant est ee que, une fois dessin´, il occupe un rectangle71 sur l’´cran ou l’organe d’affichage. Des m´thodes nomm´es e e e e getBounds, getSize, getLocation, etc., permettent d’obtenir les coordonn´es de ce rectangle. e • D´finir la taille et la position du composant. On pourrait penser que puisqu’il y a des m´thodes pour e e obtenir la taille et la position d’un composant, il y en a ´galement pour fixer ces param`tres. e e Ces m´thodes existent (elles se nomment setSize, setMinimumSize, setMaximumSize, setPreferredSize, e etc.) mais la question est plus compliqu´e qu’on ne pourrait le penser d’abord car, sauf exception, la taille e et la position d’un composant d´coulent d’une n´gociation permanente avec le gestionnaire de disposition e e (LayoutManager) du conteneur dans lequel le composant a ´t´ plac´. ee e Des explications sur les gestionnaires de disposition sont donn´es ` la section 11.7 e a • D´finir et obtenir les propri´t´s graphiques. Puisque le propre d’un composant est d’ˆtre dessin´ il est e ee e e normal qu’il comporte tout un ensemble de m´thodes pour d´finir ou consulter ses propri´t´s graphiques, e e ee comme la couleur du fond (setBackground, getBackground), la couleur de ce qui est dessin´ par-dessus e (setForeground, getForeground), la police courante (setFont, getFont), etc. Les propri´t´s graphiques d’un composant d´finissent les valeurs initiales du contexte graphique (objet ee e Graphics) utilis´ par toute op´rations de  peinture  (i.e. l’appel par la machine Java de la m´thode paint). e e e Les contextes graphiques et la m´thode paint sont expliqu´s ` la section 11.6. e e a ˆ • Etre source d’´v´nements. Puisqu’un composant est visible sur un ´cran, il est naturellement pr´destin´ e e e e e ae ` ˆtre la cible d’actions faites par l’utilisateur ` l’aide de la souris, du clavier ou de tout autre organe de a saisie disponible. Lorsqu’une telle action se produit, Java cr´e un ´v´nement, un objet d´crivant le type et les circonstances e e e e de l’action produite ; on dit que le composant sur lequel l’action s’est produite est la source de l’´v´nement. e e Java notifie alors cet ´v´nement ` un ou plusieurs objets, appel´s les auditeurs de l’´v´nement, qui sont e e a e e e cens´s d´clencher la r´action requise. e e e Ce m´canisme se manifeste au programmeur ` travers des m´thodes nomm´es addXXX Listener (la e a e e partie XXX d´pend de l’´v´nement en question), qui permettent d’attacher des auditeurs des divers types e e e d’´v´nements aux objets qui peuvent en ˆtre des sources : addMouseListener, addActionListener, etc. e e e La question des ´v´nements et de leurs auditeurs est reprise en d´tail ` la section 11.5. e e e a Conteneurs (Container) La classe java.awt.Container est une sous-classe de java.awt.Component. Un conteneur est un composant qui peut en contenir d’autres. Cela se traduit par deux m´thodes fondamentales : e • Ajouter un composant. C’est la m´thode add, sous la forme  leConteneur .add(unComposant)  qui e effectue de tels ajouts. Selon le gestionnaire de disposition en vigueur, elle peut requ´rir d’autres arguments. e
71 On

consid`re presque toujours qu’un composant occupe un rectangle, mˆme lorsqu’il ne semble pas rectangulaire. e e

c H. Garreta, 2000-2006

97

11.2 Le haut de la hi´rarchie e

11

INTERFACES GRAPHIQUES

• Associer au conteneur un gestionnaire de disposition. Un gestionnaire de disposition, ou layout manager, est un objet (invisible) qui  connaˆ  le conteneur et la liste de ses composants et qui commande la taille et ıt la position de ces derniers, cela aussi bien lors de l’affichage initial du conteneur, au moment de sa cr´ation e que, par la suite, chaque fois que la taille ou la forme du conteneur sont modifi´es. e Des explications sur les gestionnaires de disposition sont donn´es ` la section 11.7. e a Fenˆtres (Window) e La classe java.awt.Window est une sous-classe de java.awt.Container. La caract´ristique d’une fenˆtre e e est la possibilit´ d’ˆtre visible sans n´cessiter d’ˆtre incluse dans un autre composant, contrairement ` tous e e e e a les composants qui ne sont pas des fenˆtres. e La classe Window apporte donc la machinerie n´cessaire pour g´rer l’existence d’un composant sur l’organe e e d’affichage, ce qui demande, parmi d’autres choses, de g´rer la coexistence avec les autres fenˆtres – dont e e beaucoup n’ont pas de lien avec l’application en cours, ni mˆme avec Java – qui sont visibles sur l’organe e d’affichage en mˆme temps. Cette machinerie est mise en route ` travers des m´thodes comme setVisible e a e (ou show) et pack, que nous expliquerons plus en d´tail ` la section 11.4. e a D’une certaine mani`re, les fenˆtres sont le si`ge de la  vie  des interfaces graphiques. Lorsqu’une fenˆtre e e e e est rendue visible, un nouveau thread est cr´´ dans lequel s’ex´cute le programme qui d´tecte les actions ee e e de l’utilisateur sur la fenˆtre et sur chacun des composants qu’elle contient. Cela permet ` la fenˆtre d’ˆtre e a e e r´active mˆme lorsque l’application est occup´e ` d’autres tˆches. e e e a a Les diverses fenˆtres appartenant ` une mˆme application et existant ` un instant donn´ forment une e a e a e arborescence : sauf le cadre de l’application, chaque fenˆtre en r´f´rence une autre, sa propri´taire ; ene ee e semble, les fenˆtres constituent donc un arbre ayant pour racine le cadre de l’application. Cela assure, par e exemple, que la destruction du cadre principal entraˆ ınera bien la destruction de toutes les fenˆtres cr´´es e ee par l’application. Malgr´ l’importance de la classe Window il est rare qu’elle soit explicitement mentionn´e dans les applicae e tions courantes : les fonctionnalit´s de cette classe sont presque toujours exploit´es ` travers les sous-classes e e a Frame et Dialog. Cadres et boˆ ıtes de dialogue (Frame, Dialog) Les classes java.awt.Frame (cadre) et java.awt.Dialog (boˆ de dialogue) sont des sous-classes de ıte Window. Les instances de ces deux classes sont des objets bien connus de quiconque a vu on ordinateur (en marche), car de nos jours on les rencontre dans les interfaces graphiques de toutes les applications. Un cadre est une fenˆtre munie d’un bord, un bandeau de titre et, ´ventuellement, une barre de menus. e e Sur un syst`me moderne chaque application ordinaire72 a un cadre et la plupart des applications n’en ont e qu’un, si bien que l’application et son cadre finissent par se confondre dans l’esprit de l’utilisateur ; ce n’est pas fˆcheux, c’est mˆme un effet recherch´. a e e Du point de vue de l’utilisateur, le cadre d’une application remplit au moins les trois fonctions suivantes : – le cadre est l’outil standard pour modifier la taille et la forme de l’interface, – le cadre porte la barre des menus de l’application, – la fermeture du cadre produit la terminaison de l’application. Du point de vue du programmeur, un cadre est la seule fenˆtre qui n’a pas besoin de poss´der une fenˆtre e e e propri´taire. Il en d´coule que le point de d´part d’une interface graphique est toujours un cadre, et que tout e e e composant est  rattach´  ` un cadre73 : e a e e – un composant qui n’est pas une fenˆtre (Window) doit ˆtre inclus dans un conteneur, e ee e e – un composant qui est une fenˆtre mais pas un cadre doit r´f´rencer une autre fenˆtre, sa propri´taire. Les boˆ de dialogue sont elles aussi des fenˆtres munies d’un bord et d’une barre de titre mais, contraiıtes e rement aux cadres, elles sont par nature nombreuses, diversifi´es et ´ph´m`res. e e e e Les boˆ de dialogue sont l’outil standard pour afficher un message, poser une question ou demander ıtes des informations. Leur apparition est command´e par le programme, au moment requis, et elles permettent e a ` l’utilisateur de lire le message ou la question pos´e et, le cas ´ch´ant, de faire les actions demand´es (cocher e e e e des cases, remplir des champs de texte, faire des choix dans des listes, etc.) ; il y a toujours un ou plusieurs
72 Par application ordinaire nous entendons une application visible ` l’´cran qui interagit avec l’utilisateur. Cela exclut les a e applications invisibles, les  services , les  d´mons , etc. e 73 En particulier, la destruction d’un cadre produit celle de tous les composants qui lui sont rattach´s ; cela r´sout les probl`mes e e e de fenˆtres orphelines survivant ` la terminaison de l’application qui les a cr´´es, de bouts de composant oubli´s et, plus e a ee e g´n´ralement, de certains types de fuites de m´moire qui ont empoisonn´ les premi`res biblioth`ques graphiques. e e e e e e

98

c H. Garreta, 2000-2006

11 INTERFACES GRAPHIQUES

11.3

Le point de d´part : JFrame et une application minimale. e

boutons ( OK ,  Oui ,  Non ,  Open , etc.) dont la pression produit la disparition de la boˆ de ıte dialogue et, s’il y a lieu, l’acquisition par le programme des informations que l’utilisateur a donn´es en e agissant sur la boˆ ıte. Les boˆ ıtes de dialogue peuvent ˆtre modales et non modales. Aussi longtemps qu’elle est visible, une e boˆ modale bloque (c’est-`-dire rend insensibles aux actions de l’utilisateur) toutes les autres fenˆtres de ıte a e l’application, sauf celles dont la boˆ en question est propri´taire, directement ou indirectement. ıte e Les boˆ ıtes non modales n’ont pas cet effet bloquant : une boˆ de dialogue non modale et les autres ıte fenˆtres de l’application  vivent  en parall`le, ce qui peut ˆtre une cause de trouble de l’utilisateur. Les e e e boˆ de dialogue sont tr`s majoritairement modales. ıtes e Le cas des composants de Swing Swing est venu apr`s AWT et, quand il est arriv´, tous les noms int´ressants ´taient d´j` pris : Component, e e e e ea Frame, Dialog, Button, Panel, etc. Plutˆt que d’inventer des noms enti`rement nouveaux, forc´ment peu o e e significatifs, les concepteurs de Swing ont pr´f´r´ nommer les classes de Swing par les mˆmes noms que eee e AWT, avec un signe distinctif constant, un J en d´but du nom : JComponent, JFrame, JDialog, JButton, e JPanel, etc. Attention, l’oubli de ce J initial change le sens de ce qu’on ´crit, mais d’une mani`re que le compilateur ne e e peut pas toujours signaler comme une erreur ; cela introduit des dysfonctionnements plus ou moins importants dans les programmes. Les sections pr´c´dentes traitant des classes java.awt.Component, java.awt.Container, java.awt.Window, e e java.awt.Frame, java.awt.Dialog, etc. on pourrait penser qu’elles ne concernent pas Swing. C’est tout le contraire, car toutes les classes des composants de Swing sont sous-classes d’une ou plusieurs de celles-l` : a javax.swing.JComponent est (pour des raisons techniques) sous-classe de java.awt.Container, donc de java.awt.Component, et la majorit´ des composants de Swing sont sous-classes de java.awt.JComponent ; e javax.swing.JFrame et javax.swing.JDialog sont sous-classes de java.awt.Frame et java.awt.Dialog respectivement. Enfin, nos avons d´j` dit que certaines des fonctions les plus fondamentales de Swing sont assur´es par des ea e ´l´ments de AWT qui n’ont pas ´t´ ´tendus lors de la conception de Swing : les ´v´nements, les gestionnaires ee e ee e e de disposition, etc.

11.3

Le point de d´part : JFrame et une application minimale. e

Venons-en ` des choses plus pratiques. Pour commencer, voici une application parmi les plus r´duites a e qu’on puisse ´crire : e import java.awt.Color; import javax.swing.*; public class Bonjour { public static void main(String[] args) { JFrame cadre = new JFrame("Respect des traditions"); JLabel etiquette = new JLabel("Bonjour ` tous!", JLabel.CENTER); a cadre.getContentPane().add(etiquette); cadre.getContentPane().setBackground(Color.WHITE); cadre.setSize(250, 100); cadre.setVisible(true); } } L’ex´cution de ce programme affiche le cadre montr´ ` la figure 9. Il est extrˆmement simple, mais il est e ea e  vivant  : l’utilisateur peut en changer la taille et la position, le maximiser ou l’iconifier, le faire passer devant ou derri`re une autre fenˆtre, etc. e e Attention. Contrairement aux apparences, si – comme ici – le programmeur n’a pas pris les dispositions utiles, cliquer sur la case de fermeture (le bouton avec une croix ` l’extrˆme droite du bandeau de titre) a e fera disparaˆ le cadre mais ne terminera pas l’application, qui continuera ` tourner, et ` consommer des ıtre a a ressources, alors qu’elle n’aura plus aucun ´l´ment visible ! ee
c H. Garreta, 2000-2006

99

11.3 Le point de d´part : JFrame et une application minimale. e

11

INTERFACES GRAPHIQUES

Fig. 9 – Une version Swing de l’incontournable  PRINT "HELLO" 

En attendant une meilleure id´e74 on peut ´vacuer ce probl`me aga¸ant en ajoutant, apr`s la cr´ation e e e c e e du cadre, l’instruction cadre.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); Notez que, contrairement ` ce qu’on aurait pu penser (et contrairement ` ce qui se passe pour les Frame a a de AWT), un JFrame ne contient pas directement les composants qu’on souhaite voir dans le cadre. Un JFrame est un objet complexe dont un ´l´ment, le  panneau de contenu  (content pane) est le conteneur ee auquel il faut ajouter des composants. Sur la figure 9, le panneau de contenu est le rectangle blanc contenant le texte  Bonjour ` tous ! . On acc`de au panneau de contenu d’un objet JFrame par l’expression a e getContentPane()75 . En r´alit´, le programme pr´c´dent n’est pas tout ` fait minimal. Si on se contentait d’un cadre vide e e e e a on pourrait faire encore plus simple en supprimant les trois lignes ` partir de  JLabel etiquette = ... . a Mais les trois instructions restant alors sont obligatoires : – appel de new JFrame(...) pour cr´er l’objet JFrame. e Si on ´tait, comme c’est souvent le cas, en train de d´finir une sous-classe de JFrame, l’appel du e e constructeur de JFrame serait cach´ dans une instruction super(...) implicite ou explicite, e – appel de setSize(...) pour donner une taille au cadre, sinon il sera r´duit ` quelques pixels. e a A la place de setSize(...) on peut appeler la m´thode pack(), qui donne au cadre une taille juste e suffisante pour contenir les composantes qui lui sont ajout´s, mais il faut alors que ces composants e aient une taille minimum significative ; de plus, il faut appeler pack apr`s avoir ajout´ ces composants, e e – appel de setVisible() pour faire apparaˆ le cadre parmi les autres ´l´ments graphiques visibles ` ıtre ee a l’´cran et pour initier le thread charg´ de d´tecter et traiter les actions de l’utilisateur sur le cadre. e e e En pratique les cadres des applications sont destin´s ` ˆtre fortement personnalis´s, en recevant des e a e e composants divers et nombreux. C’est pourquoi il est plus commode de d´finir une sous-classe plutˆt qu’une e o instance de JFrame. Voici une nouvelle version du programme pr´c´dent, plus en accord avec ce qui se fait e e habituellement : import java.awt.Color; import javax.swing.*; public class Bonjour extends JFrame { public Bonjour() { super("Respect des traditions"); setDefaultCloseOperation(EXIT_ON_CLOSE); JLabel etiquette = new JLabel("Bonjour ` tous!", JLabel.CENTER); a getContentPane().add(etiquette); getContentPane().setBackground(Color.WHITE); setSize(250, 100); setVisible(true); } public static void main(String[] args) { JFrame cadre = new Bonjour(); } }
74 Une meilleure id´e serait un de ces dialogues qui s’instaurent dans beaucoup d’applications lorsque l’utilisateur manifeste e des envies de d´part, genre  Attention, votre travail n’est pas enregistr´, vous voulez vraiment quitter l’application ? . e e 75 Cette complication dans l’ajout des composants aux cadres de Swing a disparu dans la version 5 du JDK, car la m´thode e add de JFrame y a ´t´ surcharg´e afin qu’on puisse ´crire unCadre.add(unComposant). e e e e

100

c H. Garreta, 2000-2006

11 INTERFACES GRAPHIQUES

11.4

Le thread de gestion des ´v´nements e e

Note. Puisqu’elle n’est plus mentionn´e par la suite, la variable locale cadre est tout ` fait superflue. e a La m´thode pr´c´dente peut aussi bien s’´crire : e e e e public static void main(String[] args) { new Bonjour(); }

11.4

Le thread de gestion des ´v´nements e e

On peut ˆtre surpris par le programme pr´c´dent : le thread76 principal de l’application – celui qui se e e e cr´e quand on lance le programme, et dont la tˆche est d’appeler la m´thode main – ex´cute une unique e a e e instruction, la cr´ation d’une instance de Bonjour, puis se termine. e L’ex´cution de ce programme ne devrait donc durer qu’un instant. Pourtant, l’exp´rience montre qu’une e e interface graphique est mise en place et reste apr`s la terminaison du thread principal. e C’est donc qu’un deuxi`me thread a ´t´ cr´´ et se charge de faire vivre l’interface graphique, ce qui e e e ee veut dire d´tecter les actions de l’utilisateur, notamment ` l’aide de la souris, et r´agir en cons´quence, par e a e e exemple en modifiant l’aspect de l’interface. Tant que ce deuxi`me thread ne sera pas termin´, l’interface e e graphique existera. Ce thread est appel´  thread de traitement des ´v´nements  (event-dispatching thread). Il faut surtout e e e savoir deux choses ` son sujet : ` quel moment il est cr´´ et qu’il est le seul habilit´ ` modifier l’interface a a ee ea graphique. ´ Creation. Le thread de traitement des ´v´nements est cr´´ lorsque l’interface graphique est r´alis´e, e e ee e e c’est-`-dire affich´e ou prˆte ` l’ˆtre : a e e a e – un composant de haut niveau (JFrame, JDialog ou JApplet) est r´alis´ lors de l’appel d’une de ses e e m´thodes setVisible(true), show() ou pack(), e – la r´alisation d’un composant de haut niveau produit la r´alisation de tous les composants qu’il contient e e a ` ce moment-l`, a – l’ajout d’un composant ` un conteneur d´j` r´alis´ entraˆ la r´alisation du composant ajout´. a ea e e ıne e e ` ` Acces concurrents a l’interface. Le thread de traitement des ´v´nements est le seul habilit´ ` moe e ea difier l’interface. Imaginez une application dans laquelle le thread principal ne se termine pas imm´diatement e apr`s avoir r´alis´ le cadre de l’application ; ce serait une mauvaise id´e que de faire que ce thread, ou un e e e e autre cr´´ par la suite, ajoute ou modifie ult´rieurement des composants de l’interface car plusieurs thread ee e feraient alors des acc`s concurrents aux mˆmes objets. Il pourrait en r´sulter diverses sortes de situations e e e d’erreur et de blocage. La r`gle, appel´e  r`gle du thread unique , est la suivante : e e e Lorsqu’un composant Swing a ´t´ r´alis´, tout code qui peut affecter l’´tat de ce composant ou ee e e e en d´pendre doit ˆtre ex´cut´ dans le thread de traitement des ´v´nements. e e e e e e Qu’un code soit  ex´cut´ dans le thread de traitement des ´v´nements  signifie en pratique qu’il est e e e e ´crit dans un gestionnaire d’´v´nements (i.e. une m´thode d’un EventListener, destin´e ` ˆtre appel´e par e e e e e ae e Java en r´action ` la survenue de l’´v´nement) ou dans une m´thode appel´e par un tel gestionnaire. e a e e e e Il y a des exceptions ` cette r`gle, c’est-`-dire des op´rations sur des composants de l’interface qui peuvent a e a e ˆtre appel´es sans danger depuis n’importe quel thread. Elles sont toujours signal´es dans la documentation e e e par le texte “This method is thread safe, although most Swing methods are not.” Par exemple, les zones de texte JTextArea poss`dent des m´thodes setText(String t) et append(String e e t) permettant de remplacer ou d’allonger le texte affich´ dans la zone de texte. Ces op´rations sont thread e e safe, car le contraire serait tr`s malcommode. e D’autres op´rations thread safe bien connues sont les m´thodes repaint(...), revalidate(...), etc. e e de la classe JComponent. Ces m´thodes sont cens´es mettre ` jour l’affichage des composants, mais en r´alit´ e e a e e elles ne font que  poster  des requˆtes dans la file d’attente des ´v´nements (c’est le thread de traitement e e e des ´v´nements qui les en extraira). e e A retenir. Cons´quence pratique de la r`gle du thread unique : l’appel de setVisible(true) ou de e e show() doit ˆtre la derni`re instruction du processus de construction d’un cadre ou d’une boˆ de dialogue. e e ıte
76 Un thread, on dit aussi fil d’ex´cution ou processus l´ger, est l’entit´ constitu´e par un programme en cours d’ex´cution. e e e e e Les ´l´ments principaux de cette entit´ sont le code qui s’ex´cute et les donn´es que ce code manipule. ee e e e Le langage Java est multithread : le lancement d’une application cr´e un thread principal, dont le code est d´termin´ par la e e e m´thode main, mais ce thread peut en cr´er un ou plusieurs autres, qui s’ex´cuteront en parall`le (parall´lisme vrai si votre e e e e e machine comporte plusieurs processeurs, parall´lisme simul´ si elle n’en comporte qu’un). e e On dit que les thread sont des processus l´gers surtout pour indiquer qu’ils partagent les donn´es : lorsqu’un thread est cr´´ e e ee il acc`de ` toutes les variables du thread qui l’a cr´´. e a ee

c H. Garreta, 2000-2006

101

´ e 11.5 Ev´nements

11

INTERFACES GRAPHIQUES

S’il y a un appel de pack() alors celui-ci doit se trouver imm´diatement avant77 setVisible(true) ou e show(). La plus petite application avec interface graphique revisit´e e Respecter la r`gle du thread unique (ci-dessus) n’est pas toujours commode, ni mˆme possible. Il existe e e des composants complexes dont la mise en place requiert qu’on agisse sur eux apr`s qu’ils aient ´t´ r´alis´s e ee e e et rendus visibles, ce qui introduit la possibilit´ d’inter-blocages. e Une mani`re ´videmment correcte de r´soudre en bloc tous ces probl`mes consiste ` ex´cuter toutes e e e e a e les op´rations concernant l’interface graphique dans un seul thread, forc´ment le thread de gestion des e e ´v´nements. e e C’est pourquoi l’´quipe de d´veloppement de Java promeut d´sormais une nouvelle mani`re de cr´er et e e e e e lancer les interfaces graphiques. Le programme correct montr´ ` la section 11.3 ´tait, jusqu’en d´cembre e a e e 2003, la version canonique d’une petite application avec interface graphique, mais la forme  officielle  d’un tel programme est d´sormais la suivante : e import java.awt.Color; import javax.swing.*; public class Bonjour { private static void creerEtMontrerInterface() { JFrame cadre = new JFrame("Respect des traditions"); JLabel etiquette = new JLabel("Bonjour ` tous!", JLabel.CENTER); a cadre.getContentPane().add(etiquette); cadre.getContentPane().setBackground(Color.WHITE); cadre.setSize(250, 100); cadre.setVisible(true); } public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { creerEtMontrerInterface(); } }); } } Ce programme peut sembler obscur. Il se lit comme ceci : la m´thode SwingUtilities.invokeLater(obj ) e prend pour argument un objet Runnable (c’est-`-dire un objet poss´dant une m´thode run()) et elle produit a e e l’appel de obj .run() dans le thread de gestion des ´v´nements78 . e e Nous obtenons un objet runnable en cr´ant une instance d’une classe anonyme d´finie comme impl´mentation e e e de l’interface Runnable et ayant une m´thode run() qui se r´duit ` un appel d’une m´thode creerEtMontrere e a e Interface qui, d’´vidence, remplace ce qui ´tait pr´c´demment la m´thode main. e e e e e

11.5

´ e Ev´nements

Puisqu’un composant est visible sur un ´cran, il est naturellement pr´destin´ ` devenir la cible d’actions e e ea faites par l’utilisateur ` l’aide de la souris, du clavier ou de tout autre organe de saisie disponible. a Au moment o` elle est d´tect´e par l’ordinateur, une action de l’utilisateur provoque la cr´ation par la u e e e machine Java d’un ´v´nement, un objet qu’on peut imaginer comme un  rapport  d´crivant la nature et e e e les circonstances de cette action ; on dit que le composant qui en a ´t´ la cible est la source 79 de l’´v´nement ee e e en question. Lorsqu’un ´v´nement est cr´´, le composant qui en est la source a la charge de notifier ce fait ` tous les e e ee a objets qui ont ´t´ enregistr´ aupr`s de lui comme ´tant  concern´s  par ce type d’´v´nements et souhaitant ee e e e e e e
77 En th´orie, la s´quence  pack() ; show() ;  n’est pas en accord avec la r`gle du thread unique, puisque l’appel de show e e e est un acc`s, dans le thread principal, ` un composant d´j` r´alis´ par l’appel de pack. En pratique cela passe, car il y a bien e a ea e e peu de chances qu’un troisi`me thread fasse des acc`s aux composants d’une interface qui n’a pas encore ´t´ rendue visible. e e e e 78 Cela nous concerne peu ici, mais il faut savoir que la m´thode invokeLater(obj ) produit l’appel de obj .run() uniquement e apr`s que tous les ´v´nements en attente aient ´t´ trait´s (c’est l` l’aspect later de cette m´thode). e e e e e e a e 79 Attention, le mot est un peu trompeur : la vraie source de l’´v´nement est l’utilisateur (ou sa main qui tient la souris), e e mais cela n’int´resse notre programme qu’` partir du moment o` l’action est ressentie par la machine ; ` ce moment, c’est bien e a u a la cible de l’action de l’utilisateur qui devient le point d’apparition, ou source, de l’´v´nement. e e

102

c H. Garreta, 2000-2006

11 INTERFACES GRAPHIQUES

11.5

´ e Ev´nements

donc ˆtre pr´venus lorsqu’ils se produiraient. Bien entendu,  notifier un ´v´nement  c’est appeler une e e e e certaine m´thode, sp´cifique de l’´v´nement. e e e e Les objets qui demandent ` recevoir les notifications des ´v´nements d’un certain type doivent donc a e e poss´der les m´thodes correspondantes ; on garantit cela en imposant que ces objets soient des impl´mentations e e e d’interfaces ad hoc, nomm´es XXXListener , o` XXX caract´rise le type d’´v´nement consid´r´ : MouseListener, e u e e e ee WindowListener, ActionListener, etc. En r´sum´ : pour qu’un objet puisse recevoir les notifications d’une cat´gorie XXX d’´v´nements il faut e e e e e que sa classe impl´mente l’interface XXXListener ; cet objet peut alors ˆtre enregistr´ aupr`s d’une source e e e e d’´v´nements XXX en tant qu’auditeur (listener ) des ´v´nements XXX. e e e e Par cons´quent, si un objet est source d’´v´nements XXX alors il doit poss´der la m´thode addXXXLise e e e e tener 80 grˆce ` laquelle d’autres objets lui sont enregistr´s en qualit´ d’auditeurs de ces ´v´nements. a a e e e e 11.5.1 Exemple : d´tecter les clics de la souris. e

Donnons-nous le probl`me suivant : d´tecter les clics de la souris au-dessus d’un panneau et en afficher e e les coordonn´es. e

Fig. 10 – D´tecter les clics e La figure 10 montre notre application et l’´tat de la console d’o` on l’a lanc´e, apr`s qu’on ait cliqu´ pr`s e u e e e e des quatre coins du panneau. Voici une premi`re version de ce programme (des explications sont donn´es e e apr`s le listing) : e import javax.swing.*; import java.awt.Color; import java.awt.event.*; public class CadreAvecPanneauSensible extends JFrame { CadreAvecPanneauSensible() { super("Un cadre sensible"); setDefaultCloseOperation(EXIT_ON_CLOSE); JPanel panneau = new JPanel(); panneau.setBackground(Color.WHITE); GuetteurSouris felix = new GuetteurSouris(); panneau.addMouseListener(felix); getContentPane().add(panneau); setSize(250, 150); setVisible(true); } public static void main(String[] args) { new CadreAvecPanneauSensible(); } } class GuetteurSouris implements MouseListener { public void mousePressed(MouseEvent e) { System.out.println("Clic en (" + e.getX() + ", " + e.getY() + ")"); }
80 On peut en d´duire un moyen de d´couvrir l’ensemble des ´v´nements dont un composant peut ˆtre la source : chercher, e e e e e dans la documentation de sa classe, les m´thodes nomm´es addXXXListener. e e

c H. Garreta, 2000-2006

103

´ e 11.5 Ev´nements

11

INTERFACES GRAPHIQUES

public } public } public } public } }

void mouseReleased(MouseEvent e) { void mouseClicked(MouseEvent e) { void mouseEntered(MouseEvent e) { void mouseExited(MouseEvent e) {

Le code pr´c´dent met en ´vidence les trois ´l´ments cl´s dans tout processus d’exploitation d’actions de e e e ee e l’utilisateur : – les ´v´nements qu’il faut d´tecter et traiter ; ici, il s’agit des instances de la classe MouseEvent. Notez e e e que le programme ne montre pas la cr´ation de ces objets, pas plus que l’appel des m´thodes pour les e e exploiter, cela est l’affaire de m´thodes (h´rit´es de la classe Component) qui ne sont appel´es que par e e e e la machine Java ; – la source de ces ´v´nements, une instance indirecte de la classe java.awt.Component. Ici, la source e e des ´v´nements est l’obje JPanel qui est la valeur de la variable panneau ; e e – le gestionnaire de ces ´v´nements, un auditeur dˆment enregistr´ aupr`s de la source. Ici, cet auditeur e e u e e est une instance, nomm´e felix, de notre classe GuetteurSouris. e On constate donc que l’expression panneau.addMouseListener(felix) joue un rˆle central dans cette affaire : elle indique que panneau est consid´r´ ici comme une source o ee d’´v´nements souris81 et que ces ´v´nements, lorsqu’ils surviennent, doivent ˆtre notifi´s ` l’objet felix. e e e e e e a Les quatre m´thodes vides de la classe GuetteSouris peuvent surprendre. Il faut comprendre qu’elles e d´coulent de contraintes successives : e – d’une part, pour pouvoir ˆtre enregistr´ comme auditeur d’´v´nements souris, un objet doit appartenir e e e e a ` une classe qui impl´mente l’interface MouseListener, cela est impos´ par la d´claration de la m´thode e e e e addMouseListener, – d’autre part, l’interface MouseListener se compose de cinq m´thodes (abstraites), correspondant aux e cinq ´v´nements qu’on peut d´clencher avec une souris. e e e Note 1. Puisque la classe GuetteSouris n’est utilis´e que dans des m´thodes de la classe Cadree e AvecPanneauSensible, on aurait pu d´clarer la premi`re ` l’int´rieur de la deuxi`me. De cette mani`re, e e a e e e GuetteSouris serait devenue un classe interne (cf. section 6.6) ` la classe CadreAvecPanneauSensible. a Note 2. Rien n’oblige ` ce que le rˆle d’auditeur d’une sorte d’´v´nements soit tenu par des objets d’une a o e e classe sp´cifiquement d´finie ` cet effet. N’importe quel objet peut tenir ce rˆle – y compris celui qui est e e a o la source des ´v´nements en question – ` la condition qu’on en ait fait une impl´mentation de l’interface e e a e XXXListener correspondante. Par exemple, voici une autre ´criture de notre exemple, dans laquelle c’est le e cadre qui exploite les ´v´nements souris : e e import javax.swing.*; import java.awt.Color; import java.awt.event.*; public class CadreAvecPanneauSensible extends JFrame implements MouseListener { CadreAvecPanneauSensible() { super("Un cadre sensible"); setDefaultCloseOperation(EXIT_ON_CLOSE); JPanel panneau = new JPanel(); panneau.setBackground(Color.WHITE); panneau.addMouseListener(this);

81 Entendons-nous bien : un JPanel est toujours une source d’´v´nements souris. Mais ces ´v´nements ne sont exploit´s par e e e e e l’application que si un ou plusieurs auditeurs ont ´t´ enregistr´s. e e e

104

c H. Garreta, 2000-2006

11 INTERFACES GRAPHIQUES

11.5

´ e Ev´nements

getContentPane().add(panneau); setSize(250, 150); setVisible(true); } public static void main(String[] args) { new CadreAvecPanneauSensible(); } public void mousePressed(MouseEvent e) { System.out.println("Clic en (" + e.getX() + ", " + e.getY() + ")"); } public } public } public } public } } void mouseReleased(MouseEvent e) { void mouseClicked(MouseEvent e) { void mouseEntered(MouseEvent e) { void mouseExited(MouseEvent e) {

11.5.2

Adaptateurs et classes anonymes

Adaptateurs. Quand on ´crit l’impl´mentation d’une interface XXX Listener il est regrettable de devoir e e ´crire toutes les m´thodes de l’interface alors que seul un petit nombre d’´v´nements possibles, voire un seul, e e e e nous int´resse. e Les adaptateurs sont des impl´mentations toutes prˆtes des interfaces d’auditeurs d’´v´nements, enti`rement e e e e e faites de m´thodes vides. Chaque interface82 XXX Listener poss`de une classe XXX Adapter correspondante. e e Pour ´crire le traitement des ´v´nements qui nous int´ressent il suffit alors de d´finir une sous classe de e e e e e l’adaptateur concern´, dans laquelle seules les m´thodes pertinentes sont d´finies. e e e Par exemple, voici notre programme pr´c´dent – premi`re version – repris dans cet esprit : e e e import javax.swing.*; import java.awt.Color; import java.awt.event.*; public class CadreAvecPanneauSensible extends JFrame { CadreAvecPanneauSensible() { super("Un cadre sensible"); setDefaultCloseOperation(EXIT_ON_CLOSE); JPanel panneau = new JPanel(); panneau.setBackground(Color.WHITE); GuetteurSouris felix = new GuetteurSouris(); panneau.addMouseListener(felix); getContentPane().add(panneau); setSize(250, 150); setVisible(true); } public static void main(String[] args) { new CadreAvecPanneauSensible(); } }

82 Sauf les interfaces constitu´es d’une seule m´thode, comme ActionListener, pour lesquelles un adaptateur n’aurait aucun e e int´rˆt. e e

c H. Garreta, 2000-2006

105

´ e 11.5 Ev´nements

11

INTERFACES GRAPHIQUES

class GuetteurSouris extends MouseAdapter { public void mousePressed(MouseEvent e) { System.out.println("Clic en (" + e.getX() + ", " + e.getY() + ")"); } } Classes anonymes. On peut faire encore plus simple. Dans l’exemple pr´c´dent nous d´finissons une e e e sous-classe de MouseAdapter dans le but exclusif d’en cr´er une unique instance. Dans un tel cas de figure, e Java permet qu’on d´finisse la sous-classe dans la mˆme expression qui cr´e l’instance. La sous-classe est e e e alors sans nom, mais cela n’a pas d’importance puisqu’on n’envisage pas de lui faire d’autres instances. Cela s’´crit : e import javax.swing.*; import java.awt.Color; import java.awt.event.*; public class CadreAvecPanneauSensible extends JFrame { CadreAvecPanneauSensible() { super("Un cadre sensible"); setDefaultCloseOperation(EXIT_ON_CLOSE); JPanel panneau = new JPanel(); panneau.setBackground(Color.WHITE); MouseListener felix = new MouseAdapter() { public void mousePressed(MouseEvent e) { System.out.println("Clic en (" + e.getX() + ", " + e.getY() + ")"); } }; panneau.addMouseListener(felix); getContentPane().add(panneau); setSize(250, 150); setVisible(true); } public static void main(String[] args) { new CadreAvecPanneauSensible(); } } Note. Si on ne craint pas les ´critures compactes on peut mˆme se passer de la variable felix : e e ... panneau.addMouseListener(new MouseAdapter() { public void mousePressed(MouseEvent e) { System.out.println("Clic en (" + e.getX() + ", " + e.getY() + ")"); } }); ... 11.5.3 Principaux types d’´v´nements e e

Chaque type d’´v´nement correspond ` une interface d’auditeurs. Les principales (et les seules en AWT ) e e a sont les suivantes : MouseListener L’interface MouseListener concerne les ´v´nements isol´s 83 g´n´r´s ` l’aide de la souris. Il s’agit d’´v´nements e e e e ee a e e de bas niveau, c’est-`-dire des actions avec la souris sur des composants qui ne sont pas ´quip´s pour traduire a e e ces gestes ´l´mentaires en informations de plus haut niveau, comme c’est le cas pour les boutons, les menus, ee etc. Les m´thodes de cette interface sont : e
83 Par ´v´nements  isol´s  nous entendons les ´v´nements autres que les trains d’´v´nements que produisent les d´placements e e e e e e e e de la souris, voyez Mouse motion events, plus loin.

106

c H. Garreta, 2000-2006

11 INTERFACES GRAPHIQUES

11.5

´ e Ev´nements

void mousePressed(MouseEvent e) Appel´e lorsqu’un bouton de la souris a ´t´ press´, le pointeur se e ee e trouvant sur le composant dont on guette les ´v´nements. e e void mouseReleased(MouseEvent e) Appel´e lorsqu’un bouton de la souris a ´t´ relˆch´, alors qu’il avait e ee a e ´t´ press´ quand le pointeur se trouvait sur le composant dont on guette les ´v´nements. ee e e e e ee e e a e void mouseClicked(MouseEvent e) Appel´e lorsque la souris a ´t´  cliqu´e  (press´e puis relˆch´e au mˆme endroit), le curseur se trouvant sur le composant dont on guette les ´v´nements. De par sa e e e d´finition, cet ´v´nement est toujours pr´c´d´ d’un ´v´nement mouseReleased. La r´ciproque n’est e e e e e e e e e pas vraie : un ´v´nement mouseReleased n’est suivi d’un ´v´nement mouseClicked que si la position e e e e de la souris n’a pas chang´ depuis le dernier ´v´nement mousePressed. e e e void mouseEntered(MouseEvent e) Appel´e lorsque le pointeur de la souris commence `  survoler  le e a composant dont on guette les ´v´nements. e e void mouseExited(MouseEvent e) Appel´e lorsque le pointeur de la souris cesse de  survoler  le compoe sant dont on guette les ´v´nements. e e L’argument de ces m´thodes est un objet MouseEvent. Les m´thodes les plus int´ressantes d’un tel objet e e e sont : getX(), getY() qui renvoient les coordonn´es84 du pointeur au moment o` l’´v´nement a ´t´ notifi´, e u e e ee e getButton() qui permet de savoir quel bouton a ´t´ press´. ee e MouseMotionListener Cette interface concerne les mouvements de la souris. Lorsqu’on fait bouger la souris sans appuyer sur un de ses boutons on dit qu’on la  d´place  (move), lorsqu’on la fait bouger tout en maintenant un bouton e press´ on dit qu’on la  traˆ  (drag). Cette interface comporte donc deux m´thodes : e ıne e e void mouseMoved(MouseEvent e) Appel´e lorsque la souris change de position sans qu’aucun bouton ne soit press´. e void mouseDragged(MouseEvent e) Appel´e lorsque la souris change de position avec un de ses boutons e press´. e Il faut ˆtre plutˆt sobre en impl´mentant les m´thodes pr´c´dentes, car elles ont ` traiter des ´v´nements e o e e e e a e e qui arrivent par trains. Selon les performances du syst`me utilis´, un grand nombre d’appels de ces m´thodes e e e est g´n´r´ pendant un seul d´placement de la souris. Il faut que la r´ponse soit courte pour ne pas introduire e ee e e un asynchronisme d´sagr´able. e e KeyListener Il se produit un ´v´nement clavier chaque fois qu’une touche est press´e ou relˆch´e. Tous les composants e e e a e peuvent ˆtre sources de tels ´v´nements, il suffit qu’ils  aient le focus . Cette interface se compose de trois e e e m´thodes : e void keyPressed(KeyEvent e) Appel´e lorsqu’une touche a ´t´ press´e. e ee e void keyReleased(KeyEvent e) Appel´e lorsqu’une touche a ´t´ relˆch´e. e ee a e void keyTyped(KeyEvent e) Appel´e lorsqu’un caract`re a ´t´ saisi au clavier. e e ee Les ´v´nements keyPressed et keyReleased sont de bas niveau. Chacun correspond ` une touche unique, e e a et fournit un code num´rique (le code de touche virtuelle, ou VK code). En particulier, keyPressed est le e seul moyen de d´tecter la pression d’une touche ne produisant pas de caract`re (comme  Shift ,  Ctrl , e e etc.). L’´v´nement keyTyped, au contraire, est assez ´labor´, puisqu’il est capable d’agr´ger plusieurs touches e e e e e press´es ensemble pour former un unique caract`re : A majuscule, Control-C, etc. e e L’argument de ces m´thodes est un objet KeyEvent, dont les m´thodes les plus int´ressantes sont : e e e char getKeyChar() Renvoie le caract`re associ´ ` l’´v´nement clavier (compte tenu des ´ventuelles touches e ea e e e de modification) Dans le cas de keyPressed et keyReleased il peut n’exister aucun caract`re Unicode e e repr´sentant la touche concern´e, la valeur KeyEvent.CHAR UNDEFINED est alors renvoy´e. e e int getKeyCode() Dans le cas de keyPressed et keyReleased, renvoie le code de touche virtuelle correspondant ` la touche press´e ou relˆch´e. Dans le cas de keyTyped, le r´sultat est KeyEvent.VK UNDEFINED. a e a e e Sur beaucoup de syst`mes, si l’utilisateur maintient une touche press´e, un flot d’´v´nements keyPressed e e e e est g´n´r´. e ee
84 On

notera que, dans le cas des ´v´nements MouseExited et, surtout, MouseReleased, ces coordonn´es peuvent ˆtre n´gatives. e e e e e

c H. Garreta, 2000-2006

107

´ e 11.5 Ev´nements

11

INTERFACES GRAPHIQUES

FocusListener Les ´v´nements qui nous int´ressent ici concernent l’acquisition ou la perte du focus par un composant. e e e A tout moment, un seul composant a le focus ; par d´finition, c’est ` lui que sont adress´s les ´v´nements du e a e e e clavier. G´n´ralement, un composant acquiert le focus lorsqu’il subit un clic de la souris ; le composant qui e e avait le focus ` ce moment-l` le perd. La pression successive de la touche tabulation fait ´galement circuler a a e le focus parmi les composants plac´s sur un mˆme conteneur. e e e a void focusGained(FocusEvent e) Appel´e lorsque le composant en question acquiert le focus (c’est-`-dire que les actions sur le clavier lui seront d´sormais adress´es). e e void focusLost(FocusEvent e) Appel´e lorsque le composant perd le focus (un autre composant l’a pris). e

ActionListener Cette interface concerne des actions (avec la souris ou le clavier) sur un composant qui les traduit en une commande de plus haut niveau, comme un choix dans une liste ou un menu, une pression sur un bouton, etc. L’interface se compose d’une seule m´thode : e void actionPerformed(ActionEvent e) Appel´e lorsqu’une action a ´t´ faite (ce que  action  veut dire e ee d´pend du composant : presser un bouton, cocher ou d´cocher une case ` cocher, faire un choix dans e e a un menu, etc.). L’argument de cette m´thode est un objet ActionEvent, muni de la m´thode e e String getActionCommand() Renvoie une chaˆ qui identifie l’action en question. En r`gle g´n´rale, il ıne e e e s’agit d’une chaˆ ´crite sur le composant sur lequel l’action de l’utilisateur s’est effectu´e : le titre ıne e e d’un bouton press´ ou d’une case coch´e, le texte de l’´l´ment de liste ou de l’item de menu choisi, etc. e e ee

Fig. 11 – Trois boutons

Exemple : voici la d´finition d’une classe Panneau tr`s na¨ qui est un conteneur portant trois boutons e e ıve (voyez la figure 11) et, en mˆme temps, l’auditrice des actions dont ces boutons sont la source : e import javax.swing.*; import java.awt.event.*; class TroisBoutons extends JPanel implements ActionListener { public TroisBoutons() { JButton bouton = new JButton("Oui"); bouton.addActionListener(this); add(bouton); bouton = new JButton("Non"); bouton.addActionListener(this); add(bouton); bouton = new JButton("Euh..."); bouton.addActionListener(this); add(bouton); }

108

c H. Garreta, 2000-2006

11 INTERFACES GRAPHIQUES

11.5

´ e Ev´nements

public void actionPerformed(ActionEvent e) { if (e.getActionCommand().equals("Oui")) System.out.println("Il a dit oui"); else if (e.getActionCommand().equals("Non")) System.out.println("Il a dit non"); else System.out.println("Il ne sait pas"); } public static void main(String[] args) { JFrame cadre = new JFrame("Boutons"); cadre.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); cadre.getContentPane().add(new TroisBoutons()); cadre.pack(); cadre.setVisible(true); } } Voici une autre mani`re d’´crire une classe fonctionnellement identique ` la pr´c´dente, en utilisant les e e a e e r´f´rences des boutons plutˆt que leurs chaˆ ee o ınes action command 85 : import javax.swing.*; import java.awt.event.*; class TroisBoutons extends JPanel implements ActionListener { JButton bOui, bNon; public TroisBoutons() { bOui = new JButton("Oui"); bOui.addActionListener(this); add(bOui); bNon = new JButton("Non"); bNon.addActionListener(this); add(bNon); JButton bouton = new JButton("Euh..."); bouton.addActionListener(this); add(bouton); } public void actionPerformed(ActionEvent e) { if (e.getSource() == bOui) System.out.println("Il dit Oui"); else if (e.getSource() == bNon) System.out.println("Il dit Non"); else System.out.println("Il ne sait pas"); } public static void main(String[] args) { JFrame cadre = new JFrame("Boutons"); cadre.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); cadre.getContentPane().add(new TroisBoutons()); cadre.pack(); cadre.setVisible(true); } }
85 Cette mani`re de faire est plus efficace, puisqu’on remplace les comparaisons  equals  par des comparaisons  == , mais e cela oblige ` d´clarer des collections de variables d’instance. a e

c H. Garreta, 2000-2006

109

´ e 11.5 Ev´nements

11

INTERFACES GRAPHIQUES

ItemListener Il se produit un  ´v´nement d’item  lorsqu’un item est s´lectionn´ ou d´selectionn´. Par item nous e e e e e e entendons ici un ´l´ment d’une liste d´roulante, d’une liste de choix, une case ` cocher, etc. Une seule ee e a m´thode dans cette interface : e void itemStateChanged(ItemEvent e) Appel´e lorsqu’un item a ´t´ s´lectionn´ ou d´selectionn´. e ee e e e e AdjustmentListener Cette interface concerne les actions faites sur des barres de d´filement. Une seule m´thode : e e void adjustmentValueChanged(AdjustmentEvent e) Appel´e lorsque le curseur de la barre de d´filement e e a ´t´ d´plac´ (quelle qu’en soit la raison : on a tir´ le curseur, on a cliqu´ sur une des fl`ches de la ee e e e e e barre ou bien on a cliqu´ sur la barre en dehors du curseur). e En g´n´ral, ` la suite d’un tel ´v´nement on interroge la barre de d´filement ` l’aide de la m´thode e e a e e e a e getValue de la classe Scrollbar. Dans le cas o` l’on dispose de plusieurs barres de d´filement on peut u e utiliser la m´thode Object getSource() pour identifier celle qui est la source de l’´v´nement. e e e Ces ´v´nements ne sont pas d’un usage aussi fr´quent qu’on pourrait le penser car, dans la plupart des ape e e plications, les barres de d´filement des fenˆtres sont g´r´es par la machinerie interne des objets JScrollPane. e e ee TextListener Les ´v´nements de cette interface notifient les modifications du texte en cours de saisie dans un champ e e ou une zone de texte. Une seule m´thode : e void textValueChanged(TextEvent e) Appel´e lorsque le texte concern´ a chang´. e e e On notera que les champs de texte (JTextField et JPasswordField) produisent ´galement un ´v´nement e e e action lorsque l’utilisateur presse la touche  Entr´e , ce qui est souvent l’´v´nement r´ellement guett´. e e e e e WindowListener Les ´v´nements que cette interface concerne sont les actions sur une fenˆtre : ouverture et fermeture et, e e e dans le cas d’un cadre ou d’un dialogue, les actions sur les ´l´ments de la barre de titre. Ce qui donne les ee m´thodes : e void windowClosing(WindowEvent e) Appel´e lorsque l’utilisateur essaye de fermer la fenˆtre en agissant e e sur son  menu syst`me  ou sa case de fermeture. Dans le cas du cadre principal d’une application e c’est ici qu’on doit mettre l’´ventuel code de confirmation avant terminaison, voir l’exemple ci-dessous. e void windowActivated(WindowEvent e) Appel´e lorsque la fenˆtre est rendue active. e e void windowDeactivated(WindowEvent e) Appel´e lorsqu’une fenˆtre cesse d’ˆtre la fenˆtre active. e e e e void windowClosed(WindowEvent e) Appel´e lorsque la fenˆtre a ´t´ ferm´e, suite ` un appel de sa e e ee e a m´thode dispose. Ne pas confondre cet ´v´nement, qui n’a de sens que pour une fenˆtre fille (apr`s e e e e e la fermeture du cadre principal il n’y a plus d’application) avec l’´v´nement windowClosing. e e void windowOpened(WindowEvent e) Appel´e lorsqu’une fenˆtre est rendue visible pour la premi`re fois. e e e void windowIconified(WindowEvent e) Appel´e lorsqu’une fenˆtre est minimis´e (iconifi´e). e e e e void windowDeiconified(WindowEvent e) Appel´e lorsqu’une fenˆtre qui avait ´t´ minimis´e (iconifi´e) e e ee e e retrouve une taille normale. 11.5.4 Exemple : fermeture “prudente” du cadre principal

Voici une illustration classique de la mani`re de d´tecter et traiter les ´v´nements Window : demander e e e e a ` l’utilisateur une confirmation avant de fermer le cadre principal et mettre fin ` l’application (voyez la a figure 12). Bien sˆr, dans une vraie application, au lieu des quatre lignes ` partir de JPanel panneau = new u a JPanel() ; on verrait l’ajout au cadre de choses plus int´ressantes qu’un simple panneau vide. e Tr`s int´ressant, cet exemple montre ´galement l’emploi des tr`s utiles  boˆ de question  (JOptionPane). e e e e ıtes

110

c H. Garreta, 2000-2006

11 INTERFACES GRAPHIQUES

11.6

Peindre et repeindre

Fig. 12 – Confirmation avant fermeture

import javax.swing.*; import java.awt.event.*; import java.awt.*; public class CadreAvecSortieGardee extends JFrame { public CadreAvecSortieGardee(String titre) { super(titre); setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { sortiePrudente(); } }); JPanel panneau = new JPanel(); panneau.setPreferredSize(new Dimension(200, 150)); panneau.setBackground(Color.WHITE); getContentPane().add(panneau); pack(); setVisible(true); } void sortiePrudente() { if (JOptionPane.showConfirmDialog(this, "Voulez-vous vraiment quitter ce programme?", "Attention!", JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) System.exit(0); } public static void main(String[] args) { new CadreAvecSortieGardee("On sort pas comme on veut"); } }

11.6
11.6.1

Peindre et repeindre
La m´thode paint e

Les composants graphiques doivent ˆtre peints (sur l’´cran) : une premi`re fois lors de leur affichage e e e initial, ensuite chaque fois qu’ils ont ´t´ totalement ou partiellement modifi´s, effac´s, agrandis, etc. ee e e Tout composant qui n’est pas une fenˆtre est forc´ment inclus dans un conteneur qui, s’il n’est pas e e lui-mˆme une fenˆtre, doit ` son tour ˆtre inclus dans un autre conteneur, lui-mˆme peut-ˆtre inclus dans e e a e e e un troisi`me, etc. Cette chaˆ d’inclusions se poursuit jusqu’` une fenˆtre, le seul conteneur qui n’est pas e ıne a e oblig´ d’ˆtre inclus dans un autre. Or, la gestion que fait la machine Java des fenˆtres (en comp´tition avec e e e e les autres fenˆtres, Java ou non, visibles ` l’´cran) lui permet de d´tecter des situations comme celle-ci : e a e e une fenˆtre qui ´tait totalement ou partiellement masqu´e par une autre, vient d’ˆtre d´couverte et, par e e e e e
c H. Garreta, 2000-2006

111

11.6 Peindre et repeindre

11

INTERFACES GRAPHIQUES

cons´quent, tous ou certains des composants plac´s dans cette fenˆtre doivent ˆtre redessin´s. La machine e e e e e Java met alors dans une certaine  file d’attente de choses ` faire  une requˆte indiquant qu’il faut repeindre a e les composants qui se trouvent dans la partie endommag´e, qu’il faut restaurer86 . e Aussi longtemps que l’apparence standard d’un composant vous convient, vous n’avez pas ` vous pr´occuper a e de sa peinture : le composant prend soin de lui mˆme. L` o` cette question devient int´ressante c’est lorse a u e qu’on consid`re des composants  personnalis´s , dont l’apparence graphique souhait´e n’est pas celle que e e e leur classe pr´voit par d´faut. Souvent cela veut dire que le composant est un panneau (JPanel) et qu’on e e doit y montrer un dessin form´ de trac´s ´l´mentaires (segments, arcs, rectangles et ovales pleins, etc.) ou e e ee bien une image obtenue ` partir d’un fichier d’un format reconnu par Java, comme GIF ou JPEG. a C’est la m´thode paint qui a la responsabilit´ de peindre un composant. Tous les composants en ont une e e version, ne serait-ce que celle qu’ils ont h´rit´e de la classe Component. Pour qu’un composant ait un dessin e e personnalis´ il faut et il suffit de red´finir cette m´thode dans la classe du composant87 . e e e La m´thode paint n’est jamais explicitement appel´e par le programme ; au lieu de cela, c’est la machine e e Java qui se charge de l’appeler, chaque fois que l’apparence du composant doit ˆtre refaite. Cela arrive e principalement dans trois sortes de situations, dont seule la troisi`me est ` notre port´e : e a e – le composant doit ˆtre repeint suite ` un ´v´nement ext´rieur ` notre application, voire mˆme ` la e a e e e a e a machine Java (exemple : une autre fenˆtre est apparue devant le composant, puis a disparu), e – le composant doit ˆtre repeint ` cause d’un ´v´nement qui s’adresse bien ` l’interface graphique de notre e a e e a application mais qui est pris en charge par un ´l´ment ou une super-classe du composant (exemple : ee l’utilisateur a chang´ la taille de la fenˆtre, en agissant sur son bord), e e – le composant doit ˆtre repeint car les donn´es dont il exhibe une repr´sentation ont chang´ (la machine e e e e Java ne peut pas deviner cela, nous sommes les seuls ` le savoir). a L’argument de la m´thode paint ´tant un objet de type Graphics, il nous fait pour commencer expliquer e e ce qu’est un tel objet. 11.6.2 Les classes Graphics et Graphics2D

Un contexte graphique est un objet qui encapsule l’ensemble des informations et des outils n´cessaires e pour effectuer des op´rations graphiques. Les contextes graphiques sont instances de la classe Graphics, ou e d’une de ses sous-classes comme Graphics2D. • L’´tat d’un tel objet se manifeste ` travers un ensemble de m´thodes get (les m´thodes set correspone a e e dantes existent) dont les plus importantes sont : Shape getClip() donne la zone de coupe courante, c’est-`-dire la r´gion en dehors de laquelle les op´rations a e e graphiques sont sans effet, Rectangle getClipRect() donne l’enveloppe rectangulaire de la zone de coupe (voir ci-dessus), e e Color getColor() donne la couleur qui sera employ´e par les op´rations graphiques, aussi bien pour tracer des traits que pour remplir des figures ou pour  dessiner  des textes, Font getFont() donne la police de caract`res employ´e pour ´crire des textes, e e e FontMetrics getFontMetrics() un objet FontMetrics qui repr´sente un ensemble de mesures ` propos de e a la police de caract`res courante. e • Le comportement d’un objet Graphics est constitu´ par ses op´rations graphiques. Parmi les princie e pales : void clearRect(int x, int y, int width, int height) efface le rectangle sp´cifi´ en le peignant avec e e la couleur de fond (background) du composant, void copyArea(int x, int y, int width, int height, int dx, int dy) copie une portion de l’image en la pla¸ant ` un endroit d´fini par dx et dy, c a e Graphics create() cr´e un contexte graphique qui est un clone de celui sur lequel cette m´thode est e e appel´e, e
86 Ces requˆtes sont affect´es de la priorit´ la plus basse possible, si bien que la machine ne s’occupera de repeindre les e e e composants que lorsque elle n’aura vraiment rien d’autre ` faire ; de plus, Java ne conserve dans la file d’attente qu’un exemplaire a au plus d’une telle requˆte. On ´vite ainsi les s´ries de peintures successives que provoquerait la prise en charge prioritaire de e e e ces requˆtes, entraˆ e ınant un travail inutile et des clignotements d´sagr´ables. e e 87 Cons´quence pratique : si un composant doit avoir un dessin personnalis´, alors il devra ˆtre instance d’une classe e e e sp´cialement d´finie ` cet effet. e e a

112

c H. Garreta, 2000-2006

11 INTERFACES GRAPHIQUES

11.6

Peindre et repeindre

(x,y) arcAngle startAngle

height

width
Fig. 13 – Trac´ d’un arc e void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle) trace un arc d’ellipse mesurant arcAngle degr´s et commen¸ant au point d´fini par startAngle degr´s e c e e (l’origine est  ` 3 heures ). Cette ellipse est inscrite dans le rectangle de largeur width et de hauteur a height dont le coin sup´rieur gauche est le point de coordonn´es x et y (cf. figure 13). e e boolean drawImage(Image img, int x, int y, ImageObserver observer) dessine l’image img en pla¸ant c son coin sup´rieur gauche au point de coordonn´es x, y. e e observer est un objet destinataire des notifications sur l’´tat d’avancement de l’image qui – par e exemple dans le cas d’images obtenues sur le web – peut se charger tr`s lentement ; si la question e est sans int´rˆt (par exemple s’il s’agit d’images qui n’arrivent pas par le r´seau) on peut mettre ici ee e n’importe quel composant. void drawLine(int x1, int y1, int x2, int y2) trace un segment de droite joignant le point (x1 , y1 ) au point (x2 , y2 ), void drawRect(int x, int y, int width, int height) trace le bord d’un rectangle de largeur width et de hauteur height dont le coin sup´rieur gauche est plac´ au point de coordonn´es x et y. e e e void drawOval(int x, int y, int width, int height) trace le bord d’une ellipse d´finie par son ene veloppe rectangulaire (voyez drawRect ci-dessus),

(x,y)

arcWidth arcHeight

height

width
Fig. 14 – Un rectangle aux coins arrondis void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) trace le bord d’un rectangle (x,y,width,height) dont les coins sont des quarts de l’ellipse inscrite dans un rectangle de largeur arcWidth et de hauteur arcHeight (cf. figure 14). ıne e e void drawString(String str, int x, int y) trace la chaˆ str de telle mani`re que le coin inf´rieur gauche de l’enveloppe rectangulaire du premier caract`re soit sur le point de coordonn´es x et y, e e e void fillRect(int x, int y, int width, int height) peint l’int´rieur d’un rectangle (cf. drawRect) en utilisant la couleur courante, e void fillOval(int x, int y, int width, int height) peint l’int´rieur d’une ellipse (cf. drawOval) en utilisant la couleur courante, void fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) peint l’int´rieur d’un rectangle aux coins arrondis (cf. drawRoundRect) en utilisant la couleur courante, e void translate(int x, int y) place l’origine des coordonn´es sur le point de coordonn´es x et y (relatives e e a ` l’origine pr´c´dente). e e
c H. Garreta, 2000-2006

113

11.6 Peindre et repeindre

11

INTERFACES GRAPHIQUES

A propos de Graphics2D La classe Graphics2D ´tend la classe Graphics en permettant un contrˆle beaucoup plus sophistiqu´ de e o e la g´om´trie, les syst`mes de coordonn´es, les transformations graphiques, la couleur, l’aspect des textes, e e e e etc. Il faut savoir que la valeur du param`tre de type Graphics pass´ ` la m´thode paint est en r´alit´ un e ea e e e objet Graphics2D. Lorsque les op´rations  am´lior´es  de la classe Graphics2D sont n´cessaires, la m´thode e e e e e paint commence donc de la mani`re suivante, qui est donc l´gitime : e e public void paint(Graphics g) { super.paint(g); Graphics2D g2d = (Graphics2D) g; ... op´rations graphiques mettant en œuvre l’objet g2d e (toutes les op´rations des classes Graphics et Graphics2D sont donc l´gitimes) e e ... }

11.6.3

Exemple : la mauvaise mani`re de dessiner e

Fig. 15 – Gribouillages...

Donnons-nous le probl`me suivant (voyez la figure 15) : dessiner une ligne polygonale que l’utilisateur e d´finit en cliquant successivement sur les points qu’il souhaite placer comme sommets de la construction. e Premi`re version, nous expliquons plus loin en quoi elle n’est pas bonne : e import javax.swing.*; import java.awt.*; import java.awt.event.*; public class UnGribouillis extends JPanel { UnGribouillis() { setPreferredSize(new Dimension(600, 400)); setBackground(Color.WHITE); addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { clic(e.getX(), e.getY()); } }); }

114

c H. Garreta, 2000-2006

11 INTERFACES GRAPHIQUES

11.6

Peindre et repeindre

private int xPrec = -1; private int yPrec;

// coordonn´es du e // clic pr´c´dent e e

void clic(int x, int y) { if (xPrec >= 0) { Graphics g = getGraphics(); g.drawLine(xPrec, yPrec, x, y); } xPrec = x; yPrec = y; } public static void main(String[] args) { JFrame cadre = new JFrame("Des gribouillis"); cadre.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); cadre.getContentPane().add(new UnGribouillis()); cadre.pack(); cadre.setVisible(true); } }

Le principe de ce programme est simple : ` partir du second, chaque clic d´tect´ provoque le trac´ d’un a e e e segment joignant le point o` il s’est produit au point o` s’est produit le clic pr´c´dent. On dessine donc  ` u u e e a fonds perdu , ce qui est le d´faut de cette mani`re de faire : si la ligne polygonale est endommag´e, notre e e e application ne sera pas capable de la redessiner88 .

11.6.4

Exemple : la bonne mani`re de dessiner. e

On l’aura compris, la bonne mani`re de dessiner ne consiste pas ` tracer les ´l´ments graphiques au fur e a ee et ` mesure que leurs param`tres sont connus, mais au contraire ` m´moriser l’information requise pour tout a e a e (re-)dessiner chaque fois que cela est n´cessaire : e import import import import javax.swing.*; java.awt.*; java.awt.event.*; java.util.*;

public class UnAutreGribouillis extends JPanel { UnAutreGribouillis() { setPreferredSize(new Dimension(600, 400)); setBackground(Color.WHITE); addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { points.add(new Point(e.getX(), e.getY())); repaint(); } }); } private Vector points = new Vector();

88 Dans certains cas, le syst`me sous-jacent prend sur lui de m´moriser le contenu des fenˆtres afin d’ˆtre en mesure de les e e e e restaurer lorsqu’elles auront ´t´ partiellement ou totalement masqu´es. C’est gentil mais na¨ car cela ignore le fait que l’image e e e ıf ` montrer n’est peut-ˆtre pas la mˆme que celle qui a ´t´ sauvegard´e. a e e e e e

c H. Garreta, 2000-2006

115

11.7 Gestionnaires de disposition

11

INTERFACES GRAPHIQUES

public void paint(Graphics g) { super.paint(g); Iterator it = points.iterator(); if (it.hasNext()) { Point p = (Point) it.next(); while (it.hasNext()) { Point q = (Point) it.next(); g.drawLine(p.x, p.y, q.x, q.y); p = q; } } } public static void main(String[] args) { JFrame cadre = new JFrame("Des gribouillis"); cadre.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); cadre.getContentPane().add(new UnAutreGribouillis()); cadre.pack(); cadre.setVisible(true); } } Dans ce second programme, la r´action ` un clic de la souris n’est plus le dessin d’un segment, mais e a uniquement la m´morisation des coordonn´es du clic suivie d’un appel de la m´thode repaint89 pour indiquer e e e que le dessin du composant n’est plus valide (puisque la ligne polygonale poss`de d´sormais un sommet de e e plus, que la figure ne montre pas encore). Le dessin effectif est l’affaire de la m´thode paint, dont le programmeur doit ´crire la d´finition mais e e e jamais l’appel, car la plupart des appels de cette m´thode sont impr´visibles. La cr´ation d’un sommet de la e e e ligne polygonale est une exception : repaint ayant ´t´ appel´e, l’appel de paint est pour une fois pr´visible, ee e e mais les autres situations qui requi`rent le travail de paint (masquage de la fenˆtre, redimensionnement, e e etc.) ne peuvent pas ˆtre explicitement pr´dites et plac´es parmi les instructions du programme. e e e Note 1. Presque toujours la version red´finie de la m´thode paint commence, comme ici, par appeler e e la version h´rit´e : e e super.paint(g) ; Cela est est n´cessaire car, sinon, le fond de l’´cran ne serait pas repeint et de curieux dysfonctionnements e e apparaˆ ıtraient. Note 2. La m´thode paint est la seule (bonne) m´thode pour peindre les composants de AWT. Pour e e les composants de Swing, cependant, la m´thode paint consiste en des appels successifs de trois m´thodes e e d´l´gu´es paintComponent, paintBorder et paintChildren. C’est pourquoi, dans le cas o` notre composant ee e u a un bord et comporte des sous-composants, il vaut souvent mieux red´finir la m´thode paintComponent au e e lieu de s’en prendre ` paint. La diff´rence n’est pas bien grande : a e public class UnAutreGribouillis extends JPanel { ... public void paintComponent(Graphics g) { super.paintComponent(g); ... le reste de la m´thode est identique ` paint, ci-dessus e a ... } ... }

11.7

Gestionnaires de disposition

Le placement des composants dans les conteneurs est l’affaire des gestionnaires de disposition, ou layout managers, qui se chargent :
89 Contrairement ` ce qu’on pourrait penser, la m´thode repaint ne provoque pas un appel imm´diat de paint. Au lieu de a e e cela, elle se limite ` mettre dans la  file des choses ` faire  une requˆte de peinture, s’il n’y en avait pas d´j` une. Ce n’est a a e ea que quand toutes les autres requˆtes de cette file auront ´t´ trait´es (c’est-`-dire quand la machine Java n’aura rien d’autre ` e e e e a a faire) que la m´thode paint sera effectivement appel´e et le composant redessin´. e e e

116

c H. Garreta, 2000-2006

11 INTERFACES GRAPHIQUES

11.7

Gestionnaires de disposition

– du placement initial des composants, lors des appels de la m´thode add, e – le cas ´ch´ant, de donner une taille et une forme ` chaque composant, en fonction de sa  taille pr´f´r´e , e e a eee de son contenu et de sa disposition relativement aux autres composants dans le mˆme conteneur, e – du repositionnement des composants lorsque la taille ou la forme du conteneur change.

Fig. 16 – Un panneau avec un gestionnaire FlowLayout

Absence de gestionnaire Pour commencer, ´vacuons une question tr`s secondaire : il est possible (mais non recommand´) de se e e e passer compl`tement de gestionnaire de disposition. Cela doit ˆtre explicitement demand´, par l’instruction e e e conteneur .setLayout(null); A la suite de cette commande, chaque composant devra ˆtre explicitement dimensionn´ et positionn´ lors de e e e son ajout au conteneur (bonjour les calculs avec des pixels !). Les m´thodes pour cela sont e void setLocation(int x, int y), void setLocation(Point p) d´finit la position du coin sup´rieur gauche e e du composant (ou plutˆt de son enveloppe rectagulaire), relativement ` une origine qui est plac´e au o a e coin sup´rieur gauche du conteneur, e void setSize(int width, int height), void setSize(Dimension d) d´finit les dimensions du compoe sant (ou plutˆt de son enveloppe rectangulaire), o void setBounds(int x, int y, int width, int height), void setBounds(Rectangle r) remplit simultan´ment les fonctions de setLocation et celles de setSize. e Lorsque les composants ont ´t´ plac´s manuellement il y a int´rˆt ` interdire les changements de taille ee e ee a et de forme du conteneur. Cela s’obtient par l’expression conteneur .setResizable(false); Attention. Certains programmeurs d´butants, rebut´s par la difficult´ qu’il y a parfois ` obtenir d’un e e e a gestionnaire de disposition qu’il fasse ce qu’on veut, choisissent de placer manuellement les composants. L’exp´rience montre que, sauf de rares exceptions, se passer de gestionnaire de disposition n’est pas la e mani`re la plus simple de r´aliser une interface graphique. e e 11.7.1 FlowLayout

Un gestionnaire FlowLayout (voir figure 16) dispose les composants par lignes, de la gauche vers la droite et du haut vers le bas. Par d´faut les composants sont centr´s horizontalement et ´cart´s entre eux de 5 pixels, mais on peut e e e e changer cela au moment de l’instanciation. Par exemple, le panneau de la figure 16 a ´t´ construit par la ee s´quence suivante : e ... JPanel panneau = new JPanel(); panneau.setLayout(new FlowLayout(FlowLayout.LEFT)); panneau.add(new JButton("Un")); panneau.add(new JButton("Deux")); ... Le gestionnaire de disposition par d´faut d’un JPanel est FlowLayout. e
c H. Garreta, 2000-2006

117

11.7 Gestionnaires de disposition

11

INTERFACES GRAPHIQUES

11.7.2

BorderLayout

Un BorderLayout (voir figure 17) distingue cinq zones dans le conteneur auquel il est attach´ : le nord, e le sud, l’est, l’ouest et le centre.

Fig. 17 – Un panneau avec un gestionnaire BorderLayout Le nord et le sud, lorsqu’ils sont pr´sents, prennent toute la largeur du conteneur et ont la plus petite e hauteur qui convient aux composants qu’ils contiennent. L’est et l’ouest, lorsqu’ils sont pr´sents, prennent e toute la hauteur du conteneur moins les parties ´ventuellement occup´es par le nord et le sud, et ont la largeur e e minimale qui respecte les composants qu’ils contiennent. Enfin, le centre prend toute la place restante. Par exemple, le panneau de la figure 17 a ´t´ construit par la s´quence ee e ... JPanel panneau = new JPanel(); panneau.setLayout(new BorderLayout()); panneau.add(new JButton("Haut"), BorderLayout.NORTH); panneau.add(new JButton("Gauche"), BorderLayout.EAST); panneau.add(new JButton("Bas"), BorderLayout.SOUTH); panneau.add(new JButton("Droite"), BorderLayout.WEST); panneau.add(new JButton("Centre"), BorderLayout.CENTER); ... Le gestionnaire de disposition par d´faut du panneau de contenu d’un JFrame ou d’un JDialog est e BorderLayout. 11.7.3 GridLayout

Un gestionnaire GridLayout (voir figure 18) organise les composants selon une grille rectangulaire ayant un nombre de lignes et de colonnes convenus lors de la cr´ation du gestionnaire. Toutes les cases de cette e grille ont les mˆmes dimensions. e

Fig. 18 – Un panneau avec un gestionnaire GridLayout (5 lignes × 4 colonnes) Par exemple, le panneau de la figure 18 a ´t´ construit par la s´quence suivante : ee e ... JPanel panneau = new JPanel(); panneau.setLayout(new GridLayout(5, 4)); for (int i = 0; i <= 16; i++) panneau.add(new JButton("" + i)); ... 118
c H. Garreta, 2000-2006

11 INTERFACES GRAPHIQUES

11.7

Gestionnaires de disposition

11.7.4

GridBagLayout

Un gestionnaire GridBagLayout est plus puissant, mais bien plus complexe que les pr´c´dents. Il se base e e sur un quadrillage imaginaire du conteneur tel que l’espace occup´ par chaque composant soit une r´gion e e rectangulaire form´e par la r´union d’un certain nombre de cases de ce quadrillage. e e Lors de son ajout au conteneur, chaque composant est associ´ ` une certaine contrainte qui sp´cifie e a e quelles cases du quadrillage imaginaire le composant occupe et comment il les occupe. Ensemble, toutes ces contraintes d´finissent les dimensions des lignes et des colonnes du quadrillage. e Les contraintes sont des instances de la classe GridBagConstraints, dont les principaux champs sont : gridx : num´ro de la colonne du quadrillage (la premi`re colonne porte le num´ro 0) o` se place l’angle e e e u sup´rieur gauche du composant. e gridy : num´ro de la ligne du quadrillage (la premi`re ligne porte le num´ro 0) o` se place l’angle sup´rieur e e e u e gauche du composant. gridwidth : nombre de colonnes du quadrillage sur lesquelles le composant s’´tend. e gridheight : nombre de lignes du quadrillage sur lesquelles le composant s’´tend. e weightx : nombre exprimant la largeur de la colonne que le composant occupe ; peu importe l’unit´, pourvu e que ce soit la mˆme pour toutes les colonnes. e Mettez 0 si ce composant s’´tend sur plusieurs colonnes ou si la largeur peut se d´duire de la contrainte e e d’un autre composant de cette colonne. weighty : nombre exprimant la hauteur de la ligne que le composant occupe ; peu importe l’unit´, pourvu e que ce soit la mˆme pour toutes les lignes. e Mettez 0 si ce composant s’´tend sur plusieurs lignes ou si la hauteur peut se d´duire de la contrainte e e d’un autre composant de cette ligne. fill : indication de la mani`re dont le composant doit remplir sa zone. Les valeurs possibles sont : e – GridBagConstraints.NONE : le composant doit garder sa taille propre, – GridBagConstraints.HORIZONTAL : le composant doit remplir toute la largeur de sa zone, – GridBagConstraints.VERTICAL : le composant doit remplir toute la hauteur de sa zone, – GridBagConstraints.BOTH : le composant doit remplir toute sa zone. anchor : position du composant dans sa zone, s’il ne la remplit pas enti`rement. Les valeurs possibles sont : e GridBagConstraints.NORTH (au nord), GridBagConstraints.NORTHEAST (au nord-est), GridBagConstraints.EAST (` l’est), GridBagConstraints.SOUTHEAST (au sud-est), etc. a 11.7.5 Exemple : un panneau muni d’un GridBagLayout

Fig. 19 – Un panneau avec un gestionnaire GridBagLayout A titre d’exemple90 , proposons-nous de construire un panneau contenant les huit composants montr´s e sur la figure 19.
90 Cet exemple est d´monstratif mais inutilisable. D’une part, ce n’est pas r´aliste de mettre ce type de composants dans le e e cadre principal d’une application ; cet exemple correspond plus ` une boˆ de dialogue (JDialog) qu’` un cadre (JFrame). a ıte a D’autre part, cet exemple est construit avec des composants anonymes, ce qui rend impossible la r´cup´ration des informations e e que l’utilisateur donne en saisissant des textes dans les champs de texte ou en pressant les boutons.

c H. Garreta, 2000-2006

119

11.7 Gestionnaires de disposition

11

INTERFACES GRAPHIQUES

Fig. 20 – Le quadrillage sous-jacent au gestionnaire GridBagLayout de la figure 19

La figure 20 montre le quadrillage (qui n’existe que dans la tˆte du programmeur) par rapport auquel e les composants sont plac´s. Pour chacun on doit construire une contrainte ; cela est rapidement p´nible, ` e e a moins de se donner une m´thode auxiliaire comme notre m´thode ajout : e e

import javax.swing.*; import java.awt.*; public class TestGridBagLayout extends JFrame { TestGridBagLayout() { super("Test GridBagLayout"); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JPanel panneau = new JPanel(); panneau.setBackground(new Color(220, 220, 255)); panneau.setPreferredSize(new Dimension(400, 300)); panneau.setLayout(new GridBagLayout()); ajout(panneau, new JLabel("Nom "), 0, 0, 1, 1, 10, 10, GridBagConstraints.NONE, GridBagConstraints.EAST); ajout(panneau, new JLabel("Prenom "), 0, 1, 1, 1, 0, 10, GridBagConstraints.NONE, GridBagConstraints.EAST); ajout(panneau, new JTextField(), 1, 0, 2, 1, 0, 0, GridBagConstraints.HORIZONTAL, GridBagConstraints.CENTER); ajout(panneau, new JTextField(), 1, 1, 2, 1, 0, 0, GridBagConstraints.HORIZONTAL, GridBagConstraints.CENTER); ajout(panneau, new JButton("Soleil"), 0, 2, 1, 1, 0, 10, GridBagConstraints.NONE, GridBagConstraints.CENTER); ajout(panneau, new JButton("Ombre"), 1, 2, 1, 1, 10, 0, GridBagConstraints.NONE, GridBagConstraints.CENTER); ajout(panneau, new JButton("Indiff´rent"), e 0, 3, 2, 1, 0, 10, GridBagConstraints.NONE, GridBagConstraints.CENTER); ajout(panneau, new JButton("OK"), 2, 2, 1, 2, 10, 0, GridBagConstraints.BOTH, GridBagConstraints.CENTER); getContentPane().add(panneau); pack(); setVisible(true); }

120

c H. Garreta, 2000-2006

11 INTERFACES GRAPHIQUES

11.7

Gestionnaires de disposition

private void ajout(Container conteneur, Component composant, int gridx, int gridy, int gridwidth, int gridheight, int weightx, int weighty, int fill, int anchor) { GridBagConstraints contrainte = new GridBagConstraints(); contrainte.gridx = gridx; contrainte.gridy = gridy; contrainte.gridwidth = gridwidth; contrainte.gridheight = gridheight; contrainte.weightx = weightx; contrainte.weighty = weighty; contrainte.fill = fill; contrainte.anchor = anchor; conteneur.add(composant, contrainte); } public static void main(String[] args) { new TestGridBagLayout(); } } Ci-dessus le programmeur a fix´ ` 10 la largeur de chaque colonne et la hauteur de chaque ligne, le e a panneau tout entier ayant une largeur de 30 (cela se d´duit des composants Nom, Soleil et OK ) et une e hauteur de 40 (cela d´coule de Nom, Pr´nom, Soleil et Indiff´rent). L’unit´ de mesure est sans importance, e e e e la seule information qu’on a voulu donner ainsi est que la largeur de chaque colonne est le tiers de la largeur totale et la hauteur de chaque ligne est le quart de la hauteur totale. 11.7.6 Exemple : imbrication de gestionnaires de disposition

Les GridBagLayout ne sont pas les seuls outils pour la construction de gestionnaires d’interfaces graphiques complexes. Une autre mani`re de monter de telles interfaces, plus souple et – avec de l’exp´rience – e e plus simple, consiste ` imbriquer plusieurs panneaux (objets JPanel) les uns dans les autres, chacun muni a d’un gestionnaire ad´quat. e A ce propos on consultera avec profit l’exemple montr´ ` la section 11.8.2, Construire une boˆ de e a ıte dialogue. 11.7.7 Bordures

Fig. 21 – Des bordures, sans et avec titre

Il est possible de cr´er une bordure autour d’un composant. La mani`re la plus simple est de la faire e e fabriquer par  l’usine de bordures  (BorderFactory) selon le sch´ma : e unComposant.setBorder(BorderFactory.createXXX Border(param`tres de la bordure)) e Ci-dessus, XXX repr´sente des mots qui identifient le type de bordure souhait´. La figure 21 montre e e diverses sortes de bordures, et le jargon avec lequel elles sont nomm´es dans la classe BorderFactory. e
c H. Garreta, 2000-2006

121

11.8 Composants pr´d´finis e e

11

INTERFACES GRAPHIQUES

11.8

Composants pr´d´finis e e

Nous ne ferons pas ici une pr´sentation syst´matique des diff´rentes, et nombreuses, classes de compoe e e sants disponibles dans AWT et Swing. Cela est tr`s bien fait dans le tutoriel Java (http://java.sun.com/ e tutorial/). En particulier, ` la section A Visual Index to the Swing Components (le¸on Using Swing Coma c ponents de la s´quence Creating a GUI with JFC/Swing) on trouve une pr´sentation extrˆmement parlante e e e de tous les composants disponibles. Au lieu de cela nous allons montrer le mode d’emploi de certains des composants les plus utilis´s, ` e a travers une application tr`s na¨ g´rant le carnet d’adresses d’un club sportif. e ıve e

Fig. 22 – Cadre avec menus

11.8.1

Exemple : mise en place et emploi de menus

La premi`re version de notre programme illustre la mise en place des menus, voyez la figure 22. Les e actions sur les menus sont d´tect´es, mais les r´actions en sont, pour le moment, des appels de m´thodes e e e e vides : import javax.swing.*; import java.awt.event.*; public class ClubSportif extends JFrame { ClubSportif() { super("Amicale Bouliste de Trifouillis-les-Oies"); setSize(400, 200); initialiserLesMenus(); setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { quitter(); } }); setVisible(true); } private void initialiserLesMenus() { JMenuBar barre = new JMenuBar(); setJMenuBar(barre); JMenu menu = new JMenu("Fichier"); barre.add(menu); JMenuItem item = new JMenuItem("Ouvrir..."); menu.add(item); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { ouvrirFichier(); } }); 122
c H. Garreta, 2000-2006

11 INTERFACES GRAPHIQUES

11.8

Composants pr´d´finis e e

item = new JMenuItem("Enregistrer..."); menu.add(item); item.addActionListener(new ActionListener() public void actionPerformed(ActionEvent enregistrerFichier(); } }); menu.addSeparator(); item = new JMenuItem("Quitter"); menu.add(item); item.addActionListener(new ActionListener() public void actionPerformed(ActionEvent quitter(); } }); menu = new JMenu("Membre"); barre.add(menu); item = new JMenuItem("Cr´er..."); e menu.add(item); item.addActionListener(new ActionListener() public void actionPerformed(ActionEvent creerMembre(); } }); item = new JMenuItem("Modifier..."); menu.add(item); item.addActionListener(new ActionListener() public void actionPerformed(ActionEvent modifierMembre(); } }); item = new JMenuItem("Supprimer..."); menu.add(item); item.addActionListener(new ActionListener() public void actionPerformed(ActionEvent supprimerMembre(); } }); } private void creerMembre() { } private void modifierMembre() { } private void supprimerMembre() { } private void ouvrirFichier() { } private void enregistrerFichier() { } private void quitter() { System.exit(0); }

{ e) {

{ e) {

{ e) {

{ e) {

{ e) {

c H. Garreta, 2000-2006

123

11.8 Composants pr´d´finis e e

11

INTERFACES GRAPHIQUES

public static void main(String[] args) { new ClubSportif(); } } Note. Pour r´agir aux actions faites sur les menus nous avons choisi de donner un auditeur distinct ` e a chaque item de menu. Une autre solution aurait consist´ ` donner le mˆme auditeur ` tous les item. Cela ea e a nous aurait oblig´ ` ´crire une m´thode actionPerformed ressemblant ` ceci91 : eae e a ... public void actionPerformed(ActionEvent e) { if (e.getActionCommand().equals("Ouvrir...")) ouvrirFichier(); else if (e.getActionCommand().equals("Enregistrer...")) enregistrerFichier(); else if (e.getActionCommand().equals("Quitter")) quitter(); else if (e.getActionCommand().equals("Cr´er...")) e creerMembre(); else if (e.getActionCommand().equals("Modifier...")) modifierMembre(); else if (e.getActionCommand().equals("Supprimer")) supprimerMembre(); else throw new RuntimeException("Action command inattendue: " + e.getActionCommand()); } ...

Fig. 23 – Boˆ de dialogue de saisie ıte

11.8.2

Exemple : construire une boite de dialogue

Concevoir et mettre en place les boˆ ıtes de dialogue (dans d’autres milieux on les appelle masques de saisie) est certainement la partie la plus ennuyeuse du d´veloppement d’une interface-utilisateur graphique. e En Java la chose est rendue encore plus p´nible par la pr´sence des gestionnaires de disposition qui, tout en e e ´tant tr`s utiles pour g´rer les changements de taille et de forme des conteneurs, manifestent souvent une e e e grande r´ticence ` placer les composants selon la volont´ du programmeur. e a e Pour illustrer cette question nous allons ajouter ` notre application une boˆ de dialogue pour saisir a ıte les informations qui caract´risent un membre du club. Pour commencer, nous ne nous occupons ici que e d’assembler les composants, nous verrons ` la section suivante comment utiliser la boˆ pour acqu´rir des a ıte e informations.
91 Cette mani`re de d´tecter les actions sur les menus est certainement moins efficace que la pr´c´dente. Mais l’efficacit´ e e e e e a-t-elle une quelconque importance, quand il s’agit de r´agir ` des gestes de l’utilisateur ? e a

124

c H. Garreta, 2000-2006

11 INTERFACES GRAPHIQUES

11.8

Composants pr´d´finis e e

La figure 23 montre l’aspect de la boˆ lors de son utilisation. La figure 24 montre les diff´rents panneaux ıte e (JPanel), munis de gestionnaires de dispositions distincts, que nous avons dˆ imbriquer les uns dans les autres u pour obtenir le placement des composants que montre la figure 23.

panneauHaut panneauBis panneauTer panneauBas

panneauGauche

panneauDroite

Fig. 24 – Imbrication de panneaux pour r´aliser la boite de dialogue de la figure 23 e

Voici le constructeur de notre classe :

import java.awt.*; import java.awt.event.*; import javax.swing.*; public class BoiteDialogueMembre extends JDialog implements Sports { JTextField champNom, champPrenom; JRadioButton sexeMasculin, sexeFeminin; JTextArea zoneAdresse; JCheckBox[] casesSports; JButton boutonOK, boutonAnnuler; boolean donneesAcquises; BoiteDialogueMembre(JFrame cadre) { super(cadre, "Saisie d’un membre", true); zoneAdresse = new JTextArea(5, 20); JPanel panneauTer = new JPanel(); panneauTer.add(new JLabel("Sexe")); panneauTer.add(sexeMasculin = new JRadioButton("Homme")); panneauTer.add(sexeFeminin = new JRadioButton("Femme")); ButtonGroup groupe = new ButtonGroup(); groupe.add(sexeMasculin); groupe.add(sexeFeminin); JPanel panneauBis = new JPanel(); panneauBis.setLayout(new GridLayout(6, 1)); panneauBis.add(new JLabel("Nom")); panneauBis.add(champNom = new JTextField(20)); panneauBis.add(new JLabel("Pr´nom")); e panneauBis.add(champPrenom = new JTextField(20)); panneauBis.add(panneauTer); panneauBis.add(new JLabel("Adresse")); JPanel panneauGauche = new JPanel(); panneauGauche.setLayout(new BorderLayout()); panneauGauche.add(panneauBis, BorderLayout.CENTER); panneauGauche.add(new JScrollPane(zoneAdresse), BorderLayout.SOUTH);

c H. Garreta, 2000-2006

125

11.8 Composants pr´d´finis e e

11

INTERFACES GRAPHIQUES

JPanel panneauDroite = new JPanel(); panneauDroite.setLayout(new GridLayout(nomSport.length, 1)); casesSports = new JCheckBox[nomSport.length]; for (int i = 0; i < nomSport.length; i++) panneauDroite.add(casesSports[i] = new JCheckBox(nomSport[i])); JPanel panneauHaut = new JPanel(); panneauHaut.setLayout(new BorderLayout()); panneauHaut.add(panneauGauche, BorderLayout.CENTER); panneauHaut.add(panneauDroite, BorderLayout.EAST); panneauHaut.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 10)); JPanel panneauBas = new JPanel(); panneauBas.add(boutonOK = new JButton(" OK ")); panneauBas.add(boutonAnnuler = new JButton("Annuler")); boutonOK.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { finDuDialogue(true); } }); boutonAnnuler.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { finDuDialogue(false); } }); getContentPane().setLayout(new BorderLayout()); getContentPane().add(panneauHaut, BorderLayout.CENTER); getContentPane().add(panneauBas, BorderLayout.SOUTH); } ... d’autres membres de cette classe sont montr´s dans les sections suivantes e ... } Le constructeur de BoiteDialogueMembre ne se termine pas par le couple  pack() ; setVisible(true) ;  habituel. Ces op´rations sont ` la charge de celui qui cr´e une boˆ de dialogue, on verra pourquoi. e a e ıte Le troisi`me argument, true, de l’appel du constructeur de JDialog e super(cadre, "Saisie d’un membre", true) ; fait que la boˆ de dialogue est modale : aussi longtemps qu’elle est visible ` l’´cran, l’utilisateur ne peut ıte a e pas agir sur une autre partie de l’interface graphique de l’application. L’interface Sports, mentionn´e pr´c´demment, sert juste ` mettre en place un ensemble de constantes92 e e e a communes ` plusieurs classes : a public interface Sports { long TENNIS = 1 << long SQUASH = 1 << long NATATION = 1 << long ATHLETISME = 1 << long RANDONNEE = 1 << long FOOT = 1 << long BASKET = 1 << long VOLLEY = 1 << long PETANQUE = 1 << 0; 1; 2; 3; 4; 5; 6; 7; 8; // // // // // // // // // ou ou ou ou ou ou ou ou ou 1 2 4 8 16 32 64 128 256

String[] nomSport = { "Tennis", "Squash", "Natation", "Athl´tisme", e "Randonn´e", "Foot", "Basket", "Volley", "P´tanque" }; e e long[] valSport = { TENNIS, SQUASH, NATATION, ATHLETISME, RANDONNEE, FOOT, BASKET, VOLLEY, PETANQUE }; }
92 Ne

pas oublier que toutes les variables d’une interface sont, par d´finition, statiques et finales (i.e. des constantes de classe). e

126

c H. Garreta, 2000-2006

11 INTERFACES GRAPHIQUES

11.8

Composants pr´d´finis e e

11.8.3

Exemple : saisie de donn´es e

Dans la boˆ de dialogue que nous d´veloppons ici les seuls composants dont les ´v´nements sont d´tect´s ıte e e e e e et trait´s sont les deux boutons OK et Annuler 93 . La pression du bouton OK d´clenche la validation des e e informations port´es par les champs de la boˆ de dialogue et, en cas de succ`s, la fermeture de cette boˆ ; e ıte e ıte le bouton Annuler la ferme dans tous les cas. Voici le reste de notre classe BoiteDialogueMembre : public class BoiteDialogueMembre extends JDialog implements Sports { ... private void finDuDialogue(boolean sortieParBoutonOK) { donneesAcquises = false; if (sortieParBoutonOK) { donneesAcquises = validerDonnees(); if (donneesAcquises) dispose(); } else dispose(); } private boolean validerDonnees() { if (champNom.getText().length() == 0) JOptionPane.showMessageDialog(this, "Le champ nom est vide", "Donn´e manquante", JOptionPane.ERROR_MESSAGE); e else if (champPrenom.getText().length() == 0) JOptionPane.showMessageDialog(this, "Le champ pr´nom est vide", e "Donn´e manquante", JOptionPane.ERROR_MESSAGE); e else if ( ! sexeMasculin.isSelected() && ! sexeFeminin.isSelected()) JOptionPane.showMessageDialog(this, "Le sexe n’est pas indiqu´", e "Donn´e manquante", JOptionPane.ERROR_MESSAGE); e else if (zoneAdresse.getText().length() == 0) JOptionPane.showMessageDialog(this, "L’adresse n’est pas indiqu´e", e "Donn´e manquante", JOptionPane.ERROR_MESSAGE); e else return true; return false; } } La validation des donn´es faite ici est sommaire : nous nous contentons de v´rifier que tous les champs e e ont ´t´ renseign´s. Dans les cas r´els il y a souvent ` faire des validations plus fines. ee e e a Il est important de bien comprendre le fonctionnement de la m´thode finDuDialogue. Lorsque l’utilisae teur presse le bouton Annuler, ou bien lorsqu’il presse le bouton OK et que les donn´es sont trouv´es valides, e e alors l’appel de dispose() d´truit l’objet graphique correspondant ` la boˆ de dialogue (i.e. tout ce qui se e a ıte voit ` l’´cran), mais l’objet Java (i.e. l’instance de la classe BoiteDialogueMembre) n’est pas encore d´truit a e e car, comme nous allons le voir, il est la valeur d’une certaine variable (la variable locale dial de la m´thode e creerMembre de la classe ClubSportif). Notez que si l’utilisateur appuie sur le bouton OK et que les donn´es ne sont pas valides alors il ne se e passe rien : la m´thode finDuDialogue retourne sans voir appel´ dispose, donc la boˆ de dialogue est e e ıte toujours visible. Il faut voir enfin que, la boˆ ´tant modale, c’est l’appel de dispose fait dans finDuDialogue qui met fin ıte e au  blocage  de l’application qui ´tait en place depuis l’appel de setVisible(true) fait par le constructeur e de BoiteDialogueMembre. Voyons maintenant comment les instances de BoiteDialogueMembre rendent service ` la classe ClubSportif, a dont voici la partie nouvelle :
93 Nous l’avons d´j` dit, nous d´veloppons ici un exemple na¨ Dans une application plus s´rieuse nous pourrions d´tecter les ea e ıf. e e frappes au clavier afin de v´rifier les ´ventuelles contraintes sur les textes tap´s. e e e

c H. Garreta, 2000-2006

127

11.8 Composants pr´d´finis e e

11

INTERFACES GRAPHIQUES

import javax.swing.*; import java.awt.event.*; import java.util.*; public class ClubSportif extends JFrame implements Sports { Vector membres = new Vector(); // ce vecteur contient les membres du club // ses ´l´ments sont des objets Membre e e ... private void creerMembre() { BoiteDialogueMembre dial = new BoiteDialogueMembre(this); α dial.pack() dial.setVisible(true) β if (dial.donneesAcquises) { Membre unMembre = new Membre(); unMembre.nom = dial.champNom.getText(); unMembre.prenom = dial.champPrenom.getText(); unMembre.sexeMasculin = dial.sexeMasculin.isSelected(); unMembre.adresse = dial.zoneAdresse.getText(); unMembre.sportsPratiqu´s = 0; e for (int i = 0; i < valSport.length; i++) if (dial.casesSports[i].isSelected()) unMembre.sportsPratiqu´s |= valSport[i]; e membres.add(unMembre); } } ... } La m´thode creerMembre m´rite une explication. Elle commence par la d´claration d’une variable locale e e e dial et la construction d’un objet BoiteDialogueMembre ; ensuite, elle appelle les m´thodes pack() et e setVisible(true). Or, on va attendre tr`s longtemps le retour de ce dernier appel, car la boˆ de dialogue e ıte ´tant modale, cette expression qui a pour effet de la rendre visible a ´galement pour effet de bloquer le thread e e dans lequel la m´thode creerMembre ´tait en train de s’ex´cuter. Ainsi, le contrˆle n’atteindra le point β e e e o que lorsque la m´thode dispose de la boˆ de dialogue aura ´t´ appel´e – nous avons vu plus haut dans e ıte ee e quelles conditions cela se produit. Lorsque l’utilisateur aura mis fin au dialogue, l’objet qui est la valeur de dial ne sera plus visible mais il existera toujours (ce ne sera plus le cas lorsque, ` la fin de la m´thode creerMembre, la variable dial sera a e d´truite) et il n’y a aucune difficult´ ` r´cup´rer les valeurs de ses membres. e ea e e Notons au passage l’endroit, signal´ par le rep`re α, o` il faut donner aux champs de texte et aux cases e e u a ` cocher des valeurs initiales lorsque la saisie ne doit pas se faire ` partir de z´ro, par exemple dans le cas a e de la modification d’un membre (voyez la section 11.8.6). Est apparue ´galement dans notre application une petite classe auxiliaire nomm´e Membre94 . Le rˆle de e e o cette classe est ´vident : m´moriser les informations qui caract´risent un membre de notre club. Notez que, e e e comme la m´thode creerMembre le montre, les sports pratiqu´s sont stock´s chacun sur un bit de l’entier e e e long sportsPratiqu´s (cela limite ` 64 le nombre de sports possibles, on peut penser que c’est suffisant...) : e a public class String String boolean String long } Membre implements Serializable { nom; prenom; sexeMasculin; adresse; sportsPratiqu´s; e

94 Une fois de plus, ne pas oublier que nous d´veloppons un exemple na¨ dans lequel seule l’interface utilisateur nous int´resse. e ıf, e Dans un cas r´el, la classe Membre serait bien plus complexe et comporterait probablement des m´thodes pour garantir la e e coh´rence de l’information. e

128

c H. Garreta, 2000-2006

11 INTERFACES GRAPHIQUES

11.8

Composants pr´d´finis e e

11.8.4

Exemple : choisir un fichier

Fig. 25 – Boite de dialogue d’ouverture de fichier

Pour montrer d’autres boˆ ıtes de dialogue, en particulier les boˆ ıtes prˆtes ` l’emploi offertes par Swing e a pour choisir un fichier (voir la figure 25), nous allons ajouter ` notre application la possibilit´ de sauver et a e restaurer les membres du club dans un fichier. C’est pour avoir le droit d’en ´crire les instances dans un fichier (on dit plutˆt s´rialiser ) que nous avons e o e d´clar´ que la classe Membre impl´mente l’interface java.io.Serializable. Il s’agit d’une interface vide, qui e e e n’entraˆ donc aucune obligation sur la classe qui l’impl´mente ; l’´nonc´  implements Serializable  ıne e e e sert uniquement ` indiquer que le programmeur donne le feu vert ` la s´rialisation des instances de la classe. a a e Pour obtenir les fonctionnalit´s indiqu´es il nous suffit d’´crire les deux m´thodes enregistrerFichier e e e e et ouvrirFichier : public class ClubSportif extends JFrame implements Sports { ... private void enregistrerFichier() { JFileChooser dial = new JFileChooser(); dial.setDialogTitle("Enregistrer les membres"); d’autres personnalisations de la boˆ de dialogue pourraient apparaˆ ici ıte ıtre if (dial.showSaveDialog(this) != JFileChooser.APPROVE_OPTION) return; File nomFichier = dial.getSelectedFile(); try { FileOutputStream fos = new FileOutputStream(nomFichier); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(membres); fos.close(); } catch (IOException ex) { JOptionPane.showMessageDialog(this, ex.toString(), "Probl`me fichier", JOptionPane.ERROR_MESSAGE); e } } private void ouvrirFichier() { JFileChooser dial = new JFileChooser(); dial.setDialogTitle("Restaurer les membres"); d’autres personnalisations de la boˆ de dialogue pourraient apparaˆ ici ıte ıtre if (dial.showOpenDialog(this) != JFileChooser.APPROVE_OPTION) return; File nomFichier = dial.getSelectedFile();

c H. Garreta, 2000-2006

129

11.8 Composants pr´d´finis e e

11

INTERFACES GRAPHIQUES

try { FileInputStream fis = new FileInputStream(nomFichier); ObjectInputStream ois = new ObjectInputStream(fis); membres = (Vector) ois.readObject(); fis.close(); } catch (Exception ex) { JOptionPane.showMessageDialog(this, ex.toString(), "Probl`me fichier", JOptionPane.ERROR_MESSAGE); e } } ... }

11.8.5

Exemple : une table pour afficher des donn´es e

Fig. 26 – Le tableau des membres du club

Pour en finir avec notre exemple nous allons lui ajouter une table affichant la liste des membres du club avec certaines informations associ´es, comme montr´ sur la figure 26. e e Une table (classe JTable) est un composant relativement complexe ob´issant au mod`le de conception e e MVC dont le principe est de diviser le travail entre deux ojets : le mod`le et la vue. Le premier d´tient les e e donn´es, la seconde en fait une pr´sentation graphique. Le modele MVC est comment´ avec plus de soin ` e e e a section 11.9. Ici c’est surtout sur le mod`le que nous allons travailler ; la vue sera un objet JTable dont le comportement e par d´faut nous suffira enti`rement. e e Les devoirs d’un mod`le de table sont d´finis par l’interface TableModel, dont une impl´mentation pare e e tielle tr`s utile est AbstractTableModel. D’o` notre classe ModeleTableClub : e u import javax.swing.table.*; public class ModeleTableClub extends AbstractTableModel implements Sports { Vector membres; ModeleTableClub(Vector membres) { this.membres = membres; } public int getRowCount() { return membres.size(); } public int getColumnCount() { return return 3 + valSport.length; }

130

c H. Garreta, 2000-2006

11 INTERFACES GRAPHIQUES

11.8

Composants pr´d´finis e e

public String getColumnName(int j) { if (j == 0) return "Nom"; else if (j == 1) return "Prenom"; else if (j == 2) return "Sexe"; else return nomSport[j - 3].substring(0, 3); } public Class getColumnClass(int j) { if (j < 2) return String.class; else if (j == 2) return Character.class; else return Boolean.class; } public Object getValueAt(int i, int j) { Membre unMembre = (Membre) membres.elementAt(i); if (j == 0) return unMembre.nom; else if (j == 1) return unMembre.prenom; else if (j == 2) return new Character(unMembre.sexeMasculin ? ’M’ : ’F’); else return new Boolean((unMembre.sportsPratiqu´s & valSport[j - 3]) != 0); e } }

Dans la classe ModeleTableClub nous [re-]d´finissons celles des m´thodes impos´es par l’interface TableModel e e e que la classe abstraite AbstractTableModel ne d´finit pas ou ne d´finit pas comme il nous faut. e e Les m´thodes pr´c´dentes sont assez faciles ` comprendre : e e e a

ModeleTableClub : le constructeur sert ` m´moriser une r´f´rence sur le vecteur de membres que le tableau a e ee est cens´ repr´senter ; e e getRowCount : le nombre de lignes du tableau n’est autre que le nombre de membres du club ; e e e getColumnCount : le nombre de colonnes du tableau d´pend que des champs que nous avons d´cid´ de repr´senter (ici, le nom, le pr´nom, le sexe et les sports pratiqu´s) ; e e e a e getColumnName : donne le nom de chaque colonne, c’est-`-dire ce qu’il faut afficher dans la premi`re ligne ; e e getColumnClass : donne le type des informations de chaque colonne (suppos´e homog`ne), cela permet d’en donner une pr´sentation adapt´e comme, pour un bool´en, une case ` cocher ; e e e a getValueAt : cette m´thode est la plus importante : getValueAt(i, j) renvoie l’´l´ment qui se trouve ` e ee a la ligne i et la colonne j du tableau.

En plus de l’ajout de la classe pr´c´dente, nous devons modifier notre programme ` plusieurs endroits : e e a d’une part les variables d’instance et le constructeur de la classe principale :
c H. Garreta, 2000-2006

131

11.8 Composants pr´d´finis e e

11

INTERFACES GRAPHIQUES

public class ClubSportif extends JFrame implements Sports { Vector membres = new Vector(); AbstractTableModel modeleTable; JTable table; ClubSportif() { pr´c´dent contenu du constructeur e e modeleTable = new ModeleTableClub(membres); table = new JTable(modeleTable); getContentPane().add(new JScrollPane(table)); setVisible(true); } D’autre part, il faut faire en sorte que les changements dans la table des membres se r´percutent dans e le mod`le. Deux mani`res de faire cela : pour de petits changement, comme l’ajout d’un unique membre, il e e suffit de le notifier au mod`le. Par exemple, la m´thode creerMembre se terminera maintenant ainsi : e e ... membres.add(unMembre); modeleTable.fireTableRowsInserted(membres.size() - 1, membres.size() - 1); Pour de plus grands changements, comme lors du chargement d’un fichier, on peut carr´ment remplacer e le mod`le par un mod`le neuf. Ainsi, dans la m´thode ouvrirFichier on trouvera maintenant e e e ... membres = (Vector) ois.readObject(); modeleTable = new ModeleTableClub(membres); table.setModel(modeleTable); 11.8.6 Exemple : s´lection et modification dans une table e // // // // ce vecteur contient les membres du club ses ´l´ments sont des objets Membre e e le "mod`le" de la table e la "vue" de la table

Pour en finir avec notre exemple, voici la m´thode modifierMembre qui prend en charge les changements e des informations concernant un membre existant. Ce que cette m´thode illustre surtout, par comparaison avec e creerMembre, est la mise en place d’une boˆ de dialogue dont les composants contiennent des informations ıte initiales. private void modifierMembre() { int rang = table.getSelectedRow(); if (rang < 0) { JOptionPane.showMessageDialog(this, "Il faut d’abord s´lectionner un membre", e "Erreur", JOptionPane.ERROR_MESSAGE); return; } Membre unMembre = (Membre) membres.elementAt(rang); BoiteDialogueMembre dial = new BoiteDialogueMembre(this); dial.champNom.setText(unMembre.nom); dial.champPrenom.setText(unMembre.prenom); dial.sexeMasculin.setSelected(unMembre.sexeMasculin); dial.sexeFeminin.setSelected(!unMembre.sexeMasculin); dial.zoneAdresse.setText(unMembre.adresse); for (int i = 0; i < valSport.length; i++) { boolean b = (unMembre.sportsPratiqu´s & valSport[i]) != 0; e dial.casesSports[i].setSelected(b); } dial.pack(); dial.setVisible(true);

132

c H. Garreta, 2000-2006

11 INTERFACES GRAPHIQUES

11.9

Le mod`le MVC (Mod`le-Vue-Controleur) e e

if (dial.donneesAcquises) { unMembre.nom = dial.champNom.getText(); unMembre.prenom = dial.champPrenom.getText(); unMembre.sexeMasculin = dial.sexeMasculin.isSelected(); unMembre.adresse = dial.zoneAdresse.getText(); unMembre.sportsPratiqu´s = 0; e for (int i = 0; i < valSport.length; i++) if (dial.casesSports[i].isSelected()) unMembre.sportsPratiqu´s |= valSport[i]; e modeleTable.fireTableRowsUpdated(rang, rang); } }

11.9

Le mod`le MVC (Mod`le-Vue-Controleur) e e

Le mod`le MVC est un mod`le de conception, ou design pattern, qui consiste ` maintenir une s´paration e e a e nette entre les donn´es manipul´es par l’application (le mod`le), les pr´sentations qui en sont faites (les vues) e e e e et les dispositifs par lesquels les actions de l’utilisateur sont d´tect´es (les contrˆleurs). e e o Puisque l’utilisateur a une tendance naturelle ` agir sur les composants qu’il voit, les vues sont le plus a souvent associ´es ` des contrˆleurs. L’aspect le plus int´ressant de cette m´thodologie est la s´paration de la e a o e e e vue et du mod`le, qui garantit que le codage interne et le traitement des donn´es ne sont pas pollu´s par des e e e questions de pr´sentation ; cela permet, par exemple, que des pr´sentations diff´rentes d’un mˆme mod`le e e e e e (textuelle, graphique, sonore, etc.) soient donn´es, cons´cutivement ou simultan´ment. e e e Bien entendu, s´paration n’est pas ind´pendance : chaque changement du mod`le doit ˆtre notifi´ ` la e e e e ea vue afin que celle-ci puisse ˆtre mise ` jour et refl`te constamment l’´tat actuel du mod`le. e a e e e Les composants de Swing s’accordent au mod`le MVC : chaque composant est consid´r´ comme une vue e ee (ou plutˆt comme un couple vue-contrˆleur), et il doit ˆtre associ´ ` un mod`le qui joue le rˆle de source o o e ea e o des donn´es pour le composant. e Les m´thodes qu’un objet doit poss´der pour ˆtre le mod`le d’un composant sont d´finies par une interface e e e e e associ´e ` ce composant : ButtonModel, ListModel, TableModel, TreeModel, etc. e a Pour les composants les plus ´l´mentaires, ces mod`les sont tr`s simples. Le programmeur peut presque ee e e toujours ignorer cette question, car il a la possibilit´ d’utiliser, souvent sans le savoir, des mod`les par d´faut. e e e Par exemple, c’est un tel mod`le qui est mis en place lorsqu’on cr´e un bouton par une instruction simple, e e comme  new JButton("Oui") . La section suivante d´veloppe un exemple avec un composant, nomm´ JTree, dans lequel l’existence du e e mod`le et sa s´paration d’avec la vue ne peut ˆtre ignor´e. e e e e 11.9.1 Exemple : les vues arborescentes (JTree)

Donnons-nous le probl`me suivant (cf. figure 27) : afficher l’arborescence de tous les mots d’au plus e quatre lettres form´s avec les trois lettres a, b et c 95 . Nous allons le traiter de quatre mani`res diff´rentes, e e e correspondant aux divers choix que nous pouvons faire pour le mod`le (la structure d’arbre sous-jacente ` e a la vue JTree) et pour les nœuds dont cet arbre se compose : e ea e – Assez souvent, on n’a aucun pr´jug´ ` propos des nœuds impl´mentant l’arbre, lesquels n’ont pas besoin d’ˆtre tr`s sophistiqu´s. On peut alors utiliser des objets de la classe pr´d´finie DefaultMutableTreeNode e e e e e pour les nœuds et un objet DefaultTreeModel pour le mod`le. Ci-dessous, c’est la version 3, celle o` e u on travaille le moins. ea e e – Parfois on a d´j` une d´finition de la classe des nœuds, impos´e par d’autres parties de l’application, mais on peut l’augmenter et notamment lui ajouter ce qui lui manque pour ˆtre une impl´mentation de e e l’interface TreeNode. Il nous suffit alors de prendre pour mod`le un objet DefaultTreeModel, associ´ e e a ` de tels nœuds enrichis. C’est notre version 1 ci-apr`s. e e e – Il peut arriver qu’on nous impose par ailleurs une d´finition pr´cise de la classe des nœuds, qu’on ne peut pas enrichir. On doit alors d´finir notre propre classe du mod`le, impl´mentant l’interface e e e TreeModel,  sachant manipuler  nos nœuds sp´cifiques. Nous avons fait cela ` la version 2. e a
95 Oui,

bon, d’accord, ce n’est pas tr`s utile. Mais c’est un exemple, non ? e

c H. Garreta, 2000-2006

133

11.9 Le mod`le MVC (Mod`le-Vue-Controleur) e e

11

INTERFACES GRAPHIQUES

Fig. 27 – Repr´sentation arborescente (JTree) d’un ensemble de mots e

– Enfin, lorsqu’on d´cide comme dans la situation pr´c´dente de d´finir notre propre classe du mod`le, e e e e e il peut arriver que cette classe puisse ˆtre purement calculatoire, c’est-`-dire qu’elle ne repose pas sur e a une structure de donn´es sous-jacente. C’est le cas de notre version 4. e ´ Construction d’une structure de donnees sous-jacente. Pour commencer, supposons que les nœuds de l’arborescence nous soient impos´s par ailleurs : ce sont les instances d’une certaine classe Arbuste. e Chacun comporte une chaˆ de caract`res, un vecteur m´morisant l’ensemble des fils et une r´f´rence sur ıne e e ee le pere : public class Arbuste { private String info; private Vector fils; private Arbuste pere; public Arbuste(String info) { this.info = info; fils = new Vector(); pere = null; } public String getInfo() { return info; } public Arbuste getPere() { return pere; } public int nombreFils() { return fils.size(); } public Arbuste getFils(int i) { return (Arbuste) fils.elementAt(i); }

134

c H. Garreta, 2000-2006

11 INTERFACES GRAPHIQUES

11.9

Le mod`le MVC (Mod`le-Vue-Controleur) e e

public void ajouterFils(Arbuste unFils) { fils.add(unFils); unFils.pere = this; } public String toString() { return info; } } Voici une classe pour essayer la pr´c´dente. L’appel creerMots("", m, k ) construit l’arborescence des e e mots de k lettres form´es avec les m lettres a, b, c... e public class Essai { public static Arbuste creerMots(String debut, int large, int profond) { Arbuste res = new Arbuste(debut); if (profond > 0) for (int i = 0; i < large; i++) res.ajouterFils(creerMots(debut + (char)(’a’ + i), large, profond - 1)); return res; } public static void montrer(Arbuste arbre) { System.out.print(arbre + " "); int n = arbre.nombreFils(); for (int i = 0; i < n; i++) montrer(arbre.getFils(i)); } public static void main(String[] args) { Arbuste racine = creerMots("", 3, 4); montrer(racine); } } Voici l’affichage produit par ce programme : a aa aaa aaaa aaab aba abaa abab abac acaa acab acac acb ccba ccbb ccbc ccc aaac aab aaba aabb aabc aac aaca aacb aacc ab abb abba abbb abbc abc abca abcb abcc ac aca ... cbca cbcb cbcc cc cca ccaa ccab ccac ccb ccca cccb cccc

Vue arborescente, version 1. Pour afficher notre arborescence nous allons cr´er un composant e JTree. Un tel composant constitue une vue ; pour le cr´er il faut lui associer un mod`le, qui doit ˆtre une e e e impl´mentation de l’interface TreeModel. Or il existe une impl´mentation de TreeModel toute prˆte : la classe e e e DefaultTreeModel, qui n’a besoin pour fonctionner que d’une arborescence faite de nœuds impl´mentant e l’interface TreeNode. Notre premi`re approche consiste donc ` faire en sorte que nos objets Arbuste impl´mentent l’interface e a e TreeNode. Pour cela il nous faut ajouter ` notre classe Arbuste les m´thodes de l’interface TreeNode (notez a e que certaines sont de simples renommages des m´thodes existant d´j` dans notre classe) : e ea import java.util.*; import javax.swing.tree.*; public class Arbuste implements TreeNode { ici apparaissent les membres de la version pr´c´dente de Arbuste, e e auxquels il faut ajouter les m´thodes  obligatoires  suivantes : e public Enumeration children() { return fils.elements(); } public boolean getAllowsChildren() { return ! isLeaf(); }
c H. Garreta, 2000-2006

135

11.9 Le mod`le MVC (Mod`le-Vue-Controleur) e e

11

INTERFACES GRAPHIQUES

public TreeNode getChildAt(int childIndex) { return getFils(childIndex); } public int getChildCount() { return nombreFils(); } public int getIndex(TreeNode node) { return fils.indexOf(node); } public TreeNode getParent() { return pere; } public boolean isLeaf() { return fils.isEmpty(); } }

Voici la nouvelle classe de test, elle affiche le cadre repr´sent´ ` la figure 27 : e ea

import javax.swing.*; import javax.swing.tree.*; public class Essai { public static Arbuste creerMots(String debut, int large, int profond) { Arbuste res = new Arbuste(debut); if (profond > 0) for (int i = 0; i < large; i++) res.ajouterFils(creerMots(debut + (char)(’a’ + i), large, profond - 1)); return res; } public static void main(String[] args) { JFrame cadre = new JFrame("Des mots..."); cadre.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); cadre.setSize(300, 500); Arbuste racine = creerMots("", 3, 4); JTree vue = new JTree(new DefaultTreeModel(racine)); cadre.getContentPane().add(new JScrollPane(vue)); cadre.setVisible(true); } }

Note. Si nous avions envisag´ que l’arbre puisse ˆtre modifi´ ` la suite de son affichage, par exemple en e e ea r´action ` des actions de l’utilisateur, alors il aurait fallu que notre classe Arbuste impl´mente l’interface e a e MutableTreeNode, une sous-interface de TreeNode qui sp´cifie des m´thodes suppl´mentaires pour ajouter e e e et enlever des nœuds aux arbres. Vue arborescente, version 2. Imaginons maintenant qu’il nous soit difficile ou impossible de modifier la classe Arbuste pour en faire une impl´mentation de TreeNode. Cela nous obligera ` adopter une deuxi`me e a e mani`re de construire la vue arborescente : ne pas utiliser un mod`le par d´faut (classe DefaultTreeModel) e e e mais un mod`le que nous aurons d´fini expr`s pour travailler avec des objets Arbuste. C’est notre classe e e e ModeleArbuste, enti`rement faite de m´thodes impos´es par l’interface TreeModel : e e e 136
c H. Garreta, 2000-2006

11 INTERFACES GRAPHIQUES

11.9

Le mod`le MVC (Mod`le-Vue-Controleur) e e

import javax.swing.tree.*; import javax.swing.event.*; import java.util.*; public class ModeleArbuste implements TreeModel { private Arbuste racine; private Vector auditeurs; ModeleArbuste(Arbuste racine) { this.racine = racine; auditeurs = new Vector(); } public Object getRoot() { return racine; } public int getChildCount(Object parent) { return ((Arbuste) parent).nombreFils(); } public Object getChild(Object parent, int index) { return ((Arbuste) parent).getFils(index); } public int getIndexOfChild(Object parent, Object child) { int n = ((Arbuste) parent).nombreFils(); for (int i = 0; i < n; i++) if (((Arbuste) parent).getFils(i) == child) return i; return -1; } public boolean isLeaf(Object node) { return ((Arbuste) node).nombreFils() == 0; } public void addTreeModelListener(TreeModelListener l) { auditeurs.add(l); } public void removeTreeModelListener(TreeModelListener l) { auditeurs.remove(l); } public void valueForPathChanged(TreePath path, Object newValue) { /* Inutile */ } } Une seule ligne de la nouvelle classe Essai diff`re d’avec la version pr´c´dente. Pour construire la vue, e e e au lieu de new JTree(new DefaultTreeModel(racine)) il faut faire : ... JTree vue = new JTree(new ModeleArbuste(racine)); ... Vue arborescente, version 3. A l’oppos´ de la pr´c´dente, une autre mani`re d’aborder ce probl`me e e e e e consiste ` penser que des nœuds aussi banaux que les nˆtres existent sˆrement tout prˆts dans la biblioth`que, a o u e e ce qui devrait nous dispenser d’´crire la classe Arbuste. e En effet, si tout ce qu’on demande ` un nœud est de porter une information (peut importe son type, a pourvu que ce soit un objet), d’avoir des fils et ´ventuellement d’avoir un p`re, alors la classe Defaulte e MutableTreeNode convient parfaitement. Bien entendu, cela suppose que nous ayons le droit de renoncer ` a la classe Arbuste. Voici ce que devient, dans cette optique, la totalit´ de notre programme (il est maintenant beaucoup plus e court) :
c H. Garreta, 2000-2006

137

11.9 Le mod`le MVC (Mod`le-Vue-Controleur) e e

11

INTERFACES GRAPHIQUES

import javax.swing.*; import javax.swing.tree.*; public class Essai { public static DefaultMutableTreeNode creerMots(String debut, int large, int profond) { DefaultMutableTreeNode res = new DefaultMutableTreeNode(); res.setUserObject(debut); if (profond > 0) for (int i = 0; i < large; i++) res.insert(creerMots(debut + (char)(’a’ + i), large, profond - 1), i); return res; } public static void main(String[] args) { JFrame cadre = new JFrame("Des mots..."); cadre.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); cadre.setSize(300, 500); DefaultMutableTreeNode racine = creerMots("", 3, 4); JTree vue = new JTree(new DefaultTreeModel(racine)); cadre.getContentPane().add(new JScrollPane(vue)); cadre.setVisible(true); } } Vue arborescente, version 4. Il y a encore une autre mani`re de traiter ce probl`me ; elle consiste ` e e a examiner le mod`le d´fini ` la version 2, cens´ manipuler un arbre fait de nœuds Arbuste, et se dire qu’un tel e e a e mod`le pourrait aussi bien  faire semblant  de manipuler un arbre qui, en tant que structure de donn´es, e e n’existe pas. Voici la classe qui r´alise un tel mod`le. Puisqu’il n’y a pas d’arbre, les nœuds se confondent avec les e e informations associ´es : e import javax.swing.tree.*; import javax.swing.event.*; import java.util.*; public class ModeleArbuste implements TreeModel { private int large; private int profond; private Vector auditeurs; ModeleArbuste(int large, int profond) { this.large = large; this.profond = profond; auditeurs = new Vector(); } public Object getRoot() { return ""; } public int getChildCount(Object parent) { if (((String) parent).length() < profond) return large; else return 0; } public Object getChild(Object parent, int index) { return ((String) parent) + (char)(’a’ + index); } 138
c H. Garreta, 2000-2006

11 INTERFACES GRAPHIQUES

11.10

Images

public int getIndexOfChild(Object parent, Object child) { String s = (String) child; return s.charAt(s.length() - 1) - ’a’; } public boolean isLeaf(Object node) { return ! (((String) node).length() < profond); } public void addTreeModelListener(TreeModelListener l) { auditeurs.add(l); } public void removeTreeModelListener(TreeModelListener l) { auditeurs.remove(l); } public void valueForPathChanged(TreePath path, Object newValue) { /* inutile */ } }

11.10

Images

Dans les premi`res versions de Java la manipulation des images ob´it ` un mod`le de production et cone e a e sommation. Ayant pour objet premier les images provenant du Web, ce mod`le suppose que l’exploitation e d’une image est un processus, ´ventuellement de longue dur´e, impliquant un producteur, comme un fichier e e sur un serveur ´loign´, un consommateur, par exemple l’interface graphique d’une application qui affiche e e l’image, et un observateur , c’est-`-dire un objet qui  supervise  l’op´ration et qui re¸oit les notifications a e c concernant l’´tat d’avancement du transfert de l’image. e A cˆt´ de ce mod`le, passablement complexe, l’´quipe de d´veloppement de Java fait maintenant la oe e e e promotion d’un mod`le dit en mode imm´diat, centr´ sur la notion d’image en m´moire : fondamentalement, e e e e on s’int´resse moins au transfert de l’image (pour lequel le mod`le producteur/consommateur reste pertinent) e e qu’` ce qu’on peut faire avec l’image lorsque, une fois transf´r´e, elle est stock´e dans la m´moire du syst`me. a ee e e e

11.10.1

Exemple : utiliser des icˆnes o

Les icˆnes sont des images de taille fixe, souvent petites, g´n´ralement utilis´es pour d´corer certains o e e e e composants comme les ´tiquettes et les boutons. e L’application tr`s simple montr´e96 ` la figure 28 est faite d’une grande ´tiquette portant un texte e e a e ( Hˆtesse de l’air ` la Bardaf ) et une icˆne (Natacha, un personnage de BD des ann´es 70). Le texte o a o e peut se placer ` gauche, dans l’alignement ou ` droite de l’icˆne, ainsi qu’en haut, ` la mˆme hauteur ou en a a o a e bas de l’icˆne (les composants ´tiquettes de Swing – objets JLabel – permettent de faire cela). L’application o e comporte en outre une barre d’outils d´tachable avec six boutons, dispos´s en deux groupes de trois boutons e e mutuellement exclusifs, qui commandent la position du texte par rapport ` l’icˆne. Les boutons sont blancs a o quand ils sont au repos (non s´lectionn´s) et rouges quand ils sont s´lectionn´s. e e e e Ce programme utilise treize fichiers contenant chacun une icˆne : six icˆnes de boutons blancs (fichiers o o bhgauche.gif, bhcentre.gif, etc.), six icˆnes de boutons rouges, dont les noms sont de l´g`res alt´rations o e e e des pr´c´dents (fichiers bhgaucheR.gif, bhcentreR.gif, etc.) et le dessin central (fichier natacha.jpg). e e Tous ces fichiers sont plac´s dans un r´pertoire ayant le nom relatif images. e e

96 L’imperfection des moyens de reprographie habituellement inflig´s ` ce genre de polycopi´s nous dissuade de montrer ici de e a e vraies photographies, qui auraient pourtant ´t´ tout ` fait adapt´es ` l’illustration de notre propos. e e a e a

c H. Garreta, 2000-2006

139

11.10 Images

11

INTERFACES GRAPHIQUES

Fig. 28 – Six boutons et une (grande) ´tiquette d´cor´s d’icˆnes e e e o

import java.awt.*; import java.awt.event.*; import javax.swing.*; public class Natacha extends JFrame implements ActionListener { JLabel etiquette; JToolBar barreOutils; static String chemin = "images/"; static String nomFic[] = { "bhgauche", "bhcentre", "bhdroite", "bvhaut", "bvcentre", "bvbas" }; static int position[] = { JLabel.LEFT, JLabel.CENTER, JLabel.RIGHT, JLabel.TOP, JLabel.CENTER, JLabel.BOTTOM }; Natacha() { super("Natacha"); creerBarreOutils(); creerEtiquette(); getContentPane().add(barreOutils, BorderLayout.NORTH); getContentPane().add(etiquette, BorderLayout.CENTER); pack(); setVisible(true); } void creerBarreOutils() { barreOutils = new JToolBar(); ButtonGroup[] groupe = { new ButtonGroup(), new ButtonGroup() }; for (int i = 0; i < 6; i++) { Icon iconeBoutonAuRepos = new ImageIcon(chemin + nomFic[i] + ".gif"); Icon iconeBoutonSelect = new ImageIcon(chemin + nomFic[i] + "R.gif"); JRadioButton radioBouton = new JRadioButton(iconeBoutonAuRepos); radioBouton.setSelectedIcon(iconeBoutonSelect); radioBouton.setActionCommand(nomFic[i]); radioBouton.addActionListener(this); 140
c H. Garreta, 2000-2006

11 INTERFACES GRAPHIQUES

11.10

Images

groupe[i / 3].add(radioBouton); barreOutils.add(radioBouton); if (i == 1 || i == 3) radioBouton.setSelected(true); } } void creerEtiquette() { ImageIcon icone = new ImageIcon(chemin + "natacha.jpg"); etiquette = new JLabel("H^tesse de l’air ` la Bardaf", icone, JLabel.CENTER); o a etiquette.setIconTextGap(20); etiquette.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); etiquette.setHorizontalTextPosition(JLabel.CENTER); etiquette.setVerticalTextPosition(JLabel.TOP); } public void actionPerformed(ActionEvent e) { for (int i = 0; i < 6; i++) if (e.getActionCommand().equals(nomFic[i])) break; if (i < 3) etiquette.setHorizontalTextPosition(position[i]); else etiquette.setVerticalTextPosition(position[i]); } public static void main(String[] args) { new Natacha(); } } Note. Dans l’exemple ci-dessus, les boutons de la barre d’outils n’ont pas un texte affich´. Il faut donc e explicitement leur associer des chaˆ ınes action command afin de pouvoir les distinguer dans la m´thode e actionPerformed. Comme n’importe quel ensemble de six chaˆ ınes distinctes fait l’affaire, nous avons utilis´ e les noms des fichiers images, qui ont le m´rite d’exister et d’ˆtre deux ` deux diff´rents. e e a e 11.10.2 Exemple : charger une image depuis un fichier

Apr`s les icˆnes, qui sont des images simplifi´es qu’on ne souhaite pas transformer, int´ressons-nous aux e o e e images (quelconques) en m´moire, ou buffered images. e De telles images ont deux principales raisons d’ˆtre97 : soit ce sont des images  calcul´es , c’est-`e e a dire construites pixel ` pixel par le programme (les fameuses images de synth`se), soit ce sont des images a e  trait´es , c’est-`-dire produites ` l’ext´rieur du programme (par exemple des photographies) mais devant e a a e subir des transformations g´om´triques, des modifications des couleurs, etc. Bien entendu, dans un cas e e comme dans l’autre, il est en g´n´ral n´cessaire que ces images, une fois construites en m´moire, puissent e e e e ˆtre affich´es sur un ´cran. e e e La figure 29 correspond ` une application tr`s simple qui montre une premi`re mani`re d’obtenir une a e e e image en m´moire : la charger depuis un fichier (qui est nomm´98 ici XGorce.081102.gif et est plac´ dans e e e un certain r´pertoire images) : e import java.awt.*; import javax.swing.*; public class AfficheurdImages extends JPanel { Image uneImage;
bonne raison de s’int´resser aux images en m´moire est le double buffering. Lorsqu’on dessine directement sur l’´cran e e e une image tr`s complexe, les ´tapes de la construction sont perceptibles et l’affichage devient d´sagr´able pour l’utilisateur, e e e e particuli`rement dans le cas d’animations. Le double buffering est la technique consistant ` dessiner non pas sur l’´cran, mais e a e sur une portion de m´moire qu’on met ` l’´cran en une seule op´ration, donc de mani`re instantan´e, lorsque le trac´ est e a e e e e e enti`rement fini. e En fait, le double buffering n’a pas beaucoup d’int´rˆt pratique pour nous car les composants de Swing le pratiquent sans e e qu’il soit n´cessaire de prendre des mesures particuli`res. e e 98 Xavier Gorce dessine un strip quotidien dans Le Monde.fr, dans lequel les animaux de la forˆt commentent l’actualit´ ou e e font des remarques philosophiques.
97 Une

c H. Garreta, 2000-2006

141

11.10 Images

11

INTERFACES GRAPHIQUES

Fig. 29 – Afficher une image

AfficheurdImages() { uneImage = Toolkit.getDefaultToolkit().getImage("images/XGorce.081102.gif"); setPreferredSize(new Dimension(500, 150)); } public void paint(Graphics g) { super.paint(g); g.drawImage(uneImage, 5, 5, this); } public static void main(String[] args) { JFrame cadre = new JFrame("Afficher une image"); cadre.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); cadre.getContentPane().add(new AfficheurdImages()); cadre.pack(); cadre.setVisible(true); } } Dans la classe ci-dessus les m´thodes int´ressantes sont le constructeur et la m´thode paint : le premier e e e obtient l’image ` partir d’un fichier, la seconde l’affiche. a Remarque pointue. Ce programme fait ce qu’on lui demande de faire et il semble sans myst`re car, ` e a moins d’avoir une vue exceptionnelle, l’utilisateur ne voit pas que d`s le d´marrage l’image est (re-)dessin´e e e e un grand nombre de fois. On peut s’en apercevoir en ajoutant dans la m´thode paint la ligne e System.out.println(compteur++); (compteur est une variable d’instance priv´e). Le programme affiche l’image et, en plus, il affiche des nombres e qui permettent de constater que paint a ´t´ appel´e plus de vingt fois avant que le programme ne se calme ! ee e Cela montre le caract`re asynchrone du mod`le producteur/consommateur, dont rel`ve la m´thode e e e e getImage de la classe Toolkit. L’appel qui en est fait dans le constructeur uneImage = Toolkit.getDefaultToolkit().getImage(nom de fichier ); n’attend pas la fin du chargement de l’image ; au lieu de cela, cet appel initie le processus et retourne imm´diatement, avant que les donn´es qui constituent l’image ne soient acquises99 . Par cons´quent, l’appel e e e de paint qui est fait lors de l’affichage initial de l’interface ne dessine pratiquement rien. Ce qui sauve ce programme est le quatri`me argument (this) de l’appel de drawImage : e g.drawImage(uneImage, 5, 5, this); il indique que l’observateur de cette image est le panneau AfficherUneImage lui-mˆme, qui devient ainsi e le destinataire des notifications concernant la progression du chargement. Chacune de ces notifications provoque un appel de repaint, donc l’affichage d’un nouvel ´tat de l’image, jusqu’` ce que cette derni`re soit e a e enti`rement charg´e ; cela se passe assez vite et l’utilisateur croit voir l’affichage en une fois d’une image e e unique100 .
99 C’est la raison pour laquelle, dans le constructeur, nous ne pouvons pas donner ` notre panneau la taille effective requise a par l’image (ce qui aurait ´t´ bien pratique !) mais uniquement une taille convenue comme 500 × 150. C’est que, imm´diatement e e e apr`s l’appel de createImage, l’image n’est qu’amorc´e et elle n’a pas de taille d´finie. e e e 100 Pour s’en convaincre il suffit de remplacer this par null dans l’appel de drawImage : les notifications sur l’avancement du chargement seront perdues, il y aura un seul appel de paint et ce programme n’affichera presque rien.

142

c H. Garreta, 2000-2006

11 INTERFACES GRAPHIQUES

11.10

Images

Charger une image rang´e avec les classes e Dans l’exemple pr´c´dent on charge une image ` partir d’un fichier sp´cifi´ par son chemin dans un e e a e e syst`me de fichiers donn´ (probablement celui qui entoure le d´veloppement) : e e e uneImage = Toolkit.getDefaultToolkit().getImage("images/XGorce.081102.gif"); Se pose alors la question : comment donner le chemin du fichier image dans le cas – le plus fr´quent – o` e u l’application doit s’ex´cuter dans un environnement inconnu, distinct de celui de son d´veloppement ? e e Si le fichier de l’image est un fichier variable appartenant au syst`me dans lequel l’application s’ex´cute e e il n’y a pas de probl`me : le chemin sera saisi durant l’ex´cution, soit sous forme de texte, soit ` l’aide d’un e e a FileChooser (cf. 11.8.4). Mais comment indiquer un fichier  constant , fourni par le d´veloppeur de l’application, rang´ avec les e e classes de celle-ci, par exemple dans l’archive jar utilis´e pour distribuer l’application ? La solution consiste e a ` obtenir l’adresse du fichier par la m´thode getResource de la classe (nous disons bien la classe) en cours e d’ex´cution. Le travail est alors d´l´gu´ ` l’objet class loader qui a charg´ cette classe, en quelque sorte cela e ee ea e revient ` chercher le fichier  l` o` on a trouv´ la classe . a a u e Par exemple, si nous avons archiv´ les classes de notre application avec un r´pertoire images contenant e e le fichier en question, il suffira de remplacer la ligne montr´e pr´c´demment par : e e e URL url = AfficheurdImages.class.getResource("images/XGorce.021108.gif"); uneImage = Toolkit.getDefaultToolkit().getImage(url); Note. Dans une m´thode d’instance (c.-`-d. non statique), la premi`re des lignes ci-dessus peut s’´crire e a e e ´galement e URL url = getClass().getResource("images/XGorce.021108.gif"); 11.10.3 Exemple : construire une image pixel par pixel

La m´thode createImage(ImageProducer producteur) de la classe java.awt.Component renvoie une e image correspondant aux donn´es (les pixels) fournies par l’objet producteur. Le type de l’image (i.e. le e mode de codage des pixels) est celui qui correspond ` l’environnement graphique utilis´, logiciel et mat´riel. a e e Comme producteur on peut prendre un objet MemoryImageSource, consistant essentiellement en un tableau de nombres, chacun d´crivant un pixel selon le codage RGB. e

Fig. 30 – Une image de synth`se ! e Par exemple, le programme suivant dessine une image en noir et blanc calcul´e pixel par pixel (voyez la e figure 30) : le centre de l’image est blanc, puis les pixels s’assombrissent au fur et ` mesure qu’on s’´loigne a e du centre, proportionnellement au carr´ de la distance au centre : e import java.awt.*; import javax.swing.*; import java.awt.image.*; class AfficheurdImages extends JPanel { static final int L = 400; static final int H = 250; Image uneImage;

c H. Garreta, 2000-2006

143

11.10 Images

11

INTERFACES GRAPHIQUES

AfficheurdImages() { setPreferredSize(new Dimension(L, H)); int tabPixels[] = new int[L * H]; int i = 0; for (int y = 0; y < H; y++) { for (int x = 0; x < L; x++) { int dx = x - L / 2; int dy = y - H / 2; int r = Math.max(255 - (dx * dx + dy * dy) / 50, 0); tabPixels[i++] = (255 << 24) | (r << 16) | (r << 8) | r; } } uneImage = createImage(new MemoryImageSource(L, H, tabPixels, 0, L)); } public void paint(Graphics g) { super.paint(g); g.drawImage(uneImage, 0, 0, this); } public static void main(String[] args) { JFrame cadre = new JFrame("Afficher une image"); cadre.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); cadre.getContentPane().add(new AfficheurdImages()); cadre.pack(); cadre.setVisible(true); } }

144

c H. Garreta, 2000-2006

12 JAVA 5

12

Java 5

Si chaque version de Java a apport´ au langage un lot d’´l´ments nouveaux, Java 5 a ´t´ une avanc´e e ee ee e particuli`rement significative. Il y aurait beaucoup ` dire ` propos des nouveaut´s de cette version, mais nous e a a e nous limiterons ici aux quelques points suffisamment importants pour m´riter de figurer dans un polycopi´ e e succinct comme celui-ci. Au moment o` ce document est produit la version courante de Java est la version 5, officiellement u d´nomm´e Java 2 Platform Standard Edition version 5.0 (ou JDK 1.5, ou encore Tiger ) mais la version 1.4 e e est encore tr`s r´pandue. Vous pouvez sp´cifier quelles extensions vous autorisez dans votre programme en e e e lan¸ant la compilation avec l’option -source : c javac -source 1.4 1.5 ou 5 Classe.java

Par d´faut, la compilation se fait en accord avec la version 5. Si vous compilez avec l’option -source 1.4 e alors les extensions expliqu´es ci-apr`s ne seront pas admises ; cela peut s’av´rer utile, certaines prescriptions e e e de la version 5 provoquant parfois des messages d’avertissement copieux et obscurs lors de la compilation de sources d’origine 1.4.

12.1

Types ´num´r´s e e e

On a souvent besoin d’ensembles finis de donn´es conventionnelles, comme { nord, sud, est, ouest } ou e { lundi, mardi, mercredi, ... dimanche }, etc. Quel que soit le domaine d’application, ces donn´es partagent e certains traits caract´ristiques : e 1. Ces donn´es sont tr`s symboliques et naturellement munies de noms significatifs. C’est pourquoi il est e e regrettable de les repr´senter ` l’aide de simples nombres, comme on le fait quand on n’a pas d’autre e a moyen : 0 pour nord, 1 pour sud, 2 pour est, etc. 2. Ce sont des donn´es atomiques – comme des nombres –, ce qui doit leur donner un faible encombrement e et permettre un traitement tr`s rapide. C’est pourquoi il est regrettable de les repr´senter par des e e chaˆ ınes de caract`res, comme on le fait parfois : "nord" pour nord, "sud" pour sud, etc. e 3. Ces donn´es constituent des ensembles finis, souvent de taille r´duite, ce qui doit permettre certaines e e op´rations et certaines optimisations – par exemple, pour la repr´sentation de sous-ensembles de l’ene e semble de telles donn´es – auxquelles il faut renoncer si on repr´sente ces donn´es par des nombres ou e e e des chaˆ ınes de caract`res. e 4. Elles constituent des ensembles disjoints d’avec les autres types de donn´es (un point cardinal n’est e pas interchangeable avec un jour de la semaine !). Cela devrait permettre, de la part du compilateur, un puissant contrˆle de type qu’on n’aura pas si on repr´sente ces donn´es par des nombres ou des o e e chaˆ ınes. En Java, jusqu’` la version 1.4, on impl´mentait presque toujours les donn´es symboliques par des suites a e e de constantes num´riques : e public public public public static static static static final final final final int int int int NORD = 0; SUD = 1; EST = 2; OUEST = 3;

Cette mani`re de faire est en accord avec les deux premi`res propri´t´s ci-dessus, mais pas avec les deux e e ee suivantes. En effet, rien ne dit au compilateur que les constantes NORD, SUD, EST, OUEST constituent la totalit´ e d’un type et, surtout, ces constantes ne forment mˆme pas un type : le compilateur ne pourra pas nous aider e si par inadvertance nous affectons la valeur NORD ` une variable jourDeLaSemaine (dont les valeurs attendues a LUNDI, MARDI, etc., sont ´galement repr´sent´es par des constantes enti`res). e e e e Java 5 corrige ces d´fauts en introduisant les nouveaux types ´num´r´s ou enums, expliqu´s ci-apr`s. e e e e e e 12.1.1 Enums

La d´claration d’un type ´num´r´ se compose au moins des ´l´ments suivants (cela s’enrichira par la e e ee ee suite) : qualifieurs enum identificateur { identificateur , identificateur , ... identificateur } Deux exemples : public enum PointCardinal { NORD, SUD, EST, OUEST } public enum JourSemaine { LUNDI, MARDI, MERCREDI, JEUDI, VENDREDI, SAMEDI, DIMANCHE }
c H. Garreta, 2000-2006

145

12.1 Types ´num´r´s e ee

12

JAVA 5

Note. Les deux d´clarations pr´c´dentes introduisant des types publics, elles doivent ˆtre ´crites dans e e e e e deux fichiers s´par´s, respectivement nomm´s PointCardinal.java et JourSemaine.java. e e e Exemples d’utilisation ult´rieure : e PointCardinal direction = PointCardinal.NORD; ... JourSemaine jourDePresence = JourSemaine.MERCREDI; Notez bien que la d´claration d’un type enum n’est pas un simple artifice pour donner des noms exe pressifs ` une poign´e de nombres entiers. Les d´clarations pr´c´dentes cr´ent bien deux types nouveaux, a e e e e e PointCardinal et JourSemaine, distincts entre eux et distincts de tous les autres types, dont l’emploi pourra ˆtre finement contrˆl´ par le compilateur. e oe Pour faire comprendre la nature et les propri´t´s des valeurs d’un type enum disons pour commencer (car ee en fait c’est plus riche que cela) que la d´claration e public enum PointCardinal { NORD, SUD, EST, OUEST } a tout l’effet qu’aurait eu la d´claration suivante : e public class PointCardinal { public static final PointCardinal public static final PointCardinal public static final PointCardinal public static final PointCardinal NORD = new PointCardinal(); SUD = new PointCardinal(); EST = new PointCardinal(); OUEST = new PointCardinal();

private PointCardinal() {} // pour emp^cher la cr´ation d’autres instances e e } M´thodes des types ´num´r´s e e e e Un type ´num´r´ apparaˆ donc comme une classe dont les seules instances sont les valeurs du type e ee ıt ´num´r´. En fait, tout type ´num´r´ est sous-classe de la classe java.lang.Enum, qui introduit quelques e ee e ee m´thodes bien utiles : e String name(), String toString() Ces deux m´thodes renvoient la valeur en question, exprim´e sous forme de chaˆ de caract`res ; par e e ıne e exemple PointCardinal.NORD.name() est la chaˆ "NORD". La seconde est plus int´ressante car elle ıne e peut ˆtre red´finie. Cela s’´crit comme ceci : e e e public enum PointCardinal { NORD, SUD, EST, OUEST; public String toString() { return "<" + super.toString().toLowerCase() + ">"; } } Dans ces conditions, l’instruction System.out.println(PointCardinal.OUEST); produit l’affichage de <ouest> static ´num´ration valueOf(String nom) e e Renvoie la valeur du type ´num´r´ dont le nom – ´crit exactement comme dans la d´finition du type e ee e e ´num´r´ – est donn´. Par exemple, PointCardinal.valueOf("EST") vaut PointCardinal.EST. e ee e static ´num´ration[] values() e e Renvoie un tableau contenant toutes les valeurs du type ´num´r´. Par exemple, le code e ee PointCardinal[] directions = PointCardinal.values(); for (int i = 0; i < directions.length; i++) System.out.print(directions[i] + " "); affiche le texte  NORD SUD EST OUEST . int ordinal() Renvoie un entier qui exprime le rang de la valeur dans le type : PointCardinal.NORD.ordinal() vaut 0, PointCardinal.SUD.ordinal() vaut 1, etc. La transformation r´ciproque est facile ` obtenir : PointCardinal.values()[0] vaut PointCardie a nal.NORD, PointCardinal.values()[1] vaut PointCardinal.SUD, etc. 146
c H. Garreta, 2000-2006

12 JAVA 5

12.1

Types ´num´r´s e ee

Aiguillages command´s par des types ´num´r´s e e e e Bien qu’elles ne soient pas des nombres ou des caract`res, les valeurs des types ´num´r´s peuvent servir e e ee a ` piloter des aiguillages. Par exemple, supposons avoir la d´claration e public enum CategorieVehicule { BERLINE, COUPE, BREAK; } voici le code d’un tel aiguillage : ... CategorieVehicule categorie; ... switch (categorie) { case BERLINE: code correspondant au cas o` categorie u break; case COUPE: code correspondant au cas o` categorie u break; case BREAK: code correspondant au cas o` categorie u break; default: throw new AssertionError("\nErreur + categorie + " non trait´ e }

vaut CategorieVehicule.BERLINE

vaut CategorieVehicule.COUPE

vaut CategorieVehicule.BREAK

de programmation: cas " dans switch");

Notez que, puisque le switch est command´ par une expression de type CategorieVehicule, ` l’int´rieur e a e de celui-ci le compilateur laisse ´crire BERLINE, COUPE ou BREAK au lieu de CategorieVehicule.BERLINE, e CategorieVehicule.COUPE ou CategorieVehicule.BREAK. Notez ´galement que l’utilisation de la clause default, comme ci-dessus, est recommand´e. En proc´dant e e e ainsi, si plus tard on ajoute des valeurs au type ´num´r´ en oubliant d’ajouter les case correspondants dans e ee les switch concern´s, on en sera pr´venu (h´las, on ne sera pr´venu qu’` l’ex´cution, on aurait sans doute e e e e a e pr´f´r´ l’ˆtre durant la compilation...). eee e 12.1.2 Dictionnaires et ensembles bas´s sur des types ´num´r´s e e e e

Dictionnaires. Le nouveau type EnumMap (en r´alit´ c’est une classe param´tr´e mais pour commencer e e e e nous passerons cet aspect sous silence – voyez la fin de cette section) a ´t´ introduit pour permettre la ee construction de tables associatives dont les cl´s sont les valeurs d’un type ´num´r´, avec optimisation de e e ee l’espace et du temps d’acc`s. Cela s’emploie comme ceci : e Map permanence = new EnumMap(JourSemaine.class); ... permanence.put(JourSemaine.MARDI, "M. Jack Palmer"); permanence.put(JourSemaine.JEUDI, "Mme Raymonde Bidochon"); ... JourSemaine jour; ... String qui = (String) permanence.get(jour); if (qui != null) System.out.println("le permanent de " + jour + " est " + qui); ... Ensembles. De mani`re analogue ` EnumMap, le type EnumSet (encore une classe param´tr´e, voyez la e a e e fin de cette section) a ´t´ introduit en vue de la d´finition d’ensembles compacts et efficaces101 dont les ee e ´l´ments sont les valeurs d’un type ´num´ration donn´. ee e e e Les ensembles EnumSet supportent, comme toutes les collections, les op´rations fondamentales contains e (test d’appartenance), add et remove (ajout et suppression d’un ´l´ment), plus quelques op´rations de faee e brication particuli`rement commodes : e
101 On nous assure que ces ensembles sont r´alis´s avec des tables de bits, ils sont donc aussi peu encombrants et d’une e e manipulation aussi efficace que les ensembles qu’on peut r´aliser avec des ´l´ments repr´sent´s par des puissances de 2 et les e ee e e op´rateurs de bits & et |. e

c H. Garreta, 2000-2006

147

12.2 Emballage et d´ballage automatiques e

12

JAVA 5

static EnumSet of(Object premierElement, Object... autresElements) Construction d’un ensemble form´ des ´l´ments indiqu´s (il s’agit d’une m´thode avec nombre variable e ee e e d’arguments, cf. § 12.3.3). static EnumSet allOf(Class typeElements) Construction d’un ensemble form´ de tous ´l´ments du type ´num´r´ indiqu´. e ee e ee e static EnumSet complementOf(EnumSet s) Construction d’un ensemble form´ de tous les ´l´ments du type ´num´r´ en question qui ne sont pas e ee e ee dans l’ensemble indiqu´. e Exemple : ... EnumSet joursDePresence = EnumSet.of(JourSemaine.LUNDI, JourSemaine.MERCREDI, JourSemaine.VENDREDI); ... joursDePresence.add(JourSemaine.MARDI); joursDePresence.remove(JourSemaine.VENDREDI); ... JourSemaine jour; ... if (joursDePresence.contains(jour)) System.out.println("ok pour " + jour); ... Note. Nous ne l’avons pas fait apparaˆ dans les lignes pr´c´dentes, pour ne pas les alourdir, mais en ıtre e e r´alit´ EnumMap et EnumSet sont des classes param´tr´es (cf. § 12.5) et leurs d´clarations compl`tes prennent e e e e e e les formes – assez troublantes – suivantes : class EnumSet<E extends Enum<E>> { ... } class EnumMap<K extends Enum<K>, V> { ... }

12.2
12.2.1

Emballage et d´ballage automatiques e
Principe

En Java, les donn´es de types primitifs, byte, short, int, long, float, double, char et boolean, ne e sont pas des objets. Cela est ainsi pour des raisons ´videntes d’efficacit´, car traiter ces types de donn´es e e e comme le font les langages les plus simples permet d’utiliser des op´rations  cˆbl´es  (directement prises e a e en charge par le mat´riel) extrˆmement optimis´es. Cependant, d’un point de vue m´thodologique, cela n’est e e e e pas satisfaisant : – c’est regrettable pour la clart´ des id´es, car cela introduit deux poids et deux mesures (ce qui est vrai e e pour les objets ne l’est pas forc´ment pour les donn´es primitives, et r´ciproquement), multiplie les cas e e e particuliers, alourdit les sp´cifications, etc., e – d’un point de vue pratique c’est encore plus regrettable, car cela interdit d’employer sur des donn´es e de types primitifs les puissants outils de la biblioth`que Java comme les collections, qui ne travaillent e qu’avec des objets. Ce second d´faut ´tant consid´rable, il a bien fallu lui trouver un rem`de. Cela a consist´ (cf. § 9.1.1) e e e e e en la d´finition de huit classes Byte, Short, Integer, Long, Float, Double, Character et Boolean, en e correspondance avec les huit types primitifs ; chaque instance d’une de ces classes se compose d’une unique donn´e membre qui est une valeur du type primitif que l’instance  enveloppe . e L’op´ration consistant ` construire un tel objet enveloppant une valeur v d’un type primitif s’appelle e a  emballage de la valeur v  ; l’op´ration r´ciproque s’appelle  d´ballage de la valeur v . Ainsi, par exemple, e e e pour des valeurs de type int, ces op´rations peuvent (et, jusqu’` Java 1.4, doivent) s’´crire : e a e int unInt; ... Integer unInteger = new Integer(unInt); ... unInt = unInteger.intValue();

// emballage de unInt // d´ballage de unInt e

La bonne nouvelle est qu’en Java 5 ces deux op´rations sont devenues automatiques, c’est-`-dire ins´r´es e a ee par le compilateur l` o` elles sont n´cessaires, sans que le programmeur ait ` s’en occuper explicitement. a u e a Plus pr´cis´ment : e e 148
c H. Garreta, 2000-2006

12 JAVA 5

12.2

Emballage et d´ballage automatiques e

– dans un endroit o` un objet est attendu on peut mettre une expression d’un type primitif ; ` l’ex´cution, u a e sa valeur sera convertie en objet par construction d’une instance de la classe enveloppe correspondant au type primitif en question, – dans un endroit o` une valeur d’un type primitif est attendue on peut mettre une expression dont le u type est la classe-enveloppe correspondante ; ` l’ex´cution, l’objet sera converti dans le type primitif a e requis par appel de la m´thode typeValue(), o` type est le nom de ce type primitif. e u Exemple. Le principal domaine d’application de l’emballage et d´ballage automatiques est la mise en e œuvre de collections contenant des donn´es primitives. Par exemple, voici comment on peut r´aliser simplee e ment une file d’attente d’entiers en Java 5 : D´claration et cr´ation : e e List file = new LinkedList(); file.add(v); Op´ration  extraire un entier u de la file  : e int u = (Integer) file.remove(0); // extraction de l’´l´ment de tˆte ee e Note. Avec les collections g´n´riques expliqu´es plus loin (cf. § 12.5) les op´rations pr´c´dentes deviene e e e e e nent encore plus simples et, surtout, plus fiables : List<Integer> file = new LinkedList<Integer>(); ... file.add(v); // ici il est v´rifi´ que la conversion de v e e ... // donne un Integer int u = file.remove(0); // ce qui sort de la file est un Integer Op´rations d´riv´es et autres cons´quences e e e e Le m´canisme de l’emballage et d´ballage automatiques est simple et puissant, mais il faut prendre garde e e qu’il a de nombreuses cons´quences, dont certaines subtilit´s. e e ´ Arithmetique. Par exemple, avec la d´claration e Integer nombre; le code suivant est l´gitime e nombre = nombre + 1; le compilateur le traduira en : nombre = new Integer(nombre.intValue() + 1); De mˆme, il est possible d’´crire e e nombre++ cette expression ayant la valeur et l’effet qu’aurait l’appel d’une fonction fictive ressemblant ` ceci : a Integer nombre++() { Integer tmp = nombre; nombre = new Integer(nombre.intValue() + 1); return tmp; } Comparaison. Le cas de la comparaison est plus d´licat. Donnons-nous la situation suivante : e Integer Integer Integer Integer a b c d = = = = 123; 123; 12345; 12345; // ou Vector, ou toute autre impl´mentation de List e // add ajoute ` la fin de la liste a Op´ration  introduire un entier v dans la file  (v a ´t´ d´clar´e int) : e ee e e

et examinons l’affichage produit alors par les expressions suivantes : System.out.println( System.out.println( System.out.println( System.out.println(
c H. Garreta, 2000-2006

a c a c

== == == ==

123 ); 12345 ); b ); d ); 149

12.3 Quelques outils pour simplifier la vie du programmeur

12

JAVA 5

Sans surprise, la quatri`me expression (c == d) affiche false. En effet, c et d sont des objets construits e s´par´ment et on a d´j` expliqu´ (cf. § 3.6.3) que, pour les objets, == exprime l’identit´ (´galit´ des r´f´rences), e e ea e e e e ee non l’´quivalence. Pour tester l’´quivalence (´galit´ des valeurs) il aurait fallu ´crire c.equals(d). e e e e e En revanche, on sera peut-ˆtre ´tonn´s en constatant que les trois autres expressions affichent true. Pour e e e les deux premi`res (a == 123 et c == 12345) la question est la mˆme : il s’agit de savoir si la comparaison e e a == 123 porte sur deux valeurs primitives ([a converti en int] et 123) ou bien sur deux objets (a et [123 transform´ en Integer]). C’est le premier qui se produit : la comparaison des valeurs ´tant en g´n´ral plus e e e e int´ressante que la comparaison des r´f´rences, le compilateur pr´f`re travailler dans le type primitif. D’o` le e ee ee u r´sultat true : a et c convertis en int sont bien respectivement ´gaux aux nombres auxquels on les compare. e e Il reste le troisi`me cas, a == b, qui s’affiche true faisant penser que, bien que construits s´par´ment, a e e e et b sont le mˆme objet. Et c’est bien ce qui se passe ! En effet, dans certains cas r´put´s fr´quents, avant e e e e de provoquer un appel de new Java examine si la valeur en question n’a pas d´j` ´t´ emball´e, auquel cas eaee e l’objet-enveloppe pr´c´demment construit est r´utilis´. Ainsi, les deux lignes e e e e Integer a = 123; Integer b = 123; ont le mˆme effet que e Integer a = new Integer(123); Integer b = a; ce qui explique l’´galit´ trouv´e. e e e Les  cas r´put´s fr´quents , c’est-`-dire les valeurs pour lesquelles la machine cherche ` savoir si elles e e e a a ont d´j` ´t´ emball´es, avant de cr´er un nouvel objet, sont les petites valeurs ; plus pr´cis´ment : eaee e e e e – les bool´ens false et true, e – les valeurs de type byte, short et int comprises entre -128 et 127, – les caract`res dont le code est compris entre \u0000 (0) et \u007F (127) e 12.2.2 La r´solution de la surcharge en Java 5 e

Dans quelle mesure le m´canisme de l’emballage et d´ballage automatiques complique-t-il celui de la e e r´solution de la surcharge que Java emploie par ailleurs ? Supposons, par exemple, disposer d’une certaine e m´thode traitement surcharg´e : e e void traitement(double x) { ... } ... void traitement(Integer y) { ... } a l’occasion d’un appel comme int z; ... traitement(z); laquelle des deux m´thodes doit-on activer ? Autrement dit, le type Integer est-il consid´r´ plus proche du e ee type int que le type double, ou le contraire ? Quelle que soit la r´ponse que vous avez envie de donner, les e concepteurs de Java 5 ont d´cid´ de privil´gier une certaine forme de compatibilit´ avec Java 1.4, ainsi la e e e e conversion vers le type primitif (ici double) est pr´f´r´e. La r`gle pr´cise est la suivante : eee e e En Java 5 la r´solution de la surcharge se fait en trois temps : e 1. Le compilateur cherche d’abord ` r´soudre l’appel d’une m´thode surcharg´e sans utiliser l’emballage a e e e et d´ballage automatiques ni les arguments variables (cf. § 12.3.3). Si l’appel peut ˆtre r´solu ainsi, e e e alors les r`gles appliqu´es auront ´t´ les mˆme qu’en Java 1.4. e e ee e e e e e a e 2. Si la premi`re ´tape a ´chou´, alors le compilateur cherche ` r´soudre l’appel en se permettant des emballages et des d´ballages, mais sans consid´rer les m´thodes avec des arguments variables. e e e e e e a e e 3. Enfin, si l’´tape 2 a ´chou´ aussi, alors le compilateur cherche ` r´soudre l’appel en consid´rant les m´thodes avec des arguments variables. e

12.3
12.3.1

Quelques outils pour simplifier la vie du programmeur
Importation de membres statiques

Ou : comment raccourcir encore les noms de certaines entit´s ? La directive import que nous connaissons e permet de mentionner une classe publique sans avoir ` pr´fixer son nom par celui de son paquetage. C’est a e 150
c H. Garreta, 2000-2006

12 JAVA 5

12.3

Quelques outils pour simplifier la vie du programmeur

bien, mais cela n’empˆche pas que les autres entit´s manipul´es dans les programmes, comme les membres e e e des classes, ont tendance ` avoir des noms ` rallonges. Ne pourrait-on pas faire d’autres simplifications du a a mˆme genre ? e Pour les membres ordinaires – non statiques – on ne peut pas grand chose : ils doivent ˆtre pr´fix´s par e e e un objet, cela est dans leur nature mˆme de membres d’instance, c’est-`-dire n´cessairement li´s ` un objet. e a e e a Seule simplification, bien connue : la plupart du temps, quand cet objet est this il peut ˆtre omis. e La situation n’est pas la mˆme pour les membres de classe (membres qualifi´s static), ce qui a permis e e de simplifier leur emploi en Java 5. Jusqu’` Java 1.4, ces membres devaient ˆtre ´crits pr´fix´s par le nom a e e e e de leur classe ; il s’agit d’´viter d’´ventuelles collisions de noms, ce qui reste un risque potentiel qui n’est pas e e souvent effectif : les membres de classes diff´rentes ont une tendance naturelle ` avoir des noms diff´rents. e a e Ne pourrait-on pas, lorsqu’il n’y a pas de collision, permettre que les noms des membres statiques soient utilis´s sans pr´fixe ? e e C’est la nouvelle directive import static qui rend ce service. Elle s’emploie sous deux formes : import static nomCompletDeClasse.nomDeMembreStatique ; import static nomCompletDeClasse.* ; Dans la premi`re forme, le nom du membre statique indiqu´ pourra ˆtre utilis´ seul. Dans la deuxi`me e e e e e forme, tous les membres statiques de la classe indiqu´e pourront ˆtre utilis´s seuls. e e e Exemple. Le programme Sinus affiche le sinus d’un angle donn´ en degr´s. Version traditionnelle : e e public class Sinus { public static void main(String[] args) { double x = Double.parseDouble(args[0]) * Math.PI / 180; double y = Math.sin(x); System.out.println("sin(" + x + ") = " + y); } } Malgr´ les apparences, la version avec des membres statiques import´s est plus l´g`re : e e e e import static java.lang.Math.*; import static java.lang.Double.parseDouble; import static java.lang.System.out; public class Sinus { public static void main(String[] args) { double x = parseDouble(args[0]) * PI / 180; double y = sin(x); out.println("sin(" + x + ") = " + y); } } Note 1. La directive import static se r´v`le bien utile pour simplifier l’emploi des types ´num´r´s (cf. e e e ee § 12.1.1), puisque les valeurs de ces derniers sont des membres (implicitement) statiques. Par exemple, ´tant e donn´ le type e public enum JourSemaine { LUNDI, MARDI, MERCREDI, JEUDI, VENDREDI, SAMEDI, DIMANCHE } dans un programme o` on doit utiliser ces valeurs, l’´criture de la directive u e import static JourSemaine.*; permettra d’´crire LUNDI, MARDI, etc. au lieu de JourSemaine.LUNDI, JourSemaine.MARDI, etc. e Note 2. Bien entendu, l’emploi de import static fait apparaˆ ıtre le risque de conflits de noms que l’´criture explicite des noms des classes aurait ´vit´. Il faut signaler cependant que ce risque est bien minimis´ e e e e par le fait que le compilateur ne se d´clare en situation de conflit que lorsqu’il est plac´ devant un acc`s ` e e e a une variable ou un appel de m´thode repr´sentant pour lui une ambigu¨ e insoluble. e e ıt´ Pour illustrer cette bonne volont´ du compilateur, imaginez deux classes A et B ainsi d´finies e e public class A { public static void fon(int i) { ... } ... }
c H. Garreta, 2000-2006

151

12.3 Quelques outils pour simplifier la vie du programmeur

12

JAVA 5

public class B { public static void fon(float x) { ... } ... } Il est remarquable de constater que dans un fichier qui comporte les deux lignes suivantes import static A.fon; import static B.fon; ... // ou A.* // ou B.*

la m´thode fon sera trait´e comme une m´thode surcharg´e ordinaire : sur la rencontre d’un appel comme e e e e fon(u), l’une ou l’autre d´finition sera choisie selon le type de u. e 12.3.2 Boucle for am´lior´e e e

Une nouvelle construction, dite boucle for am´lior´e, a ´t´ ajout´e au langage dans le but d’all´ger e e ee e e l’´criture de deux sortes de boucles extrˆmement fr´quentes : le parcours d’un tableau et le parcours d’une e e e collection. 1 Si tableau est un tableau d’´l´ments d’un certain type TypeElement, qui peut ˆtre un type primitif, ˚ ee e un tableau ou une classe, la boucle for (i = 0; i < tableau.length; i++) exploiter( tableau[i] ) peut s’´crire plus simplement : e for (TypeElement elt : tableau) exploiter( elt ) 2 ˚Si liste est une structure de donn´es susceptible d’ˆtre parcourue au moyen d’un it´rateur – c’est-`e e e a dire si liste impl´mente la nouvelle interface java.lang.Iterable – la boucle e for (Iterator iter = liste.iterator(); iter.hasNext(); ) exploiter( (TypeElement)iter.next() ) peut s’´crire plus simplement : e for (TypeElement elt : liste) exploiter( elt) Exemple 1. Pour illustrer le parcours d’un tableau, voici un bout de code qui affiche une matrice : double[][] matrice = new double[NL][NC]; ... acquisition des valeurs de la matrice ... for (double[] ligne : matrice) { for (double x : ligne) System.out.print(x + " "); System.out.println(); } Exemple 2. Pour illustrer le parcours d’une collection, voici un bout de code qui effectue un certain traitement sur chaque ´l´ment d’un vecteur donn´, en prenant soin de parcourir non pas le vecteur en ee e question mais le r´sultat du clonage102 de ce dernier (notez que la sp´cification de la boucle for am´lior´e e e e e garantit que l’op´ration v.clone() sera effectu´e une seule fois, en commen¸ant la boucle) : e e c Vector v = new Vector(); ... for (Object o : (Vector) v.clone()) traitement(o);
102 Parcourir un clone au lieu de parcourir la structure elle-mˆme paraˆ bizarre, mais c’est ce qu’on doit faire s’il est possible e ıt que le traitement modifie le vecteur – par exemple, en lui ajoutant ou en lui supprimant des ´l´ments. ee

152

c H. Garreta, 2000-2006

12 JAVA 5

12.3

Quelques outils pour simplifier la vie du programmeur

Mise en garde. On peut trouver beaucoup de qualit´s ` la nouvelle boucle for am´lior´e, mais il faut e a e e bien r´aliser qu’elle ne peut pas remplacer en toute circonstance la boucle for ordinaire, tout simplement e parce qu’elle n’offre pas d’indication sur la position dans la structure de l’´l´ment en cours d’exploitation. ee Ainsi, un code aussi simple que le suivant ne gagne pas en concision ni en efficacit´ lorsqu’on le transforme e afin de l’´crire en utilisant la boucle for am´lior´e : e e e int i; ... for (i = 0; i < tab.length; i++) if ( condition( tab[i] ) break; utilisation de la valeur de i Objets it´rables e Peut-on parcourir ses propres structures de donn´es avec des boucles for am´lior´es ? Dit autrement, e e e quelles caract´ristiques doit avoir un objet pour pouvoir figurer dans une expression comme  for ( d´clae e ration : objet)...  ? C’est tr`s simple, il suffit que l’objet impl´mente l’interface Iterable, laquelle se e e compose d’une unique m´thode : e Iterator<T> iterator(); Par exemple, voici la d´finition d’une classe Intervalle dont les instances peuvent ˆtre utilis´es dans les e e e boucles for pour repr´senter des intervalles de nombres entiers103 : e public class Intervalle implements Iterable<Integer> { private int inf, sup; public Intervalle(int inf, int sup) { this.inf = inf; this.sup = sup; } public Iterator<Integer> iterator() { return new Iterator<Integer>() { private int pos = inf; public boolean hasNext() { return pos <= sup; } public Integer next() { return pos++; // emballage automatique } public void remove() { } }; } } Exemple d’emploi104 (notez qu’il y a d´ballage automatique des valeurs successivement produites par e l’it´rateur, puisque i est d´clar´e int) : e e e ... Intervalle plage = new Intervalle(-5, 15); for (int i : plage) System.out.print(i + " "); ... Affichage obtenu : -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
103 Nous utilisons ici les types g´n´riques – cf. § 12.5 – Iterable<Integer> et Iterator<Integer> qui, avec le d´ballage e e e automatique qu’ils induisent, rendent cet exemple beaucoup plus int´ressant. e 104 Il s’agit ici d’un exemple purement d´monstratif. Pour parcourir les entiers compris entre deux bornes inf et sup il est sans e doute plus efficace d’utiliser une boucle for classique : for (int i = inf ; i <= sup ; i++) ...

c H. Garreta, 2000-2006

153

12.3 Quelques outils pour simplifier la vie du programmeur

12

JAVA 5

12.3.3

M´thodes avec une liste variable d’arguments e

Java 5 permet d’appeler une mˆme m´thode105 avec un nombre d’arguments effectifs qui varie d’un appel e e a ` un autre. Ces arguments variables doivent partager un type commun, qui peut ˆtre une super-classe de e leurs types effectifs. Dans la d´claration de la m´thode, ce type commun doit apparaˆ suivi de trois points e e ıtre  ...  et un identificateur. Exemple : void afficherScores(int dossard, String nom, Number... scores) { corps de la m´thode e } A l’int´rieur de la m´thode, l’identificateur associ´ aux arguments variables (ici scores) est le nom d’un e e e tableau dont les ´l´ments sont les arguments effectifs figurant dans l’appel. Voici une ´criture possible de ee e afficherScores : void afficherScores(int dossard, String nom, Number... scores) { System.out.println(nom + " dossard n˚ " + dossard); for (int i = 0; i < scores.length; i++) System.out.println(" " + scores[i] + " points"); } Cette m´thode devra ˆtre appel´e avec pour arguments un entier, une chaˆ de caract`res et un nombre e e e ıne e quelconque d’instances de sous-classes de la classe Number. Par exemple : afficherScores(100, "Durand", new Integer(10)); afficherScores(101, "Dupond", new Integer(10), new Integer(15)); afficherScores(102, "Dubois", new Integer(10), new Long(15), new Double(20.5)); On peut ´crire les appels de cette m´thode plus simplement, en mettant ` profit l’emballage automatique e e a (cf. § 12.2) : afficherScores(102, "Dubois", 10, 15, 20.5); D’autre part, ce m´canisme peut aussi ˆtre utilis´ avec des listes variables d’arguments de types primitifs. e e e La d´claration de la m´thode afficherScores aurait pu ˆtre e e e void afficherScores(int dossard, String nom, double... scores) { le corps de la m´thode reste le mˆme e e } Remarque 1. Quand une m´thode comporte une liste variable d’arguments elle peut ˆtre appel´e de e e e deux mani`res : e – en faisant correspondre ` l’argument variable un certain nombre d’arguments individuels distincts a (ayant des types compatibles avec la d´claration), e – en faisant correspondre ` l’argument variable un unique tableau (d’´l´ments ayant un type compatible). a ee Ainsi, les deux appels suivants sont ´quivalents : e int[] tab = { 10, 20, 30, 25, 15 }; ... afficherScores(102, "Dubois", 10, 20, 30, 25, 15); afficherScores(102, "Dubois", tab); A l’int´rieur de la m´thode, rien ne permet de distinguer le premier appel du second. e e Remarque 2. La possibilit´ d’avoir des listes variables d’arguments interf`re manifestement avec le e e m´canisme de la surcharge. Imaginez deux m´thodes distinctes avec les prototypes suivants : e e void calcul(int x, int y) { ... } void calcul(int... z) { ... } La question est : comment est r´solu un appel tel que que calcul(a, b) ? La r´ponse ` ´t´ donn´e au e e aee e § 12.2.2 : le compilateur essaie d’abord de r´soudre la surcharge sans prendre en compte les m´thodes avec e e liste variable d’arguments ; il ne consid`re ces m´thodes que lorsqu’une telle r´solution ´choue. Ici, l’appel e e e e calcul(a, b) activera donc la premi`re m´thode, tandis que des appels comme calcul(a) ou calcul(a, e e b, c) activeraient la deuxi`me. e
105 Attention, nous ne parlons pas ici de surcharge, c’est-`-dire de la possibilit´ d’appeler des m´thodes distinctes ayant le a e e mˆme nom, mais bien d’appeler une mˆme m´thode avec un nombre d’arguments qui diff`re d’un appel ` un autre. e e e e a

154

c H. Garreta, 2000-2006

12 JAVA 5

12.3

Quelques outils pour simplifier la vie du programmeur

12.3.4

Entr´es-sorties simplifi´es : printf et Scanner e e

La m´thode printf e Les vieux routiers de la programmation en C n’en croiront pas leurs yeux : ` l’occasion de la version 5 a on a incorpor´ ` Java leur ch`re fonction printf ! Mais oui ! Exemple (inutile mais d´monstratif) : ea e e ... double x = 123456; for (int i = 0; i < 8; i++) { System.out.printf("%03d | %12.4f |%n", i, x); x /= 10; } ... Affichage obtenu : 000 001 002 003 004 005 006 007 | | | | | | | | 123456,0000 12345,6000 1234,5600 123,4560 12,3456 1,2346 0,1235 0,0123 | | | | | | | |

La m´thode printf appartient aux classes PrintStream (flux d’octets en sortie repr´sentant des donn´es e e e mises en forme) et PrintWriter (flux de caract`res en sortie repr´sentant des donn´es mises en forme). Dans e e e les deux cas il y en a deux versions : public fluxEnQuestion printf(Locale locale, String format, Object... args); public fluxEnQuestion printf(String format, Object... args); La premi`re forme permet de pr´ciser quelles particularit´s locales doivent ˆtre employ´es, la deuxi`me e e e e e e utilise les particularit´s locales courantes. Dans tous les cas la m´thode renvoie comme r´sultat le flux sur e e e lequel elle a ´t´ appel´e (cela permet les cascades, genre : flux .printf(...).printf(...)). ee e L’argument format est une chaˆ compos´e de caract`res ordinaires, qui seront simplement recopi´s ıne e e e dans le flux de sortie, parmi lesquels se trouvent un certain nombre de sp´cifications de format qui indiquent e comment faut-il mettre en forme les donn´es, repr´sent´es par l’argument variable args, plac´es ` la suite e e e e a de l’argument format. Les sp´cifications de format se pr´sentent ainsi (les crochets expriment le caract`re facultatif de ce qu’ils e e e encadrent) : %[drapeau][largeur ][.precision]type Les principaux drapeaux sont - : commande le cadrage ` gauche, a + : pr´cise que les donn´es num´riques doivent toujours avoir un signe, e e e espace : indique que les nombres positifs doivent ˆtre pr´c´d´s d’un blanc, e e e e 0 : sp´cifie que les espaces libres pr´c´dant les nombres doivent ˆtre remplis avec 0, e e e e Largeur et pr´cision sont des entiers : le premier pr´cise le nombre total de caract`res que la donn´e doit e e e e occuper une fois mise en forme ; le second le nombre de chiffres apr`s la virgule. e Les principales indications de type sont : %% : pour indiquer le caract`re % e %b, %B : pour afficher un bool´en sous la forme false, true [resp. FALSE, TRUE]. e %c, %C : pour afficher un caract`re dont la donn´e correspondante (qui doit ˆtre de type Byte, Short, e e e Character ou Integer) exprime le code interne. %d : pour afficher un entier ´crit en base 10. e %e, %E : pour afficher un nombre flottant en notation exponentielle. %f : pour afficher un nombre flottant sans utiliser la notation exponentielle. %g, %G : pour afficher un nombre flottant avec la notation qui convient le mieux ` sa valeur. a %n : pour afficher l’indicateur de fin de ligne de la plate-forme utilis´e (c’est plus fiable que \n). e
c H. Garreta, 2000-2006

155

12.3 Quelques outils pour simplifier la vie du programmeur

12

JAVA 5

%s, %S : pour afficher une chaˆ de caract`res. ıne e %x, %X : pour afficher un nombre en hexad´cimal. e Il y a aussi de nombreuses – pas moins de 32 ! – sp´cifications de format concernant la date et l’heure. e Vous trouverez toutes les informations sur la m´thode printf dans la documentation de l’API, ` l’occasion e a de la classe java.util.Formatter. La classe Scanner La classe java.util.Scanner est faite de m´thodes pour lire simplement un flux de donn´es format´es. e e e Ensemble, ces m´thodes rendent le mˆme genre de services que la fonction scanf bien connue des programe e meurs C. On construit un objet Scanner au-dessus d’un flux d’octets ou de caract`res pr´alablement ouvert ; dans e e le cas – le plus simple – de la lecture ` la console, ce flux est System.in. a Les m´thodes de la classe Scanner les plus int´ressantes sont certainement : e e byte nextByte() Lecture de la prochaine valeur de type byte (c’est-`-dire : consommation de tous les caract`res se a e pr´sentant dans le flux d’entr´e qui peuvent faire partie d’une donn´es de ce type, construction et e e e renvoi de la valeur correspondante). short nextShort() Lecture de la prochaine valeur de type short. int nextInt() Lecture de la prochaine valeur de type int. long nextLong() Lecture de la prochaine valeur de type long. float nextFloat() Lecture de la prochaine valeur de type float. double nextDouble() Lecture de la prochaine valeur de type double. String nextLine() Lecture de tous les caract`res se pr´sentant dans le flux d’entr´e jusqu’` une marque de fin de ligne et e e e a renvoi de la chaˆ ainsi construite. La marque de fin de ligne est consomm´e, mais n’est pas incorpor´e ıne e e a ` la chaˆ produite. ıne BigInteger nextBigInteger() Lecture du prochain grand entier. BigDecimal nextBigDecimal() Lecture du prochain grand d´cimal. e Exemple tr`s simple : e public class TestScanner { public static void main(String[] args) { Scanner entree = new Scanner(System.in); System.out.print("nom et pr´nom? "); e String nom = entree.nextLine(); System.out.print("^ge? "); a int age = entree.nextInt(); System.out.print("taille (en m)? "); float taille = entree.nextFloat(); System.out.println("lu: " + nom + ", " + age + " ans, " + taille + " m"); } } Ex´cution de ce programme : e 156
c H. Garreta, 2000-2006

12 JAVA 5

12.4 Annotations

nom et pr´nom? Tombal Pierre e a ^ge? 32 taille (en m)? 1,72 lu: Tombal Pierre, 32 ans, 1.72 m Erreurs de lecture. Les erreurs lors de la lecture de donn´es sont des fautes que le programmeur ne e peut pas ´viter, quel que soit le soin apport´ ` la conception de son programme. Il doit donc se contenter de e ea faire le n´cessaire pour les d´tecter lors de l’ex´cution et y r´agir en cons´quence. En Java cela passe par le e e e e e m´canisme des exceptions : e ... double x = 0; for (;;) { System.out.print("Donnez x: "); try { x = entree.nextDouble(); break; } catch (InputMismatchException ime) { entree.nextLine(); System.out.println("Erreur de lecture - Recommencez"); } } System.out.print("x = " + x); ... Remarquez, dans l’exemple pr´c´dent, comment dans le cas d’une erreur de lecture il convient de  nete e toyer  le tampon d’entr´e (c’est le rˆle de l’instruction entree.nextLine() ;) afin que la tentative suivante e o puisse se faire avec un tampon vide. ´ ´ Tests de presence de donnees. La classe Scanner offre aussi toute une s´rie de m´thodes bool´ennes e e e pour tester le type de la prochaine donn´e disponible, sans lire cette derni`re : e e boolean hasNext() Y a-t-il une donn´e disponible pour la lecture ? e boolean hasNextLine() Y a-t-il une ligne disponible pour la lecture ? boolean hasNextByte() La prochaine donn´e ` lire est-elle de type byte ? e a boolean hasNextShort() La prochaine donn´e ` lire est-elle de type short ? e a boolean hasNextInt() La prochaine donn´e ` lire est-elle de type int ? e a boolean hasNextLong() La prochaine donn´e ` lire est-elle de type long ? e a boolean hasNextFloat() La prochaine donn´e ` lire est-elle de type float ? e a boolean hasNextDouble() La prochaine donn´e ` lire est-elle de type double ? e a boolean hasNextBigInteger() La prochaine donn´e ` lire est-elle un grand entier ? e a boolean hasNextBigDecimal() La prochaine donn´e ` lire est-elle un grand d´cimal ? e a e

12.4
12.4.1

Annotations
Principe

Les annotations, on dit aussi m´ta-donn´es, sont un m´canisme permettant d’accrocher des informations e e e  p´riph´riques  aux classes et m´thodes. Ces informations ne modifient pas la s´mantique des programmes, e e e e mais s’adressent ` des outils qui auraient ` manipuler ces derniers. Pour cette raison, les annotations ne a a disparaissent pas lors de la compilation106 ; au contraire, elles sont reconnues et trait´es par le compilateur e
106 Cela est une diff´rence notable entre les annotations et, par exemple, les commentaires de documentation (adress´s ` l’outil e e a javadoc) dont il ne reste aucune trace apr`s la compilation. e

c H. Garreta, 2000-2006

157

12.4 Annotations

12 JAVA 5

et sont conserv´es avec les classes produites – sauf instruction contraire. En contrepartie, cela les oblige ` e a ob´ir ` une syntaxe aussi contraignante que celle des programmes. e a Pour pouvoir ˆtre utilis´es, les annotations doivent d’abord ˆtre d´clar´es par un texte, d´finissant ce e e e e e e qu’on appelle un type annotation, qui est bizarrement voisin d’une d´claration d’interface : e public @interface nomTypeAnnotation { d´claration des ´l´ments de l’annotation e ee } Les ´l´ments d’une annotation sont des donn´es, analogues ` des variables finales (i.e. des constantes), ee e a mais il faut les d´clarer par des expressions qui ressemblent ` des d´clarations de m´thodes : e a e e type nomElement(); avec la particularit´ qu’on peut indiquer une valeur par d´faut : e e type nomElement() default valeur ; Par exemple, voici un type annotation ` quatre ´l´ments destin´e ` signaler les m´thodes d’une classe a ee e a e dont on estime qu’elles peuvent ˆtre am´lior´es : e e e public @interface AmeliorationRequise { int identification(); String synopsis(); String auteur() default "(plusieurs)"; String date() default "non indiqu´e"; e } Lorsqu’un type annotation a un seul ´l´ment, celui-ci doit se nommer value : ee public @interface Copyright { String value(); } Enfin, un type annotation peut aussi n’avoir aucun ´l´ment ; elle est alors dite annotation de marquage ee (marker annotation) : public @interface Test { } Voici une classe dont les m´thodes ont ´t´ annot´es en utilisant les trois types annotations pr´c´dents. e ee e e e Notez que – dans le cas d’une annotation ` un seul ´l´ment, on peut ´crire  annotation(valeur )  au lieu de a ee e  annotation(value = valeur )  – dans le cas d’une annotation sans ´l´ments, on peut ´crire  annotation  au lieu de  annotation()  ee e public class ObjetAnnote { @Test public void unePremiereMethode(arguments) { corps de la m´thode e } @Copyright("Editions Dupuis, 2004") public void uneAutreMethode(arguments) { corps de la m´thode e } @AmeliorationRequise(identification = 80091, synopsis = "Devrait ^tre interruptible", e date = "11/09/2004") // ici auteur aura la valeur "(plusieurs)" public void uneTroisiemeMethode(arguments) { corps de la m´thode e } autres m´thodes e }

158

c H. Garreta, 2000-2006

12 JAVA 5

12.4 Annotations

12.4.2

Exploitation des annotations

A l’ex´cution, l’acc`s aux annotations port´es par un ex´cutable (ensemble de classes) se fait ` travers e e e e a les m´thodes de l’interface java.lang.AnnotatedElement, dont voici une d´finition succincte : e e package java.lang.reflect; public interface AnnotatedElement { // renvoie un tableau contenant toutes les annotations port´es par cet ´l´ment : e ee Annotation[] getAnnotations(); // renvoie le tableau des annotations faites directement (non h´rit´es) sur cet ´l´ment : e e ee Annotation[] getDeclaredAnnotations(); // renvoie true si et seulement si cet ´l´ment porte une annotation ayant le type indiqu´ : ee e boolean isAnnotationPresent(Class<? extends Annotation> annotationType); // renvoie l’annotation de cet ´l´ment ayant le type indiqu´, ou null si elle n’existe pas ee e <T extends Annotation> T getAnnotation(Class<T> annotationType); } Bien entendu, cette interface est d´sormais impl´ment´e par la plupart des classes qui constituent le e e e m´canisme de la r´flexion de Java (cf. § 9.3) : Class, Constructor, Field, Method, Package, etc. Un e e exemple est montr´ ` la fin de la section 12.4.1. ea Voyons cela ` travers deux exemples simples : a Exemple 1. Le programme suivant essaie toutes les m´thodes portant l’annotation Test dans une classe e quelconque, donn´e par son nom (en supposant que toutes ces m´thodes sont statiques et sans argument) ; e e le point important dans cet exemple est la m´thode isAnnotationPresent : e import java.lang.reflect.*; public class ExecuterTests { public static void main(String[] args) throws ClassNotFoundException { int nbrSucces = 0; int nbrEchecs = 0; for (Method uneMethode : Class.forName(args[0]).getMethods()) { if (uneMethode.isAnnotationPresent(Test.class)) { try { uneMethode.invoke(null); nbrSucces++; } catch (Throwable ex) { System.out.println("Test " + uneMethode + " ´chec: " + ex.getCause()); e nbrEchecs++; } } } System.out.println(nbrSucces + " succ`s et " + nbrEchecs + " ´checs"); e e } } Exemple 2. Le programme suivant affiche les valeurs de toutes les propri´t´s de toutes les annotations ee attach´es ` toutes les m´thodes d’une classe donn´e par son nom : e a e e import java.lang.annotation.Annotation; import java.lang.reflect.Method;

c H. Garreta, 2000-2006

159

12.4 Annotations

12 JAVA 5

public class MethodesAnnotees { public static void main(String[] args) throws Exception { Method[] methodes = Class.forName(args[0]).getMethods(); for (Method uneMethode : methodes) { Annotation[] annotations = uneMethode.getAnnotations(); if (annotations.length > 0) { System.out.println(uneMethode.getName()); for (Annotation uneAnnotation : annotations) { Class type = uneAnnotation.annotationType(); System.out.println(" " + type.getName()); for (Method propr : type.getDeclaredMethods()) System.out.println(" " + propr.getName() + " : " + propr.invoke(uneAnnotation)); } } } } } Par exemple, s’il ´tait appel´ avec pour argument le nom de la classe ObjetAnnote montr´e ` la page e e e a 158, ce programme afficherait : unePremiereMethode Test uneAutreMethode Copyright value : Editions Dupuis, 2004 uneTroisiemeMethode AmeliorationRequise identification : 80091 synopsis : Devrait ^tre interruptible e auteur : (plusieurs) date : 11/09/2004 12.4.3 Annotations pr´d´finies e e

Un petit nombre (appel´ ` grandir ?) d’annotations sont pr´d´finies et peuvent ˆtre utilis´es sans autre ea e e e e pr´caution : e @Deprecated – Le compilateur affichera un avertissement lors de tout emploi d’un membre ainsi annot´107 . e @Override – Indique que la m´thode ainsi annot´e doit red´finir une m´thode h´rit´e : si tel n’est pas le e e e e e e cas, un message d’erreur sera produit par le compilateur. @SuppressWarnings("type d’avertissement") – Demande au compilateur de ne pas produire une certaine sorte de messages d’avertissement durant la compilation de l’´l´ment de code ainsi annot´. ee e Par exemple, cette annotation permet que le clonage d’un vecteur g´n´rique puisse se faire  en sie e lence 108 : Vector<String> a = new Vector<String>(); ... @SuppressWarnings("unchecked") Vector<String> b = (Vector<String>) a.clone(); ...

12.4.4

M´ta-annotations e

Les annotations d´finies par l’utilisateur peuvent ˆtre annot´es ` leur tour, par des annotations appel´es e e e a e alors m´ta-annotations. Les types des m´ta-annotations sont pr´d´finis ; ` l’heure actuelle il y en a quatre : e e e e a
107 L’annotation @Deprecated rend donc un service qui ´tait assur´ pr´c´demment par la marque @deprecated ´crite dans un e e e e e commentaire  /** ... */  ` l’adresse de l’outil javadoc. a 108 A propos des conversions  unchecked  voir l’explication des types bruts ` la section 12.5.2 a

160

c H. Garreta, 2000-2006

12 JAVA 5

12.5

G´n´ricit´ e e e

@Documented – Indique que l’annotation que cette m´ta-annotation annote doit ˆtre prise en compte par e e l’outil javadoc et d’autres outils similaires, comme cela est fait pour les autres ´l´ments (classes, ee interfaces, etc.) qui composent le programme source. @Inherited – Indique que l’annotation concern´e est automatiquement h´rit´e. Si un membre est ainsi e e e annot´ et ses descendants (sous-classes, m´thodes red´finies, etc.) ne le sont pas, alors ces derniers e e e prendront automatiquement cette annotation. @Retention – Indique jusqu’` quel point du processus de d´veloppement l’annotation concern´e doit ˆtre a e e e conserv´e. Les valeurs possibles sont e – RetentionPolicy.SOURCE : l’annotation sera ´limin´e par le compilateur (lequel aura cependant e e  vu  l’annotation, contrairement ` ce qui arrive pour un commentaire), a – RetentionPolicy.CLASS : le compilateur laissera l’annotation dans la classe compil´e mais la mae chine virtuelle l’´liminera lors du chargement de la classe, e – RetentionPolicy.RUNTIME : l’annotation sera conserv´e dans la classe compil´e et retenue par la e e machine virtuelle, si bien qu’elle sera accessible – par les m´canisme de la r´flexion – pendant e e l’ex´cution du programme. e La valeur par d´faut est RetentionPolicy.CLASS. e @Target – Indique sur quel type d’´l´ment l’annotation porte. Plusieurs types peuvent ˆtre indiqu´s en ee e e mˆme temps (la valeur de cette m´ta-annotation est un tableau de tels types). Les types possibles sont e e – ElementType.ANNOTATION TYPE : type annotation, – ElementType.CONSTRUCTOR : constructeur, – ElementType.FIELD : champ (variable d’instance), – ElementType.LOCAL VARIABLE : variable locale, – ElementType.METHOD : m´thode, e – ElementType.PACKAGE : paquetage, – ElementType.PARAMETER : param`tre d’une m´thode, e e e e – ElementType.TYPE : classe, interface ou type ´num´ration (enum). La valeur par d´faut est tous les types. e

12.5

G´n´ricit´ e e e

Embl´matique de Java 5, la g´n´ricit´ est peut-ˆtre la plus importante des extensions du langage faites e e e e e a ` l’occasion de cette version. Il s’agit d’obtenir en Java rien moins que les services que rendent les templates en C++, c’est-`-dire la possibilit´ de d´finir et d’employer des classes et des m´thodes param´tr´es par des a e e e e e types qui interviennent dans leur d´finition. e Par exemple, une classe Truc ´tant d´finie par ailleurs, cela nous permettra de d´clarer une collection v e e e comme ´tant de type liste de trucs (cela s’´crira List<Truc>) au lieu du simple type List, c’est-`-dire liste e e a d’objets quelconques. Les b´n´fices obtenus sont faciles ` entrevoir : e e a – lors d’ajouts d’´l´ments ` v, la v´rification que ceux-ci sont bien de type Truc, ee a e – lors d’acc`s ` des ´l´ments de v, la certitude que les objets obtenus sont de type Truc. e a ee Voici ` quoi cela ressemblera : a // d´claration et cr´ation de la liste e e List<Truc> liste = new Vector<Truc>(); ... // remplissage de la liste for (int i = 0; i < n; i++) { liste.add(expression); // ici il est contrˆl´ que expression est un Truc oe } ... // exploitation des ´l´ments de la liste ee for (int i = liste.size() - 1; i >= 0; i--) { Truc q = liste.get(i); // pas besoin de conversion, ce qui sort de la liste // est connu comme ´tant de type Truc e exploitation du truc q } En permettant aux compilateurs d’effectuer des contrˆles de type nombreux et fins, la g´n´ricit´ augmente o e e e significativement la qualit´ et la fiabilit´ des programmes. H´las, dans certains langages elle en augmente e e e aussi la complication, parfois de mani`re consid´rable. On apprendra donc avec soulagement qu’en Java : e e
c H. Garreta, 2000-2006

161

12.5 G´n´ricit´ e e e

12 JAVA 5

– la g´n´ricit´ est totalement prise en charge au moment de la compilation et n’entraˆ aucune modifie e e ıne cation de la machine java, – la g´n´ricit´ est totalement compatible avec le langage pr´existant : l` o` un objet  ` l’ancienne  est e e e e a u a attendu on peut mettre une instance d’une classe g´n´rique, et r´ciproquement109 . e e e 12.5.1 Classes et types param´tr´s e e

Pour commencer il nous faut trois nouveaux concepts : 1 ˚Une classe param´tr´e est une d´claration de classe dans laquelle le nom de la classe est suivi des signes e e e e e < > encadrant une liste de types param`tres (´ventuellement) contraints s´par´s, lorsqu’il y en a plusieurs, e e par des virgules. Exemple : class Machin<A, B extends Number, C extends Collection & Cloneable> { ... } Comme on le devine ci-dessus, les contraintes, lorsqu’elles existent, sont de la forme (les crochets signalent le caract`re facultatif de ce qu’ils encadrent) : e extends classeOuInterface [ & interface ... & interface ] cette expression doit ˆtre comprise, selon que classseOuInterface est une classe ou une interface, respectivee ment comme extends classe implements interface, ... interface ou bien comme implements interface, interface, ... interface 2 Les types variables (ou types param`tres) sont les identificateurs constituant la liste des types pa˚ e ram`tres d’une classe param´tr´e (A, B et C110 dans l’exemple pr´c´dent). A l’int´rieur de la classe, c’est-`-dire e e e e e e a dans les d´finitions de ses membres, ces identificateurs jouent le rˆle de types. Exemple : e o public class Machin<A, B extends Number, C extends Collection & Cloneable > { A descripteur; B valeur; C listeTransactions; public Machin(A descripteur, B valeur) { this.descripteur = descripteur; this.valeur = valeur; ... } ... } 3 Un type param´tr´ est le nom d’une classe param´tr´e suivi des signes < > encadrant une liste de ˚ e e e e  vrais  types. Cela repr´sente donc le choix d’un ´l´ment dans l’ensemble (infini) de types d´fini par la e ee e classe param´tr´e. De telles formules apparaissent le plus souvent dans des d´clarations de variables et de e e e m´thodes. Exemple111 : e Machin<String, Integer, Vector<Truc>> unMachin = new Machin<String, Integer, Vector<Truc>>("un exemple", 100); Attention. L’op´ration consistant ` produire un type param´tr´ ` partir d’une classe param´tr´e (en e a e ea e e rempla¸ant les types param`tres par des types effectifs) est souvent appel´e instanciation de la classe pac e e ram´tr´e. On prendra garde ` l’ambigu¨ e ainsi introduite, ce terme d´signant aussi la cr´ation d’un objet ` e e a ıt´ e e a partir [d’un constructeur] de sa classe : new Integer(1000) : instanciation de la classe Integer ; le r´sultat est un objet, e Vector<Integer> : instanciation de la classe param´tr´e Vector<E> ; le r´sultat est une classe. e e e
souvent cela entraˆ ınera un avertissement de la part du compilateur (il vous dira que votre programme  uses unchecked or unsafe operations ), mais pas un diagnostic d’erreur. 110 La recommandation officielle est de donner ` ces types param`tres des noms tr`s courts, en majuscules et sans signification : a e e A, B, C ou T1, T2, T3, etc. Cela afin qu’` l’int´rieur d’une classe param´tr´e on puisse distinguer du premier coup d’œil les types a e e e param`tres des  vrais  types, qui ont g´n´ralement des noms significatifs, et des membres, qui sont ´crits plutˆt en minuscules. e e e e o 111 Les programmeurs C++ noteront avec plaisir que le compilateur Java ne prend pas toujours deux > cons´cutifs pour une e occurrence de l’op´rateur >>. Mais oui, l’humanit´ progresse ! e e
109 Assez

162

c H. Garreta, 2000-2006

12 JAVA 5

12.5

G´n´ricit´ e e e

Ce discours devient critique lorsque ces deux op´rations sont invoqu´es dans la mˆme expression : e e e new Vector<Integer>() : instanciation de la classe param´tr´e Vector<E> et instanciation de e e la classe ainsi obtenue. Utilisation des classes param´tr´es e e On l’aura compris, le plus beau domaine d’utilisation des classes et types param´tr´s est celui des cole e lections. On ne s’´tonnera donc pas, en consultant la documentation de l’API, de d´couvrir que toutes les e e interfaces et les classes collections de la biblioth`que ont ´t´ remplac´es par une nouvelle forme param´tr´e, e ee e e e Collection<E>, Vector<E>, Iterator<E>, etc. Ces nouvelles classes se combinent tr`s bien avec l’emballage/d´ballage automatiques (cf. section 12.2) e e et permettent l’´criture de programmes plus simples et plus fiables que par le pass´. Voici un exemple que e e nous avons d´j` montr´ : le programme suivant compte le nombre d’apparitions de chaque mot donn´ dans ea e e la ligne de commande. En introduisant une table associative (objet Map) dont les cl´s sont n´cessairement e e des chaˆ ınes de caract`res et les valeurs n´cessairement des Integer le programmer augmente sensiblement e e la fiabilit´ de son programme : e import java.util.*; public class Frequence { public static void main(String[] args) { Map<String, Integer> tableAssoc = new TreeMap<String, Integer>(); for (String mot : args) { Integer freq = tableAssoc.get(mot); tableAssoc.put(mot, freq == null ? 1 : freq + 1); } System.out.println(tableAssoc); } } Pour montrer la d´finition d’une classe param´tr´e, voici celle de la classe Paire dont les instances e e e repr´sentent des paires de choses : e public class Paire<P, S> { private P premier; private S second; public Paire(P premier, S second) { this.premier = premier; this.second = second; } public P getPremier() { return premier; } public S getSecond() { return second; } public String toString() { return "<" + premier + "," + second + ">"; } public Paire<S, P> swap() { return new Paire<S, P>(second, premier); } public static <A, B> Paire<A, B> makeInstance(A premier, B second) { return new Paire<A, B>(premier, second); } ... }
c H. Garreta, 2000-2006

163

12.5 G´n´ricit´ e e e

12 JAVA 5

12.5.2

Types bruts

Comme il a ´t´ dit, pour ce qui est de la g´n´ricit´, Java 5 est compatible avec les versions pr´c´dentes ee e e e e e du langage. Il faut donc que les nouvelles classes param´tr´es puissent ˆtre utilis´es par des programmes e e e e qui ne pratiquent pas la g´n´ricit´ et, inversement, il faut que d’anciennes classes d´j` ´crites puissent ˆtre e e e eae e employ´es dans des programmes qui exploitent massivement la g´n´ricit´. e e e e C’est pourquoi toute classe param´tr´e est consid´r´e par le compilateur comme compatible avec sa e e ee version sans param`tres, appel´e le type brut (raw type) correspondant ` la classe param´tr´e. Par exemple, e e a e e Paire est le type brut associ´ ` la classe param´tr´e Paire<P, S>. ea e e Ainsi, avec la classe d´finie ci-dessus, les quatre lignes suivantes sont accept´es par le compilateur e e ... Paire<String, Number> p1 = new Paire<String, Number>("un", 1); Paire<String, Number> p2 = new Paire("deux", 2); Paire p3 = new Paire<String, Number>("trois", 3); Paire p4 = new Paire("quatre", 4); ... A l’essai on constate que les lignes concernant p2 et p4 donnent lieu ` un avertissement signalant un appel a sans contrˆle du constructeur de la classe Paire<P, S>. Dans le cas de p2, le compilateur est ´galement o e chagrin´ en constatant une affectation sans contrˆle112 de p2 (d´clar´ de type Paire<P, S>) par un objet e o e e du type brut Paire. On peut supprimer ces messages d’avertissement (apr`s avoir acquis la certitude qu’ils ne signalent pas e une erreur) en utilisant une annotation @SuppressWarnings comme expliqu´ ` la section 12.4 : ea ... @SuppressWarnings("unchecked") Paire<String, Number> p2 = new Paire("deux", 2); ... Note. A l’ex´cution tous les types param´tr´s construits au-dessus d’un mˆme type brut sont consid´r´s e e e e ee comme d´finissant la mˆme classe. Par exemple, le code suivant affiche true : e e ... Paire<String, Number> a = new Paire<String, Number>(...); Paire<Point, Rectangle> b = new Paire<Point, Rectangle>(...); ... System.out.println(a.getClass() == b.getClass()); ... 12.5.3 Types param´tr´s et jokers e e

La g´n´ricit´ est une notion plus complexe qu’il n’y paraˆ il faut de la prudence y compris face ` des e e e ıt, a propri´t´s qui semblent ´videntes. Par exemple, si la classe Chat est une sous-classe de la classe Mammifere, ee e il semble naturel de penser que List<Chat> est une sous-classe de List<Mammifere> (si un chat est une sorte de mammif`re alors une liste de chats est une sorte de liste de mammif`res, cela paraˆ clair !). e e ıt C’est pourtant compl`tement erron´. Pour le voir il suffit de se rappeler que  A sous-classe de B  e e signifie  tout ce qu’on peut demander ` un B on peut le demander ` un A . Or, il y a des choses qu’on a a peut demander ` une liste de mammif`res et pas ` une liste de chats : par exemple, l’insertion d’un chien. a e a Pour ˆtre plus pr´cis, supposons que des classes Animal, Mammifere, Chat et Chien aient ´t´ d´finies, e e ee e avec les relations d’h´ritage que leurs noms sugg`rent : e e class Animal { ... } class Mammifere extends Animal { ... } class Chat extends Mammifere { ... }
112 Cette affectation est sans contrˆle parce que les concepteurs de Java 5 ont voulu que la g´n´ricit´ soit trait´e durant la o e e e e compilation et qu’elle ne modifie ni le code produit ni la machine virtuelle. Si la g´n´ricit´ avait ´t´ trait´e ` l’ex´cution il e e e e e e a e aurait ´t´ facile de contrˆler de telles op´rations. e e o e

164

c H. Garreta, 2000-2006

12 JAVA 5

12.5

G´n´ricit´ e e e

class Chien extends Mammifere { ... } Consid´rons d’autre part une m´thode qui prend pour argument une liste de mammif`res : e e e void uneMethode(List<Mammifere> menagerie) { ... } et envisageons divers appels possibles de cette m´thode : e void uneAutreMethode() { List<Animal> listeAnimaux = new Vector<Animal>(); List<Mammifere> listeMammiferes = new Vector<Mammifere>(); List<Chat> listeChats = new Vector<Chat>(); code qui ins`re des ´l´ments dans les listes pr´c´dentes e ee e e uneMethode(listeMammiferes); uneMethode(listeAnimaux); uneMethode(listeChats); } L’appel (a) est correct. L’appel (b) est erron´ et ce n’est pas une surprise : une liste d’animaux – pouvant e contenir des poissons ou des oiseaux – ne peut pas prendre la place d’une liste de mammif`res. Ce qui est e peut-ˆtre surprenant est que l’appel (c) soit incorrect ´galement : pour comprendre pourquoi il suffit de e e r´aliser que uneMethode peut contenir l’instruction (l´gitime, d’apr`s la d´claration de menagerie) : e e e e menagerie.add(new Chien()); Ce serait tout ` fait incoh´rent de laisser faire l’ajout d’un chien dans une liste de chats ! a e Jokers Bon, d’accord, une liste de chats n’est pas une liste de mammif`res. Mais alors, comment faire pour e qu’une m´thode puisse prendre pour argument aussi bien une liste de mammif`res qu’une liste de chats, ou e e une liste de chiens, etc. ? Ou bien, comment signifier que l’argument d’une m´thode est, par exemple, une e liste d’une sorte de mammif`res ? e Les syntaxes nouvelles  ?  (signifiant une classe quelconque) et  ? extends uneClasse  (signifiant  une sous-classe quelconque de uneClasse ) ont ´t´ introduites ` cet effet. Exemple : ee a void uneMethode(List<? extends Mammifere> menagerie) { ... } Maintenant, l’appel (c) qui posait probl`me est devenu l´gal : e e void uneAutreMethode() { ... uneMethode(listeMammiferes); uneMethode(listeAnimaux); uneMethode(listeChats); ... } (a) (b) (c) OK ERREUR ERREUR

// OK // ERREUR // OK

Evidemment, si l’appel ci-dessus est accept´ c’est que la notation < ? extends Mammifere> impose des e contraintes additionnelles. Ainsi, ` l’int´rieur de uneMethode, la liste menagerie sera consid´r´e comme un a e ee objet en lecture seule et toute modification de la liste sera interdite : void uneMethode(List<? extends Mammifere> menagerie) { ... Mammifere mam = menagerie.get(i); // Pas de probl`me : ce qui sort de la liste peut e ... // certainemnt ˆtre vu comme un Mammifere e menagerie.add(new Chien()); ... }
c H. Garreta, 2000-2006

// ERREUR (ben oui, menagerie est peut-ˆtre e // une liste de chats... !)

165

12.5 G´n´ricit´ e e e

12 JAVA 5

Sym´trique de la pr´c´dente, la syntaxe nouvelle  ? super uneClasse  (signifiant  une super-classe e e e quelconque de uneClasse ) permet, par exemple, de d´finir des listes qu’on pourra modifier ` l’aide d’objets e a de type uneClasse : void uneTroisiemeMethode(List<? super Mammifere> menagerie) { ... } Les appels l´gitimes ne seront pas les mˆmes que pr´c´demment : e e e e void uneAutreMethode() { ... uneTroisiemeMethode(listeMammiferes); uneTroisiemeMethode(listeAnimaux); uneTroisiemeMethode(listeChats); ... }

// OK // OK // ERREUR

Ce qu’il est permis de faire ` l’int´rieur de la m´thode change aussi : a e e void uneTroisiemeMethode(List<? super Mammifere> menagerie) { ... Mammifere mam = menagerie.get(i); // ERREUR ( menagerie est peut-ˆtre e ... // une liste de simples animaux) ... menagerie.add(new Chien()); // Pas de probl`me : un chien peut certainement e ... // prendre la place d’un ´l´ment de menagerie ee } 12.5.4 Limitations de la g´n´ricit´ e e e

Cette section obscure et incertaine peut ˆtre ignor´e en premi`re lecture. e e e Contrairement ` d’autres langages, comme C++, o` les classes g´n´riques sont instanci´es d’abord et a u e e e compil´es ensuite, en Java les classes g´n´riques sont compil´es d’abord et instanci´es plus tard. D’autre e e e e e part, on nous assure que la machine virtuelle Java n’a pas subi la moindre modification lors de l’introduction de la g´n´ricit´ dans le langage. e e e Cela explique pourquoi certaines op´rations impliquant des types param`tres sont possibles, et d’autres e e non. Pour le dire simplement : un type param`tre T ne peut ˆtre employ´ que pour ´crire des expressions e e e e qui peuvent ˆtre compil´es sans connaˆ e e ıtre les d´tails pr´cis (structure, taille, m´thodes...) du type que T e e e repr´sente113 . Ainsi, un type param`tre peut ˆtre utilis´ : e e e e – dans des d´clarations de membres, de variables locales ou d’arguments formels, e – dans des conversions de type (casts). En revanche, un type param`tre ne peut pas ˆtre employ´ e e e e – en repr´sentation d’un type primitif, – comme type des ´l´ments dans la d´finition d’un tableau, ee e – intervenant dans la d´finition d’un membre statique, e – en position de constructeur d’une classe, comme dans  new T() , – dans une comparaison comme  T == une classe  o – dans le rˆle d’un objet (instance de la classe Class), comme dans  T.getName()  A titre d’exemple, voici quelques bons et mauvais usages des types param`tres : e public class Zarbi<T> { T x; static T y; Zarbi<Integer> z; // Pas de probl`me e // *** ERREUR *** // OK

113 Si cela vous aide, dites-vous que le code produit par la compilation d’une classe param´tr´e est le mˆme que celui qu’on e e e aurait obtenu si on avait remplac´ tous les types param`tres par Object – en faisant abstraction des avertissements et erreurs e e ` propos des types que cela aurait soulev´. a e

166

c H. Garreta, 2000-2006

12 JAVA 5

12.5

G´n´ricit´ e e e

Zarbi<int> t; public T getX() { return x; } public Zarbi(Object o) { x = (T) o; } public Zarbi() { x = new T(); } public boolean estOk(Object x) { return x.getClass() == T; } } 12.5.5 M´thodes g´n´riques e e e

// *** ERREUR *** // Tr`s bien e

// Parfait

// *** ERREUR ***

// *** ERREUR ***

Comme les classes param´tr´es, les m´thodes peuvent elles aussi d´pendre d’une liste de types param`tres. e e e e e Ces param`tres, encadr´s par les signes < >, doivent ˆtre plac´s imm´diatement devant le type du r´sultat e e e e e e de la m´thode, selon le sch´ma suivant : e e qualifieurs < types-param`tres > type-du-r´sultat nom-m´thode ( arguments ) e e e Par exemple, la m´thode suivante prend pour arguments une liste d’objets d’un certain type T, un type e inconnu durant la compilation, et renvoie l’´l´ment qui est au milieu de la liste ; l’argument est donc de type ee List<T> et le r´sultat de type T : e public static <T> T elementCentral(List<T> liste) { int n = liste.size(); if (n > 0) return liste.get(n / 2); else return null; } Tr`s souvent114 , les types param`tres des m´thodes g´n´riques apparaissent dans les types de leurs e e e e e arguments formels (c’est le cas dans la m´thode ci-dessus). Elles ont alors cette particularit´ : pour les e e instancier il suffit de les appeler ; le compilateur d´duit les types param`tres des arguments effectifs figurant e e dans l’appel. Exemples d’appels de la m´thode pr´c´dente : e e e Vector<Integer> v = new Vector<Integer>(); LinkedList<String> ll = new LinkedList<String>(); ... ajout de valeurs aux listes v et ll ... Integer i = elementCentral(v); String s = elementCentral(ll); ... Sans qu’il ait fallu l’indiquer explicitement, lors du premier appel de la m´thode elementCentral le compie lateur aura pris T ≡ Integer et, lors du deuxi`me, T ≡ String. e Un autre exemple de m´thode g´n´rique a ´t´ donn´e par la m´thode makeInstance de la classe Paire e e e ee e e (cf. page 163) :
114 Il n’est pas formellement requis que les types param`tres d’une m´thode param´tr´e apparaissent dans les d´clarations des e e e e e arguments. Cependant, ` l’usage, on s’aper¸oit que cela est une obligation de fait pour obtenir des m´thodes correctes et utiles. a c e

c H. Garreta, 2000-2006

167

12.5 G´n´ricit´ e e e

12 JAVA 5

public class Paire<P, S> { private P premier; private S second; public Paire(P premier, S second) { this.premier = premier; this.second = second; } public static <A, B> Paire<A, B> makeInstance(A premier, B second) { return new Paire<A, B>(premier, second); } ... } Avec cela, nous avons deux mani`res de construire des paires d’objets : soit en explicitant les types des e membres de la paire : new Paire<Double, String>(246.5, "Km"); soit en laissant le compilateur d´duire ces types des arguments d’un appel de makeInstance : e Paire.makeInstance(246.5, "Km");

•••

168

c H. Garreta, 2000-2006

Index
+ (concat´nation de chaˆ e ınes), 20 .TYPE, 70 .class, 70 = (copie superficielle), 17 == (comparaison superficielle), 19 abs, 59 abstract, 49 AbstractCollection, 63 AbstractList, 63 AbstractMap, 64 AbstractSequentialList, 63 AbstractSet, 64 AbstractTableModel, 130 abstraite (classe), 49 abstraite (m´thode), 49 e acos, 60 ActionListener, 108 actionPerformed, 108 Adaptateurs, 105 add, 67 addActionListener, 122 addMouseListener, 104 AdjustmentListener, 110 adjustmentValueChanged, 110 aiguillage et type ´num´r´, 147 e e e algorithme, 67 analyse lexicale, 78 annotations, 157 anonyme (classe), 38 anonyme (tableau), 15 appendReplacement, 89 appendTail, 89 arguments variables, 154 Array, 70 ArrayIndexOutOfBoundsException, 13 ArrayList, 63, 69 Arrays, 69 asin, 60 asList, 69 assert, 56 AssertionError, 56 atan, 60 atan2, 60 auditeur d’´v´nements, 103 e e AWT, 96 base (classe de), 40 BigDecimal, 61 BigInteger, 60 binarySearch, 68–70 boˆ de dialogue, 98 ıte bool´nne (constante), 9 e Boolean, 59 boolean, 10 BorderFactory, 121 BorderLayout, 118 boucle for am´lior´e, 152 e e break, 23 brut (type), 164 buffered image, 141 BufferedInputStream, 72 BufferedOutputStream, 72 BufferedReader, 73 BufferedWriter, 74 Byte, 58 byte, 10 ByteArrayInputStream, 72 ByteArrayOutputStream, 72 caract`re, 9 e case, 147 catch, 53 ceil, 59 CENTER, 118 chaˆ de caract`res (constante), 20 ıne e char, 10 Character, 59 CharArrayReader, 73 CharArrayWriter, 74 class, 28 classe, 28 classe d´riv´e, 40 e e classe de base, 40 clearRect, 112 clonage, 17 clone, 17 Cloneable, 18 CloneNotSupportedException, 18 Collection, 50, 62, 66 Collections, 67 commentaire, 8 Comparable, 67 comparaison d’objets, 19 Comparator, 67 compare, 67 compareTo, 67 compile, 86, 88 Component, 97 concat´nation de chaˆ e ınes, 20 constant (membre), 35 constante (litt´rale), 9 e constante de classe, 36 constructeur, 34, 43 Constructor, 70 Container, 97 contains, 66 containsKey, 67 containsValue, 67 contrˆl´e (exception), 55 oe conversion (entre objets), 15, 44 conversion (entre types primitifs), 10 copie d’objet, 17 copy, 69 copyArea, 112 cos, 60

INDEX

INDEX

create, 112 createImage, 143 DataInputStream, 72 DataOutputStream, 72 Date, 83 DateFormat, 83 d´ballage automatique, 148 e DecimalFormat, 80 DecimalFormatSymbols, 80 DefaultMutableTreeNode, 137 Deprecated, 160 Dialog, 98 directe (sous-classe ou super-classe), 40 DO NOTHING ON CLOSE, 110 Documented, 161 Double, 59 double, 10 double buffering, 141 drawArc, 113 drawImage, 113 drawLine, 113 drawOval, 113 drawRect, 113 drawRoundRect, 113 drawString, 113 dynamique (liaison), 41 dynamique (type), 15 E, 59 EAST, 118 emballage automatique, 148 enableassertions, 57 encapsulation, 28 end, 88 enti`re (constante), 9 e entr´es-sorties, 72 e enum, 145 Enumeration, 66 EnumMap, 147 EnumSet, 147 equals, 19, 21, 70 Error, 54 ´tiquette, 23 e ´v´nement, 97, 102 e e ´v´nements (thread de traitement), 101 e e event-dispatching thread, 101 ex´cutable (classe), 25 e Exception, 54 exception, 53 exception contrˆl´e, 55 oe EXIT ON CLOSE, 100 exp, 59 expression r´guli`re, 86 e e extends, 165 extends, 40 false, 9 fichier binaire, 75 fichier compil´, 24 e fichier de texte, 76 170

fichier source, 24, 25 Field, 70 File, 75 FileDescriptor, 75 FileInputStream, 72 FileOutputStream, 72 FileReader, 73 FileWriter, 74 fill, 69, 70 FilterInputStream, 72 FilterOutputStream, 72 FilterReader, 73 FilterWriter, 74 final, 48 final (membre), 35 finale (classe), 48 finale (m´thode), 48 e finalize, 37 finally, 53 find, 88 Float, 58 float, 10 floor, 59 flottante (constante), 9 FlowLayout, 117 focusGained, 108 FocusListener, 108 focusLost, 108 for (boucle for am´lior´e), 152 e e Format, 80 formatage de donn´es, 80 e forName, 71 Frame, 98 g´n´ralisation, 44 e e g´n´rique (classe), 162 e e g´n´rique (m´thode), 167 e e e gestionnaire de disposition, 98, 116 get, 67 getActionCommand, 108, 124 getAnnotations, 159 getBounds, 97 getClass, 71 getContentPane, 99, 100 getDeclaredAnnotations, 159 getDefaultToolkit, 141 getImage, 141 getResource, 143 goto, 23 Graphics, 112 Graphics2D, 112 GregorianCalendar, 83 GridBagConstraints, 119 GridBagLayout, 119 GridLayout, 118 HashMap, 64 HashSet, 64 hasMoreElements, 66 hasNext, 65, 157 hasNextBigDecimal, 157
c H. Garreta, 2000-2006

INDEX

INDEX

hasNextBigInteger, 157 hasNextByte, 157 hasNextDouble, 157 hasNextFloat, 157 hasNextInt, 157 hasNextLine, 157 hasNextLong, 157 hasNextShort, 157 h´ritage, 39, 40 e icˆne, 139 o identificateur, 8 IdentityHashMap, 65 image, 139 ImageIcon, 140 implements, 50 import, 24, 25 import static, 151 indexOfSubList, 68 Inherited, 161 initialisation d’un objet, 33 initialisation d’un tableau, 14 InputStream, 72 InputStreamReader, 73 instance, 28 int, 10 Integer, 25, 58 interface, 50, 158 interface-utilisateur graphique, 96 intern, 21 interne (classe), 37 InterruptedException, 91, 94 introspection, 70 intValue, 58 invokeLater, 102 IOException, 54 isAnnotationPresent, 159 isEmpty, 66 ItemListener, 110 itemStateChanged, 110 Iterable, 153 Iterator, 62, 65 jar, 27 jar (fichier), 27 Java 5, 145 java.awt, 96 java.lang, 25 java.lang.AnnotatedElement, 159 java.lang.Class, 70 java.lang.reflect, 70 java.math, 60 java.text, 80 java.util, 61 javadoc, 8 javax.swing, 96 JButton, 125 JCheckBox, 125 JDialog, 125 JFC, 96 JFileChooser, 129
c H. Garreta, 2000-2006

JFrame, 99, 100 JMenu, 122 JMenuBar, 122 JMenuItem, 122 jokers (dans les types param´tr´s), 164 e e JOptionPane, 127 JRadioButton, 125 JTable, 130 JTextArea, 125 JTextField, 125 JTree, 133 KeyListener, 107 keyPressed, 107 keyReleased, 107 keyTyped, 107 lang, 25 lastIndexOfSubList, 68 layout manager, 98, 116 length, 13 liaison dynamique, 41 liaison statique, 41 LineNumberReader, 73 LinkedHashMap, 65 LinkedHashSet, 64 LinkedList, 63 List, 62, 67 list, 69 listener, 97, 103 Long, 58 long, 10 main, 25 MANIFEST.MF, 27 manifeste (fichier), 27 Map, 62, 67 masquage d’un membre, 41 Matcher, 86 matcher, 87, 88 matches, 87 Math, 25, 59 max, 59, 68 membre d’instance, 29 message, 28 m´ta-donn´es, 157 e e Method, 70 m´thode, 28 e m´thode d’instance, 29 e m´thode de classe, 31 e m´thode virtuelle, 46 e min, 59, 68 mod`le-vue-contrˆleur, 133 e o modale (boˆ de dialogue), 99 ıte mode imm´diat (mod`le), 139 e e MouseAdapter, 105 mouseClicked, 104, 107 mouseDragged, 107 mouseEntered, 104, 107 mouseExited, 104, 107 MouseListener, 104, 106 171

INDEX

INDEX

MouseMotionListener, 107 mouseMoved, 107 mousePressed, 104, 107 mouseReleased, 104, 107 MutableTreeNode, 136 name, 146 nCopies, 69 new, 11, 12, 14, 34 next, 65 nextBigDecimal, 156 nextBigInteger, 156 nextByte, 156 nextDouble, 156 nextElement, 66 nextFloat, 156 nextInt, 156 nextLine, 156 nextLong, 156 nextShort, 156 NORTH, 118 notifyAll, 94 null, 12 NullPointerException, 13 NumberFormat, 80 Object, 41 Object (classe), 25 ObjectInputStream, 72 ObjectOutputStream, 72 objet, 28 observateur d’une image, 139 ordinal, 146 OutputStream, 72 OutputStreamWriter, 74 Override, 160 pack, 100, 101 package, 24, 25 paint, 97, 112 paquet de classes, 24 paquets et r´pertoires, 26 e param´tr´ (type), 162 e e param´tr´e (classe), 162 e e param`tre (type), 162 e parseInt, 25, 58 particularisation, 44 passage par r´f´rence, 12 ee passage par valeur, 12 Pattern, 86 PI, 59 PipedInputStream, 72 PipedOutputStream, 72 PipedReader, 73 PipedWriter, 74 Pixel, 40 Point, 40 polymorphisme, 44 pow, 60 primitif (type), 10 printf, 155 172

PrintStream, 72 PrintWriter, 74 priv´ (membre), 32 e private, 32 processus l´ger, 90 e producteur-consommateur, 94 prot´g´ (membre), 32 e e protected, 32 public, 25, 28, 32 public (membre), 32 publique (classe), 25 PushbackInputStream, 72 PushbackReader, 73 put, 67 random, 59 RandomAccessFile, 75 Reader, 73 recherche (algorithme), 68 recherche des m´thodes, 41 e red´finition des m´thodes, 31, 42 e e red´finition et surcharge, 31, 42 e r´f´rence, 11 ee r`gle du thread unique, 101 e r´guli`re (expression), 86 e e relation d’ordre, 67 remove, 67 r´pertoires et paquets, 26 e replaceAll, 69, 89 Retention, 161 reverse, 69 reverseOrder, 68 rint, 59 rotate, 69 round, 59 run, 90 Runnable, 90 RuntimeException, 54 Scanner, 156 s´mantique des r´f´rences, 11 e ee s´mantique des valeurs, 11 e SequenceInputStream, 72 s´rialisation, 84 e Set, 62, 67 set, 67 setBorder, 121 setDefaultCloseOperation, 100 setJMenuBar, 122 setLayout, 117 setResizable, 117 setSize, 97, 99, 100 setVisible, 98–101 Short, 58 short, 10 show, 101 shuffle, 69 sin, 60 singleton, 69 singletonList, 69 singletonMap, 69
c H. Garreta, 2000-2006

INDEX

INDEX

size, 66 sleep, 91 sort, 68, 70 SortedMap, 63 SortedSet, 62 sous-classe, 40 SOUTH, 118 split, 86 Stack, 64 start, 88, 90, 95 statique (liaison), 41 statique (type), 15 stop, 92 StreamTokenizer, 75 String, 20, 25 StringBuffer, 20 StringReader, 73 StringWriter, 74 super, 41, 166 super(), 43 super-classe, 40 SuppressWarnings, 160 surcharge d’un membre, 41 surcharge des m´thodes, 31 e surcharge et red´finition, 31 e swap, 69 Swing, 96 SwingUtilities, 102 switch, 147 symboliques, donn´es, 145 e synchronized, 93 synchronizedCollection, 69 synchronizedList, 69 synchronizedMap, 69 System, 25 tableau, 12 tan, 60 Target, 161 TextListener, 110 textValueChanged, 110 this, 30, 34, 35 this(), 35 Thread, 90 thread, 90 thread safe (m´thode), 101 e throw, 54 Throwable, 54 throws, 54 Toolkit, 141 toString, 21, 42, 46 transient, 84 translate, 113 TreeMap, 65 TreeModel, 136 TreeNode, 135 TreeSet, 64 tri (algorithme), 68 true, 9 try, 53 type primitif, 10
c H. Garreta, 2000-2006

Unicode, 8 unit´ de compilation, 24 e unmodifiableCollection, 69 unmodifiableList, 69 unmodifiableMap, 69 UnsupportedOperationException, 66 valueOf, 21, 58, 59, 146 values, 146 variable (type), 162 variable d’instance, 28, 29 variable de classe, 31 variables (listes d’arguents), 154 Vector, 63 virtuelle (m´thode), 46 e wait, 94 WeakHashMap, 65 WEST, 118 Window, 98 windowActivated, 110 windowClosed, 110 windowClosing, 110 windowDeactivated, 110 windowDeiconified, 110 windowIconified, 110 WindowListener, 110 windowOpened, 110 Writer, 74

173

Sign up to vote on this title
UsefulNot useful