Вы находитесь на странице: 1из 835

Au cur de Java

volume 1 Notions fondamentales


8e dition Cay S. Horstmann et Gary Cornell

Le prsent ouvrage est la traduction de Core Java, vol. I Fundamentals, 8e d., de Cay Hortsmann et Gary Cornell, publi par Pearson Education Inc./Prentice Hall, Copyright 2008 Sun Microsystem Inc. Authorized translation from the English language edition, entitled CORE JAVA, VOL. I FUNDAMENTALS, 8th Edition By CAY HORTSMANN and GARY CORNELL, published by Pearson Education Inc., publishing as Prentice Hall, Copyright 2008 Sun Microsystem Inc., 4150 Network Circle, Santa Clara, California 95054 USA. All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage retrieval system, without permission from Pearson Education, Inc. French language edition published by PEARSON EDUCATION FRANCE, Copyright 2008

Publi par Pearson Education France 47 bis, rue des Vinaigriers 75010 PARIS Tl. : 01 72 74 90 00 Mise en pages : TyPAO

Titre original : Core Java, volume 1 Fundamentals Traduit de lamricain par : Christiane Silhol et Nathalie Le Guillou de Penanros ISBN original : 0-13-235476-4 Copyright 2008 Sun Microsystems, Inc. Tous droits rservs

ISBN : 978-2-7440-4080-1 Copyright 2009 Pearson Education France Sun Microsystems Inc. 901 San Antonio Road, Palo Alto, California 94303 USA

Tous droits rservs. Aucune reprsentation ou reproduction, mme partielle, autre que celles prvues larticle L. 122-5 2 et 3 a) du code de la proprit intellectuelle ne peut tre faite sans lautorisation expresse de Pearson Education France ou, le cas chant, sans le respect des modalits prvues larticle L. 122-10 dudit code.

Table des matires

Introduction .................................................................................................................................. Avertissement au lecteur .......................................................................................................... A propos de ce livre ................................................................................................................. Conventions .............................................................................................................................. Exemples de code ..................................................................................................................... Chapitre 1. Une introduction Java ......................................................................................... Java, plate-forme de programmation ........................................................................................ Les termes cls du livre blanc de Java ...................................................................................... Simplicit ............................................................................................................................ Orient objet ....................................................................................................................... Compatible avec les rseaux ............................................................................................... Fiabilit ............................................................................................................................... Scurit ............................................................................................................................... Architecture neutre ............................................................................................................. Portabilit ........................................................................................................................... Interprt ............................................................................................................................. Performances leves .......................................................................................................... Multithread ......................................................................................................................... Java, langage dynamique .................................................................................................... Les applets Java et Internet ...................................................................................................... Bref historique de Java ............................................................................................................. Les ides fausses les plus rpandues concernant Java .............................................................. Chapitre 2. Lenvironnement de programmation de Java ...................................................... Installation du kit de dveloppement Java ................................................................................ Tlcharger le JDK ............................................................................................................. Congurer le chemin dexcution ...................................................................................... Installer la bibliothque et la documentation ...................................................................... Installer les exemples de programmes ................................................................................ Explorer les rpertoires de Java ..........................................................................................

1 1 2 4 5 7 7 8 8 9 10 10 10 11 12 12 12 13 13 14 15 17 21 21 22 24 26 26 27

IV

Table des matires

Choix de lenvironnement de dveloppement .......................................................................... Utilisation des outils de ligne de commande ............................................................................ Conseils pour la recherche derreurs .................................................................................. Utilisation dun environnement de dveloppement intgr ...................................................... Localiser les erreurs de compilation ................................................................................... Excution dune application graphique .................................................................................... Elaboration et excution dapplets ........................................................................................... Chapitre 3. Structures fondamentales de la programmation Java ......................................... Un exemple simple de programme Java ................................................................................... Commentaires .......................................................................................................................... Types de donnes ..................................................................................................................... Entiers ................................................................................................................................. Types virgule ottante ...................................................................................................... Le type char ...................................................................................................................... Type boolen ....................................................................................................................... Variables ................................................................................................................................... Initialisation des variables ....................................................................................................... Constantes ........................................................................................................................... Oprateurs ................................................................................................................................ Oprateurs dincrmentation et de dcrmentation ............................................................ Oprateurs relationnels et boolens .................................................................................... Oprateurs binaires ............................................................................................................. Fonctions mathmatiques et constantes .............................................................................. Conversions de types numriques ...................................................................................... Transtypages ....................................................................................................................... Parenthses et hirarchie des oprateurs ............................................................................ Types numrs .................................................................................................................. Chanes ..................................................................................................................................... Sous-chanes ....................................................................................................................... Concatnation ..................................................................................................................... Les chanes sont inaltrables .............................................................................................. Test dgalit des chanes ................................................................................................... Points de code et units de code ......................................................................................... LAPI String .................................................................................................................... Lire la documentation API en ligne .................................................................................... Construction de chanes ...................................................................................................... Entres et sorties ....................................................................................................................... Lire les caractres entrs ..................................................................................................... Mise en forme de lafchage .............................................................................................. Entre et sortie de chiers ..................................................................................................

28 28 30 31 34 35 37 41 42 45 45 46 47 48 49 50 51 52 52 54 54 55 56 57 58 58 59 60 60 60 61 62 63 64 66 69 70 70 72 76

Table des matires

Flux dexcution ....................................................................................................................... Porte dun bloc .................................................................................................................. Instructions conditionnelles ................................................................................................ Boucles .............................................................................................................................. Boucles dtermines ........................................................................................................... Slections multiples linstruction switch .................................................................... Interrompre le ux dexcution .......................................................................................... Grands nombres ....................................................................................................................... Tableaux ................................................................................................................................... La boucle "for each" ........................................................................................................... Initialiseurs de tableaux et tableaux anonymes .................................................................. Copie des tableaux .............................................................................................................. Paramtres de ligne de commande ..................................................................................... Tri dun tableau ................................................................................................................... Tableaux multidimensionnels ............................................................................................. Tableaux irrguliers ............................................................................................................ Chapitre 4. Objets et classes ...................................................................................................... Introduction la programmation oriente objet ....................................................................... Les classes .......................................................................................................................... Les objets ............................................................................................................................ Identication des classes .......................................................................................................... Relations entre les classes ................................................................................................... Utilisation des classes existantes .............................................................................................. Objets et variables objet ..................................................................................................... La classe GregorianCalendar de la bibliothque Java ..................................................... Les mthodes daltration et les mthodes daccs ............................................................ Dnition de vos propres classes ............................................................................................. Une classe Employee .......................................................................................................... Travailler avec plusieurs chiers source ............................................................................. Analyser la classe Employee ............................................................................................. Premiers pas avec les constructeurs .................................................................................... Paramtres implicites et explicites ...................................................................................... Avantages de lencapsulation ............................................................................................. Privilges daccs fonds sur les classes ............................................................................ Mthodes prives ................................................................................................................ Champs dinstance final .................................................................................................. Champs et mthodes statiques .................................................................................................. Champs statiques ................................................................................................................ Constantes statiques ............................................................................................................ Mthodes statiques .............................................................................................................

78 78 79 82 86 90 92 94 96 97 98 99 101 102 105 108 111 112 113 113 114 115 116 117 120 121 128 128 131 132 132 134 135 137 137 138 138 138 139 140

VI

Table des matires

Mthodes "factory" ............................................................................................................. La mthode main ............................................................................................................... Paramtres des mthodes ......................................................................................................... Construction dun objet ........................................................................................................... Surcharge ............................................................................................................................ Initialisation des champs par dfaut ................................................................................... Constructeurs par dfaut ..................................................................................................... Initialisation explicite de champ ......................................................................................... Noms de paramtres ........................................................................................................... Appel dun autre constructeur ............................................................................................ Blocs dinitialisation ........................................................................................................... Destruction des objets et mthode nalize ......................................................................... Packages ................................................................................................................................... Importation des classes ....................................................................................................... Imports statiques ................................................................................................................. Ajout dune classe dans un package ................................................................................... Visibilit dans un package .................................................................................................. Le chemin de classe .................................................................................................................. Dnition du chemin de classe ........................................................................................... Commentaires pour la documentation ...................................................................................... Insertion des commentaires ................................................................................................ Commentaires de classe ...................................................................................................... Commentaires de mthode ................................................................................................. Commentaires de champ .................................................................................................... Commentaires gnraux ..................................................................................................... Commentaires de package et densemble ........................................................................... Extraction des commentaires .............................................................................................. Conseils pour la conception de classes .................................................................................... Chapitre 5. Lhritage ................................................................................................................. Classes, superclasses et sous-classes ........................................................................................ Hirarchie dhritage .......................................................................................................... Polymorphisme ................................................................................................................... Liaison dynamique ............................................................................................................. Empcher lhritage : les classes et les mthodes final .................................................. Transtypage ......................................................................................................................... Classes abstraites ................................................................................................................ Accs protg ..................................................................................................................... Object : la superclasse cosmique .............................................................................................. La mthode equals ........................................................................................................... Test dgalit et hritage .....................................................................................................

141 141 144 150 150 151 151 152 153 153 154 158 158 159 161 161 164 165 168 168 169 169 170 171 171 172 172 173 177 178 184 185 186 189 190 192 197 198 198 199

Table des matires

VII

La mthode hashCode ....................................................................................................... La mthode toString ....................................................................................................... Listes de tableaux gnriques ................................................................................................... Accder aux lments dune liste de tableaux .................................................................... Compatibilit entre les listes de tableaux brutes et types ................................................. Enveloppes dobjets et autoboxing ........................................................................................... Mthodes ayant un nombre variable de paramtres ........................................................... Classes dnumration .............................................................................................................. Rexion .................................................................................................................................. La classe Class ................................................................................................................. Introduction linterception dexceptions .......................................................................... La rexion pour analyser les caractristiques dune classe .............................................. La rexion pour lanalyse des objets lexcution .......................................................... La rexion pour crer un tableau gnrique ..................................................................... Les pointeurs de mthodes ................................................................................................. Conseils pour lutilisation de lhritage ................................................................................... Chapitre 6. Interfaces et classes internes ................................................................................ Interfaces .................................................................................................................................. Proprits des interfaces ..................................................................................................... Interfaces et classes abstraites ............................................................................................ Clonage dobjets ....................................................................................................................... Interfaces et callbacks .............................................................................................................. Classes internes ........................................................................................................................ Accder ltat dun objet laide dune classe interne .................................................... Rgles particulires de syntaxe pour les classes internes ................................................... Utilit, ncessit et scurit des classes internes ................................................................ Classes internes locales ...................................................................................................... Accs aux variables final partir de mthodes externes ................................................. Classes internes anonymes ................................................................................................. Classes internes statiques .................................................................................................... Proxies ...................................................................................................................................... Proprits des classes proxy ............................................................................................... Chapitre 7. Programmation graphique .................................................................................... Introduction Swing ................................................................................................................ Cration dun cadre .................................................................................................................. Positionnement dun cadre ....................................................................................................... Proprits des cadres .......................................................................................................... Dterminer une taille de cadre adquate ............................................................................. Afchage des informations dans un composant .......................................................................

202 204 210 212 216 217 220 221 223 224 226 227 232 237 240 244 247 248 253 254 255 261 264 265 268 269 271 272 274 276 280 284 287 288 291 294 296 296 300

VIII

Table des matires

Formes 2D ................................................................................................................................ Couleurs ................................................................................................................................... Texte et polices ......................................................................................................................... Afchage dimages .................................................................................................................. Chapitre 8. Gestion des vnements .......................................................................................... Introduction la gestion des vnements ................................................................................. Exemple : gestion dun clic de bouton ............................................................................... Etre confortable avec les classes internes ........................................................................... Crer des couteurs contenant un seul appel de mthode .................................................. Exemple : modication du "look and feel" ......................................................................... Classes adaptateurs ............................................................................................................. Actions ..................................................................................................................................... Evnements de la souris ..................................................................................................... Hirarchie des vnements AWT ............................................................................................. Evnements smantiques et de bas niveau ......................................................................... Chapitre 9. Swing et les composants dinterface utilisateur ................................................... Swing et larchitecture Modle-Vue-Contrleur ...................................................................... Modles de conception ....................................................................................................... Larchitecture Modle-Vue-Contrleur .............................................................................. Une analyse Modle-Vue-Contrleur des boutons Swing .................................................. Introduction la gestion de mise en forme .............................................................................. Gestionnaire BorderLayout ................................................................................................ Disposition des grilles ........................................................................................................ Entre de texte .......................................................................................................................... Champs de texte .................................................................................................................. Etiquettes et composants dtiquetage ................................................................................ Champs de mot de passe ..................................................................................................... Zones de texte ..................................................................................................................... Volets de dlement ............................................................................................................ Composants du choix ............................................................................................................... Cases cocher .................................................................................................................... Boutons radio ...................................................................................................................... Bordures ............................................................................................................................. Listes droulantes ............................................................................................................... Curseurs .............................................................................................................................. Menus ....................................................................................................................................... Cration dun menu ............................................................................................................ Icnes et options de menu ..................................................................................................

305 312 315 323 327 327 330 334 337 337 341 344 352 359 361 365 366 366 367 370 372 374 376 380 380 382 383 384 385 387 387 390 394 398 402 408 408 410

Table des matires

IX

Options de menu avec cases cocher et boutons radio ...................................................... Menus contextuels .............................................................................................................. Caractres mnmoniques et raccourcis clavier ................................................................... Activation et dsactivation des options de menu ................................................................ Barres doutils .................................................................................................................... Bulles daide ....................................................................................................................... Mise en forme sophistique ...................................................................................................... Gestionnaire GridBagLayout .............................................................................................. GroupLayout ....................................................................................................................... Cration sans gestionnaire de mise en forme ..................................................................... Gestionnaires de mise en forme personnaliss ................................................................... Squence de tabulation ....................................................................................................... Botes de dialogue .................................................................................................................... Botes de dialogue doptions .............................................................................................. Cration de botes de dialogue ............................................................................................ Echange de donnes ............................................................................................................ Botes de dialogue Fichier .................................................................................................. Slecteurs de couleur .......................................................................................................... Chapitre 10. Dployer des applets et des applications ............................................................. Les chiers JAR ....................................................................................................................... Le manifeste ....................................................................................................................... Fichiers JAR excutables .................................................................................................... Les ressources ..................................................................................................................... Verrouillage ........................................................................................................................ Java Web Start .......................................................................................................................... Le bac sable ..................................................................................................................... Code sign .......................................................................................................................... LAPI JNLP ....................................................................................................................... Les applets ................................................................................................................................ Un petit applet .................................................................................................................... Conversion dune application en applet .............................................................................. Balises HTML et attributs pour applets .............................................................................. La balise object ................................................................................................................... Passer des informations un applet avec des paramtres ................................................... Accder aux images et chiers audio ................................................................................. Le contexte dapplet ........................................................................................................... La communication interapplets .......................................................................................... Faire afcher des informations par le navigateur ............................................................... Cest un applet et cest aussi une application ! ...................................................................

411 412 414 416 420 422 425 427 436 445 446 450 451 452 461 466 472 483 491 492 493 494 495 498 498 502 503 505 513 514 517 518 521 521 526 528 528 528 530

Table des matires

Stockage des prfrences dapplications .................................................................................. Concordances de proprits ................................................................................................ LAPI Preferences .............................................................................................................. Chapitre 11. Exceptions, consignation, assertion et mise au point ......................................... Le traitement des erreurs .......................................................................................................... Le classement des exceptions ............................................................................................. Signaler les exceptions sous contrle ................................................................................. Comment lancer une exception .......................................................................................... Crer des classes dexception ............................................................................................. Capturer les exceptions ............................................................................................................ Capturer des exceptions multiples ...................................................................................... Relancer et enchaner les exceptions .................................................................................. La clause nally .................................................................................................................. Analyser les traces de piles ................................................................................................. Quelques conseils sur lutilisation des exceptions ................................................................... Les assertions ........................................................................................................................... Activation et dsactivation des assertions ........................................................................... Vrication des paramtres avec des assertions ................................................................. Utiliser des assertions pour documenter des hypothses .................................................... La consignation ........................................................................................................................ Consignation de base .......................................................................................................... Consignation avance ......................................................................................................... Modier la conguration du gestionnaire de journaux ...................................................... La localisation .................................................................................................................... Les gestionnaires ................................................................................................................ Les ltres ............................................................................................................................ Les formateurs .................................................................................................................... Une astuce de consignation ................................................................................................ Les techniques de mise au point ............................................................................................... Utiliser une fentre de console ........................................................................................... Tracer les vnements AWT ............................................................................................... Le robot AWT ..................................................................................................................... Utiliser un dbogueur ............................................................................................................... Chapitre 12. Programmation gnrique ................................................................................... Pourquoi la programmation gnrique ? .................................................................................. Y a-t-il un programmeur gnrique dans la salle ? ............................................................. Dnition dune classe gnrique simple ................................................................................. Mthodes gnriques ................................................................................................................ Limites pour variables de type .................................................................................................

535 536 540 547 548 549 551 553 554 555 557 557 558 561 564 567 568 568 569 570 571 571 574 575 576 579 579 580 587 593 595 598 602 607 608 609 610 612 613

Table des matires

XI

Code gnrique et machine virtuelle ........................................................................................ Traduire les expressions gnriques ................................................................................... Traduire les mthodes gnriques ...................................................................................... Appeler un code existant .................................................................................................... Restrictions et limites ............................................................................................................... Les paramtres de type ne peuvent pas tre instancis avec les types primitifs ................. Les informations sur le type dexcution ne fonctionnent quavec les types bruts ............ Vous ne pouvez pas lancer ou intercepter des instances dune classe gnrique ............... Les tableaux de types avec paramtres ne sont pas autoriss ............................................. Vous ne pouvez pas instancier des variables de type .......................................................... Les variables de type ne sont pas valables dans des contextes statiques des classes gnriques ........................................................................................................ Attention aux conits aprs un effacement ........................................................................ Rgles dhritage pour les types gnriques ............................................................................ Types joker ............................................................................................................................... Limites de supertypes pour les jokers ................................................................................. Jokers sans limites .............................................................................................................. Capture de caractres joker ................................................................................................. Rexion et gnrique .............................................................................................................. Utilisation des paramtres Class<T> pour la concordance de type ................................... Informations de type gnrique dans la machine virtuelle ................................................. Chapitre 13. Collections ............................................................................................................. Les interfaces de collection ...................................................................................................... Sparer les interfaces dune collection et leur implmentation .......................................... Interfaces de collection et ditration dans la bibliothque Java ........................................ Suppression dlments ...................................................................................................... Mthodes utilitaires gnriques .......................................................................................... Les collections concrtes .......................................................................................................... Listes chanes .................................................................................................................... Listes de tableaux ............................................................................................................... Tables de hachage ............................................................................................................... Arbres ................................................................................................................................. Queues et deques ................................................................................................................ Queues de priorit ............................................................................................................... Cartes .................................................................................................................................. Classes de cartes et de set spcialises ............................................................................... La structure des collections ...................................................................................................... Les vues et les emballages .................................................................................................. Les oprations de masse ..................................................................................................... Conversion entre collections et tableaux ............................................................................

615 616 617 619 620 620 620 621 622 622 624 624 625 627 628 630 631 634 635 636 643 643 644 646 649 649 651 652 660 661 664 670 671 673 677 681 685 691 692

XII

Table des matires

Algorithmes .............................................................................................................................. Trier et mlanger ................................................................................................................. Recherche binaire ............................................................................................................... Algorithmes simples ........................................................................................................... Ecrire vos propres algorithmes ........................................................................................... Les anciennes collections ......................................................................................................... La classe Hashtable ............................................................................................................ Les numrations ................................................................................................................ Cartes de proprits ............................................................................................................ Piles .................................................................................................................................... Les ensembles de bits ......................................................................................................... Chapitre 14. Multithreads .......................................................................................................... Quest-ce quun thread ? .......................................................................................................... Utiliser des threads pour laisser une chance aux autres tches ........................................... Interrompre des threads ............................................................................................................ Les tats dun thread ........................................................................................................... Threads termins ................................................................................................................. Proprits dun thread .............................................................................................................. Priorits dun thread ........................................................................................................... Threads dmons .................................................................................................................. Gestionnaire dexceptions non rcupres ......................................................................... Synchronisation ........................................................................................................................ Exemple de condition de course ......................................................................................... Explication des conditions de course ................................................................................. Verrous dobjet ................................................................................................................... Objets de condition ............................................................................................................. Le mot cl synchronized ..................................................................................................... Blocs synchroniss ............................................................................................................. Le concept des moniteurs ................................................................................................... Champs volatiles ................................................................................................................. Verrous morts ...................................................................................................................... Test de verrous et temporisations ....................................................................................... Lire et crire des verrous .................................................................................................... Pourquoi les mthodes stop et suspend ne sont plus utilises .................................................. Queues de blocage .................................................................................................................... Collections compatibles avec les threads ................................................................................. Cartes, jeux et queues efcaces .......................................................................................... CopyOnWriteArray ........................................................................................................... Anciennes collections compatibles avec les threads .......................................................... Callable et Future .....................................................................................................................

693 694 696 698 699 700 700 701 702 702 703 707 708 713 718 721 722 724 724 725 725 727 727 731 732 735 740 743 745 745 747 750 751 752 754 760 761 762 763 764

Table des matires

XIII

Executors .................................................................................................................................. Pools de threads .................................................................................................................. Excution programme ....................................................................................................... Contrle de groupes de tches ............................................................................................ Synchronizers ........................................................................................................................... Smaphores ......................................................................................................................... Verrous Countdown ............................................................................................................ Barrires ............................................................................................................................. Exchanger ........................................................................................................................... Queues synchrones ............................................................................................................. Exemple : pause et reprise dune animation ....................................................................... Threads et Swing ................................................................................................................ Excution de tches longues ............................................................................................... Utilisation du travailleur Swing .......................................................................................... La rgle du thread unique ................................................................................................... Annexe. Les mots cls de Java ................................................................................................... Index ..............................................................................................................................................

768 769 772 773 774 775 776 776 777 777 777 783 784 788 795 797 801

Introduction
Avertissement au lecteur
Vers la n de 1995, le langage de programmation Java surgit sur la grande scne dInternet et obtint immdiatement un norme succs. La prtention de Java est de constituer la colle universelle capable de connecter les utilisateurs aux informations, que celles-ci proviennent de serveurs Web, de bases de donnes, de fournisseurs dinformations ou de toute autre source imaginable. Et Java se trouve en bonne position pour relever ce d. Il sagit dun langage de conception trs robuste qui a t adopt par la majorit des principaux fournisseurs, lexception de Microsoft. Ses caractristiques intgres de scurit offrent un sentiment de conance aux programmeurs comme aux utilisateurs des applications. De plus, Java intgre des fonctionnalits qui facilitent grandement certaines tches de programmation avances, comme la gestion rseaux, la connectivit bases de donnes ou le dveloppement dapplications multitches. Depuis le lancement de Java, Sun Microsystems a mis sept rvisions majeures du kit de dveloppement Java. Au cours des onze dernires annes, lAPI (interface de programmation dapplication) est passe de 200 plus de 3 000 classes. Elle traite maintenant des domaines aussi divers que la construction de linterface utilisateur, la gestion des bases de donnes, linternationalisation, la scurit et le traitement du code XML. Louvrage que vous tenez entre les mains est le premier volume de la huitime dition de Au cur de Java. Chaque dition a suivi la sortie du kit de dveloppement daussi prs que possible et, chaque fois, nous avons rcrit le livre pour y inclure les toutes dernires fonctionnalits. Cette dition a t mise jour pour traiter des nouveauts de Java Standard Edition (SE) 6. Ce livre, comme les ditions prcdentes, sadresse essentiellement aux programmeurs professionnels dsireux dutiliser Java pour dvelopper de vritables projets. Nous considrons que le lecteur possde dj une solide habitude de la programmation autre que Java et quil fuit les ouvrages qui fourmillent dexemples dpourvus dutilit pratique (comme les grille-pain, les animaux du zoo ou le "texte nerveux"). Vous nen trouverez pas ici. Notre objectif est de vous permettre dapprhender le langage et la bibliothque Java, et non de vous donner lillusion que vous les comprenez. Vous trouverez de nombreux programmes de dmonstration qui abordent la plupart des sujets traits dans cet ouvrage. Ces programmes sont volontairement simples an que le lecteur puisse se concentrer sur les points importants. Nanmoins, dans la plupart des cas, il sagit dapplications utiles qui pourront vous servir de base pour le dveloppement de vos propres projets. Nous supposons galement que vous souhaitez apprendre les caractristiques avances de Java ; cest pourquoi nous tudierons en dtail :
m m m

la programmation oriente objet ; le mcanisme de rexion de Java et de proxy ; les interfaces et classes internes ;

Au cur de Java 2 - Notions fondamentales

m m m m m m

le modle dcouteur dvnement ; la conception dinterfaces graphiques avec la bote outils Swing ; la gestion des exceptions ; la programmation gnrique ; le cadre des collections ; la simultanit.

Enn, compte tenu de lexplosion de la bibliothque de classes de Java, nous avons d rpartir ltude de toutes les fonctionnalits sur deux volumes. Le premier, que vous avez en main, se concentre sur les concepts fondamentaux du langage Java, ainsi que sur les bases de la programmation dune interface graphique. Le second volume traite plus exhaustivement des fonctionnalits dentreprise et de la programmation avance des interfaces utilisateur. Il aborde les sujets suivants :
m m m m m m m m m m m

les chiers et les ux ; les objets distribus ; les bases de donnes ; les composants GUI avancs ; les mthodes natives ; le traitement XML ; la programmation rseau ; le graphisme avanc ; linternationalisation ; JavaBeans ; les annotations.

Pour cette dition, nous avons rorganis le contenu des deux volumes. Le multithread, notamment, est trait dans le Volume I, car il est devenu trs important, la loi de Moore tant dsormais obsolte. Lors de la rdaction dun ouvrage comme celui-ci, il est invitable de commettre des erreurs et des inexactitudes. Nous avons donc prpar sur le site Web http://horstmann.com/corejava une liste de questions courantes, de corrections et dexplications. Un formulaire permettant de signaler des bogues et de suggrer des amliorations est plac stratgiquement la n de la page des corrections (pour vous encourager la lire). Ne soyez pas du si nous ne rpondons pas chaque requte ou si nous ne vous crivons pas rapidement. Nous lisons tous les e-mails et apprcions vos commentaires, qui nous permettent damliorer les futures versions de cet ouvrage.

A propos de ce livre
Le Chapitre 1 prsentera les caractristiques de Java qui le distinguent des autres langages de programmation. Nous expliquerons les intentions des concepteurs du langage et nous montrerons dans quelle mesure ils sont parvenus leurs ns. Nous terminerons par un historique de Java et nous prciserons la manire dont il a volu.

Introduction

Le Chapitre 2 vous indiquera comment tlcharger et installer le JDK et les exemples de programme du livre. Nous vous guiderons ensuite dans la compilation et lexcution de trois programmes Java typiques : une application console, une application graphique et un applet, grce du JDK brut, un diteur de texte activ pour Java et un IDE Java. Nous entamerons au Chapitre 3 une tude approfondie du langage, en commenant par les lments de base : les variables, les boucles et les fonctions simples. Si vous tes un programmeur C ou C++, tout cela ne vous posera pas de problme, car la syntaxe employe est comparable celle de C. Si vous avez une autre formation, par exemple en Visual Basic, nous vous conseillons de lire attentivement ce chapitre. La programmation oriente objet (POO) est maintenant au cur des mthodes modernes de programmation et Java est un langage orient objet. Le Chapitre 4 prsentera lencapsulation la premire des deux notions fondamentales de lorientation objet et le mcanisme du langage Java qui permet de limplmenter, cest--dire les classes et les mthodes. En plus des rgles de Java, nous vous proposerons des conseils pour une bonne conception oriente objet. Nous aborderons ensuite le merveilleux outil javadoc qui permet de transformer les commentaires de votre code en une documentation au format HTML. Si vous tes un habitu du C++, vous pourrez vous contenter de parcourir rapidement ce chapitre. Les programmeurs qui ne sont pas familiariss avec la programmation oriente objet doivent se donner le temps dtudier ces concepts avant de poursuivre leur exploration de Java. Les classes et lencapsulation ne constituent quune partie du concept de POO et le Chapitre 5 introduira lautre lment essentiel : lhritage. Celui-ci permet de rcuprer une classe existante et de la modier selon vos besoins. Il sagit l dune technique fondamentale de la programmation Java. Le mcanisme dhritage de Java est comparable celui de C++. Ici encore, les programmeurs C++ pourront se concentrer uniquement sur les diffrences entre les deux langages. Le Chapitre 6 vous montrera comment utiliser la notion dinterface. Les interfaces permettent de dpasser le modle dhritage simple vu au Chapitre 5. En matrisant les interfaces, vous pourrez proter pleinement de lapproche oriente objet de la programmation Java. Nous traiterons galement dans ce chapitre une caractristique technique trs utile de Java, appele classe interne. Les classes internes permettent dobtenir des programmes plus propres et plus concis. Au Chapitre 7, nous commencerons vritablement la programmation dapplications. Tout programmeur en Java doit connatre un minimum de programmation de GUI, des bases que vous trouverez dans ce volume. Nous vous montrerons comment crer des fentres, y dessiner et y tracer des gures gomtriques, formater du texte avec diffrentes polices et afcher des images. Le Chapitre 8 sera consacr une tude dtaille du modle dvnement AWT, la bote outils de fentre abstraite. Nous verrons comment crire le code permettant de rpondre des vnements tels que des clics de la souris ou des frappes de touches. Vous verrez par la mme occasion comment grer des lments de linterface utilisateur graphique comme les boutons ou les panneaux. Le Chapitre 9 examinera de manire approfondie loutil Swing, qui permet de crer des interfaces graphiques multi-plates-formes. Vous apprendrez tout ce quil faut savoir sur les diffrents types de boutons, les composants de saisie, les bordures, les barres de dlement, les zones de listes, les menus et les botes de dialogue. Certains de ces composants, plus avancs, seront tudis dans le Volume II. Le Chapitre 10 indique comment dployer des programmes, sous forme dapplications ou dapplets. Nous expliquons comment emballer des programmes dans les chiers JAR et livrer des applications

Au cur de Java 2 - Notions fondamentales

sur Internet avec Java Web Start et les mcanismes dapplet. Enn, nous verrons la manire dont les programmes Java peuvent stocker et rcuprer des informations de conguration lorsquils ont t dploys. Le Chapitre 11 traitera de la gestion des exceptions, un mcanisme robuste qui sappuie sur le fait que mme les bons programmes peuvent subir des dommages. Les exceptions fournissent un moyen efcace de sparer le code normal de traitement et la gestion derreurs. Bien entendu, mme si vous avez scuris votre programme en grant toutes les conditions exceptionnelles, il nest pas certain quil fonctionne parfaitement. La seconde partie de ce chapitre vous donnera quelques astuces de dbogage. Pour terminer, nous vous accompagnerons dans une session de dbogage. Le Chapitre 12 donne une vue densemble de la programmation gnrique, une avance majeure de Java SE 5.0. Elle facilite la lecture de vos programmes, tout en les scurisant. Nous vous montrerons comment utiliser des types forts et comment traiter les complexits lies aux transtypages peu srs, mais aussi comment grer les complexits naissant du besoin de compatibilit avec les anciennes versions de Java. Le Chapitre 13 traitera des cadres de collections de la plate-forme Java. Lorsque vous souhaiterez collecter plusieurs objets pour les rcuprer plus tard, vous utiliserez une collection adapte votre situation, au lieu de simplement faire entrer les lments dans un tableau. Ce chapitre indique comment proter des collections standard dj cres pour vous. Le Chapitre 14 clturera ce livre avec une discussion sur le multithreading, qui permet de programmer des tches raliser en parallle (un thread est un ux de contrle dans un programme). Nous vous indiquerons comment congurer les threads et en grer la synchronisation. Le multithreading a beaucoup volu dans Java SE 5.0 et nous vous en montrerons les nouveaux mcanismes. LAnnexe est consacre aux mots cls du langage Java.

Conventions
Comme cest lusage dans la plupart des ouvrages dinformatique, nous employons la police Courier pour le code des programmes.
INFO ASTUCE
Les infos et les astuces sont repres par une de ces deux icnes.

ATTENTION
Une icne "Attention" vous prvient sil y a du danger lhorizon.

INFO C++
De nombreuses notes dinfo C++ prcisent les diffrences entre Java et C++. Vous pouvez ignorer ces notes si vous ntes pas familiaris avec ce langage.

Introduction

API Java

Java est accompagn dune importante bibliothque de programmation (API, Application Programming Interface). Lorsque nous utilisons pour la premire fois un appel lAPI, nous proposons galement une brve description dans une note "API" situe la n de la section. Ces descriptions sont un peu plus informelles que celles de la documentation ofcielle en ligne, mais nous esprons quelles sont plus instructives. Les programmes dont le code source se trouve sur le Web sont fournis sous forme de listings, comme ceci :
Listing 2.4 : WelcomeApplet.java

Exemples de code
Vous trouverez sur le site des ditions Pearson Education (www.pearsoneducation.fr) tous les exemples du livre, sous forme compresse. Ils peuvent tre dcompresss avec un des outils courants du march ou avec lutilitaire jar du kit JDK. Reportez-vous au Chapitre 2 pour en savoir plus sur linstallation du JDK et des listings dexemple.

1
Une introduction Java
Au sommaire de ce chapitre

Java, plate-forme de programmation Les termes cls du livre blanc de Java Les applets Java et Internet Bref historique de Java Les ides fausses les plus rpandues concernant Java
La premire version de Java, sortie en 1996, a fait natre beaucoup de passions, pas seulement au niveau de la presse informatique, mais galement dans la presse plus gnraliste comme The New York Times, The Washington Post et Business Week. Java prsente lavantage dtre le premier et le seul langage de programmation dont lhistoire est conte la radio. Il est plutt amusant de revisiter cette poque pionnire, nous vous prsenterons donc un bref historique de Java dans ce chapitre.

Java, plate-forme de programmation


Dans la premire dition de cet ouvrage, nous avons d crire ceci : "La rputation de Java en tant que langage informatique est exagre : Java est assurment un bon langage de programmation. Il sagit, sans aucun doute, de lun des meilleurs disponibles pour un programmeur srieux. Java aurait, potentiellement, pu tre un grand langage de programmation, mais il est probablement trop tard pour cela. Lorsquun langage commence tre exploit, se pose le problme de la compatibilit avec le code existant." Un haut responsable de Sun Microsystems, que nous ne nommerons pas, a adress notre diteur de nombreux commentaires sur ce paragraphe. Mais, avec le temps, notre pronostic semble savrer. Java prsente de trs bonnes fonctionnalits (nous les verrons en dtail plus loin dans ce chapitre). Il a pourtant sa part dinconvnients, et les derniers ajouts ne sont pas aussi agrables que les premiers, et ce pour des raisons de compatibilit. Toutefois, comme nous le disions dans la premire dition, Java na jamais t quun langage. Mme sil en existe foison, peu font beaucoup dclats. Java est une plate-forme complte, disposant

Au cur de Java 2 - Notions fondamentales

dune importante bibliothque, dune grande quantit de code rutilisable et dun environnement dexcution qui propose des services tels que la scurit, la portabilit sur les systmes dexploitation et le ramasse-miettes automatique. En tant que programmeur, vous voulez un langage la syntaxe agrable et la smantique comprhensible (donc, pas de C++). Java rpond ces critres, comme des dizaines dautres langages. Certains vous proposent la portabilit, le ramasse-miettes et des outils du mme genre, mais ils ne disposent pas de vraie bibliothque, ce qui vous oblige dployer la vtre si vous souhaitez utiliser de beaux graphiques, le rseau ou laccs aux bases de donnes. Java regroupe tout cela : un langage de qualit, un environnement dexcution idoine et une grande bibliothque. Cest cette combinaison qui fait de Java une proposition laquelle de nombreux programmeurs ne rsistent pas.

Les termes cls du livre blanc de Java


Les auteurs de Java ont crit un important livre blanc qui prsente les objectifs et les ralisations de leur conception. Ce livre sarticule autour des onze termes cls suivants :
Simplicit Orient objet Compatible avec les rseaux Fiabilit Scurit Architecture neutre Portabilit Interprt Performances leves Multithread Dynamique

Dans la suite de ce chapitre, nous allons :


m

rsumer, par lintermdiaire dextraits du livre blanc, ce que les concepteurs de Java ont voulu traduire avec chacun de ces termes cls ; exprimer ce que nous pensons de chaque terme, partir de notre exprience de la version actuelle de Java.
INFO

A lheure o nous crivons ces lignes, le livre blanc est disponible ladresse suivante : http://java.sun.com/docs/ white/langenv/. Le rsum des onze mots cls gure ladresse http://java.sun.com/docs/overviews/java/javaoverview-1.html.

Simplicit
*

Nous avons voulu crer un systme qui puisse tre programm simplement, sans ncessiter un apprentissage sotrique, et qui tire parti de lexprience standard actuelle. En consquence, mme si nous pensions que C++ ne convenait pas, Java a t conu de faon relativement proche de ce langage dans le dessein de faciliter la comprhension du systme. De nombreuses fonctions compliques, mal comprises, rarement utilises, de C++, qui nous semblaient par exprience apporter plus dinconvnients que davantages, ont t supprimes de Java.

Chapitre 1

Une introduction Java

La syntaxe de Java reprsente rellement une version amliore de C++. Les chiers den-tte, larithmtique des pointeurs (ou mme une syntaxe de pointeur), les structures, les unions, la surcharge doprateur, les classes de base virtuelles, etc., ne sont plus ncessaires (tout au long de cet ouvrage, nous avons inclus des notes Info C++ dcrivant plus prcisment les diffrences entre Java et C++). Les concepteurs nont cependant pas tent de modier certaines fonctions piges de C++ telles que linstruction switch. Si vous connaissez C++, le passage la syntaxe de Java vous semblera facile. Si vous tes habitu un environnement de programmation visuel (tel que Visual Basic), le langage Java vous paratra plus complexe. Une partie de la syntaxe vous semblera trange (mme si sa matrise est rapide). Plus important encore, la programmation en Java ncessitera davantage de travail. Lintrt de Visual Basic rside dans le fait que son environnement visuel de conception fournit de faon presque automatique une grande partie de linfrastructure dune application. En Java, la fonctionnalit quivalente doit tre programme manuellement, gnralement laide dune quantit respectable de code. Il existe cependant des environnements de dveloppement tiers qui permettent lcriture de programmes laide doprations "glisser-dplacer".
*

Un autre avantage de sa simplicit est sa petite taille. Lun des buts de Java est de permettre des logiciels de sexcuter intgralement sur de modestes machines. Ainsi, la taille cumule de linterprteur de base et du support des classes est denviron 40 Ko ; pour supporter les classes standard ainsi que la gestion multitraitement (threads), il faut ajouter 175 Ko.

Cest un exploit. Toutefois, sachez que la taille de la bibliothque a atteint des proportions considrables. Il existe maintenant une Java Micro Edition spare, avec une bibliothque plus petite, convenant aux priphriques intgrs.

Orient objet
*

Pour rester simples, disons que la conception oriente objet est une technique de programmation qui se concentre sur les donnes (les objets) et sur les interfaces avec ces objets. Pour faire une analogie avec la menuiserie, on pourrait dire quun menuisier "orient objet" sintresse essentiellement la chaise (lobjet) quil fabrique et ensuite aux outils utiliss pour la fabriquer. Par opposition, le menuisier "non orient objet" penserait dabord ses outils. Les facilits orientes objet de Java sont essentiellement celles de C++...

Au cours des trente dernires annes, la programmation oriente objet a prouv ses avantages, et il est inconcevable quun langage de programmation moderne nen tire pas parti. En fait, les fonctionnalits orientes objet de Java sont comparables celles de C++. Les diffrences majeures rsident dans lhritage multiple (que Java a remplac par le concept plus simple des interfaces) et le modle objet de Java (mtaclasses), que nous verrons au Chapitre 5.
INFO
Si vous navez aucune exprience des langages de programmation oriente objet, prenez soin de lire attentivement les Chapitres 4 6. Ils vous expliquent ce quest la programmation oriente objet et pour quelles raisons elle est plus utile la programmation de projets sophistiqus que ne le sont les langages procduraux comme C ou Basic.

10

Au cur de Java 2 - Notions fondamentales

Compatible avec les rseaux


*

Java possde une importante bibliothque de routines permettant de grer les protocoles TCP/IP tels que HTTP et FTP. Les applications Java peuvent charger des objets sur Internet et y accder via des URL avec la mme facilit quelles accdent un chier local sur le systme.

Nous avons trouv que les fonctionnalits rseau de Java taient la fois ables et dutilisation aise. Toute personne ayant essay de faire de la programmation pour Internet avec un autre langage se rjouira de la simplicit de Java lorsquil sagit de mettre en uvre des tches lourdes, comme louverture dune connexion avec une socket (nous verrons les rseaux dans le Volume II de cet ouvrage). Le mcanisme dinvocation de mthode distance (RMI) autorise la communication entre objets distribus (voir galement le Volume II).

Fiabilit
*

Java a t conu pour que les programmes qui lutilisent soient ables sous diffrents aspects. Sa conception encourage le programmeur traquer prventivement les ventuels problmes, lancer des vrications dynamiques en cours dexcution et liminer les situations gnratrices derreurs... La seule et unique grosse diffrence entre C/C++ et Java rside dans le fait que ce dernier intgre un modle de pointeur qui carte les risques dcrasement de la mmoire et dendommagement des donnes.

Voil encore une caractristique fort utile. Le compilateur Java dtecte de nombreux problmes qui, dans dautres langages, ne sont visibles quau moment de lexcution. Tous les programmeurs qui ont pass des heures rcuprer une mmoire corrompue par un bogue de pointeur seront trs heureux dexploiter cette caractristique de Java. Si vous connaissez dj des langages comme Visual Basic, qui nexploitent pas les pointeurs de faon explicite, vous vous demandez srement pourquoi ce problme est si important. Les programmeurs en C nont pas cette chance. Ils ont besoin des pointeurs pour accder des chanes, des tableaux, des objets et mme des chiers. Dans Visual Basic, vous nemployez de pointeur pour aucune de ces entits, pas plus que vous navez besoin de vous soucier de leurs allocations en mmoire. En revanche, certaines structures de donnes sont difciles implmenter sans laide de pointeurs. Java vous donne le meilleur des deux mondes. Vous navez pas besoin des pointeurs pour les constructions habituelles, comme les chanes et les tableaux. Vous disposez de la puissance des pointeurs en cas de ncessit, par exemple, pour construire des listes chanes. Et cela en toute scurit dans la mesure o vous ne pouvez jamais accder un mauvais pointeur ni faire des erreurs dallocation de mmoire, pas plus quil nest ncessaire de vous protger des "fuites" de mmoire.

Scurit
*

Java a t conu pour tre exploit dans des environnements serveur et distribus. Dans ce but, la scurit na pas t nglige. Java permet la construction de systmes inaltrables et sans virus.

Dans la premire dition, nous dclarions : "Il ne faut jamais dire fontaine je ne boirai plus de ton eau." Et nous avions raison. Peu aprs la publication de la premire version du JDK, un groupe dexperts de la scurit de luniversit de Princeton a localis les premiers bogues des caractristiques de scurit de Java 1.0. Sun Microsystems a encourag la recherche sur la scurit de Java, en

Chapitre 1

Une introduction Java

11

rendant publiques les caractristiques et limplmentation de la machine virtuelle et des bibliothques de scurit. Il a rpar rapidement tous les bogues connus. Quoi quil en soit, Java se charge de rendre particulirement difcile le contournement des mcanismes de scurit. Jusqu prsent, les bogues qui ont t localiss taient trs subtils et relativement peu nombreux. Ds le dpart, Java a t conu pour rendre impossibles certains types dattaques et, parmi eux :
m m m

la surcharge de la pile dexcution, une attaque commune des vers et des virus ; lendommagement de la mmoire situe lextrieur de son propre espace de traitement ; la lecture ou lcriture des chiers sans autorisation.

Avec le temps, un certain nombre de fonctionnalits relatives la scurit ont t ajoutes Java. Depuis la version 1.1, Java intgre la notion de classe signe numriquement (voir Au cur de Java 2 Volume II, ditions CampusPress), qui vous permet de savoir qui la crite. Votre degr de conance envers son auteur va dterminer lampleur des privilges que vous allez accorder la classe sur votre machine.
INFO
Le mcanisme concurrent de mise disposition de code, labor par Microsoft et fond sur sa technologie ActiveX, emploie exclusivement les signatures numriques pour assurer la scurit. Bien videmment, cela nest pas sufsant. Tous les utilisateurs des produits Microsoft peuvent conrmer que les programmes proposs par des concepteurs bien connus rencontrent des dfaillances qui provoquent des dgts. Le modle de scurit de Java est nettement plus puissant que celui dActiveX dans la mesure o il contrle lapplication en cours dexcution et lempche de faire des ravages.

Architecture neutre
*

Le compilateur gnre un format de chier objet dont larchitecture est neutre le code compil est excutable sur de nombreux processeurs, partir du moment o le systme dexcution de Java est prsent. Pour ce faire, le compilateur Java gnre des instructions en bytecode (ou pseudo-code) qui nont de lien avec aucune architecture dordinateur particulire. Au contraire, ces instructions ont t conues pour tre la fois faciles interprter, quelle que soit la machine, et faciles traduire la vole en code machine natif.

Lide nest pas nouvelle. Il y a plus de trente ans, la mise en uvre originale de Pascal par Niklaus Wirth et le systme Pascal UCSD utilisaient tous deux la mme approche. Bien entendu, linterprtation des bytecodes est forcment plus lente que lexcution des instructions machine pleine vitesse. On peut donc douter de la pertinence de cette ide. Toutefois, les machines virtuelles ont le choix de traduire les squences de bytecode frquemment utilises en code machine, une procdure appele compilation en "juste--temps" (just-in-time ou JIT). Cette stratgie sest rvle tellement efcace que la plate-forme .NET de Microsoft va jusqu reposer sur une machine virtuelle. La machine virtuelle prsente dautres avantages. Elle accrot la scurit car elle est en mesure de vrier le comportement des suites dinstructions. Certains programmes vont jusqu produire des bytecodes la vole, en amliorant dynamiquement les capacits dun programme en cours dexcution.

12

Au cur de Java 2 - Notions fondamentales

Portabilit
*

A la diffrence de C et de C++, on ne trouve pas les aspects de dpendance de la mise en uvre dans la spcication. Les tailles des types de donnes primaires sont spcies, ainsi que le comportement arithmtique qui leur est applicable.

Par exemple, en Java, un int est toujours un entier en 32 bits. Dans C/C++, int peut reprsenter un entier 16 bits, un entier 32 bits ou toute autre taille dcide par le concepteur du compilateur. La seule restriction est que le type int doit contenir au moins autant doctets quun shortint et ne peut pas en contenir plus quun longint. Le principe dune taille xe pour les types numriques limine les principaux soucis du portage dapplications. Les donnes binaires sont stockes et transmises dans un format xe, ce qui met n la confusion sur lordre des octets. Les chanes sont enregistres au format Unicode standard.
*

Les bibliothques intgres au systme dnissent des interfaces portables. Par exemple, il existe une classe Window abstraite, accompagne de ses mises en uvre pour UNIX, Windows et Macintosh.

Tous ceux qui ont essay savent que llaboration dun programme compatible avec Windows, Macintosh et dix versions dUNIX, reprsente un effort hroque. Java 1.0 a accompli cet effort en proposant un kit doutils simples sachant "coller" aux principaux composants dinterface utilisateur dun grand nombre de plates-formes. Malheureusement, le rsultat tait une bibliothque qui donnait, avec beaucoup de travail, des programmes peine acceptables sur diffrents systmes. En outre, on rencontrait souvent des bogues diffrents sur les diffrentes implmentations graphiques. Mais ce ntait quun dbut. Il existe de nombreuses applications pour lesquelles la portabilit est plus importante que lefcacit de linterface utilisateur, et ce sont celles qui ont bnci des premires versions de Java. Aujourdhui, le kit doutils de linterface utilisateur a t entirement rcrit de manire ne plus dpendre de linterface utilisateur de lhte. Le rsultat est nettement plus cohrent et nous pensons quil est beaucoup plus intressant que dans les prcdentes versions de Java.

Interprt
*

Linterprteur de Java peut excuter les bytecodes directement sur nimporte quelle machine sur laquelle il a t port. Dans la mesure o la liaison est un processus plus incrmentiel et lger, le processus de dveloppement peut se rvler plus rapide et exploratoire.

La liaison incrmentielle prsente des avantages, mais ils ont t largement exagrs. Quoi quil en soit, nous avons trouv les outils de dveloppement relativement lents. Aujourdhui, les bytecodes sont traduits en code machine par le compilateur en juste--temps.

Performances leves
*

En rgle gnrale, les performances des bytecodes interprts sont tout fait sufsantes ; il existe toutefois des situations dans lesquelles des performances plus leves sont ncessaires. Les bytecodes peuvent tre traduits la vole (en cours dexcution) en code machine pour lunit centrale destine accueillir lapplication.

Aux premiers temps de Java, de nombreux utilisateurs rejetaient la formule "performances leves". Il existe toutefois aujourdhui des compilateurs JIT (just-in-time, juste--temps) dune qualit telle

Chapitre 1

Une introduction Java

13

quils concurrencent les compilateurs traditionnels et, dans certains cas, les dpassent du fait quils disposent de davantage dinformations. Par exemple, un compilateur JIT est capable didentier du code souvent excut et doptimiser exclusivement la vitesse de celui-ci. Une optimisation plus pousse consiste liminer les appels de fonctions. Le compilateur en juste--temps connat les classes qui ont t charges. Il peut utiliser la suppression lorsque, en fonction de la collection de classes actuellement charge, une fonction particulire nest jamais crase. Il peut ensuite annuler cette optimisation si ncessaire.

Multithread
*

Les avantages du multithread sont une meilleure interractivit et un meilleur comportement en temps rel.

Si vous avez dj essay de programmer le multithread dans un autre langage, vous allez tre agrablement surpris de la simplicit de cette tche dans Java. Avec lui, les threads sont galement capables de tirer parti des systmes multiprocesseurs. Le ct ngatif rside dans le fait que les implmentations de threads sur les plates-formes principales sont trs diffrentes, et Java ne fait aucun effort pour tre indpendant de la plate-forme cet gard. La seule partie de code identique entre les diffrentes machines est celle de lappel du multithread. Java se dcharge de limplmentation du multithread sur le systme dexploitation sous-jacent ou sur une bibliothque de threads. Malgr tout, la simplicit du multithread est lune des raisons principales du succs de Java pour le dveloppement ct serveur.

Java, langage dynamique


*

Sur plusieurs points, Java est un langage plus dynamique que C ou C++. Il a t conu pour sadapter un environnement en volution constante. Les bibliothques peuvent ajouter librement de nouvelles mthodes et variables sans pour autant affecter leurs clients. La recherche des informations de type excution dans Java est simple.

Cette fonction est importante lorsque lon doit ajouter du code un programme en cours dexcution. Le premier exemple est celui du code tlcharg depuis Internet pour sexcuter dans un navigateur. Avec la version 1.0 de Java, la recherche dinformations de type excution tait relativement complexe. A lheure actuelle, les versions courantes de Java procurent au programmeur un aperu complet de la structure et du comportement de ses objets. Cela se rvle trs utile pour les systmes ncessitant une analyse des objets au cours de lexcution, tels que les gnrateurs graphiques de Java, les dbogueurs volus, les composants enchables et les bases de donnes objet.
INFO
Peu aprs les premiers succs de Java, Microsoft a sorti un produit intitul J++ qui prsente un lien de parent avec Java. Pour lheure, Microsoft ne prend plus en charge J++ mais a introduit un nouveau langage, appel C#, qui prsente aussi de nombreuses similarits avec Java mais utilise une autre machine virtuelle. Il existe mme un J# pour faire migrer les applications J++ vers la machine virtuelle utilise par C#. Sachez que nous ne reviendrons pas sur J++, C# ou J# dans cet ouvrage.

14

Au cur de Java 2 - Notions fondamentales

Les applets Java et Internet


Lide de base est simple : les utilisateurs tlchargent les bytecodes Java depuis Internet et les excutent sur leurs propres machines. Les programmes Java sexcutant sur les pages Web sont nomms applets. Pour utiliser un applet, vous devez disposer dun navigateur Web compatible Java, qui excutera les bytecodes. Vous naurez besoin dinstaller aucun logiciel. Sun fournit sous licence le code source de Java et afrme quaucun changement naffectera le langage et la structure de la bibliothque de base. Vous obtiendrez la dernire version du programme ds que vous visiterez la page Web contenant lapplet. Et surtout, grce la scurit de la machine virtuelle, vous naurez plus vous inquiter des attaques provenant dun code hostile. Lorsque lutilisateur tlcharge un applet, il sagit presque de lintgration dune image dans une page Web. Lapplet sinsre dans la page et le texte se rpartit dans son espace. Le fait est que limage est vivante. Elle ragit aux commandes utilisateur, change dapparence et transfre les donnes entre lordinateur qui prsente lapplet et lordinateur qui la sert. La Figure 1.1 prsente un exemple intressant de page Web dynamique, un applet qui permet dafcher des molcules et ralise des calculs sophistiqus. A laide de la souris, vous pouvez faire pivoter chaque molcule et zoomer dessus, pour mieux en comprendre la structure. Ce type de manipulation directe est impossible avec des pages Web statiques, elle nest possible quavec les applets (vous trouverez cet applet ladresse http://jmol.sourceforge.net). A leur apparition, les applets ont suscit une grande excitation. Beaucoup pensent que lattirance pour les applets a t responsable de la formidable popularit de Java. Or, cette premire excitation sest rapidement transforme en frustration. Diffrentes versions de Netscape et dInternet Explorer excutaient des versions diffrentes de Java, certaines particulirement obsoltes. Cette situation malheureuse a compliqu de plus en plus le dveloppement dapplets capables de tirer parti de la version la plus actuelle de Java. Aujourdhui, la plupart des pages Web utilisent simplement JavaScript ou Flash pour obtenir des effets dynamiques dans le navigateur. Java, quant lui, est devenu le langage le plus populaire pour dvelopper des applications ct serveur qui produisent des pages Web et ralisent la logique en arrire-plan.
Figure 1.1
Lapplet Jmol.

Chapitre 1

Une introduction Java

15

Bref historique de Java


Cette section prsente un bref historique de lvolution de Java. Elle se rfre diverses sources publies (et, plus important encore, un entretien avec les crateurs de Java paru dans le magazine en ligne SunWorld de juillet 1995). La naissance de Java date de 1991, lorsquun groupe dingnieurs de Sun, dirig par Patrick Naughton et James Gosling, voulut concevoir un petit langage informatique adapt des appareils de consommation comme les dcodeurs du cble TV. Ces appareils ne disposant que de trs peu de puissance ou de mmoire, le langage devait tre concis et gnrer un code trs strict. Des constructeurs diffrents tant susceptibles de choisir des units centrales diffrentes, il tait galement important que le langage ne soit pas li une seule architecture. Le nom de code du projet tait "Green". Les exigences dun code concis et indpendant de la plate-forme conduisirent lquipe reprendre le modle que certaines implmentations de Pascal avaient adopt au dbut de lavnement des PC. Niklaus Wirth, linventeur de Pascal, avait prconis la conception dun langage portable gnrant du code intermdiaire pour des machines hypothtiques (souvent nommes machines virtuelles de l, la machine virtuelle Java ou JVM). Ce code pouvait alors tre utilis sur toute machine disposant de linterprteur appropri. Les ingnieurs du projet Green utilisaient galement une machine virtuelle, ce qui rsolvait leur principal problme. Les employs de Sun avaient toutefois une culture UNIX. Ils ont donc bas leur langage sur C++ plutt que sur Pascal. Au lieu de crer un langage fonctionnel, ils ont mis au point un langage orient objet. Cependant, comme Gosling lannonce dans linterview, "depuis toujours, le langage est un outil et non une n". Gosling dcida de nommer son langage "Oak" (probablement parce quil apprciait la vue de sa fentre de bureau, qui donnait sur un chne). Les employs de Sun se sont aperus plus tard que ce nom avait dj t attribu un langage informatique. Ils lont donc transform en Java. Ce choix sest rvl heureux. Malheureusement, personne ne fut intress pour le produire chez Sun. Lquipe de Green dut trouver dautres ouvertures pour commercialiser sa technologie. Toutefois, aucune des grandes socits dlectronique de grande consommation na t intresse. Le groupe soumit alors un nouveau projet. Il proposa la conception dun dcodeur de cble TV capable de grer de nouveaux services cbls, tels que la vido la demande. Malgr cela, le contrat na pu tre dcroch (pour la petite histoire, la compagnie qui la obtenu tait dirige par le mme Jim Clark qui avait dmarr Netscape une entreprise qui a beaucoup particip au succs de Java). Le projet Green (sous le nouveau nom de "First Person Inc.") a pass lanne 1993 et la moiti de 1994 rechercher des acheteurs pour sa technologie peine perdue (Patrick Naughton, lun des fondateurs du groupe et principal agent marketing, prtend avoir parcouru plus de 80 000 km en avion pour vendre sa technologie). First Person a t dissoute en 1994. Pendant ce temps, le World Wide Web dInternet devenait de plus en plus important. Llment cl du Web est le navigateur qui traduit la page hypertexte lcran. En 1994, la plupart des gens utilisaient Mosaic, un navigateur Web non commercialis et issu du Supercomputing Center de luniversit de lIllinois en 1993 (alors quil tait encore tudiant, Marc Andreessen avait particip la cration de Mosaic pour 6,85 $ lheure. Par la suite, il a obtenu la notorit et la fortune en tant que cofondateur et directeur de technologie de Netscape). Lors dune interview pour le SunWorld, Gosling a dclar quau milieu de lanne 1994, les dveloppeurs de langage avaient ralis quils pouvaient crer un navigateur vraiment cool. Il sagissait

16

Au cur de Java 2 - Notions fondamentales

dune des rares choses dans le courant client/serveur qui ncessitait certaines des actions bizarres quils avaient ralises : larchitecture neutre, le temps rel, la abilit, la scurit des questions qui taient peu importantes dans le monde des stations de travail. Ils ont donc cr un navigateur. En fait, le vrai navigateur a t cr par Patrick Naughton et Jonathan Payne. Il a ensuite volu pour donner naissance au navigateur HotJava. Ce dernier a t crit en Java pour montrer la puissance de ce langage. Mais les concepteurs avaient galement lesprit la puissance de ce qui est actuellement nomm les applets. Ils ont donc donn la possibilit au navigateur dexcuter le code au sein des pages Web. Cette "dmonstration de technologie" fut prsente au SunWorld le 23 mai 1995 et elle fut lorigine de lengouement pour Java, qui ne sest pas dmenti. Sun a diffus la premire version de Java dbut 1996. On a rapidement ralis que cette version ne pouvait tre utilise pour un dveloppement dapplications srieux. Elle permettait, bien sr, de crer un applet de texte anim qui se dplaait de faon alatoire sur un fond. Mais il tait impossible dimprimer... Cette fonctionnalit ntait pas prvue par la version 1.0. Son successeur, la version 1.1, a combl les fosss les plus vidents, a grandement amlior la capacit de rexion et ajout un nouveau modle pour la programmation GUI. Elle demeurait pourtant assez limite. Les grands projets de la confrence JavaOne de 1998 taient la future version de Java 1.2. Cette dernire tait destine remplacer les premires botes outils graphiques et GUI damateur par des versions sophistiques et modulaires se rapprochant beaucoup plus que leurs prdcesseurs de la promesse du "Write Once, Run Anywhere" (un mme programme sexcute partout). Trois jours aprs (!) sa sortie en dcembre 1998, le service marketing de Sun a transform le nom, qui est devenu Java 2 Standard Edition Software Development Kit Version 1.2 ! Outre "lEdition Standard", deux autres ditions ont t introduites : "Micro Edition" pour les services intgrs comme les tlphones portables, et "Entreprise Edition" pour le traitement ct serveur. Cet ouvrage se concentre sur lEdition Standard. Les versions 1.3 et 1.4 constituent une amlioration incrmentielle par rapport la version Java 2 initiale, avec une bibliothque standard en pleine croissance, des performances accrues et, bien entendu, un certain nombre de bogues corrigs. Pendant ce temps, la majeure partie de la passion gnre par les applets Java et les applications ct client a diminu, mais Java est devenu la plateforme de choix pour les applications ct serveur. La version 5.0 est la premire depuis la version 1.1 qui actualise le langage Java de manire signicative (cette version tait numrote, lorigine, 1.5, mais le numro est devenu 5.0 lors de la confrence JavaOne de 2004). Aprs de nombreuses annes de recherche, des types gnriques ont t ajouts ( peu prs comparables aux modles C++), le d tant dintgrer cette fonctionnalit sans exiger de changements de la machine virtuelle. Plusieurs autres fonctionnalits utiles ont t inspires par le C# : une boucle for each, lautoboxing (passage automatique entre type de base et classes encapsulantes) et les mtadonnes. Les changements de langage continuent poser des problmes de compatibilit, mais plusieurs de ces fonctionnalits sont si sduisantes que les programmeurs devraient les adopter rapidement. La version 6 (dpourvue du sufxe .0) est sortie n 2006. Une fois de plus, le langage na pas connu de changement, mais les performances et la bibliothque ont t amliores. Le Tableau 1.1 montre lvolution du langage Java et de la bibliothque. Vous le voyez, la taille de linterface de programmation dapplication (API) a considrablement augment.

Chapitre 1

Une introduction Java

17

Tableau 1.1 : Evolution du langage Java

Version 1.0 1.1 1.2 1.3 1.4 5.0

Anne 1996 1997 1998 2000 2004 2004

Nouvelles fonctionnalits du langage Le langage lui-mme Classes internes Aucune Aucune Assertions Classes gnriques, boucle for each, varargs, autoboxing, mtadonnes, numrations, importation static Aucune

Nombre de classes et dinterfaces 211 477 1 524 1 840 2 723 3 279

2006

3 777

Les ides fausses les plus rpandues concernant Java


Nous clturons ce chapitre par une liste de quelques ides fausses concernant Java. Elles seront accompagnes de leur commentaire. Java est une extension de HTML. Java est un langage de programmation. HTML reprsente une faon de dcrire la structure dune page Web. Ils nont rien en commun, lexception du fait quil existe des extensions HTML permettant dinsrer des applets Java sur une page Web. Jutilise XML, je nai donc pas besoin de Java. Java est un langage de programmation ; XML est une manire de dcrire les donnes. Vous pouvez traiter des donnes XML avec tout langage de programmation, mais lAPI Java en contient une excellente prise en charge. En outre, de nombreux outils XML tiers trs importants sont mis en place en Java. Voir le Volume II pour en savoir plus. Java est un langage de programmation facile apprendre. Aucun langage de programmation aussi puissant que Java nest facile. Vous devez toujours distinguer la facilit de lcriture de programmes triviaux et la difcult que reprsente un travail srieux. Considrez galement que quatre chapitres seulement de ce livre traitent du langage Java. Les autres chapitres dans les deux volumes traitent de la faon de mettre le langage en application, laide des bibliothques Java. Celles-ci contiennent des milliers de classes et dinterfaces, et des dizaines de milliers de fonctions. Vous navez heureusement pas besoin de connatre chacune dentre elles, mais vous devez cependant tre capable den reconnatre un grand nombre pour pouvoir obtenir quelque chose de raliste.

18

Au cur de Java 2 - Notions fondamentales

Java va devenir un langage de programmation universel pour toutes les plates-formes. En thorie, cest possible, et il sagit certainement du souhait de tous les vendeurs, lexception de Microsoft. Il existe cependant de nombreuses applications, dj parfaitement efcaces sur les ordinateurs de bureau, qui ne fonctionneraient pas sur dautres units ou lintrieur dun navigateur. Ces applications ont t crites de faon tirer parti de la vitesse du processeur et de la bibliothque de linterface utilisateur native. Elles ont t portes tant bien que mal sur toutes les plates-formes importantes. Parmi ces types dapplications gurent les traitements de texte, les diteurs dimages et les navigateurs Web. Ils sont crits en C ou C++, et nous ne voyons aucun intrt pour lutilisateur nal les rcrire en Java. Java est simplement un autre langage de programmation. Java est un bon langage de programmation. De nombreux programmeurs le prfrent C, C++ ou C#. Mais des centaines de bons langages de programmation nont jamais russi percer, alors que des langages avec des dfauts vidents, tels que C++ et Visual Basic, ont remport un large succs. Pourquoi ? Le succs dun langage de programmation est bien plus dtermin par la qualit du systme de support qui lentoure que par llgance de sa syntaxe. Existe-t-il des bibliothques utiles, pratiques et standard pour les fonctions que vous envisagez de mettre en uvre ? Des vendeurs doutils ont-ils cr de bons environnements de programmation et de dbogage ? Le langage et lensemble des outils sintgrent-ils avec le reste de linfrastructure informatique ? Java a du succs parce que ses bibliothques de classes vous permettent de raliser facilement ce qui reprsentait jusqualors une tche complexe. La gestion de rseau et les multithreads en sont des exemples. Le fait que Java rduise les erreurs de pointeur est un bon point. Il semble que les programmeurs soient plus productifs ainsi. Mais il ne sagit pas de la source de son succs. Larrive de C# rend Java obsolte. C# a repris plusieurs bonnes ides de Java, par exemple un langage de programmation propre, une machine virtuelle et un ramasse-miettes. Mais, quelles quen soient les raisons, le C# a galement manqu certaines bonnes choses, comme la scurit et lindpendance de la plate-forme. Si vous apprciez Windows, optez pour le C#. Mais, si lon en juge par les offres demploi, Java reste le langage prfr de la majorit des dveloppeurs. Java est un outil propritaire, il faut donc lviter. Sun Microsystems distribue Java sous licence aux distributeurs et aux utilisateurs naux. Mme si Sun possde un contrle total sur Java, il a, par le biais de la "Communaut Java", impliqu de nombreuses autres socits dans le dveloppement de versions et la conception de nouvelles bibliothques. Le code source de la machine virtuelle et des bibliothques est disponible gratuitement, mais pour tude uniquement et non pour modication et redistribution. Jusqu prsent, Java est en "source ferme, mais fonctionne bien". Cette situation a considrablement chang en 2007, lorsque Sun a annonc la disponibilit des futures versions de Java dans le cadre de la GPL (General Public Licence ou licence publique gnrale), la licence open source de Linux. Reste voir comment Sun grera Java lavenir, mais il ne fait aucun

Chapitre 1

Une introduction Java

19

doute que le ct open source de Java a constitu une volution trs courageuse, qui prolongera la vie de Java de nombreuses annes. Java est interprt, il est donc trop lent pour les applications srieuses. Aux premiers jours de Java, le langage tait interprt. Aujourdhui, sauf sur les plates-formes "Micro" comme les tlphones portables, la machine virtuelle Java utilise un compilateur JIT. Les hot spots de votre code sexcuteront aussi rapidement en Java quen C++ et mme plus rapidement dans certains cas. Java est moins rapide que le C++. Le dmarrage de la machine virtuelle peut tre lent et les GUI Java sont plus lents que leurs homologues natifs car ils sont conus indpendamment de la plate-forme. Le public se plaint depuis des annes de la lenteur des applications Java. Toutefois, les ordinateurs actuels sont plus rapides. Un programme Java lent sexcutera un peu mieux que ces programmes C++ incroyablement rapides dil y a quelques annes. Pour lheure, ces plaintes semblent obsoltes et certains dtracteurs ont tendance viser plutt la laideur des interfaces utilisateur de Java que leur lenteur. Tous les programmes Java sexcutent dans une page Web. Tous les applets Java sexcutent dans un navigateur Web. Cest la dnition mme dun applet un programme Java sexcutant dans un navigateur. Mais la plupart des programmes Java sont des applications autonomes qui sexcutent indpendamment dun navigateur Web. De nombreux programmes Java sexcutent en fait sur des serveurs Web et produisent le code des pages Web. La majeure partie des programmes de cet ouvrage sont autonomes. Les applets sont bien sr un sujet passionnant. Mais les programmes Java autonomes sont plus importants et plus utiles dans la pratique. Les programmes Java reprsentent un risque majeur pour la scurit. Aux premiers temps de Java, son systme de scurit a quelquefois t pris en dfaut, et ces incidents ont t largement comments. La plupart sont dus limplmentation de Java dans un navigateur spcique. Les chercheurs ont considr ce systme de scurit comme un d relever et ont tent de dtecter les failles de larmure de Java. Les pannes techniques dcouvertes ont toutes t rapidement corriges et, notre connaissance, aucun des systmes actuels na jamais t pris en dfaut. Pour replacer ces incidents dans une juste perspective, prenez en compte les millions de virus qui attaquent les chiers excutables de Windows ainsi que les macros de Word. De rels dgts sont alors causs, mais curieusement, peu de critiques sont mises concernant la faiblesse de la plateforme concerne. Le mcanisme ActiveX dInternet Explorer pourrait galement reprsenter une cible facile prendre en dfaut, mais ce systme de scurit est tellement simple contourner que trs peu de chercheurs ont pens publier leur dcouverte. Certains administrateurs systme ont mme dsactiv Java dans les navigateurs de leur entreprise, alors quils continuent autoriser les utilisateurs tlcharger des chiers excutables, des contrles ActiveX et des documents Word. Il sagit dun comportement tout fait ridicule actuellement, le risque de se voir attaqu par un applet Java hostile est peu prs comparable au risque de mourir dans un accident davion. Le risque dinfection inhrent louverture dun document Word est, au contraire, comparable au risque de mourir en traversant pied une autoroute surcharge.

20

Au cur de Java 2 - Notions fondamentales

JavaScript est une version simplie de Java. JavaScript, un langage de script que lon peut utiliser dans les pages Web, a t invent par Netscape et sappelait lorigine LiveScript. On trouve dans la syntaxe de JavaScript des rminiscences de Java, mais il nexiste aucune relation ( lexception du nom, bien sr) entre ces deux langages. Un sous-ensemble de JavaScript est standardis sous le nom de ECMA-262. JavaScript est plus troitement intgr dans les navigateurs que ne le sont les applets Java. En particulier, un programme JavaScript peut modier le document afch, tandis quun applet ne peut que contrler lapparence dune zone limite. Avec Java, je peux remplacer mon ordinateur par une "bote noire Internet" bon march. Lors de la premire sortie de Java, certains pariaient gros l-dessus. Depuis la premire dition de cet ouvrage, nous pensons quil est absurde dimaginer que lon puisse abandonner une machine de bureau puissante et pratique pour une machine limite sans mmoire locale. Cependant, un ordinateur rseau pourvu de Java est une option plausible pour une "initiative zro administration". En effet, vous liminez ainsi le cot des ordinateurs de lentreprise, mais mme cela na pas tenu ses promesses. Par ailleurs, Java est devenu largement distribu sur les tlphones portables. Nous devons avouer que nous navons pas encore vu dapplication Java indispensable fonctionnant sur les tlphones portables, mais les jeux et les conomiseurs dcran usuels semblent bien se vendre sur de nombreux marchs.
ASTUCE
Pour obtenir des rponses aux questions communes sur Java, consultez les FAQ Java sur le Web : http:// www.apl.jhu.edu/~hall/java/FAQs-and-Tutorials.html.

2
Lenvironnement de programmation de Java
Au sommaire de ce chapitre

Installation du kit de dveloppement Java Choix dun environnement de dveloppement Utilisation des outils de ligne de commande Utilisation dun environnement de dveloppement intgr Excution dune application graphique Elaboration et excution dapplets
Ce chapitre traite de linstallation du kit de dveloppement Java (JDK) et de la faon de compiler et dexcuter diffrents types de programmes : les programmes consoles, les applications graphiques et les applets. Vous lancez les outils JDK en tapant les commandes dans une fentre shell. De nombreux programmeurs prfrent toutefois le confort dun environnement de dveloppement intgr. Vous apprendrez utiliser un environnement disponible gratuitement, pour compiler et excuter les programmes Java. Faciles comprendre et utiliser, les environnements de dveloppement intgrs peuvent ncessiter des ressources importantes et tre lourds utiliser pour de petits programmes. Comme solution intermdiaire, vous disposez des diteurs de texte appelant le compilateur Java et excutant les programmes Java. Lorsque vous aurez matris les techniques prsentes dans ce chapitre et choisi vos outils de dveloppement, vous serez prt aborder le Chapitre 3, o vous commencerez explorer le langage de programmation Java.

Installation du kit de dveloppement Java


Les versions les plus compltes et les plus rcentes du kit de dveloppement Java (JDK) sont disponibles auprs de Sun Microsystems pour Solaris, Linux et Windows. Certaines versions de Java sont disponibles en divers degrs de dveloppement pour Macintosh et de nombreuses autres plates-formes, mais ces versions sont fournies sous licence et distribues par les fournisseurs de ces plates-formes.

22

Au cur de Java 2 - Notions fondamentales

INFO
Certaines distributions de Linux possdent des versions premballes du JDK. Ainsi, par exemple, sous Ubuntu, vous pouvez installer le JDK en installant simplement le package sun-java6-jdk avec apt-get ou la GUI Synaptic.

Tlcharger le JDK
Pour tlcharger le JDK, accdez au site Web de Sun ; vous devrez passer une grande quantit de jargon avant de pouvoir obtenir le logiciel. Voyez le Tableau 2.1 pour en obtenir un rsum. Vous avez dj rencontr labrviation JDK (Java Development Kit). Pour compliquer un peu les choses, les versions 1.2 1.4 du kit taient connues sous le nom de Java SDK (Software Development Kit). Sachez que vous retrouverez des mentions occasionnelles de cet ancien acronyme. Il existe galement un JRE (Java Runtime Environment) contenant la machine virtuelle, mais pas le compilateur. Or, cela nest pas adapt pour les dveloppeurs, il est plutt destin aux utilisateurs nayant pas besoin du compilateur. Vous rencontrerez aussi trs souvent le terme "Java SE". Il signie "Java Standard Edition", par opposition Java EE (Entreprise Edition) et Java ME (Micro Edition). Le terme "Java 2" est apparu en 1998, lanne o les commerciaux de Sun ont considr quaugmenter le numro de version par une dcimale ne traduisait pas correctement les nouveauts du JDK 1.2. Or, ils ne sen sont aperus quaprs la sortie et ont donc dcid de conserver le numro 1.2 pour le kit de dveloppement. Les versions conscutives ont t numrotes 1.3, 1.4 et 5.0. La plate-forme a toutefois t renomme de "Java" en "Java 2". Ce qui nous donne donc Java 2 Standard Edition Software Development Kit version 5.0, soit J2SE SDK 5.0. Cela peut tre assez dsarmant pour les ingnieurs, mais cest l le ct cach du marketing. Heureusement, en 2006, le bon sens a repris le dessus. Le surnom Java 2, parfaitement inutile, a donc t abandonn, et la version actuelle de Java Standard Edition sest appele Java SE 6. Vous trouverez toujours, par moments, une rfrence aux versions 1.5 et 1.6, ce sont simplement des synonymes des versions 5.0 et 6. Enn, lorsque Sun apporte un changement mineur pour corriger des problmes urgents, il nomme cette opration une mise jour. Ainsi, la premire mise jour du kit de dveloppement pour Java SE 6 sintitule ofciellement JDK 6u1 et porte le numro de version interne 1.6.0_01. Pour les utilisateurs de Solaris, de Linux ou de Windows, accdez ladresse http://java.sun.com/ javase pour tlcharger le JDK. Demandez la version 6 ou suprieure, puis choisissez votre plateforme. Ne vous inquitez pas si le logiciel est quali de "mise jour". Le module contient la version actuelle de lensemble du JDK. Sun sort parfois des modules contenant la fois le Java Development Kit et un environnement de dveloppement intgr. Cet environnement a, selon les poques, t nomm Forte, Sun ONE Studio, Sun Java Studio et NetBeans. Nous ne savons pas ce que les arcanes du marketing auront trouv lorsque vous visiterez le site Web de Sun. Nous vous suggrons de ninstaller pour lheure que le Java Development Kit. Si vous dcidez par la suite dutiliser lenvironnement de dveloppement intgr de Sun, tlchargez-le simplement ladresse http://netbeans.org.

Chapitre 2

Lenvironnement de programmation de Java

23

Tableau 2.1 : Le jargon de Java

Nom Java Development Kit Java Runtime Environment Standard Edition Enterprise Edition Micro Edition Java 2 Software Development Kit Mise jour (Update) NetBeans

Acronyme JDK JRE SE EE ME J2 SDK u

Explication Le logiciel destin aux programmeurs qui souhaitent crire des programmes Java Le logiciel destin aux clients qui veulent excuter des programmes Java La plate-forme Java utiliser sur les ordinateurs de bureau et les applications de serveur simples La plate-forme Java pour les applications de serveur complexes La plate-forme Java utiliser sur les tlphones portables et autres petits appareils Un terme obsolte dsignant les versions de Java entre 1998 et 2006 Terme obsolte dsignant le JDK entre 1998 et 2006 Terme employ par Sun pour dsigner un correctif de bogue Lenvironnement de dveloppement intgr de Sun

Une fois le JDK tlcharg, suivez les instructions dinstallation, qui sont fonction de la plate-forme. A lheure o nous crivons, elles taient disponibles ladresse http://java.sun.com/javase/6/ webnotes/install/index.html. Seules les instructions dinstallation et de compilation pour Java dpendent du systme. Une fois Java install et oprationnel, les autres informations fournies dans ce livre sappliqueront votre situation. Lindpendance vis--vis du systme est un avantage important de Java.
INFO
La procdure dinstallation propose un rpertoire dinstallation par dfaut incluant le numro de version de Java JDK, comme jdk1.6.0. Ceci peut paratre pnible, mais le numro de version est nalement assez pratique, puisque vous pouvez tester facilement une nouvelle version du JDK. Sous Windows, nous vous recommandons fortement de ne pas accepter lemplacement par dfaut avec des espaces dans le nom du chemin, comme C:\Program Files\jdk1.6.0. Enlevez simplement la partie Program Files. Dans cet ouvrage, nous dsignons le rpertoire dinstallation par jdk. Par exemple, lorsque nous faisons rfrence au rpertoire jdk/bin, nous dsignons le rpertoire ayant le nom /usr/local/jdk1.6.0/bin ou C:\jdk1.6.0\bin.

24

Au cur de Java 2 - Notions fondamentales

Congurer le chemin dexcution


Aprs avoir install le JDK, vous devez effectuer une tape supplmentaire : ajouter le rpertoire jdk/bin au chemin dexcution, la liste des rpertoires que traverse le systme dexploitation pour localiser les chiers excutables. Les directives concernant cette tape varient galement en fonction du systme dexploitation.
m

Sous UNIX (y compris Solaris ou Linux), la procdure pour modier le chemin dexcution dpend du shell que vous utilisez. Si vous utilisez le C shell (qui est le dfaut pour Solaris), vous devez ajouter une ligne analogue celle qui suit la n de votre chier ~/.cshrc :
set path=(/usr/local/jdk/bin $path)

Si vous utilisez le Bourne Again shell (dfaut pour Linux), ajoutez une ligne analogue celle qui suit, la n de votre chier ~/.bashrc ou ~/.bash_profile :
export PATH=/usr/local/jdk/bin:$PATH
m

Sous Windows, connectez-vous en tant quadministrateur. Ouvrez le Panneau de conguration, passez en afchage classique et slectionnez Systme. Sous Windows NT/2000/XP, la bote de dialogue des proprits systme souvre immdiatement. Sous Vista, slectionnez les paramtres systme avancs (voir Figure 2.1). Dans la bote de dialogue des proprits du systme, cliquez sur longlet Paramtres systme avancs, puis sur le bouton Variables denvironnement. Parcourez la fentre Variables systme pour rechercher la variable nomme Path (voir Figure 2.2). Ajoutez le rpertoire jdk\bin au dbut du chemin, en ajoutant un point-virgule pour sparer la nouvelle entre, de la faon suivante :
c:\jdk\bin;le reste

Sauvegardez votre conguration. Toute nouvelle fentre console que vous lancerez comprendra le chemin correct. Procdez de la faon suivante pour vrier que vous avez effectu les manipulations appropries. Dmarrez une fentre shell. Tapez la ligne :
java -version

Appuyez sur la touche Entre. Vous devez obtenir un afchage comparable ce qui suit :
java version "1.6.0_01" Java(TM) SE Runtime Environment (build 1.6.0_01-b06) Java HotSpot(TM) Client VM (build 1.6.0_01-b06, mixed mode, sharing)

Si, la place, vous obtenez un message du type "java: command not found" ou "The name specied is not recognized as an internal or external command, operable program or batch le", messages qui signalent une commande ou un chier erron, vous devez vrier votre installation.
INFO
Selon la version de Windows, une fentre shell souvre dune des manires suivantes. Sous Windows NT/2000/XP, choisissez loption Excuter du menu Dmarrer et tapez cmd. Sous Vista, cliquez sur Dmarrer et tapez cmd. Appuyez sur Entre pour ouvrir la fentre shell. Si vous navez jamais utilis de fentre shell, nous vous proposons un didacticiel enseignant les bases de la ligne de commande. De nombreux services informatiques proposent des didacticiels sur le Web, notamment ladresse http://www.cs.sjsu.edu/faculty/horstman/CS46A/windows/tutorial.html.

Chapitre 2

Lenvironnement de programmation de Java

25

Figure 2.1
Lancement de la bote de dialogue des proprits du systme sous Windows Vista.

Figure 2.2
Paramtrage de la variable denvironnement Path sous Windows Vista.

26

Au cur de Java 2 - Notions fondamentales

Installer la bibliothque et la documentation


Les chiers source de bibliothque sont fournis dans le JDK sous la forme dun chier compress src.zip et vous devez le dcompresser pour avoir accs au code source. La procdure suivante est hautement recommande. 1. Assurez-vous que le JDK est install et que le rpertoire jdk/bin gure dans le chemin dexcution. 2. Ouvrez une fentre shell. 3. Positionnez-vous dans le rpertoire jdk (par ex. /usr/local/jdk1.6.0 ou C:\jdk1.6.0). 4. Crez un sous-rpertoire src.
mkdir src cd src

5. Excutez la commande :
jar xvf ../src.zip

(ou jarxvf..\src.zip sous Windows).


ASTUCE
Le chier src.zip contient le code source pour toutes les bibliothques publiques. Vous pouvez obtenir dautres codes source (pour le compilateur, la machine virtuelle, les mthodes natives et les classes prives helper) ladresse http://download.java.net/jdk6.

La documentation est contenue dans un chier compress spar du JDK. Vous pouvez tlcharger la documentation ladresse http://java.sun.com/javase/downloads. Procdez de la faon suivante : 1. Assurez-vous que le JDK est install et que le rpertoire jdk/bin gure dans le chemin dexcution. 2. Copiez le chier zip de documentation dans le rpertoire qui contient le rpertoire jdk. Le chier est nomm jdk-version-doc.zip, o version ressemble 6. 3. Lancez une fentre shell. 4. Positionnez-vous dans le rpertoire jdk. 5. Excutez la commande :
jar xvf jdk-version-doc.zip

o version est le numro de version appropri.

Installer les exemples de programmes


Il est conseill dinstaller les exemples de programmes que vous pouvez tlcharger ladresse www.pearsoneducation.fr. Les programmes sont compresss dans un chier zip corejavavol1.zip. Dcompressez-les dans un rpertoire spar que nous vous recommandons dappeler CoreJavaBook. Procdez de la faon suivante :

Chapitre 2

Lenvironnement de programmation de Java

27

1. Assurez-vous que le JDK est install et que le rpertoire jdk/bin gure dans le chemin dexcution. 2. Crez un rpertoire nomm CoreJavaBook. 3. Copiez le chier corejavavol1.zip dans ce rpertoire. 4. Lancez une fentre shell. 5. Positionnez-vous dans le rpertoire CoreJavaBook. 6. Excutez la commande :
jar xvf corejavavol1.zip

Explorer les rpertoires de Java


Au cours de votre tude, vous aurez examiner des chiers source Java. Vous devrez galement, bien sr, exploiter au maximum la documentation bibliothque. Le Tableau 2.2 prsente larborescence des rpertoires du JDK.
Tableau 2.2 : Arborescence des rpertoires de Java

Structure du rpertoire jdk bin demo docs include jre lib src

Description Le nom peut tre diffrent, par exemple jdk5.0 Compilateur et outils Dmos Documentation de la bibliothque au format HTML (aprs dcompression de j2sdkversion-doc.zip) Fichiers pour les mthodes natives (voir Volume II) Fichiers denvironnement dexcution de Java Fichiers de bibliothque Source de bibliothque (aprs dcompression de src.zip)

Les deux sous-rpertoires les plus importants sont docs et src. Le rpertoire docs contient la documentation de la bibliothque Java au format HTML. Vous pouvez la consulter laide de tout navigateur Web tel que Netscape.
ASTUCE
Dnissez un signet dans votre navigateur pour le chier docs/api/index.html. Vous consulterez frquemment cette page au cours de votre tude de la plate-forme Java !

28

Au cur de Java 2 - Notions fondamentales

Le rpertoire src contient le code source de la partie publique des bibliothques de Java. Lorsque ce langage vous sera plus familier, ce livre et les informations en ligne ne vous apporteront sans doute plus les infos dont vous avez besoin. Le code source de Java constituera alors un bon emplacement o commencer les recherches. Il est quelquefois rassurant de savoir que lon a toujours la possibilit de se plonger dans le code source pour dcouvrir ce qui est rellement ralis par une fonction de bibliothque. Si vous vous intressez, par exemple, la classe System, vous pouvez consulter src/java/lang/System.java.

Choix de lenvironnement de dveloppement


Si vous avez lhabitude de programmer avec Microsoft Visual Studio, vous tes accoutum un environnement de dveloppement qui dispose dun diteur de texte intgr et de menus vous permettant de compiler et dexcuter un programme avec un dbogueur intgr. La version de base du JDK ne contient rien de tel, mme approximativement. Tout se fait par lentre de commandes dans une fentre shell. Mme si cela peut paratre lourd, il sagit dune comptence essentielle. A la premire installation de Java, vous devrez mettre au propre votre installation avant dinstaller un environnement de dveloppement. De plus, en ralisant vous-mme les tapes de base, vous apprhenderez mieux ce que fait lenvironnement de dveloppement dans votre dos. Toutefois, lorsque vous aurez matris les bases de la compilation et de lexcution des programmes Java, vous aurez besoin dun environnement de dveloppement professionnel. Au cours de la dernire dcennie, ces environnements sont devenus si puissants et si commodes quil nest tout simplement pas logique de sen passer. Deux excellents choix sont les programmes gratuits Eclipse et NetBeans. Nous vous prsentons dans ce chapitre une mise en route dEclipse, qui est un peu plus performant que NetBeans, mme si ce dernier rattrape rapidement son retard. Bien entendu, si vous disposez dj dun environnement de dveloppement, nhsitez pas lutiliser. Auparavant, nous recommandions dutiliser un diteur de texte comme Emacs, JEdit ou TextPad pour des programmes simples. Nous ne le faisons plus car les environnements de dveloppement intgrs sont aujourdhui rapides et pratiques. En rsum, nous supposons que vous savez utiliser les outils de base du JDK et que vous devriez vous familiariser avec un environnement de dveloppement intgr.

Utilisation des outils de ligne de commande


Commenons par le plus difcile : compiler et lancer un programme Java partir de la ligne de commande. 1. Ouvrez un shell. 2. Positionnez-vous dans le rpertoire CoreJavaBook/v1ch02/Welcome (le rpertoire CoreJavaBook est celui dans lequel vous avez install le code source pour les exemples du livre, tel quexpliqu prcdemment). 3. Entrez les commandes suivantes :
javac Welcome.java java Welcome

Vous devez voir apparatre le message de la Figure 2.3 lcran.

Chapitre 2

Lenvironnement de programmation de Java

29

Figure 2.3
Compilation et excution de Welcome.java.

Flicitations ! Vous venez de compiler et dexcuter votre premier programme Java. Que sest-il pass ? Le programme javac est le compilateur Java. Il compile le chier Welcome.java en un chier Welcome.class. Le programme java lance la machine virtuelle Java. Il interprte les bytecodes que le compilateur a placs dans le chier class.
INFO
Si vous obtenez un message derreur relatif la ligne
for (String g: greeting)

cela signie que vous utilisez probablement une ancienne version du compilateur Java. Java SE 5.0 a introduit plusieurs fonctions utiles au langage de programmation Java dont nous proterons dans cet ouvrage. Si vous utilisez une ancienne version, vous devez rcrire la boucle comme suit :
for (int i = 0; i < greeting.length; i++) System.out.println(greeting[i]);

Le programme Welcome est extrmement simple. Il se contente dafcher un message sur lcran. Vous pouvez examiner ce programme dans le Listing 2.1 nous en expliquerons le fonctionnement dans le prochain chapitre.
Listing 2.1 : Welcome.java
/** * Ce programme affiche un message de bienvenue des auteurs. * @version 1.20 2004-02-28 * @author Cay Horstmann */

30

Au cur de Java 2 - Notions fondamentales

public class Welcome { public static void main(String[] args) { String[] greeting = new String[3]; greeting[0] = "Welcome to Core Java"; greeting[1] = "by Cay Horstmann"; greeting[2] = "and Gary Cornell"; for (String g: greeting) System.out.println(g); } }

Conseils pour la recherche derreurs


A lheure des environnements de dveloppement visuels, les programmeurs nont pas lhabitude de lancer des programmes dans une fentre shell. Tant de choses peuvent mal tourner et mener des rsultats dcevants. Surveillez particulirement les points suivants :
m

Si vous tapez le programme manuellement, faites attention aux lettres majuscules et minuscules. En particulier, le nom de classe est Welcome et non welcome ou WELCOME. Le compilateur requiert un nom de chier Welcome.java. Lorsque vous excutez le programme, vous spciez un nom de classe (Welcome) sans extension .java ni .class. Si vous obtenez un message tel que "Bad command or le name" ou "javac: command not found", signalant une commande errone, vous devez vrier votre installation, en particulier la conguration du chemin dexcution. Si javac signale une erreur "cannot read: Welcome.java", signalant une erreur de lecture du chier, vriez si ce chier est prsent dans le rpertoire. Sous UNIX, vriez que vous avez respect la casse des caractres pour Welcome.java. Sous Windows, utilisez la commande shell dir, et non loutil Explorateur graphique. Certains diteurs de texte (en particulier le Bloc-notes) ajoutent systmatiquement une extension .txt aprs chaque chier. Si vous utilisez le Bloc-notes pour modier Welcome.java, le chier sera enregistr sous le nom Welcome.java.txt. Dans la conguration par dfaut de Windows, lExplorateur conspire avec le Bloc-notes et masque lextension .txt, car elle est considre comme un "type de chier connu". Dans ce cas, vous devez renommer le chier laide de la commande shell ren ou le renregistrer, en plaant des guillemets autour du nom de chier : "Welcome.java".

Si java afche un message signalant une erreur "java.lang.NoClassDefFoundError", vriez soigneusement le nom de la classe concerne. Si linterprteur se plaint que welcome contient un w minuscule, vous devez rmettre la commande javaWelcome avec un W majuscule. Comme toujours, la casse doit tre respecte dans Java.

Chapitre 2

Lenvironnement de programmation de Java

31

Si linterprteur signale un problme concernant Welcome/java, vous avez accidentellement tap javaWelcome.java. Rmettez la commande javaWelcome.
m

Si vous avez tap javaWelcome et que la machine virtuelle ne trouve pas la classe Welcome, vriez si quelquun a congur la variable denvironnement CLASSPATH sur votre systme. Il nest gnralement pas conseill de dnir cette variable globalement, mme si certains installateurs de logiciels mal crits sous Windows le font. Pour lannuler temporairement dans la fentre shell actuelle, tapez :
set CLASSPATH=

Cette commande fonctionne sous Windows et UNIX/Linux avec le shell C. Sous UNIX/Linux, avec le shell Bourne/bash, utilisez :
export CLASSPATH=
m

Si vous recevez un message derreur sur une nouvelle construction de langage, vriez que votre compilateur supporte Java SE 5.0. Si vous avez trop derreurs dans votre programme, tous les messages vont dler trs vite. Le compilateur envoie les messages vers la sortie derreur standard, ce qui rend leur capture difcile sils occupent plus dun cran. Utilisez loprateur shell 2> pour rediriger les erreurs vers un chier :
javac MyProg.java 2> errors.txt

ASTUCE
Il existe ladresse http://java.sun.com/docs/books/tutorial/getStarted/cupojava/ un excellent didacticiel qui explore en dtail les piges qui peuvent drouter les dbutants.

Utilisation dun environnement de dveloppement intgr


Dans cette section, vous apprendrez compiler un programme laide dEclipse, un environnement de dveloppement intgr gratuit disponible ladresse http://eclipse.org. Eclipse est crit en Java mais, comme il utilise une bibliothque de fentre non standard, il nest pas aussi portable que Java. Il en existe nanmoins des versions pour Linux, Mac OS X, Solaris et Windows. Il existe dautres IDE populaires mais Eclipse est actuellement le plus usit. Voici les tapes de dmarrage : 1. Aprs le dmarrage dEclipse, choisissez File -> New Project. 2. Slectionnez "Java Project" dans la bote de dialogue de lassistant (voir Figure 2.4). Ces captures dcran proviennent dEclipse 3.2. Votre version sera peut-tre lgrement diffrente. 3. Cliquez sur Next. Indiquez le nom du projet, savoir "Welcome", et tapez le nom de chemin complet jusquau rpertoire qui contient Welcome.java ; consultez la Figure 2.5. 4. Vriez que loption intitule "Create project in workspace" est dcoche. 5. Cliquez sur Finish. Le projet est maintenant cr.

32

Au cur de Java 2 - Notions fondamentales

Figure 2.4
Bote de dialogue New Project dans Eclipse.

Figure 2.5
Conguration dun projet Eclipse.

Chapitre 2

Lenvironnement de programmation de Java

33

6. Cliquez sur le triangle situ dans le volet de gauche prs de la fentre de projet pour louvrir, puis cliquez sur le triangle prs de "Default package". Double-cliquez sur Welcome.java. Une fentre souvre avec le code du programme (voir Figure 2.6).
Figure 2.6
Modication dun chier source avec Eclipse.

7. Cliquez du bouton droit sur le nom du projet (Welcome), dans le volet le plus gauche. Slectionnez Run -> Run As -> Java Application. Une fentre apparat au bas de la fentre. Le rsultat du programme sy afche (voir Figure 2.7).
Figure 2.7
Excution dun programme dans Eclipse.

34

Au cur de Java 2 - Notions fondamentales

Localiser les erreurs de compilation


Notre programme ne devrait pas contenir derreur de frappe ou de bogue (aprs tout, il ne comprend que quelques lignes de code). Supposons, pour la dmonstration, quil contienne une coquille (peuttre mme une erreur de syntaxe). Essayez dexcuter le chier en modiant la casse de String de la faon suivante :
public static void main(string[] args)

Compilez nouveau le programme. Vous obtiendrez des messages derreur (voir Figure 2.8) qui signalent un type string inconnu. Cliquez simplement sur le message. Le curseur se positionne sur la ligne correspondante dans la fentre ddition. Ce comportement vous permet de corriger rapidement vos erreurs.
ASTUCE
Bien souvent, un rapport derreur dEclipse est accompagn de licne dune ampoule. Cliquez dessus pour obtenir une liste des correctifs proposs.

Figure 2.8
Des messages derreur dans Eclipse.

Ces instructions doivent vous amener vouloir travailler dans un environnement intgr. Nous tudierons le dbogueur Eclipse au Chapitre 11.

Chapitre 2

Lenvironnement de programmation de Java

35

Excution dune application graphique


Le programme Welcome ne prsentait pas beaucoup dintrt. Excutons maintenant une application graphique. Il sagit dun afcheur simple de chiers image. Compilons ce programme et excutonsle depuis la ligne de commande. 1. Ouvrez une fentre shell. 2. Placez-vous sur le rpertoire CoreJavaBook/v1ch02/ImageViewer. 3. Tapez :
javac ImageViewer.java java ImageViewer

Une nouvelle fentre de programme apparat avec notre visionneuse, ImageViewer (voir Figure 2.9). Slectionnez maintenant File/Open et recherchez une image ouvrir (nous avons inclus quelques exemples de chiers dans le mme rpertoire). Pour fermer le programme, cliquez sur le bouton de fermeture dans la barre de titre ou droulez le menu systme et choisissez Quitter (pour compiler et excuter ce programme dans un diteur de texte ou un environnement de dveloppement intgr, procdez comme prcdemment. Par exemple, dans le cas dEmacs, choisissez JDE -> Compile, puis JDE -> Run App.
Figure 2.9
Excution de lapplication ImageViewer.

Nous esprons que vous trouverez ce programme pratique et intressant. Examinez rapidement le code source. Ce programme est plus long que le prcdent, mais il nest pas trs compliqu en comparaison avec la quantit de code qui aurait t ncessaire pour crire une application analogue en C ou C++. Ce type de programme est bien sr trs facile crire avec Visual Basic ou plutt copier et coller. Le JDK ne propose pas de gnrateur dinterface visuel, vous devez donc tout programmer la main (voir Listing 2.2). Vous apprendrez crer des programmes graphiques tels que celui-ci aux Chapitres 7 9.

36

Au cur de Java 2 - Notions fondamentales

Listing 2.2 : ImageViewer.java


import import import import java.awt.EventQueue; java.awt.event.*; java.io.*; javax.swing.*;

/** * Un programme permettant dafficher des images. * @version 1.22 2007-05-21 * @author Cay Horstmann */ public class ImageViewer { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { JFrame frame = new ImageViewerFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); } } /** * Un cadre avec une tiquette permettant dafficher une image. */ class ImageViewerFrame extends JFrame { public ImageViewerFrame() { setTitle("ImageViewer"); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); // utiliser une tiquette pour afficher les images label = new JLabel(); add(label); // configurer le slecteur de fichiers chooser = new JFileChooser(); chooser.setCurrentDirectory(new File(".")); // configurer la barre de menus JMenuBar menuBar = new JMenuBar(); setJMenuBar(menuBar); JMenu menu = new JMenu("File"); menuBar.add(menu); JMenuItem openItem = new JMenuItem("Open"); menu.add(openItem); openItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) {

Chapitre 2

Lenvironnement de programmation de Java

37

// montrer la bote de dialogue du slecteur int result = chooser.showOpenDialog(null); // en cas de slection dun fichier, dfinir comme icne // de ltiquette if (result == JFileChooser.APPROVE_OPTION) { String name = chooser.getSelectedFile().getPath(); label.setIcon(new ImageIcon(name)); } } }); JMenuItem exitItem = new JMenuItem("Exit"); menu.add(exitItem); exitItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { System.exit(0); } }); } private private private private } JLabel label; JFileChooser chooser; static final int DEFAULT_WIDTH = 300; static final int DEFAULT_HEIGHT = 400;

Elaboration et excution dapplets


Les deux premiers programmes prsents dans cet ouvrage sont des applications Java, des programmes autonomes comme tout programme natif. Comme nous lavons mentionn dans le dernier chapitre, la rputation de Java est due en grande partie sa capacit excuter des applets dans un navigateur Web. Nous allons vous montrer comment crer et excuter un applet partir de la ligne de commande. Puis nous allons charger lapplet dans lditeur dapplets fourni avec le JDK et enn, nous lafcherons dans un navigateur Web. Ouvrez un shell et placez-vous dans le rpertoire CoreJavaBook/v1ch02/WelcomeApplet, puis saisissez les commandes suivantes :
javac WelcomeApplet.java appletviewer WelcomeApplet.html

La Figure 2.10 montre ce que vous voyez dans la fentre de lafcheur dapplets. La premire commande, qui nous est maintenant familire, est la commande dappel du compilateur Java. Celui-ci compile le chier source WelcomeApplet.java et produit le chier de bytecodes WelcomeApplet.class. Cependant, nous nexcutons pas cette fois linterprteur Java, mais nous invoquons le programme appletviewer. Ce programme est un outil particulier inclus avec le JDK qui vous permet de tester rapidement un applet. Vous devez lui transmettre un nom de chier HTML plutt que le nom dun chier de classe java. Le contenu du chier WelcomeApplet.html est prsent dans le Listing 2.3 ci-aprs.

38

Au cur de Java 2 - Notions fondamentales

Figure 2.10
Lapplet WelcomeApplet dans lafcheur dapplets.

Listing 2.3 : WelcomeApplet.html


<html> <head> <title>WelcomeApplet</title> </head> <body> <hr/> <p> This applet is from the book <a href="http://www.horstmann.com/corejava.html">Core Java</a> by <em>Cay Horstmann</em> and <em>Gary Cornell</em>, published by Sun Microsystems Press. </p> <applet code="WelcomeApplet.class" width="400" height="200"> <param name="greeting" value="Welcome to Core Java!"/> </applet> <hr/> <p><a href="WelcomeApplet.java">The source.</a></p> </body> </html>

Si vous connaissez le code HTML, vous remarquerez quelques instructions standard et la balise applet qui demande lafcheur de charger lapplet dont le code est stock dans WelcomeApplet.class. Cet afcheur dapplets ignore toutes les balises HTML except la balise applet. Malheureusement, la situation des navigateurs est un peu confuse.
m

Firefox prend en charge Java sous Windows, Linux et Mac OS X. Pour tester ces applets, tlchargez simplement la dernire version, rendez-vous sur le site http://java.com et utilisez le systme de vrication des versions pour chercher si vous devez installer le plug-in Java. Certaines versions dInternet Explorer ne prennent pas du tout en charge Java. Dautres ne prennent en charge que la trs obsolte machine virtuelle Microsoft Java. Si vous excutez Internet Explorer, accdez ladresse http://java.com et installez le plug-in Java. Safari est intgr dans limplmentation Macintosh Java de Macintosh sous OS X, qui prend en charge Java SE 5.0 au moment o nous rdigeons.

A condition davoir un navigateur compatible avec une version moderne de Java, vous pouvez tenter de charger lapplet dans le navigateur.

Chapitre 2

Lenvironnement de programmation de Java

39

1. Dmarrez votre navigateur. 2. Slectionnez Fichier -> Ouvrir (ou lquivalent). 3. Placez-vous sur le rpertoire CoreJavaBook/v1ch02/WelcomeApplet. Vous devez voir apparatre le chier WelcomeApplet.html dans la bote de dialogue. Chargez le chier. 4. Votre navigateur va maintenant charger lapplet avec le texte qui lentoure. Votre cran doit ressembler celui de la Figure 2.11. Vous pouvez constater que cette application est rellement dynamique et adapte lenvironnement Internet. Cliquez sur le bouton Cay Horstmann, lapplet commande au navigateur dafcher la page Web de Cay. Cliquez sur le bouton Gary Cornell, lapplet lance lafchage dune fentre de courrier lectronique avec ladresse e-mail de Gary prenregistre.
Figure 2.11
Excution de lapplet WelcomeApplet dans un navigateur.

Vous remarquerez quaucun de ces deux boutons ne fonctionne dans lafcheur dapplets. Cet afcheur na pas la possibilit denvoyer du courrier ou dafcher une page Web, il ignore donc vos demandes. Lafcheur dapplets est uniquement destin tester les applets de faon isole, mais vous devrez placer ces dernires dans un navigateur pour contrler leur interaction avec le navigateur et Internet. Pour terminer, le code de lapplet est prsent dans le Listing 2.4. A ce niveau de votre tude, contentez-vous de lexaminer rapidement. Nous reviendrons sur lcriture des applets au Chapitre 10.
Listing 2.4 : WelcomeApplet.java
import import import import java.awt.*; java.awt.event.*; java.net.*; javax.swing.*;

40

Au cur de Java 2 - Notions fondamentales

/** * Cet applet affiche un message des auteurs. * @version 1.22 2007-04-08 * @author Cay Horstmann */ public class WelcomeApplet extends JApplet { public void init() { EventQueue.invokeLater(new Runnable() { public void run() { setLayout(new BorderLayout()); JLabel label = new JLabel(getParameter("greeting"), SwingConstants.CENTER); label.setFont(new Font("Serif", Font.BOLD, 18)); add(label, BorderLayout.CENTER); JPanel panel = new JPanel(); JButton cayButton = new JButton("Cay Horstmann"); cayButton.addActionListener(makeAction ("http://www.horstmann.com")); panel.add(cayButton); JButton garyButton = new JButton("Gary Cornell"); garyButton.addActionListener(makeAction ("mailto:gary_cornell@apress.com")); panel.add(garyButton); add(panel, BorderLayout.SOUTH); } }); } private ActionListener makeAction(final String urlString) { return new ActionListener() { public void actionPerformed(ActionEvent event) { try { getAppletContext().showDocument(new URL(urlString)); } catch(MalformedURLException e) { e.printStackTrace(); } } }; } }

Au cours de ce chapitre, vous avez appris les mcanismes de la compilation et de lexcution des programmes Java. Vous tes maintenant prt aborder le Chapitre 3, o vous attaquerez lapprentissage du langage Java.

3
Structures fondamentales de la programmation Java
Au sommaire de ce chapitre

Un exemple simple de programme Java Commentaires Types de donnes Variables Oprateurs Chanes Entres et sorties Flux de contrle Grands nombres Tableaux
Nous supposons maintenant que vous avez correctement install le JDK et que vous avez pu excuter les exemples de programmes proposs au Chapitre 2. Il est temps daborder la programmation. Ce chapitre vous montrera comment sont implments en Java certains concepts fondamentaux de la programmation, tels que les types de donnes, les instructions de branchement et les boucles. Malheureusement, Java ne permet pas dcrire facilement un programme utilisant une interface graphique il faut connatre de nombreuses fonctionnalits pour construire des fentres, ajouter des zones de texte, des boutons et les autres composants dune interface. La prsentation des techniques exiges par une interface graphique nous entranerait trop loin de notre sujet les concepts fondamentaux de la programmation et les exemples de programmes proposs dans ce chapitre ne seront que des programmes conus pour illustrer un concept. Tous ces exemples utilisent simplement une fentre shell pour lentre et la sortie dinformations.

42

Au cur de Java 2 - Notions fondamentales

Si vous tes un programmeur C++ expriment, vous pouvez vous contenter de parcourir ce chapitre et de vous concentrer sur les rubriques Info C++. Les programmeurs qui viennent dun autre environnement, comme Visual Basic, rencontreront la fois des concepts familiers et une syntaxe trs diffrente : nous leur recommandons de lire ce chapitre attentivement.

Un exemple simple de programme Java


Examinons le plus simple des programmes Java ; il se contente dafcher un message la console :
public class FirstSample { public static void main(String[] args) { System.out.println("We will not use Hello, World!"); } }

Mme si cela doit prendre un peu de temps, il est ncessaire de vous familiariser avec la prsentation de cet exemple ; les lments qui le composent se retrouveront dans toutes les applications. Avant tout, prcisons que Java est sensible la casse des caractres (majuscules et minuscules). Si vous commettez la moindre erreur en tapant, par exemple, Main au lieu de main, le programme ne pourra pas sexcuter ! Etudions maintenant le code source, ligne par ligne. Le mot cl public est appel un modicateur daccs (ou encore un spcicateur daccs ou spcicateur de visibilit) ; les modicateurs dterminent les autres parties du programme qui peuvent tre utilises par notre exemple. Nous reparlerons des modicateurs au Chapitre 5. Le mot cl class est l pour vous rappeler que tout ce que lon programme en Java se trouve lintrieur dune classe. Nous tudierons en dtail les classes dans le prochain chapitre, mais considrez ds prsent quune classe est une sorte de conteneur renfermant la logique du programme qui dnit le comportement dune application. Comme nous lavons vu au Chapitre 1, les classes sont des briques logicielles avec lesquelles sont construites toutes les applications ou applets Java. Dans un programme Java, tout doit toujours se trouver dans une classe. Derrire le mot cl class se trouve le nom de la classe. Les rgles de Java sont assez souples en ce qui concerne lattribution des noms de classes. Ceux-ci doivent commencer par une lettre et peuvent ensuite contenir nimporte quelle combinaison de lettres et de chiffres. Leur taille nest pas limite. Il ne faut cependant pas attribuer un mot rserv de Java (comme public ou class) un nom de classe (vous trouverez une liste des mots rservs dans lAnnexe). Comme vous pouvez le constater avec notre classe FirstSample, la convention gnralement admise est de former les noms de classes avec des substantifs commenant par une majuscule. Lorsquun nom est constitu de plusieurs mots, placez linitiale de chaque mot en majuscule. Il faut donner au chier du code source le mme nom que celui de la classe publique, avec lextension .java. Le code sera donc sauvegard dans un chier baptis FirstSample.java (rptons que la casse des caractres est importante, il ne faut pas nommer le chier firstsample.java). Si vous navez pas commis derreur de frappe en nommant le chier et en tapant le code source, la compilation de ce code produira un chier contenant le pseudo-code de la classe. Le compilateur Java nomme automatiquement le chier de pseudo-code FirstSample.class et le sauvegarde dans le mme rpertoire que le chier source. Lorsque tout cela est termin, lancez le programme laide de la commande suivante : javaFirstSample.

Chapitre 3

Structures fondamentales de la programmation Java

43

Ne spciez pas lextension .class. Lexcution du programme afche simplement la chane We will not use Hello, World! la console. Lorsque vous tapez la commande
java NomDeClasse

pour lancer un programme compil, la machine virtuelle dmarre toujours lexcution par les instructions de la mthode main de la classe spcie. Par consquent, vous devez crire une mthode main dans le chier source de la classe pour que le code puisse tre excut. Bien entendu, il est possible dajouter vos propres mthodes une classe et de les appeler partir de la mthode main (vous verrez au prochain chapitre comment crire vos propres mthodes).
INFO
Selon la spcication du langage Java, la mthode main doit tre dclare public. La spcication est le document ofciel qui dcrit le langage Java. Vous pouvez le consulter ou le tlcharger ladresse http://java.sun.com/docs/ books/jls. Toutefois, plusieurs versions du lanceur Java avaient pour intention dexcuter les programmes Java mme lorsque la mthode main ntait pas public. Un programmeur a alors rdig un rapport de bogue. Pour le voir, consultez le site http://bugs.sun.com/bugdatabase/index.jsp et entrez le numro didentication 4252539. Le bogue a toutefois fait lobjet de la mention "clos, ne sera pas rsolu". Un ingnieur Sun a ajout une explication indiquant que la spcication de la machine virtuelle ( ladresse http://java.sun.com/docs/books/vmspec) nobligeait pas ce que main soit public et a prcis que "le rsoudre risquait dentraner des problmes". Heureusement, le bon sens a ni par parler. Le lanceur Java de Java SE 1.4 et au-del oblige ce que la mthode main soit public. Cette histoire est assez intressante. Dun ct, il est dsagrable de voir que des ingnieurs dassurance qualit, souvent dbords et pas toujours au fait des aspects pointus de Java, prennent des dcisions contestables sur les rapports de bogues. De lautre, il est remarquable que Sun place les rapports de bogues et leurs rsolutions sur le Web, an que tout le monde puisse les tudier. La "parade des bogues" est une ressource trs utile pour les programmeurs. Vous pouvez mme "voter" pour votre bogue favori. Ceux qui runiront le plus de suffrages pourraient bien tre rsolus dans la prochaine version du JDK.

Remarquez les accolades dans le code source. En Java, comme en C/C++, les accolades sont employes pour dlimiter les parties (gnralement appeles blocs) de votre programme. En Java, le code de chaque mthode doit dbuter par une accolade ouvrante { et se terminer par une accolade fermante }. La manire demployer des accolades a provoqu une inpuisable controverse. Nous employons dans cet ouvrage un style dindentation classique, en alignant les accolades ouvrantes et fermantes de chaque bloc. Comme les espaces ne sont pas pris en compte par le compilateur, vous pouvez utiliser le style de prsentation que vous prfrez. Nous reparlerons de lemploi des accolades lorsque nous tudierons les boucles. Pour linstant, ne vous proccupez pas des mots cls staticvoid, songez simplement quils sont ncessaires la compilation du programme. Cette curieuse incantation vous sera familire la n du Chapitre 4. Rappelez-vous surtout que chaque application Java doit disposer dune mthode main dclare de la manire suivante :
public class NomDeClasse { public static void main(String[] args) { Instructions du programme } }

44

Au cur de Java 2 - Notions fondamentales

INFO C++
Les programmeurs C++ savent ce quest une classe. Les classes Java sont comparables aux classes C++, mais certaines diffrences risquent de vous induire en erreur. En Java, par exemple, toutes les fonctions sont des mthodes dune classe quelconque (la terminologie standard les appelle des mthodes et non des fonctions membres). Ainsi, Java requiert que la mthode main se trouve dans une classe. Sans doute tes-vous galement familiaris avec la notion de fonction membre statique en C++. Il sagit de fonctions membres dnies lintrieur dune classe et qui noprent pas sur des objets. En Java, la mthode main est toujours statique. Prcisons enn que, comme en C/C++, le mot cl void indique que la mthode ne renvoie aucune valeur. Contrairement C/C++, la mthode main ne renvoie pas un "code de sortie" au systme dexploitation. Si la mthode main se termine normalement, le programme Java a le code de sortie 0 qui lindique. Pour terminer le programme avec un code de sortie diffrent, utilisez la mthode System.exit.

Portez maintenant votre attention sur ce fragment de code :


{ System.out.println("We will not use Hello, World!"); }

Les accolades dlimitent le dbut et la n du corps de la mthode. Celle-ci ne contient quune seule instruction. Comme dans la plupart des langages de programmation, vous pouvez considrer les instructions Java comme les phrases du langage. En Java, chaque instruction doit se terminer par un point-virgule. En particulier, les retours la ligne ne dlimitent pas la n dune instruction, et une mme instruction peut donc occuper plusieurs lignes en cas de besoin. Le corps de la mthode main contient une instruction qui envoie une ligne de texte vers la console. Nous employons ici lobjet System.out et appelons sa mthode println. Remarquez que le point sert invoquer la mthode. Java utilise toujours la syntaxe
objet.mthode(paramtres)

pour ce qui quivaut un appel de fonction. Dans ce cas prcis, nous appelons la mthode println et lui passons une chane en paramtre. La mthode afche la chane sur la console. Elle passe ensuite la ligne an que chaque appel println afche la chane spcie sur une nouvelle ligne. Notez que Java, comme C/C++, utilise les guillemets pour dlimiter les chanes (pour plus dinformations, voir la section de ce chapitre consacre aux chanes). Comme les fonctions de nimporte quel langage de programmation, les mthodes de Java peuvent utiliser zro, un ou plusieurs paramtres (certains langages les appellent arguments). Mme si une mthode ne prend aucun paramtre, il faut nanmoins employer des parenthses vides). Il existe par exemple une variante sans paramtres de la mthode println qui imprime une ligne vide. Elle est invoque de la faon suivante :
System.out.println();

INFO

System.out dispose galement dune mthode print qui najoute pas de retour la ligne en sortie. Par exemple, System.out.print("Bonjour") imprime "Bonjour" sans retourner la ligne. La sortie suivante apparatra immdiatement derrire le "r" de "Bonjour".

Chapitre 3

Structures fondamentales de la programmation Java

45

Commentaires
Les commentaires de Java, comme ceux de la plupart des langages de programmation, napparaissent pas dans le programme excutable. Vous pouvez donc ajouter autant de commentaires que vous le souhaitez sans craindre de goner la taille du code compil. Java propose trois types de commentaires. Le plus courant est //, qui dbute un commentaire allant jusqu la n de ligne :
System.out.println("We will not use Hello, World!"); // Ce gag est-il trop connu ?

Lorsque des commentaires plus longs sont ncessaires, il est possible de placer // au dbut de chaque ligne, ou vous pouvez utiliser /* et */ pour dlimiter le dbut et la n dun long commentaire. Nous voyons ce type de dlimiteur au Listing 3.1.
Listing 3.1 : FirstSample.java
/** * Voici le premier exemple de programme de Au coeur de Java, Chapitre3 * @version 1.01 1997-03-22 * @author Gary Cornell */ public class FirstSample { public static void main(String[] args) { System.out.println("We will not use Hello, World!"); } }

Il existe un troisime type de commentaire utilisable pour la gnration automatique de documentation. Ce type de commentaire commence par /** et se termine par */. Pour en savoir plus sur la gnration automatique de documentation, consultez le Chapitre 4.
ATTENTION
Les commentaires /* */ ne peuvent pas tre imbriqus en Java. Autrement dit, pour dsactiver un bloc de code, il ne suft pas de lenfermer entre un /* et un */, car ce bloc peut lui-mme contenir un dlimiteur */.

Types de donnes
Java est un langage fortement typ. Cela signie que le type de chaque variable doit tre dclar. Il existe huit types primitifs en Java. Quatre dentre eux sont des types entiers (integer), deux sont des types rels virgule ottante, un est le type caractre char utilis pour le codage Unicode (voir la section consacre au type char) et le type boolean, pour les valeurs boolennes (vrai/faux).
INFO
Java dispose dun package arithmtique de prcision arbitraire. Cependant, les "grands nombres", comme on les appelle, sont des objets Java et ne constituent pas un nouveau type Java. Vous apprendrez les utiliser plus loin dans ce chapitre.

46

Au cur de Java 2 - Notions fondamentales

Entiers
Les types entiers reprsentent les nombres sans partie dcimale. Les valeurs ngatives sont autorises. Java dispose des quatre types prsents au Tableau 3.1.
Tableau 3.1 : Les types entiers de Java

Type int short long byte

Occupation en mmoire 4 octets 2 octets 8 octets 1 octet

Intervalle (limites incluses) 2 147 483 648 2 147 483 647 (un peu plus de 2 milliards) 32768 32767 9 223 372 036 854 775 808 9 223 372 036 854 775 807 128 127

Le type int se rvle le plus pratique dans la majorit des cas. Bien entendu, si vous dsirez exprimer le nombre des habitants de la plante, vous devrez employer le type long. Les types byte et short sont essentiellement destins des applications spcialises, telles que la gestion bas niveau des chiers ou la manipulation de tableaux volumineux, lorsque loccupation mmoire doit tre rduite au minimum. En Java, la plage valide des types de nombres entiers ne dpend pas de la machine sur laquelle sexcute le code. Cela pargne bien des efforts au programmeur souhaitant porter un logiciel dune plate-forme vers une autre, ou mme entre diffrents systmes dexploitation sur une mme plate-forme. En revanche, les programmes C et C++ utilisent le type dentier le plus efcace pour chaque processeur. Par consquent, un programme C qui fonctionne bien sur un processeur 32 bits peut provoquer un dpassement de capacit sur un systme 16 bits. Comme les programmes Java doivent sexcuter de la mme manire sur toutes les machines, les plages de valeur des diffrents types sont xes. Le sufxe des entiers longs est L (par exemple, 4000000000L). Le prxe des entiers hexadcimaux est 0x (par exemple, 0xCAFE). Les valeurs octales ont le prxe 0. Par exemple, 010 vaut 8. Cela peut prter confusion, il est donc dconseill davoir recours aux constantes octales.

INFO C++
En C et C++, int reprsente le type entier qui dpend de lordinateur cible. Sur un processeur 16 bits, comme le 8086, les entiers sont cods sur 2 octets. Sur un processeur 32 bits, comme le SPARC de Sun, ils sont cods sur 4 octets. Sur un Pentium Intel, le codage du type entier C et C++ dpend du systme dexploitation : 2 octets sous DOS et Windows 3.1, 4 octets sous Windows en mode 32 bits. En Java, la taille de tous les types numriques est indpendante de la plate-forme utilise. Remarquez que Java ne possde pas de type non sign.

Chapitre 3

Structures fondamentales de la programmation Java

47

Types virgule ottante


Les types virgule ottante expriment les nombres rels disposant dune partie dcimale. Il existe deux types virgule ottante, prsents au Tableau 3.2.
Tableau 3.2 : Les types virgule ottante

Type float double

Occupation en mmoire 4 octets 8 octets

Intervalle Environ 3.40282347E + 38F (6 ou 7 dcimales signicatives) Environ 1.79769313486231570E + 308 (15 chiffres signicatifs)

Le terme double indique que les nombres de ce type ont une prcision deux fois suprieure ceux du type float (on les appelle parfois nombres double prcision). On choisira de prfrence le type double dans la plupart des applications. La prcision limite de float se rvle insufsante dans de nombreuses situations. On ne lemploiera que dans les rares cas o la vitesse de calcul (plus leve pour les nombres prcision simple) est importante pour lexcution, ou lorsquune grande quantit de nombres doit tre stocke (an dconomiser la mmoire). Les nombres de type float ont pour sufxe F, par exemple 3.402F. Les nombres dcimales exprims sans ce sufxe F, par exemple 3.402, sont toujours considrs comme tant du type double. Pour ces derniers, il est galement possible de spcier un sufxe D, par exemple 3.402D.
INFO
Depuis Java SE 5.0, vous pouvez spcier des nombres virgule ottante en hexadcimal. Par exemple, 0.125 = 23 quivaut 0x1.0p-3. Dans la notation hexadcimale, vous utilisez p, et non e, pour indiquer un exposant.

Tous les calculs en virgule ottante respectent la spcication IEEE 754. En particulier, il existe trois valeurs spciales en virgule ottante, qui servent indiquer les dpassements et les erreurs :
m m m

innit positive ; innit ngative ; NaN (Not a Number pas un nombre).

Par exemple, le rsultat de la division dun nombre positif par 0 est "innit positive". Le calcul 0/0 ou la racine carre dun nombre ngatif donne "NaN".
INFO
Il existe des constantes Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY et Double.NaN (ainsi que les constantes correspondantes Float) permettant de reprsenter ces valeurs spciales. Elles sont cependant rarement utilises dans la pratique. En particulier, vous ne pouvez tester
if (x == Double.NaN) // nest jamais vrai

pour vrier si un rsultat particulier est gal Double.NaN. Toutes les valeurs "pas un nombre" sont considres comme distinctes. Vous pouvez cependant employer la mthode Double.isNaN:
if (Double.isNaN(x)) // vrifier si x est "pas un nombre"

48

Au cur de Java 2 - Notions fondamentales

ATTENTION
Les nombres virgule ottante ne conviennent pas aux calculs nanciers, dans lesquels les erreurs darrondi sont inadmissibles. La commande System.out.println(2.0 - 1.1) produit 0.8999999999999999, et non 0.9 comme vous pourriez le penser. Ces erreurs darrondi proviennent du fait que les nombres virgule ottante sont reprsents dans un systme de nombres binaires. Il nexiste pas de reprsentation binaire prcise de la fraction 1/10, tout comme il nexiste pas de reprsentation prcise de la fraction 1/3 dans le systme dcimal. Si vous voulez obtenir des calculs numriques prcis sans erreurs darrondi, utilisez la classe BigDecimal, prsente plus loin dans ce chapitre.

Le type char
Le type char sert dcrire des caractres individuels. Le plus souvent, il sagira de constantes de caractres. Par exemple, A est une constante de caractre de valeur 65. Elle diffre de "A", une chane contenant un seul caractre. Les units de code Unicode peuvent sexprimer sous forme de valeurs hexadcimales allant de \u0000 \uFFFF. Ainsi, \u2122 correspond au symbole de marque commerciale () et \u03C0 correspond la lettre grecque Pi (). Outre les squences dchappement \u, indiquant le codage en Unicode, il existe plusieurs squences dchappement pour les caractres spciaux, comme cela est indiqu au Tableau 3.3. Vous pouvez les utiliser dans des constantes et des chanes de caractres, comme \u2122 ou "Hello\ n". La squence dchappement \u (mais aucune des autres squences dchappement) peut mme tre utilise en dehors des constantes et des chanes de caractres entre apostrophes ou entre guillemets. Lexemple :
public static void main(String\u005B\u005D args)

est tout fait autoris, \u005B et \u005D sont les codages pour [ et ].
Tableau 3.3 : Squences dchappement pour les caractres spciaux

Squence dchappement \b \t \n \r \" \ \\

Nom Retour arrire Tabulation Sparation de ligne Retour chariot Guillemet Apostrophe Barre oblique inverse

Valeur Unicode \u0008 \u0009 \u000a \u000d \u0022 \u0027 \u005c

Pour bien comprendre le type char, vous devez connatre le schma de codage Unicode. Unicode a t invent pour surmonter les limitations des schmas de codage traditionnels. Avant lui, il existait de nombreuses normes diffrentes : ASCII aux Etats-Unis, ISO 8859-1 pour les langues dEurope de lEst, KOI-8 pour le russe, GB18030 et BIG-5 pour le chinois, etc. Deux problmes se posaient. Une valeur de code particulire correspondait des lettres diffrentes selon le schma de codage.

Chapitre 3

Structures fondamentales de la programmation Java

49

De plus, le codage des langues ayant de grands jeux de caractres avait des longueurs variables : certains caractres communs taient cods sous la forme doctets simples, dautres ncessitaient deux octets ou plus. Unicode a t conu pour rsoudre ces problmes. Aux dbuts des efforts dunication, dans les annes 1980, un code de largeur xe sur 2 octets tait plus que sufsant pour coder tous les caractres utiliss dans toutes les langues du monde, et il restait encore de la place pour une future extension (ou, du moins, cest ce que lon pensait). En 1991 est sorti Unicode 1.0, qui utilisait lgrement moins de la moiti des 65 536 valeurs de code disponibles. Java a t conu ds le dpart pour utiliser les caractres Unicode 16 bits, ce qui constituait une avance majeure sur les autres langages de programmation, qui utilisaient des caractres sur 8 bits. Malheureusement, au l du temps, ce qui devait arriver arriva. Unicode a grossi au-del des 65 536 caractres, principalement du fait de lajout dun trs grand jeu didogrammes utiliss pour le chinois, le japonais et le coren. Le type char 16 bits est dsormais insufsant pour dcrire tous les caractres Unicode. Nous ferons appel la terminologie pour expliquer comment ce problme a t rsolu en Java, et ce depuis Java SE 5.0. Un point de code est une valeur de code associe un caractre dans un schma de codage. Dans la norme Unicode, les points de code sont crits en hexadcimal et prxs par U+, par exemple U+0041 pour le point de code de la lettre A. Unicode possde des points de code regroups en 17 plans de codes. Le premier, appel plan multilingue de base, est constitu des caractres Unicode "classiques" ayant les points de code U+0000 U+FFFF. Seize autres plans, ayant les points de code U+10000 U+10FFFF, contiennent les caractres complmentaires. Le codage UTF-16 permet de reprsenter tous les points de code Unicode dans un code de longueur variable. Les caractres du plan multilingue de base sont reprsents sous forme de valeurs de 16 bits, appeles units de code. Les caractres complmentaires sont englobs sous forme de paires conscutives dunits de code. Chacune des valeurs dune paire de codage se situe dans une plage inutilise de 2 048 octets du plan multilingue de base, appel zone de remplacement (de U+D800 U+DBFF pour la premire unit de code, de U+DC00 U+DFFF pour la deuxime unit de code). Ceci est assez intressant, car vous pouvez immdiatement dire si une unit de code procde un codage sur un seul caractre ou sil sagit de la premire ou de la deuxime partie dun caractre complmentaire. Par exemple, le symbole mathmatique pour le jeu dentiers a le point de code U+1D56B et est cod par les deux units de code U+D835 et U+DD6B (voir http://en.wikipedia.org/wiki/UTF-16 pour obtenir une description de lalgorithme de codage). En Java, le type char dcrit une unit de code en codage UTF-16. Nous recommandons fortement de ne pas utiliser le type char dans vos programmes, moins dtre laise dans la manipulation des units de code UTF-16. Il vaut presque mieux traiter les chanes (ce que nous verrons plus loin) sous forme de types de donnes abstraits.

Type boolen
Le type boolean peut avoir deux valeurs, false (faux) ou true (vrai). Il est employ pour lvaluation de conditions logiques. Les conversions entre valeurs entires et boolennes sont impossibles.

50

Au cur de Java 2 - Notions fondamentales

INFO C++
En C++, des nombres et mme des pointeurs peuvent tre employs la place des valeurs boolennes. La valeur 0 quivaut la valeur boolenne false, et une valeur non zro quivaut true. Ce nest pas le cas avec Java. Les programmeurs Java sont donc mis en garde contre ce type daccidents :
if (x = 0) // pardon... je voulais dire x == 0

En C++, ce test est compil et excut, il donne toujours le rsultat false. En Java, la compilation du test choue, car lexpression entire x = 0 ne peut pas tre convertie en une valeur boolenne.

Variables
En Java, toute variable a un type. Vous dclarez une variable en spciant dabord son type, puis son nom. Voici quelques exemples de dclarations :
double salary; int vacationDays; long earthPopulation; boolean done;

Remarquez que chaque dclaration se termine par un point-virgule. Il est ncessaire, car une dclaration est une instruction Java complte. Un nom de variable doit dbuter par une lettre et doit tre une squence de lettres ou de chiffres. Notez que le sens des termes "lettres" et "chiffres" est plus large en Java que dans beaucoup dautres langages. Une lettre est dnie comme lun des caractres A...Z, a...z, _, ou nimporte quel caractre Unicode reprsentant une lettre dans une langue quelconque. Par exemple, un utilisateur franais ou allemand peut employer des caractres accentus, tels que ou , dans un nom de variable. De mme, les lecteurs grecs peuvent utiliser . Dune manire comparable, un chiffre est un des caractres 0 9, ou nimporte quel caractre Unicode reprsentant un chiffre dans une langue. Des espaces ou des symboles tels que + ou ne peuvent pas tre employs. Tous les caractres dun nom de variable sont signicatifs, ainsi que la casse de chaque caractre (minuscule ou majuscule). La longueur possible dun nom est thoriquement illimite.
ASTUCE
Si vous vous demandez lesquels des caractres Unicode sont des "lettres" pour Java, vous pouvez le savoir en appelant les mthodes isJavaIdentifierStart et isJavaIdentifierPart de la classe Character.

Il est videmment interdit de donner une variable le mme nom quun mot rserv de Java (voir, dans lAnnexe, la liste des mots rservs). Il est possible de dclarer plusieurs variables sur une seule ligne, comme suit :
int i, j; // ce sont deux entiers en Java

Ce style de dclaration nest pas recommand. Si vous dnissez chaque variable sparment, vos programmes seront plus faciles lire.

Chapitre 3

Structures fondamentales de la programmation Java

51

INFO
Vous avez vu que les noms taient sensibles la casse. Par exemple, hireday et hireDay sont deux noms diffrents. En rgle gnrale, nemployez pas deux noms diffrant seulement par la casse des caractres. Il est toutefois difcile parfois de dnir un nom correct pour une variable. Les programmeurs attribuent alors frquemment la variable le mme nom que le type, par exemple :
Box box; // OK--Box est le type et box le nom de la variable

Cependant, une meilleure solution consiste attribuer un prxe "a" la variable


Box aBox;

Initialisation des variables


Aprs avoir dclar une variable, vous devez explicitement linitialiser laide dune instruction daffectation. Vous ne devez pas utiliser une variable non initialise. Le compilateur Java signale la suite dinstructions suivantes comme une erreur :
int vacationDays; System.out.println(vacationDays); //ERREUR--variable non-initialise

Laffectation dune variable dclare se fait laide du symbole dgalit (=), prcd gauche du nom de la variable et suivi droite par une expression Java reprsentant la valeur approprie :
int vacationDays; vacationDays = 12;

Une fonctionnalit de Java permet de dclarer et dinitialiser simultanment une variable en une seule instruction, de la faon suivante :
int vacationDays = 12;

Prcisons enn que Java permet de dclarer des variables nimporte o dans le code. Par exemple, le code suivant est valide en Java :
double salary = 65000.0; System.out.println(salary); int vacationDays = 12; // vous pouvez dclarer la variable ici

En Java, il est recommand de dclarer les variables aussi prs que possible du point de leur premire utilisation.
INFO C++
C et C++ font une distinction entre une dclaration et une dnition de variables. Par exemple,
int i = 10;

est une dnition, alors que


extern int i;

est une dclaration. En Java, il ny a pas de dclaration spare de la dnition.

52

Au cur de Java 2 - Notions fondamentales

Constantes
En Java, le mot cl final sert dsigner une constante. Voici un exemple :
public class Constants { public static void main(String[] args) { final double CM_PER_INCH = 2.54; double paperWidth = 8.5; double paperHeight = 11; System.out.println("Paper size in centimeters: " +paperWidth * CM_PER_INCH+" by " +paperHeight * CM_PER_INCH); } }

Le mot cl final signie que vous affectez une valeur la variable une seule fois, et une fois pour toutes. Par convention, les noms des constantes sont entirement en majuscules. Il est plus courant de crer une constante qui est accessible plusieurs mthodes de la mme classe. Les mthodes de ce genre sont appeles gnralement constantes de classe. On dnit une constante de classe laide des mots cls static final. Voici un exemple utilisant une constante de classe :
public class Constants2 { public static void main(String[] args) { double paperWidth = 8.5; double paperHeight = 11; System.out.println("Paper size in centimeters: " +paperWidth * CM_PER_INCH+" by " +paperHeight * CM_PER_INCH); } public static final double CM_PER_INCH = 2.54; }

Notez que la dnition de la constante de classe apparat en dehors de la mthode main. La constante peut donc aussi tre employe dans dautres mthodes de la mme classe. De plus, si (comme dans notre exemple), la constante est dclare public, les mthodes des autres classes peuvent aussi utiliser la constante sous la forme Constants2.CM_PER_INCH, dans notre exemple.
INFO C++
Bien que const soit un mot rserv de Java, il nest pas utilis actuellement. Cest le mot cl final qui permet de dnir une constante.

Oprateurs
Les habituels oprateurs arithmtiques + * / sont respectivement utiliss en Java pour les oprations daddition, de soustraction, de multiplication et de division. Loprateur de division / indique une division entire si les deux arguments sont des entiers et une division en virgule ottante dans les

Chapitre 3

Structures fondamentales de la programmation Java

53

autres cas. Le reste dune division entire est reprsent par le symbole %. Par exemple, 15/2 donne 7; 15% 2 donne 1 et 15.0/2 donne 7.5. Notez que la division dun entier par 0 dclenche une exception, alors que la division dune valeur en virgule ottante par 0 donne un rsultat inni ou NaN. Les oprateurs arithmtiques peuvent tre utiliss en combinaison avec loprateur daffectation pour simplier lcriture dune affectation. Par exemple, linstruction
x += 4;

quivaut linstruction
x = x+4;

En rgle gnrale, placez loprateur arithmtique gauche du signe =, par exemple *= ou %=.
INFO
Lun des intrts vidents de la programmation en langage Java est la portabilit. Un calcul doit aboutir au mme rsultat quelle que soit la machine virtuelle sur laquelle il est excut. Dans le cas de calculs avec des nombres virgule ottante, il est tonnamment difcile dobtenir cette portabilit. Le type double utilise 64 bits pour le stockage dune valeur numrique, mais certains processeurs emploient des registres virgule ottante de 80 bits. Ces registres ajoutent une prcision lors des tapes intermdiaires dun calcul. Examinez, par exemple, le calcul suivant :
double w = x * y / z;

De nombreux processeurs Intel vont calculer x * y et placer le rsultat dans un registre 80 bits, puis diviser par z pour nalement tronquer le rsultat 64 bits. Cela peut donner un rsultat plus prcis et viter un dpassement dexposant. Cependant le rsultat peut tre diffrent de celui dun calcul effectu constamment sur 64 bits. Pour cette raison, la spcication initiale de la machine virtuelle Java prvoyait la troncation de tous les calculs intermdiaires, au grand dam de la communaut numrique. Non seulement les calculs tronqus peuvent provoquer un dpassement de capacit, mais ils sont aussi plus lents, du fait du temps ncessaire aux oprations de troncation. Cest pourquoi le langage Java a t actualis pour tenir compte des besoins conictuels de performance optimale et de reproductibilit parfaite. Par dfaut, les concepteurs de machine virtuelle peuvent maintenant utiliser une prcision tendue pour les calculs intermdiaires. Toutefois, les mthodes balises avec le mot cl strictfp doivent utiliser des oprations virgule ottante strictes menant des rsultats reproductibles. Vous pouvez, par exemple, baliser la mthode main de la faon suivante :
public static strictfp void main(String[] args)

Toutes les instructions au sein de main auront alors recours des calculs virgule ottante stricts. Si vous balisez une classe laide de strictfp, toutes ses mthodes utiliseront les calculs virgule ottante stricts. Les dtails sordides dpendent essentiellement du comportement des processeurs Intel. En mode par dfaut, les rsultats intermdiaires sont autoriss utiliser un exposant tendu, mais pas une mantisse tendue (les puces Intel grent la troncation de la mantisse sans perte de performance). Par consquent, la seule diffrence entre les modes par dfaut et strict est que les calculs stricts peuvent engendrer des dpassements de capacit, la diffrence des calculs par dfaut. Si vos yeux sarrondissent la lecture de cette Info, ne vous inquitez pas. Le dpassement de capacit en virgule ottante ne se pose pas pour la majorit des programmes courants. Nous nutiliserons pas le mot cl strictfp dans cet ouvrage.

54

Au cur de Java 2 - Notions fondamentales

Oprateurs dincrmentation et de dcrmentation


Les programmeurs savent videmment quune des oprations les plus courantes effectues sur une variable numrique consiste lui ajouter ou lui retrancher 1. Suivant les traces de C et de C++, Java offre des oprateurs dincrmentation et de dcrmentation : n++ ajoute 1 la valeur courante n et n-- retranche 1 cette valeur. Ainsi, cet exemple :
int n = 12; n++;

donne n la valeur 13. Comme ces oprateurs modient la valeur dune variable, ils ne peuvent pas tre appliqus des nombres. Par exemple, 4++ nest pas une instruction valide. Ces oprateurs peuvent en ralit prendre deux formes ; vous avez vu la forme "sufxe", o loprateur est situ aprs loprande : n++. Il peut galement tre plac en prxe : ++n. Dans les deux cas, la variable est incrmente de 1. La diffrence entre ces deux formes napparat que lorsquelles sont employes dans des expressions. Lorsquil est plac en prxe, loprateur effectue dabord laddition ; en sufxe, il fournit lancienne valeur de la variable :
int int int int m n a b = = = = 7; 7; 2 * ++m; // maintenant a vaut 16, m vaut 8 2 * n++; // maintenant b vaut 14, n vaut 8

Nous dconseillons vivement lemploi de loprateur ++ dans dautres expressions, car cela entrane souvent un code confus et des bogues difciles dtecter. Loprateur ++ a bien videmment donn son nom au langage C++, mais il a galement provoqu une des premires plaisanteries propos de ce langage. Les anti-C++ ont fait remarquer que mme le nom du langage contenait un bogue : "En fait, il devrait sappeler ++C, car on ne dsire employer le langage quaprs son amlioration."

Oprateurs relationnels et boolens


Java offre le jeu complet doprateurs relationnels. Un double signe gal, ==, permet de tester lgalit de deux oprandes. Par exemple, lvaluation de
3 == 7

donne un rsultat faux (false). Utilisez != pour pratiquer un test dingalit. Par exemple, le rsultat de
3!= 7

est vrai (true). De plus, nous disposons des habituels oprateurs < (infrieur ), > (suprieur ), <= (infrieur ou gal ) et >= (suprieur ou gal ). Suivant lexemple de C++, Java utilise && comme oprateur "et" logique et || comme oprateur "ou" logique. Le point dexclamation ! reprsente loprateur de ngation. Les oprateurs && et || sont valus de manire optimise (en court-circuit). Le deuxime argument nest pas valu si le premier dtermine dj la valeur. Si vous combinez deux expressions avec loprateur &&,
expression 1 && expression 2

Chapitre 3

Structures fondamentales de la programmation Java

55

la valeur de la deuxime expression nest pas calcule si la premire a pu tre value false (puisque le rsultat nal serait false de toute faon). Ce comportement peut viter des erreurs. Par exemple, dans lexpression
x!= 0 && 1 / x > x+y // pas de division par 0

la seconde partie nest jamais value si x gale zro. Par consquent, 1/x nest pas calcul si x vaut zro, et une erreur "division par zro" ne peut pas se produire. De mme, si la premire expression est value true, la valeur de expression1 || expression2 est automatiquement true, sans que la deuxime expression doive tre value. Enn, Java gre loprateur ternaire ? qui se rvle utile loccasion. Lexpression
condition? expression 1: expression 2

est value expression1 si la condition est true, expression2 sinon. Par exemple,
x < y? x: y

donne le plus petit entre x et y.

Oprateurs binaires
Lorsquon manipule des types entiers, il est possible demployer des oprateurs travaillant directement sur les bits qui composent ces entiers. Certaines techniques de masquage permettent de rcuprer un bit particulier dans un nombre. Les oprateurs binaires sont les suivants :
&("et") |("ou") ^("ou exclusif") ~("non")

Ces oprateurs travaillent sur des groupes de bits. Par exemple, si n est une variable entire, alors la dclaration
int quatrimeBitPartantDeDroite = (n & 8) / 8;

renvoie 1 si le quatrime bit partir de la droite est 1 dans la reprsentation binaire de n, et zro dans le cas contraire. Lemploi de & avec la puissance de deux approprie permet de masquer tous les bits sauf un.
INFO
Lorsquils sont appliqus des valeurs boolean, les oprateurs & et | donnent une valeur boolean. Ces oprateurs sont comparables aux oprateurs && et ||, except que & et | ne sont pas valus de faon optimise "court-circuit". Cest--dire que les deux arguments sont valus en premier, avant le calcul du rsultat.

Il existe galement des oprateurs >> et << permettant de dcaler un groupe de bits vers la droite ou vers la gauche. Ces oprateurs se rvlent pratiques lorsquon veut construire des masques binaires :
int quatrimeBitPartantDeDroite = (n & (1 << 3)) >> 3;

Signalons enn quil existe galement un oprateur >>> permettant de remplir les bits de poids fort avec des zros, alors que >> tend le bit de signature dans les bits de poids fort. Il nexiste pas doprateur <<<.

56

Au cur de Java 2 - Notions fondamentales

ATTENTION
Largument droite des oprateurs de dcalage est rduit en modulo 32 ( moins quil ny ait un type long du ct gauche, auquel cas le ct droit est rduit en modulo 64). Par exemple, la valeur de 1 << 35 est la mme que 1 << 3 soit 8.

INFO C++
En C/C++, rien ne vous garantit que >> accomplit un dcalage arithmtique (en conservant le signe) ou un dcalage logique (en remplissant les bits avec des zros). Les concepteurs de limplmentation sont libres de choisir le mcanisme le plus efcace. Cela signie que loprateur >> de C/C++ nest rellement dni que pour des nombres qui ne sont pas ngatifs. Java supprime cette ambigut.

Fonctions mathmatiques et constantes


La classe Math contient un assortiment de fonctions mathmatiques qui vous seront parfois utiles, selon le type de programmation que vous ralisez. Pour extraire la racine carre dun nombre, vous disposez de la mthode sqrt :
double x = 4; double y = Math.sqrt(x); System.out.println(y); // affiche 2.0

INFO
Il existe une subtile diffrence entre les mthodes println et sqrt. La mthode println opre sur un objet, System.out, dni dans la classe System. Mais la mthode sqrt dans la classe Math nopre pas sur un objet. Une telle mthode est qualie de statique. Vous tudierez les mthodes statiques au Chapitre 4.

Le langage de programmation Java ne dispose pas doprateur pour lever une quantit une puissance : vous devez avoir recours la mthode pow de la classe Math. Linstruction
double y = Math.pow(x, a);

dnit y comme la valeur x leve la puissance a (xa). La mthode pow a deux paramtres qui sont du type double, et elle renvoie galement une valeur double. La classe Math fournit les fonctions trigonomtriques habituelles :
Math.sin Math.cos Math.tan Math.atan Math.atan2

et la fonction exponentielle et son inverse, le logarithme naturel :


Math.exp Math.log

Il y a enn deux constantes qui donnent les approximations les plus proches possibles des constantes mathmatiques et e :
Math.PI Math.E

Chapitre 3

Structures fondamentales de la programmation Java

57

ASTUCE
Depuis Java SE 5.0, vous pouvez viter le prxe Math pour les mthodes mathmatiques et les constantes en ajoutant la ligne suivante en haut de votre chier source :
import static java.lang.Math.*;

Par exemple,
System.out.println("The square root of \u03C0 is " + sqrt(PI));

Nous verrons les importations statiques au Chapitre 4.

INFO
Les fonctions de la classe Math utilisent les routines dans lunit virgule ottante du calculateur pour une meilleure performance. Si des rsultats totalement prvisibles sont plus importants que la rapidit, employez plutt la classe StrictMath. Elle implmente les algorithmes provenant de la bibliothque mathmatique fdlibm "Freely Distributable Math Library", qui garantit des rsultats identiques quelle que soit la plate-forme. Consultez le site http:// www.netlib.org/fdlibm/index.html pour obtenir la source de ces algorithmes (alors que fdlibm fournit plus dune dnition pour une fonction, la classe StrictMath respecte la version IEEE 754 dont le nom commence par un "e").

Conversions de types numriques


Il est souvent ncessaire de convertir un type numrique en un autre. La Figure 3.1 montre les conversions lgales :
Figure 3.1
Conversions lgales entre types numriques.

char

byte

short

int

long

float

double

Les six ches noires la Figure 3.1 indiquent les conversions sans perte dinformation. Les trois ches grises indiquent celles pouvant souffrir dune perte de prcision. Par exemple, un entier large tel que 123456789 a plus de chiffres que ne peut en reprsenter le type float. Sil est converti en type float, la valeur rsultante a la magnitude correcte, mais elle perd en prcision :
int n = 123456789; float f = n; // f vaut 1.234567 92E8

Lorsque deux valeurs sont combines laide dun oprateur binaire (par exemple, n+f o n est un entier et f une valeur virgule ottante), les deux oprandes sont convertis en un type commun avant la ralisation de lopration :
m

Si lun quelconque des oprandes est du type double, lautre sera converti en type double.

58

Au cur de Java 2 - Notions fondamentales

m m m

Sinon, si lun quelconque des oprandes est du type float, lautre sera converti en type float. Sinon, si lun quelconque des oprandes est du type long, lautre sera converti en type long. Sinon, les deux oprandes seront convertis en type int.

Transtypages
Dans la section prcdente, vous avez vu que les valeurs int taient automatiquement converties en valeurs double en cas de besoin. Dautre part, il existe videmment des cas o vous voudrez considrer un double comme un entier. Les conversions numriques sont possibles en Java, mais bien entendu, au prix dune perte possible dinformations. Les conversions risquant des pertes dinformations sont faites laide de transtypages (ou conversions de type). La syntaxe du transtypage fournit le type cible entre parenthses, suivi du nom de la variable. Par exemple :
double x = 9.997; int nx = (int) x;

La variable nx a alors la valeur 9, puisque la conversion dun type ottant en un type entier fait perdre la partie fractionnaire. Si vous voulez arrondir un nombre virgule ottante en lentier le plus proche (lopration la plus utile dans la plupart des cas), utilisez la mthode Math.round :
double x = 9.997; int nx = (int) Math.round(x);

Maintenant, la variable nx a la valeur 10. Vous devez toujours utiliser le transtypage (int) si vous appelez round. Cest parce que la valeur renvoye par la mthode round est du type long et quun long ne peut qutre affect un int avec un transtypage explicite, puisquil existe une possibilit de perte dinformations.
ATTENTION
Si vous essayez de convertir un nombre dun type en un autre qui dpasse ltendue du type cible, le rsultat sera un nombre tronqu ayant une valeur diffrente. Par exemple, (byte) 300 vaut en ralit 44.

INFO C++
Vous ne pouvez convertir des valeurs boolean en quelque type numrique que ce soit. Cela vite les erreurs courantes. Si, exceptionnellement, vous voulez convertir une valeur boolean en un nombre, vous pouvez avoir recours une expression conditionnelle telle que b? 1: 0.

Parenthses et hirarchie des oprateurs


La hirarchie normale des oprations en Java est prsente au Tableau 3.4. En labsence de parenthses, les oprations sont ralises dans lordre hirarchique indiqu. Les oprateurs de mme niveau sont traits de gauche droite, sauf pour ceux ayant une association droite, comme indiqu dans le tableau. Par exemple, && ayant une priorit suprieure ||, lexpression
a && b || c

signie
(a && b) || c

Chapitre 3

Structures fondamentales de la programmation Java

59

Etant donn que += a une associativit de droite gauche, lexpression


a += b += c

signie
a += (b += c)

En fait, la valeur de b += c (qui est la valeur de b aprs laddition) est ajoute a.


INFO C++
Contrairement C et C++, Java ne dispose pas dun oprateur "virgule". Il est nanmoins possible dutiliser une liste dexpressions spares par des virgules comme premier ou troisime lment dune instruction for.

Tableau 3.4 : Prsance des oprateurs

Oprateurs [] . () (appel de mthode) ! ~ ++ --+ (unaire) - (unaire) () (transtypage) new * /% + << >> >>> < <= > >= instanceof ==!= & ^ | && || ?: = += -= *= /= %= &= |= ^= <<= >>= >>>=

Associativit De la gauche vers la droite De la droite vers la gauche De la gauche vers la droite De la gauche vers la droite De la gauche vers la droite De la gauche vers la droite De la gauche vers la droite De la gauche vers la droite De la gauche vers la droite De la gauche vers la droite De la gauche vers la droite De la gauche vers la droite De la droite vers la gauche De la droite vers la gauche

Types numrs
Une variable ne doit quelquefois contenir quun jeu de valeurs limit. Vous pouvez par exemple vendre des vtements ou des pizzas en quatre formats : petit, moyen, grand, trs grand. Bien sr, ces formats pourraient tre cods sous forme dentiers 1, 2, 3, 4 ou de caractres S, M, L et X. Mais cette conguration est sujette erreur. Une variable peut trop facilement contenir une valeur errone (comme 0 ou m). Depuis Java SE 5.0, vous pouvez dnir votre propre type numr ds quune situation se prsente. Un type numr possde un nombre ni de valeurs nommes. Par exemple,
enum Size { SMALL, MEDIUM, LARGE, EXTRA_LARGE };

60

Au cur de Java 2 - Notions fondamentales

Vous pouvez maintenant dclarer des variables de ce type :


Size s = Size.MEDIUM;

Une variable de type Size ne peut contenir que lune des valeurs listes dans la dclaration de type ou la valeur spciale null qui indique que la variable nest dnie sur aucune valeur. Nous verrons les types numrs plus en dtail au Chapitre 5.

Chanes
Les chanes sont des suites de caractres Unicode. Par exemple, la chane "Java\u2122" est constitue de cinq caractres Unicode J, a, v, a et . Java ne possde pas de type chane intgr. En revanche, la bibliothque standard Java contient une classe prdnie appele, assez naturellement, String. Chaque chane entre guillemets est une instance de la classe String :
String e = ""; // une chane vide String greeting = "Hello";

Sous-chanes
Pour extraire une sous-chane partir dune chane plus grande, utilisez la mthode substring de la classe String. Par exemple,
String greeting = "Hello"; String s = greeting.substring(0, 3);

cre une chane constitue des caractres "Hel". Le deuxime paramtre de substring est la premire unit de code que vous ne souhaitez pas copier. Ici, nous voulons copier les units de code des positions 0, 1 et 2 (de la position 0 la position 2 incluse). Pour substring, cela va de la position 0 incluse la position 3 (exclue). On reconnat un avantage au fonctionnement de substring : il facilite le calcul du nombre dunits de code prsentes dans la sous-chane. La chane s.substring(a, b) a toujours les units de code b-a. Par exemple, la sous-chane "Hel" a 3 0 = 3 units de code.

Concatnation
Java, comme la plupart des langages de programmation, autorise lemploi du signe + pour joindre (concatner) deux chanes :
String expletive = "Expletive"; String PG13 = "deleted"; String message = expletive+PG13;

Le code qui prcde donne la variable message le contenu "Expletivedeleted" (remarquez quil ninsre pas despace entre les mots : le signe + accole les deux chanes exactement telles quelles sont fournies). Lorsque vous concatnez une chane et une valeur qui nest pas une chane, cette valeur est convertie en chane (comme vous le verrez au Chapitre 5, tout objet Java peut tre converti en chane).

Chapitre 3

Structures fondamentales de la programmation Java

61

Voici un exemple :
int age = 13; String rating = "PG"+age;

qui donne la chane rating la valeur "PG13". Cette caractristique est utilise couramment pour lafchage. Par exemple, linstruction
System.out.println("The answer is "+answer);

est parfaitement valide et afchera la rponse voulue avec un espacement correct (notez lespace derrire le mot is).

Les chanes sont inaltrables


La classe String ne fournit pas de mthode permettant de modier un caractre dans une chane existante. Au cas o vous voudriez transformer le contenu de la variable greeting en "Help!", vous ne pouvez pas remplacer directement la dernire position de greeting par p et par !. Si vous tes un programmeur C, vous devez vous sentir frustr. Et, pourtant, la solution est trs simple en Java : il suft de rcuprer la sous-chane conserver et de la concatner avec les caractres remplacer :
greeting= greeting.substring(0, 3)+ "p!";

Cette instruction donne la variable greeting la valeur "Help!". Comme il nest pas possible de modier individuellement des caractres dans une chane Java, la documentation indique que les objets de la classe String sont inaltrables. Tout comme le nombre 3 vaut toujours 3, la chane "Hello" contient toujours la suite de caractres H, e, l, l, o. Il est impossible de changer cette valeur. En revanche, comme nous venons de le voir, il est possible de modier le contenu de la variable chane greeting et de lui faire rfrencer une chane diffrente (de mme quune variable numrique contenant la valeur 3 peut recevoir la valeur 4). On pourrait penser que tout cela nest pas trs performant. Ne serait-il pas plus simple de changer les units de code au lieu de construire une nouvelle chane ? Oui et non. En vrit, il nest pas trs intressant de gnrer une nouvelle chane contenant la concatnation de "Hel" et de "p!". Mais les chanes inaltrables possdent pourtant un norme avantage : le compilateur peut les partager. Pour comprendre cette technique, imaginez que les diverses chanes rsident dans un pool commun. Les variables chanes, quant elles, pointent sur des positions dans le pool. Si vous copiez une variable chane, la chane dorigine et la copie partagent les mmes caractres. Tout bien considr, les concepteurs de Java ont estim que lefcacit du partage des chanes surpassait linconvnient de la modication des chanes par extraction de sous-chanes et concatnation. Examinez vos propres programmes ; la plupart du temps, vous ne modiez sans doute pas les chanes vous vous contentez de les comparer. Bien entendu, il existe des situations o une manipulation directe se rvle plus efcace (par exemple lorsquon assemble des chanes partir de caractres individuels taps au clavier ou rcuprs dans un chier). Pour ces cas-l, Java fournit une classe spare, que nous dcrivons plus loin dans ce chapitre.

62

Au cur de Java 2 - Notions fondamentales

INFO C++
Les programmeurs C sont souvent stupfaits lorsquils rencontrent des chanes Java pour la premire fois, car ils considrent les chanes comme des tableaux de caractres :
char greeting[] = "Hello";

La comparaison nest pas bonne ; une chane Java ressemble davantage un pointeur char*:
char* greeting = "Hello";

Lorsque vous remplacez la valeur de greeting par une autre chane, le code Java excute peu prs ce qui suit :
char* temp = malloc(6); strncpy(temp, greeting, 3); strcpy(temp+3, "p!", 3); greeting = temp;

greeting pointe maintenant vers la chane "Help!". Et mme le plus intgriste des programmeurs C doit reconnatre que la syntaxe de Java est plus agrable quune srie dappels strncpy. Mais que se passe-t-il si nous affectons une nouvelle valeur greeting?
greeting = "Howdy";

La chane dorigine ne va-t-elle pas occuper inutilement la mmoire, puisquelle a t alloue dans le pool ? Heureusement, Java rcupre automatiquement la mmoire libre. Si un bloc de mmoire nest plus utilis, il nira par tre recycl. Si vous programmez en C++ et utilisez la classe string dnie par ANSI C++, vous vous sentirez laise avec le type String de Java. Les objets string de C++ se chargent automatiquement de lallocation et de la rcupration de la mmoire. La gestion de la mmoire est accomplie explicitement par les constructeurs, les oprateurs daffectations et les destructeurs. Cependant, les chanes C++ sont altrables il est possible de modier individuellement un caractre dans une chane.

Test dgalit des chanes


Pour savoir si deux chanes sont gales, utilisez la mthode equals; lexpression
s.equals(t)

donne true si les chanes s et t sont gales, et false dans le cas contraire. Notez que s et t peuvent tre des variables chanes ou des constantes chanes. Par exemple,
"Hello".equals(greeting)

est parfaitement valide. Pour tester si deux chanes sont identiques lexception de la casse des caractres, utilisez la mthode equalsIgnoreCase :
"Hello".equalsIgnoreCase("hello")

Attention, nemployez pas loprateur == pour tester lgalit de deux chanes ! Cet oprateur dtermine seulement si les chanes sont stockes au mme emplacement. Il est vident que si deux chanes se trouvent la mme adresse, elles doivent tre gales. Mais des copies de chanes identiques peuvent tre stockes des emplacements diffrents dans la mmoire :
String greeting = "Hello"; //initialise la chane greeting if (greeting == "Hello") . . . // probablement vrai

Chapitre 3

Structures fondamentales de la programmation Java

63

if (greeting.substring(0, 3) == "Hel") . . . // probablement faux

Si la machine virtuelle faisait en sorte de toujours partager les chanes identiques, loprateur == pourrait tre utilis pour un test dgalit. Mais seules les constantes chanes sont partages, et non les chanes cres laide de loprateur + ou de la mthode substring. Par consquent, nemployez jamais == pour comparer des chanes, car cela pourrait gnrer le pire des bogues : un bogue intermittent qui semble se produire alatoirement.
INFO C++
Si vous tes familiaris avec la classe string de C++, vous devez tre particulirement attentif aux tests dgalit, car la classe string surcharge loprateur == pour tester lgalit du contenu de deux chanes. Il est peut-tre dommage que les chanes Java aient un "aspect gnral" comparable celui des valeurs numriques, mais quelles se comportent comme des pointeurs lors des tests dgalit. Les concepteurs auraient pu rednir == pour ladapter aux chanes, comme ils lont fait pour loprateur +, mais aucun langage nest parfait. Pour comparer des chanes, les programmeurs C nemploient pas loprateur ==, mais la fonction strcmp. La mthode analogue en langage Java est compareTo. Vous pouvez crire
if (greeting.compareTo("Hello") == 0) . . .

mais il est plus pratique dappeler la mthode equals.

Points de code et units de code


Les chanes Java sont implmentes sous forme de suites de valeurs char. Comme nous lavons vu, le type char est une unit de code permettant de reprsenter des points de code Unicode en codage UTF-16. Les caractres Unicode les plus souvent utiliss peuvent tre reprsents par une seule unit de code. Les caractres complmentaires exigent, quant eux, une paire dunits de code. La mthode length produit le nombre dunits de code exig pour une chane donne dans le codage UTF-16. Par exemple :
String greeting = "Hello"; int n = greeting.length(); // vaut 5

Pour obtenir la bonne longueur, cest--dire le nombre de points de code, appelez


int cpCount = greeting.codePointCount(0, greeting.length());

Lappel s.chartAt(n) renvoie lunit de code prsente la position n, o n est compris entre 0 et s.length() -1. Par exemple :
char first = greeting.charAt(0); // le premier est H char last = greeting.charAt(4); // le dernier est o

Pour accder au ime point de code, utilisez les instructions


int index = greeting.offsetByCodePoints(0, i); int cp = greeting.codePointAt(index);

64

Au cur de Java 2 - Notions fondamentales

INFO
Java compte les units de code dans les chanes dune manire particulire : la premire unit de code dune chane occupe la position 0. Cette convention est ne dans le C, une poque o il existait une raison technique dmarrer les positions 0. Cette raison a disparu depuis longtemps, et seule la nuisance demeure. Toutefois, les programmeurs sont tellement habitus cette convention que les concepteurs Java ont dcid de la garder.

Pourquoi nous proccuper tant des units de code ? Etudiez la phrase


is the set of integers

Le caractre

exige deux units de code en codage UTF-16. Appeler

char ch = sentence.charAt(1)

ne renvoie pas un espace mais la deuxime unit de code de . Pour viter ce problme, il vaut mieux ne pas utiliser le type char, qui est de trop bas niveau. Si votre code traverse une chane et que vous souhaitiez tudier chaque point de code tour tour, utilisez ces instructions :
int cp = sentence.codePointAt(i); if (Character.isSupplementaryCodePoint(cp)) i += 2; else i++;

Heureusement, la mthode codePointAt peut dire si une unit de code est la premire ou la seconde moiti dun caractre complmentaire, elle renvoie le bon rsultat quel que soit le cas. Ainsi, vous pouvez revenir en arrire avec les instructions suivantes :
i--; int cp = sentence.codePointAt(i); if (Character.isSupplementaryCodePoint(cp)) i--;

LAPI String
La classe String de Java contient plus de 50 mthodes. Beaucoup dentre elles sont assez utiles pour tre employes couramment. La note API qui suit prsente les mthodes qui nous semblent les plus intressantes pour le programmeur.
INFO
Vous rencontrerez ces notes API dans tout louvrage. Elles vous aideront comprendre linterface de programmation Java, ou API (Application Programming Interface). Chaque note API commence par le nom dune classe, tel que java.lang.String la signication du nom de package java.lang sera explique au Chapitre 4. Le nom de la classe est suivi des noms, explications et descriptions des paramtres dune ou plusieurs mthodes de cette classe. Nous ne donnons pas la liste de toutes les mthodes dune classe donne, mais seulement celles qui sont utilises le plus frquemment, dcrites sous une forme concise. Consultez la documentation en ligne si vous dsirez obtenir une liste complte des mthodes dune classe. Nous indiquons galement le numro de version dans laquelle une classe particulire a t introduite. Si une mthode a t ajoute par la suite, elle prsente un numro de version distinct.

Chapitre 3

Structures fondamentales de la programmation Java

65

java.lang.String 1.0

char charAt(int index)

Renvoie lunit de code situe la position spcie. Vous ne voudrez probablement pas appeler cette mthode moins dtre intress par les units de code de bas niveau.

int codePointAt(int index) 5.0

Renvoie le point de code qui dmarre ou se termine lemplacement spci.


int offsetByCodePoints(int startIndex, int cpCount) 5.0

Renvoie lindice du point de code do pointe cpCount, depuis le point de code jusqu startIndex.

int compareTo(String other)

Renvoie une valeur ngative si la chane se trouve avant other (dans lordre alphabtique), une valeur positive si la chane se trouve aprs other ou un 0 si les deux chanes sont identiques.

boolean endsWith(String suffix)

Renvoie true si la chane se termine par suffix.


boolean equals(Object other)

Renvoie true si la chane est identique other.


boolean equalsIgnoreCase(String other)

Renvoie true si la chane est identique other, sans tenir compte de la casse.
int indexOf(String str) int indexOf(String str, int fromIndex) int indexOf(int cp) int indexOf(int cp, int fromIndex)

Renvoient la position de dpart de la premire sous-chane gale str ou au point de code cp, en commenant par la position 0 ou par fromIndex ou 1 si str napparat pas dans cette chane.

int lastIndexOf(String str) int lastIndexOf(String str, int fromIndex) int lastIndexOf(int cp) int lastIndexOf(int cp, int fromIndex)

Renvoient la position de dpart de la dernire sous-chane gale str ou au point de code cp, en commenant la n de la chane ou par fromIndex.

int length()

Renvoie la taille (ou longueur) de la chane.


int codePointCount(int startIndex, int endIndex) 5.0

Renvoie le nombre de points de code entre startIndex et endIndex-1. Les substitutions sans paires sont considres comme des points de code.

String replace(CharSequence oldString, CharSequence newString)

Renvoie une nouvelle chane, obtenue en remplaant tous les caractres oldString de la chane par les caractres newString. Vous pouvez fournir des objets String ou StringBuilder pour les paramtres CharSequence.

66

Au cur de Java 2 - Notions fondamentales

boolean startsWith(String prefix)

Renvoie true si la chane commence par prefix.


String substring(int beginIndex) String substring(int beginIndex, int endIndex)

Renvoient une nouvelle chane compose de toutes les units de code situes entre beginIndex et, soit la n de la chane, soit endIndex-1.

String toLowerCase()

Renvoie une nouvelle chane compose de tous les caractres de la chane dorigine, mais dont les majuscules ont t converties en minuscules.

String toUpperCase()

Renvoie une nouvelle chane compose de tous les caractres de la chane dorigine, mais dont les minuscules ont t converties en majuscules.

String trim()

Renvoie une nouvelle chane en liminant tous les espaces qui auraient pu se trouver devant ou derrire la chane dorigine.

Lire la documentation API en ligne


Vous avez vu que la classe String comprend quantit de mthodes. Il existe de plus des milliers de classes dans les bibliothques standard, avec bien dautres mthodes encore. Il est impossible de mmoriser toutes les classes et mthodes utiles. Il est donc essentiel que vous puissiez facilement consulter la documentation API en ligne concernant les classes et mthodes de la bibliothque standard. La documentation API fait partie du JDK. Elle est au format HTML. Pointez votre navigateur Web sur le sous-rpertoire docs/api/index.html de votre installation JDK. Vous verrez apparatre un cran comme celui de la Figure 3.2.
Figure 3.2
Les trois panneaux de la documentation API.

Chapitre 3

Structures fondamentales de la programmation Java

67

Lcran est divis en trois fentres. Une petite fentre en haut gauche afche tous les packages disponibles. Au-dessous, une fentre plus grande numre toutes les classes. Cliquez sur un nom de classe pour faire apparatre la documentation API pour cette classe dans la fentre de droite (voir Figure 3.3). Par exemple, pour obtenir des informations sur les mthodes de la classe String, faites dler la deuxime fentre jusqu ce que le lien String soit visible, puis cliquez dessus.

Figure 3.3
Description de la classe String.

Faites dler la fentre de droite jusqu atteindre le rsum de toutes les mthodes, tries par ordre alphabtique (voir Figure 3.4). Cliquez sur le nom dune mthode pour afcher sa description dtaille (voir Figure 3.5). Par exemple, si vous cliquez sur le lien compareToIgnoreCase, vous obtiendrez la description de la mthode compareToIgnoreCase.
ASTUCE
Affectez ds maintenant un signet la page docs/api/index.html dans votre navigateur.

68

Au cur de Java 2 - Notions fondamentales

Figure 3.4
Liste des mthodes de la classe String.

Figure 3.5
Description dtaille dune mthode de la classe String.

Chapitre 3

Structures fondamentales de la programmation Java

69

Construction de chanes
Pour le traitement dune saisie, vous devez parfois construire des chanes partir de caractres individuels ou de mots dun chier. La concatnation serait totalement inefcace dans ce cas. A chaque concatnation, un nouvel objet String est construit. Lopration prend du temps et consomme de la mmoire mais la classe StringBuilder permet dviter ce problme. Procdez comme suit pour construire une chane partir de petits lments. Construisez dabord un constructeur de chane vide :
StringBuilder builder = new StringBuilder();

Nous verrons les constructeurs et loprateur new en dtail au Chapitre 4. Chaque fois que vous devez ajouter une partie, appelez la mthode append.
builder.append(ch); // annexe un seul caractre builder.append(str); // annexe une chane

Lorsque vous avez termin de construire la chane, appelez la mthode toString. Vous obtiendrez un objet String avec la suite de caractres contenue dans le constructeur.
String completedString = builder.toString();

INFO
La classe StringBuilder a t introduite dans le JDK 5.0. Son prdcesseur, StringBuffer, est lgrement moins efcace mais permet plusieurs threads dajouter ou de supprimer des caractres. Si toute la modication des chanes survient dans un seul thread, ce qui est gnralement le cas, utilisez plutt StringBuilder. Les API des deux classes sont identiques.

Les notes dAPI suivantes contiennent les mthodes les plus importantes pour la classe StringBuilder.
java.lang.StringBuilder 5.0

StringBuilder()

Construit un constructeur de chane vide.


int length

Renvoie le nombre dunits de code du constructeur ou du tampon.


StringBuilder append(String str)

Annexe une chane et renvoie this.


StringBuilder append(char c)

Annexe une unit de code et renvoie this.


StringBuilder appendCodePoint(int cp)

Annexe un point de code, en le transformant en une ou deux units de code et renvoie this.
void setCharAt(int i, char c)

Dnit la ime unit de code sur c.


StringBuilder insert(int offset, String str)

Insre une chane la position offset et renvoie this.

70

Au cur de Java 2 - Notions fondamentales

StringBuilder insert(int offset, char c)

Insre une unit de code la position offset et renvoie this.


StringBuilder delete(int startIndex, int endIndex)

Supprime les units de code avec les dcalages startIndex endIndex - 1 et renvoie this.
String toString()

Renvoie une chane contenant les mmes donnes que le constructeur ou le tampon.

Entres et sorties
Pour rendre nos programmes dexemple plus intressants, il faut accepter les saisies et mettre correctement en forme le programme. Bien entendu, les langages de programmation modernes utilisent une interface graphique pour recueillir la saisie utilisateur. Mais la programmation de cette interface exige plus doutils et de techniques que ce que nous avons disposition pour le moment. Lintrt pour linstant tant de se familiariser avec le langage de programmation Java, nous allons nous contenter de notre humble console pour lentre et la sortie. La programmation des interfaces graphiques est traite aux Chapitres 7 9.

Lire les caractres entrs


Vous avez pu constater combien il tait simple dafcher une sortie sur lunit de "sortie standard" (cest--dire la fentre de la console) en appelant System.out.println. La lecture dune "entre standard" System.in nest pas aussi simple. La lecture dune entre au clavier se fait en construisant un Scanner attach System.in.
Scanner in = new Scanner(System.in);

Les diverses mthodes de la classe Scanner permettent ensuite de lire les entres. Par exemple, la mthode nextLine lit une ligne saisie :
System.out.print("What is your name? "); String name = in.nextLine();

Ici, nous utilisons la mthode nextLine car la saisie pourrait contenir des espaces. Pour lire un seul mot (dlimit par des espaces), appelez
String firstName = in.next();

Pour lire un entier, utilisez la mthode nextInt :


System.out.print("How old are you? "); int age = in.nextInt();

De mme, la mthode nextDouble lit le prochain chiffre virgule ottante. Le programme du Listing 3.2 demande le nom de lutilisateur et son ge, puis afche un message du style
Hello, Cay. Next year, youll be 46

Enn, ajoutez la ligne


import java.util.*;

Chapitre 3

Structures fondamentales de la programmation Java

71

au dbut du programme. La classe Scanner est dnie dans le package java.util. Ds que vous utilisez une classe qui nest pas dnie dans le package de base java.lang, vous devez utiliser une directive import. Nous tudierons les packages et les directives import plus en dtail au Chapitre 4.
Listing 3.2 : InputTest.java
import java.util.*; /** * Ce programme montre une entre de console * @version 1.10 2004-02-10 * @author Cay Horstmann */ public class InputTest { public static void main(String[] args) { Scanner in = new Scanner(System.in); // rcuprer la premire entre System.out.print("What is your name? "); String name = in.nextLine(); // rcuprer la seconde entre System.out.print("How old are you? "); int age = in nextInt(); // afficher la sortie la console System.out.println("Hello, "+name + ". Next year, youll be "+(age+1)); } }

INFO
La classe Scanner ne convient pas pour la lecture dun mot de passe partir dune console puisque lentre est tout fait visible pour tous. Java SE 6 introduit une classe Console rserve cet usage. Pour lire un mot de passe, utilisez le code suivant :
Console cons = System.console(); String username = cons.readLine("User name: "); char[] passwd = cons.readPassword("Password: ");

Pour des raisons de scurit, le mot de passe est renvoy dans un tableau de caractres plutt que dans une chane. Une fois que vous avez trait le mot de passe, crasez immdiatement les lments du tableau par une valeur de remplissage (le traitement des tableaux est trait plus loin dans ce chapitre). Le traitement des entres avec un objet Console nest pas aussi pratique quavec un Scanner. Vous ne pouvez lire quune ligne dentre la fois. Il nexiste pas de mthodes pour lire des mots ou des nombres individuels.

java.util.Scanner 5.0 Scanner(InputStream in)

Construit un objet Scanner partir du ux de saisie donn.

String nextLine()

Lit la prochaine ligne saisie.

72

Au cur de Java 2 - Notions fondamentales

String next()

Lit le prochain mot saisi (dlimit par un espace).


int nextInt() double nextDouble()

Lisent et transforment la prochaine suite de caractres qui reprsente un entier ou un nombre virgule ottante.

boolean hasNext()

Teste sil y a un autre mot dans la saisie.


boolean hasNextInt() boolean hasNextDouble()

Testent si la prochaine suite de caractres reprsente un entier ou un nombre virgule ottante.


java.lang.System 1.0 static Console console() 6

Renvoie un objet Console pour interagir avec lutilisateur via une fentre console si cette interaction est possible, null dans les autres cas. Un objet Console est disponible pour tout programme lanc dans une fentre console. Dans le cas contraire, la disponibilit dpend du systme.
java.io.Console 6 static char[] readPassword(String prompt, Object... args) static String readLine(String prompt, Object... args)

Afchent linvite et lisent la saisie utilisateur jusqu la n de la ligne dentre. Les paramtres args peuvent servir fournir des arguments de mise en forme, comme cest indiqu la section suivante.

Mise en forme de lafchage


Linstruction System.out.print(x) permet dafcher un nombre x la console. Cette instruction afchera x avec le maximum de chiffres diffrents de zro (pour le type donn). Par exemple,
double x = 10000.0 / 3.0; System.out.print(x);

afche
3333.3333333333335

Cela pose un problme si vous dsirez afcher, par exemple, des euros et des centimes. Dans les prcdentes versions de Java, lafchage des nombres posait quelques problmes. Heureusement, Java SE 5.0 a rapatri la vnrable mthode printf de la bibliothque C. Par exemple, lappel
System.out.printf("%8.2f", x);

afche x avec une largeur de champ de 8 caractres et une prcision de 2 caractres. En fait, lafchage contient un espace pralable et les sept caractres
3333.33

Vous pouvez fournir plusieurs paramtres printf, et notamment


System.out.printf("Hello, %s. Next year, youll be %d", name, age);

Chapitre 3

Structures fondamentales de la programmation Java

73

Chacun des spcicateurs de format qui commencent par le caractre % est remplac par largument correspondant. Le caractre de conversion qui termine un spcicateur de format indique le type de la valeur mettre en forme : f est un nombre virgule ottante, s une chane et d une valeur dcimale. Le Tableau 3.5 montre tous les caractres de conversion.
Tableau 3.5 : Conversions pour printf

Caractre de conversion d x o f e g a s c b h tx % n

Type Entier dcimal Entier hexadcimal Entier octal Virgule xe, virgule ottante Virgule ottante exponentielle Virgule ottante gnrale (le plus court entre e et f) Virgule ottante hexadcimale Chane Caractre Valeur boolenne Code de hachage Date et heure Symbole du pourcentage Sparateur de ligne fonction de la plate-forme

Exemple 159 9f 237 15.9 1.59e+01

0x1.fccdp3 Hello H true 42628b2 Voir Tableau 3.7 %

Vous pouvez galement spcier des drapeaux qui contrleront lapparence du rsultat mis en forme. Le Tableau 3.6 numre les drapeaux. Par exemple, le drapeau virgule ajoute des sparateurs de groupe. Ainsi,
System.out.printf("%,.2f", 1000.0 / 3.0);

afche
3,333.33

Vous pouvez afcher plusieurs drapeaux, par exemple "%,(.2f", pour utiliser des sparateurs de groupe et inclure les nombres ngatifs entre parenthses.
INFO
Vous pouvez utiliser la conversion s pour mettre en forme des objets arbitraires. Lorsquun objet arbitraire implmente linterface Formattable, la mthode formatTo de lobjet est appele. Dans le cas contraire, cest la mthode toString qui est appele pour transformer lobjet en chane. Nous traiterons de la mthode toString au Chapitre 5 et des interfaces au Chapitre 6.

74

Au cur de Java 2 - Notions fondamentales

Tableau 3.6 : Drapeaux pour printf

Drapeau + espace 0 ( , # (pour format f) # (pour format x ou o) $

Rle Afche le signe des nombres positifs et ngatifs. Ajoute un espace avant les nombres positifs. Ajoute des zros pralables. Justie le champ gauche. Entoure le nombre ngatif de parenthses. Ajoute des sparateurs de groupe. Inclut toujours une dcimale. Ajoute le prxe 0x ou 0. Indique lindice de largument mettre en forme ; par exemple, %1$d %1$x afche le premier argument en dcimal et hexadcimal. Met en forme la mme valeur que la spcication prcdente ; par exemple, %d %<x afche le mme nombre en dcimal et hexadcimal.

Exemple +3333.33 | 3333.33| 003333.33 |3333.33| (3333.33) 3,333.33 3,333 0xcafe 159 9F

<

159 9F

Vous pouvez utiliser la mthode statique String.format pour crer une chane mise en forme sans lafcher :
String message = String.format("Hello, %s. Next year, youll be %d", name, age);

Mme si nous ne dcrirons pas le type Date en dtail avant le Chapitre 4, pour tre complets, voyons brivement les options de mise en forme de la date et de lheure de la mthode printf. On utilise un format deux lettres commenant par t et se terminant par lune des lettres du Tableau 3.7. Par exemple,
System.out.printf("%tc", new Date());

afche la date et lheure courantes au format


Mon Feb 09 18:05:19 PST 2004

Tableau 3.7 : Caractres de conversion de la date et de lheure

Caractre de conversion C F D T r R

Type Date et heure compltes Date au format ISO 8601 Date au format amricain (mois/jour/anne) Heure sur 24 heures Heure sur 12 heures Heure sur 24 heures sans secondes

Exemple Mon Feb 09 18:05:19 PST 2004 2004-02-09 02/09/2004 18:05:19 06:05:19 pm 18:05

Chapitre 3

Structures fondamentales de la programmation Java

75

Tableau 3.7 : Caractres de conversion de la date et de lheure (suite)

Caractre de conversion Y y C B b ou h m d e A a j H k I l M S L N P p z Z s Q

Type Anne sur quatre chiffres (avec zros pralables, le cas chant) Deux derniers chiffres de lanne (avec zro pralable, le cas chant) Deux premiers chiffres de lanne (avec zro pralable, le cas chant) Nom complet du mois Nom du mois abrg Mois sur deux chiffres (avec zro pralable, le cas chant) Jour sur deux chiffres (avec zro pralable, le cas chant) Jour sur deux chiffres (avec zro pralable, le cas chant) Jour de la semaine complet Jour de la semaine abrg Jour de lanne sur trois chiffres (avec zros pralables, le cas chant), entre 001 et 366 Heure sur deux chiffres (avec zro pralable, le cas chant), entre 00 et 23 Heure sur deux chiffres (sans zro pralable), entre 0 et 23 Heure sur deux chiffres (avec zro pralable, le cas chant), entre 01 et 12 Heure sur deux chiffres (sans zro pralable), entre 1 et 12 Minutes sur deux chiffres (avec zro pralable, le cas chant) Secondes sur deux chiffres (avec zro pralable, le cas chant) Millimes de seconde, sur trois chiffres (avec zros pralables, le cas chant) Nanosecondes sur neuf chiffres (avec zros pralables, le cas chant) Indicateur du matin ou de laprs-midi en majuscules Indicateur du matin ou de laprs-midi en minuscules Dcalage numrique RFC 822 du GMT Fuseau horaire Secondes depuis le 1970-01-01 00:00:00 GMT Millimes de seconde depuis le 1970-01-01 00:00:00 GMT

Exemple 2004 04 20 February Feb 02 09 9 Monday Mon 069 18 18 06 6 05 19 047 047000000 PM pm -0800 PST 1078884319 1078884319047

76

Au cur de Java 2 - Notions fondamentales

Comme vous pouvez le voir au Tableau 3.7, certains des formats produisent une partie seulement dune date donne, par exemple le jour ou le mois. Il serait pourtant assez stupide de fournir la date plusieurs fois pour la mettre totalement en forme. Cest pourquoi une chane peut indiquer lindice de largument mettre en forme. Lindice doit suivre immdiatement le % et se terminer par un $. Par exemple,
System.out.printf("%1$s %2$tB %2$te, %2$tY", "Due date:", new Date());

afche
Due date: February 9, 2004

Vous pouvez aussi utiliser le drapeau <. Il indique quil faut utiliser largument avec le format qui prcde. Ainsi, linstruction
System.out.printf("%s %tB %<te, %<tY", "Due date:", new Date());

produit le mme rsultat que linstruction prcdente.


ATTENTION
Les valeurs dindice dargument commencent 1, et non 0 : %1$... met en forme le premier argument. Ceci vite la confusion avec le drapeau 0.

Vous avez maintenant vu toutes les fonctionnalits de la mthode printf. La Figure 3.6 prsente un diagramme syntaxique des spcicateurs de mise en forme.
format-specifier:

%
argument index

conversion character

flag

width

precision

t Figure 3.6
Syntaxe du spcicateur de mise en forme.

conversion character

INFO
Plusieurs rgles de mise en forme sont spciques aux paramtres rgionaux. En France, par exemple, le sparateur des dizaines est un point, et non une virgule, et Monday devient lundi. Vous verrez dans le Volume II comment modier le comportement de vos applications en fonction des pays.

Entre et sortie de chiers


Pour lire partir dun chier, construisez un objet Scanner partir dun objet File, comme ceci :
Scanner in = new Scanner(new File("myfile.txt"));

Si le nom du chier contient des barres obliques inverses, noubliez pas de les chapper toutes avec une autre barre oblique inverse : "c:\\mydirectory\\myfile.txt".

Chapitre 3

Structures fondamentales de la programmation Java

77

Vous pouvez maintenant lire partir du chier, en utilisant lune des mthodes Scanner dj dcrites. Pour crire dans un chier, construisez un objet PrintWriter. Dans le constructeur, fournissez simplement le nom du chier :
PrintWriter out = new PrintWriter("myfile.txt");

Si le chier nexiste pas, vous pouvez simplement utiliser les commandes print, println et printf comme pour lafchage sur System.out.
ATTENTION
Vous pouvez construire un Scanner avec un paramtre de chane, mais le scanner interprte la chane sous forme de donnes et non de nom de chier. Par exemple, si vous appelez
Scanner in = new Scanner("myfile.txt"); // ERREUR ?

le scanner voit dix caractres de donnes : m, y, f, etc. Ce nest probablement pas ce qui a t prvu dans ce cas.

INFO
Lorsque vous prcisez un nom de chier relatif, comme "myfile.txt", "mydirectory/myfile.txt" ou "../ myfile.txt", le chier est situ par rapport au rpertoire dans lequel la machine virtuelle Java pralable au JDK 5.0 a t dmarre. Si vous avez lanc le programme partir dun shell de commande, en excutant
java MyProg

le rpertoire de dpart correspond au rpertoire courant du shell de commande. Toutefois, si vous utilisez un environnement de dveloppement intgr, le rpertoire de dpart est contrl par lIDE. Vous trouverez lemplacement du rpertoire avec cet appel :
String dir = System.getProperty("user.dir");

Si vous rencontrez des difcults localiser les chiers, envisagez dutiliser des noms de chemin absolus, comme "c:\\mydirectory\\myfile.txt" ou "/home/me/mydirectory/myfile.txt".

Vous lavez vu, vous pouvez accder aux chiers aussi simplement que vous utilisez System.in et System.out. Il y a toutefois un pige : si vous construisez un Scanner avec un chier qui nexiste pas ou un PrintWriter avec un nom de chier qui ne peut pas tre cr, une exception survient. Le compilateur Java considre ces exceptions plus srieuses quune exception "diviser par zro", par exemple. Nous verrons au Chapitre 11 diverses manires de grer les exceptions. Pour lheure, indiquez simplement au compilateur que vous tes conscient de la possibilit dune exception de chier introuvable ("le not found"). Pour cela, balisez la mthode main par une clause throws, comme ceci :
public static void main(String[] args) throws FileNotFoundException { Scanner in = new Scanner(new File("myfile.txt")); . . . }

Vous avez maintenant vu comment lire et crire des chiers contenant des donnes de texte. Pour aborder des sujets plus avancs, comme le traitement des diffrents codages de caractres, le traitement des donnes binaires, la lecture des rpertoires et lcriture de chiers zip, consultez le Chapitre 1 du Volume II.

78

Au cur de Java 2 - Notions fondamentales

INFO
Lorsque vous lancez un programme partir dun shell de commande, vous pouvez utiliser la syntaxe de redirection de votre shell et attacher un chier System.in et System.out:
java MyProg < myfile.txt > output.txt

Vous navez plus vous inquiter ensuite de grer lexception FileNotFoundException.

java.util.Scanner 5.0

Scanner(File f)

Construit un Scanner qui lit les donnes partir du chier donn.


Scanner(String data)

Construit un Scanner qui lit les donnes partir de la chane donne.


java.io.PrintWriter 1.1

PrintWriter(File f)

Construit un PrintWriter qui crit des donnes dans le chier concern.


PrintWriter(String fileName)

Construit un PrintWriter qui crit des donnes dans le chier ayant le nom concern.
java.io.File 1.0

File(String fileName)

Construit un objet File qui dcrit un chier avec le nom donn. Sachez que le chier na pas besoin dexister pour linstant.

Flux dexcution
Comme tout langage de programmation, Java gre les instructions conditionnelles et les boucles an de contrler le ux dexcution (ou ux de contrle). Nous commencerons par tudier les instructions conditionnelles avant de passer aux boucles. Nous terminerons par linstruction switch, qui peut tre employe lorsque vous devez tester les nombreuses valeurs possibles dune mme expression.
INFO C++
Les constructions du ux dexcution de Java sont comparables celles de C et de C++, quelques exceptions prs. Il nexiste pas dinstruction goto, mais une version "tiquete" de break peut tre employe pour sortir dune boucle imbrique (l o vous auriez peut-tre employ goto dans un programme C). Enn, Java SE 5.0 ajoute une variante la boucle for qui na pas son pareil en C ou C++. Elle est identique la boucle foreach du C#.

Porte dun bloc


Avant dexaminer les structures de contrle, vous devez savoir ce quest un bloc. Un bloc, ou instruction compose, est un groupe dinstructions simples dlimit par une paire daccolades. Les blocs dterminent la porte des variables. Ils peuvent tre imbriqus lintrieur dun autre bloc. Voici un bloc imbriqu dans le bloc de la mthode main :

Chapitre 3

Structures fondamentales de la programmation Java

79

public static void main(String[] args) { int n; . . . { int k; . . . } // k nest dfini que jusquici }

Prcisons quil nest pas possible de dclarer des variables homonymes dans deux blocs imbriqus. Lexemple suivant est une erreur et le programme ne peut pas tre compil :
public static void main(String[] args) { int n; . . . { int k; int n; // erreur--impossible de redfinir n dans un bloc imbriqu . . . } }

INFO C++
En C++, il est possible de rednir une variable lintrieur dun bloc imbriqu. La dnition la plus interne cache alors la dnition externe. Cela constitue une source derreur, et Java ne lautorise pas.

Instructions conditionnelles
Linstruction conditionnelle en Java prend la forme :
if (condition) instruction

La condition doit tre incluse entre parenthses. En Java, comme dans la plupart des langages de programmation, vous souhaiterez souvent excuter plusieurs instructions lorsquune condition est vraie. Dans ce cas, vous utiliserez un bloc dinstructions qui prend la forme :
{ instruction1 instruction2 . . . }

Par exemple :
if (yourSales >= target) { performance = "Satisfactory"; bonus = 100; }

Dans cet extrait de code, toutes les instructions qui se trouvent lintrieur des accolades seront excutes lorsque la valeur de yourSales sera suprieure la valeur de target (voir Figure 3.7).

80

Au cur de Java 2 - Notions fondamentales

Figure 3.7
Organigramme de linstruction if.
NON yourSales target

OUI

performance =Satisfactory

bonus=100

INFO
Un bloc (parfois appel instruction compose) permet de regrouper plus dune instruction (simple) dans une structure de programmation Java qui, sans ce regroupement, ne pourrait contenir quune seule instruction (simple).

Linstruction conditionnelle de Java a laspect suivant (voir Figure 3.8) :


if (condition) instruction1 else instruction2

Par exemple :
if (yourSales >= target) { performance = "Satisfactory"; bonus = 100+0.01 * (yourSales - target); } else { performance = "Unsatisfactory"; bonus = 0; }

La partie else est toujours facultative. Une directive else est toujours associe linstruction if la plus proche. Par consquent, dans linstruction
if (x <= 0) if (x == 0) sign = 0; else sign = -1;

Chapitre 3

Structures fondamentales de la programmation Java

81

Figure 3.8
Organigramme de linstruction if/else.

OUI

yourSales

target

NON

performance =Satisfactory

performance =Unsatisfactory

bonus= 100+0.01* (yourSalestarget)

bonus=0

la directive else appartient au second if. Bien entendu, il est conseill dutiliser les accolades pour prciser ce code :
if (x <=0) { if (x == 0) sign = 0; else sign = -1; }

Des squences rptes if . . . else if . . . sont trs frquentes (voir Figure 3.9). Par exemple :
if (yourSales >= 2 * target) { performance = "Excellent"; bonus = 1000; } else if (yourSales >= 1.5 * target) { performance = "Fine"; bonus = 500; } else if (yourSales >= target) { performance = "Satisfactory"; bonus = 100; } else { System.out.println("Youre fired"); }

82

Au cur de Java 2 - Notions fondamentales

yourSales 2*target

OUI

performance =Excellent

bonus=1000

NON

yourSales 1.5*target

OUI

performance =Fine

bonus=500

NON

OUI yourSales target

performance =Satisfactory

bonus=100

NON

Print Youre fired

Figure 3.9
Organigramme de linstruction if/else if (branchement multiple).

Boucles
La boucle while excute une instruction (qui peut tre une instruction de bloc) tant quune condition est vraie. Sa forme gnrale est la suivante :
while (condition) instruction

La boucle while ne sexcute jamais si la condition est fausse ds le dpart (voir Figure 3.10).

Chapitre 3

Structures fondamentales de la programmation Java

83

Figure 3.10
Organigramme de linstruction while.
NON balance < goal

OUI

update balance

years++

Print years

Dans le Listing 3.3, nous crivons un programme permettant de dterminer combien de temps sera ncessaire pour conomiser une certaine somme vous permettant de prendre une retraite bien mrite, en supposant que vous dposiez chaque anne une mme somme dargent un taux dintrt spci. Dans notre exemple, nous incrmentons un compteur et nous mettons jour le total cumul dans le corps de la boucle jusqu ce que le total excde le montant souhait :
while (balance < goal) { balance += payment; double interest = balance * interestRate / 100; balance += interest; years++; } System.out.println(years + " years.");

84

Au cur de Java 2 - Notions fondamentales

Ne vous ez pas ce programme pour prvoir votre retraite. Nous avons laiss de ct quelques dtails comme lination et votre esprance de vie. Le test dune boucle while est effectu avant lexcution du corps de la boucle. Par consquent, ce bloc peut ne jamais tre excut. Si vous voulez tre certain que le bloc soit excut au moins une fois, vous devez placer la condition de test en n de boucle. Pour cela, employez une boucle do/while, dont voici la syntaxe :
do instruction while (condition);

Cette boucle excute linstruction (gnralement un bloc) avant de tester la condition. Le programme rexcute le bloc avant deffectuer un nouveau test, et ainsi de suite. Par exemple, le code du Listing 3.4 calcule le nouveau solde de votre compte retraite, puis vous demande si vous tes prt partir la retraite :
do { balance += payment; double interest = balance * interestRate / 100; balance += interest; year++; // afficher le solde actuel . . . // demander si prt prendre la retraite // et rcuprer la rponse . . . } while (input.equals("N"));

Tant que la rponse de lutilisateur est "N", la boucle est rpte (voir Figure 3.11). Ce programme est un bon exemple dune boucle devant tre excute au moins une fois, car lutilisateur doit pouvoir vrier le solde avant de dcider sil est sufsant pour assurer sa retraite.
Listing 3.3 : Retirement.java
import java.util.*; /** * Ce programme prsente une boucle <code>while</code>. * @version 1.20 2004-02-10 * @author Cay Horstmann */ public class Retirement { public static void main(String[] args) { // lire les infos entres Scanner in = new Scanner(System.in); System.out.print("How much do you need to retire? "); double goal = in.nextDouble(); System.out.print("How much money will you contribute every year?"); double payment = in.nextDouble(); System.out.print("Interst rate in %: "); double interestRate = in.nextDouble();

Chapitre 3

Structures fondamentales de la programmation Java

85

double balance = 0; int years = 0; // mettre jour le solde du compte tant que cible non atteinte while (balance < goal) { // ajouter versements et intrts de cette anne balance += payment; double interest = balance * interestRate / 100; balance += interest; years++; } System.out.println ("You can retire in "+years+" years."); } }

Figure 3.11
Organigramme de linstruction do/while.
actualiser le solde

imprimer le solde demander "Ready to retire? (Y/N)"

lire l'entre

OUI input="N"

NON

86

Au cur de Java 2 - Notions fondamentales

Listing 3.4 : Retirement2.java


import java.util.*; /** * Ce programme prsente une boucle <code>do/while</code>. * @version 1.20 2004-02-10 * @author Cay Horstmann */ public class Retirement2 { public static void main(String[] args) { Scanner in = new Scanner(System.in); System.out.print("How much money will you contribute every year?"); double payment = in.nextDouble(); System.out.print("Interest rate in %: "); double interestRate = in.nextDouble(); double balance = 0; int year = 0; String input; // mettre jour le solde du compte tant que lutilisateur // nest pas prt prendre sa retraite do { // ajouter versements et intrts de cette anne balance += payment; double interest = balance * interestRate / 100; balance += interest; year++; // afficher le solde actuel System.out.println("After year %d, your balance is %,.2f%n", year, balance); // demander si prt pour la retraite System.out.print("Ready to retire? (Y/N)"); input = in.next(); } while (input.equals("N")); } }

Boucles dtermines
La boucle for est une construction trs gnrale pour grer une itration contrle par un compteur ou une variable similaire, mis jour aprs chaque itration. Comme le montre la Figure 3.12, le code suivant afche les nombres 1 10 sur lcran :
for (int i = 1; i <= 10; i++) System.out.println(i);

Chapitre 3

Structures fondamentales de la programmation Java

87

Le premier lment de linstruction for contient gnralement linitialisation du compteur. Le deuxime lment fournit la condition de test qui sera vrie avant chaque passage dans la boucle ; le troisime indique comment le compteur doit tre mis jour. Bien que Java, comme C++, autorise pratiquement nimporte quelle expression dans les trois lments dune boucle for, une convention tacite fait que ces lments doivent respectivement se contenter dinitialiser, de tester et de mettre jour la mme variable compteur. Il est possible dcrire des boucles trs absconses si lon ne respecte pas cette convention.
Figure 3.12
Organigramme de linstruction for.
i=1

10

NON

OUI

Print i

i++

Mme en suivant cette rgle, de nombreuses possibilits sont offertes. Il est ainsi possible de crer des boucles dcrmentales :
for (int i = 10; i > 0; i--) System.out.println("Counting down . . . "+i); System.out.println("Blastoff!");

88

Au cur de Java 2 - Notions fondamentales

ATTENTION
Soyez prudent lorsque vous testez lgalit de deux nombres rels. Une boucle for comme celle-ci :
for (double x = 0; x!= 10; x += 0.1) . . .

risque de ne jamais se terminer. Du fait des erreurs darrondi, la valeur nale risque de ntre jamais atteinte. Par exemple, dans la boucle ci-dessus, x saute de 9.99999999999998 10.09999999999998, puisquil nexiste pas de reprsentation binaire exacte pour 0.1.

Lorsque vous dclarez une variable dans le premier lment dune instruction for, la porte de cette variable stend jusqu la n du corps de la boucle :
for (int i = 1; i <= 10; i++) { . . . } // i nest plus dfini ici

En particulier, si vous dnissez une variable lintrieur dune instruction for, vous ne pouvez pas utiliser cette variable en dehors de la boucle. En consquence, si vous dsirez utiliser la valeur nale du compteur en dehors dune boucle for, la variable compteur doit tre dclare en dehors de cette boucle !
int i; for (i = 1; i <= 10; i++) { . . . } // i est toujours dfini ici

En revanche, vous pouvez dnir des variables de mme nom dans des boucles for spares :
for (int i = 1; i <= 10; i++) { . . . } . . . for (int i = 11; i <= 20; i++) // ok pour redfinir i { . . . }

Bien entendu, une boucle for quivaut une boucle while. Pour tre plus prcis,
for (int i = 10; i > 0; i--) System.out.println("Counting down . . . " + i);

quivaut exactement :
int i = 10; while (i > 0) { System.out.println("Counting down . . . " + i); i--; }

Le Listing 3.5 montre un exemple typique de boucle for. Le programme calcule vos chances de gagner une loterie. Si vous devez, par exemple, trouver 6 des nombres de 1 50 pour gagner, il y a

( 50 49 48 47 46 45 ) ----------------------------------------------------------------------(1 2 3 4 5 6)

Chapitre 3

Structures fondamentales de la programmation Java

89

tirages possibles, et vous avez une chance sur 15 890 700. Bonne chance ! En gnral, si vous choisissez k nombres parmi n, il y a

n ( n 1 ) ( n 2 ) ... ( n k + 1 ) ------------------------------------------------------------------------------------------1 2 3 ... k


tirages possibles. La boucle for qui suit calcule cette valeur :
int lotteryOdds = 1; for (int i = 1; i <= k; i++) lotteryOdds = lotteryOdds * (n - i+1) / i;

INFO
Voir un peu plus loin pour obtenir une description de la boucle for gnralise (aussi appele boucle "for each") ajoute Java SE 5.0.

Listing 3.5 : LotteryOdds.java


import java.util.*; /** * Ce programme prsente une boucle <code>for</code>. * @version 1.20 2004-02-10 * @author Cay Horstmann */ public class LotteryOdds { public static void main(String[] args) { Scanner in = new Scanner(System.in); System.out.print("How many numbers do you need to draw? "); int k = in.nextInt(); System.out.print("What is the highest number you can draw? "); int n = in.nextInt(); /* calculer le binme n * (n - 1) * (n - 2) * . . . * (n - k+1) ------------------------------------------1 * 2 * 3 * . . . * k */ int lotteryOdds = 1; for (int i = 1; i <= k; i++) lotteryOdds = lotteryOdds * (n - i+1) / i; System.out.println ("Your odds are 1 in "+lotteryOdds+". Good luck!"); } }

90

Au cur de Java 2 - Notions fondamentales

Slections multiples linstruction switch


La construction if/else peut se rvler assez lourde quand vous devez traiter plusieurs slections et de multiples alternatives. Java dispose de linstruction switch qui reproduit exactement celle de C et C++, y compris ses dfauts. Par exemple, si vous crez un systme de menu ayant quatre alternatives, comme celui de la Figure 3.13, vous pouvez utiliser un code comparable celui-ci :
Scanner in = new Scanner(System.in); System.out.print("Select an option (1, 2, 3, 4)"); int choice = in.nextInt(); switch (choice) { case 1: . . . break; case 2: . . . break; case 3: . . . break; case 4: . . . break; default: // entre incorrecte . . . break; }

Lexcution commence ltiquette case dont la valeur correspond la valeur de slection, puis elle se poursuit jusqu une instruction break ou jusqu la n du bloc switch. Si aucune correspondance nest trouve, la clause default est excute, si elle existe.
ATTENTION
Il est possible dexcuter plusieurs alternatives. Si vous oubliez dajouter la clause break la n dune alternative, lexcution se poursuit avec lalternative qui suit ! Ce comportement est trs dangereux et constitue une cause commune derreur. Cest pourquoi nous nutilisons pas linstruction switch dans nos programmes.

Notez que les valeurs de case doivent tre des entiers ou des constantes numres. Il nest pas possible de tester des chanes. Par exemple, le code suivant est erron :
String input = . . .; switch (input) // ERREUR { case "A": // ERREUR . . . break; . . . }

Chapitre 3

Structures fondamentales de la programmation Java

91

Figure 3.13
Organigramme de linstruction switch.
OUI choix = 1

...

NON

OUI choix = 2

...

NON

OUI choix = 3

...

NON

OUI choix = 4

...

NON (par dfaut) mauvaise saisie

92

Au cur de Java 2 - Notions fondamentales

Lorsque vous utilisez linstruction switch avec des constantes numres, vous navez pas besoin de fournir le nom de lnumration dans chaque tiquette, il est dduit de la valeur switch. Par exemple,
Size sz = . . .; switch (sz) { case SMALL: // inutile dutiliser Size.SMALL . . . break; . . . }

Interrompre le ux dexcution
Bien que les concepteurs de Java aient conserv goto en tant que mot rserv, ils ne lont pas inclus dans le langage. En gnral, lemploi dinstructions goto est considr comme inlgant et maladroit. Certains programmeurs pensent nanmoins que les forces anti-goto sont alles trop loin (un fameux article de Donald Knuth sintitule "La programmation structure avec des goto"). Ils avancent que si lutilisation frquente de goto est dangereuse, quitter immdiatement une boucle peut parfois tre utile. Les concepteurs de Java ont admis cette thse et ont mme ajout une nouvelle instruction pour ce mcanisme : linterruption "tiquete" (labeled break). Observons dabord linstruction break normale. La mme instruction qui est employe pour sortir dun bloc switch permet de quitter une boucle. Par exemple :
while (years <= 100) { balance += payment; double interest = balance * interestRate / 100; balance += interest; if (balance >= goal) break; years++; }

Lexcution de programme quitte la boucle si years > 100 au dbut de la boucle, ou si balance >= goal au milieu de la boucle. Bien entendu, vous auriez pu calculer la mme valeur pour years sans ajouter break, de la faon suivante :
while (years <= 100 && balance < goal) { balance += payment; double interest = balance * interestRate / 100; balance += interest; if (balance < goal) years++; }

Notez toutefois que le test balance < goal est reproduit deux fois dans cette version. Pour viter cette redondance, certains programmeurs prfrent linstruction break. Contrairement C++, Java propose galement une instruction dinterruption tiquete permettant de quitter des boucles imbriques. Il arrive quun vnement particulier survienne au sein dune boucle profondment imbrique. Dans ce cas, il est souhaitable de quitter lensemble des boucles, et pas seulement celle qui a vu surgir cet vnement. Il ne serait pas simple de programmer cette situation en ajoutant des conditions supplmentaires aux diverses boucles.

Chapitre 3

Structures fondamentales de la programmation Java

93

Nous allons prsenter un exemple qui montre le fonctionnement de ce mcanisme. Remarquez que ltiquette doit prcder la plus externe des boucles que vous souhaitez quitter. Elle doit tre suivie de deux-points (:) :
Scanner in = new Scanner(System.in); int n; read_data: while (. . .) // cette instruction de boucle est tiquete { . . . for (. . .) // cette boucle interne nest pas tiquete { System.out.print("Enter a number >= 0"); n = in.nextInt(); if (n < 0) // Ne doit pas se produire, impossible de continuer break read_data; // sortir de la boucle de lecture . . . } } // cette instruction est excute immdiatement aprs le break tiquet if (n < 0) // vrifier si situation anormale { // traiter situation anormale } else { // poursuivre le traitement normal }

Si lentre est invalide, linstruction break tiquete saute aprs le bloc tiquet. Comme avec toute utilisation de break, il vous faut alors effectuer un test pour savoir si la boucle sest termine normalement ou si elle a t interrompue.
INFO
Curieusement, vous pouvez attribuer une tiquette nimporte quelle instruction, y compris une instruction if ou un bloc dinstructions. Par exemple :
tiquette: { . . . if (condition) break tiquette; // sortie du bloc . . . } // saut ici lors de lexcution de linstruction break

Si linstruction goto vous manque vraiment, et que vous puissiez placer un bloc qui se termine juste avant lendroit o vous voulez sauter, une instruction break fera laffaire ! Cette approche nest bien entendu pas conseille. Notez aussi que vous ne pouvez sauter quen dehors dun bloc, jamais dans un bloc.

Il existe enn une instruction continue qui, comme linstruction break, interrompt le ux normal dexcution. Linstruction continue transfre le contrle en tte de la boucle englobante la plus interne. En voici un exemple :

94

Au cur de Java 2 - Notions fondamentales

Scanner in = new Scanner(System.in); while (sum < goal) { System.out.print("Enter a number: "); n = in.nextInt(); if (n < 0) continue; sum += n; // nest pas excut si n < 0 }

Si n < 0, linstruction continue saute immdiatement en tte de boucle et nexcute pas le reste de litration en cours. Si linstruction continue est employe dans une boucle for, elle provoque un saut vers la partie "mise jour" de la boucle for. Par exemple :
for (count = 1; count <= 100; count++) { System.out.print("Enter a number, -1 to quit: "); n = in.nextInt(); if (n < 0) continue; sum += n; // nest pas excut si n < 0 }

Si n < 0, linstruction continue provoque un saut vers linstruction count++. Il existe aussi une forme tiquete de linstruction continue qui provoque un saut vers len-tte de la boucle portant ltiquette correspondante.
ASTUCE
De nombreux programmeurs trouvent les instructions break et continue peu claires. Ces instructions sont absolument facultatives ; vous pouvez toujours exprimer la mme logique sans y avoir recours. Dans cet ouvrage, nous nutilisons jamais ces instructions.

Grands nombres
Si la prcision des types de base entier et ottant nest pas sufsante, vous pouvez avoir recours des classes trs utiles du package java.math, appeles BigInteger et BigDecimal. Ces classes permettent de manipuler des nombres comprenant une longue squence arbitraire de chiffres. La classe BigInteger implmente une arithmtique de prcision arbitraire pour les entiers et BigDecimal fait la mme chose pour les nombres virgule ottante. Utilisez la mthode statique valueOf pour transformer un nombre ordinaire en grand nombre :
BigInteger a = BigInteger.valueOf(100);

Il nest malheureusement pas possible dutiliser les oprateurs mathmatiques habituels tels que + et * pour combiner des grands nombres. Vous devez, la place, avoir recours des mthodes telles que add et multiply dans les classes des grands nombres :
BigInteger c = a.add(b); // c = a+b BigInteger d = c.multiply(b.add(BigInteger.valueOf(2))); // d = c * (b+2)

Chapitre 3

Structures fondamentales de la programmation Java

95

INFO C++
Contrairement C++, Java ne prvoit pas de surcharge programmable des oprateurs. Il nest pas possible au programmeur de la classe BigInteger de rednir les oprateurs + et * pour leur attribuer les oprations add et multiply des classes BigInteger. Les concepteurs ont en fait surcharg loprateur + pour indiquer la concatnation de chanes, mais ils ont choisi de ne pas surcharger les autres oprateurs, sans donner au programmeur la possibilit de le faire dans leurs propres classes.

Le Listing 3.6 montre le programme lotteryOdds du Listing 3.5 modi, an de fonctionner avec les grands nombres. Par exemple, si vous tes invit participer une loterie pour laquelle vous devez choisir 60 nombres parmi 490 possibles, ce programme vous dira que vous avez une chance sur 716395843461995557415116222540092933411717612789263493493351013459481104668848. Bonne chance ! Le programme du Listing 3.5 comprenait linstruction de calcul suivante :
lotteryOdds = lotteryOdds * (n - i+1) / i;

Avec lutilisation des grands nombres, linstruction quivalente devient :


lotteryOdds = lotteryOdds.multiply(BigInteger.valueOf(n - i+1)) .divide(BigInteger.valueOf(i));

Listing 3.6 : BigIntegerTest.java


import java.math.*; import java.util.*; /** * Ce programme utilise les grands nombres pour calculer les * chances de gagner le gros lot la loterie * @version 1.20 2004-02-10 * @author Cay Horstmann */ public class BigIntegerTest { public static void main(String[] args) { Scanner in = new Scanner(System.in); System.out.print("How many numbers do you need to draw? "); int k = in.nextInt(); System.out.print("What is the highest number you can draw? "); int n = in.nextInt(); /* * Calculer le binme * n * (n - 1) * (n - 2) * . . . * (n - k+1) * ------------------------------------------* 1 * 2 * 3 * . . . * k */ BigInteger lotteryOdds = BigInteger.valueOf(1);

96

Au cur de Java 2 - Notions fondamentales

for (int i = 1; i <= k; i++) lotteryOdds = lotteryOdds .multiply(BigInteger.valueOf(n - i+1)) .divide(BigInteger.valueOf(i)); System.out.println("Your odds are 1 in "+lotteryOdds + ". Good luck!"); } } java.math.BigInteger 1.1

BigInteger add(BigInteger other) BigInteger subtract(BigInteger other) BigInteger multiply(BigInteger other) BigInteger divide(BigInteger other) BigInteger mod(BigInteger other)

Renvoient respectivement la somme, la diffrence, le produit, le quotient et le reste de BigInteger et de other.

int compareTo(BigInteger other)

Renvoie 0 si BigInteger est gal other, un rsultat ngatif sil est infrieur other et un rsultat positif sinon.

static BigInteger valueOf(long x)

Renvoie un grand entier dont la valeur est gale x.


java.math.BigDecimal 1.1

BigDecimal add(BigDecimal other) BigDecimal subtract(BigDecimal other) BigDecimal multiply(BigDecimal other) BigDecimal divide(BigDecimal other, roundingMode mode) 5.0

Renvoient respectivement la somme, la diffrence, le produit ou le quotient de BigDecimal et de other. Pour calculer le quotient, vous devez fournir un mode darrondi. Le mode RoundingMode.HALF_UP est celui que vous avez tudi lcole (cest--dire arrondi au chiffre infrieur si 0 4, arrondi au chiffre suprieur si 5 9). Cela convient pour les calculs de routine. Consultez la documentation API en ce qui concerne les autres modes darrondi.

int compareTo(BigDecimal other)

Renvoie 0 si BigDecimal est gal other, un rsultat ngatif sil est infrieur other et un rsultat positif sinon.

static BigDecimal valueOf(long x) static BigDecimal valueOf(long x, int scale)

Renvoient un grand dcimal dont la valeur est gale x or x/10scale.

Tableaux
Un tableau est une structure de donnes qui stocke une srie de valeurs du mme type. Vous accdez chaque valeur individuellement laide dun entier indice. Par exemple, si a est un tableau dentiers, a[i] est le ime entier du tableau.

Chapitre 3

Structures fondamentales de la programmation Java

97

Vous dclarez une variable tableau en spciant son type qui est le type dlment suivi de [] et le nom de la variable tableau. Voici, par exemple, la dclaration dun tableau a dentiers :
int[] a;

Cette instruction ne dclare toutefois que la variable a. Elle ninitialise pas a comme un tableau. Loprateur new cre le tableau
int[] a = new int[100];

Cette instruction cre un tableau de 100 entiers. Lorsque vous crez un tableau de nombres, tous les lments sont initialiss avec 0 (les tableaux de boolean sont initialiss avec false, les tableaux dobjets le sont avec des valeurs null).
INFO
Vous pouvez dnir une variable de tableau soit sous la forme
int[] a;

soit sous la forme


int a[];

La plupart des programmeurs Java prfrent le premier style car il spare nettement le type int[] (tableau dentiers) du nom de la variable.

Les lments du tableau sont numrots de 0 99 (et non de 1 100). Une fois que le tableau est cr, vous pouvez remplir ses lments, par exemple laide dune boucle :
int[] a = new int[100]; for (int i = 0; i < 100; i++) a[i] = i; // remplit le tableau avec les valeurs de 0 99

ATTENTION
Si vous construisez un tableau de 100 lments et que vous essayiez daccder llment a[100] (ou tout autre indice en dehors de la plage 0 99), votre programme se terminera avec une exception "array index out of bounds" (indice de tableau hors limites).

Vous pouvez trouver le nombre des lments dun tableau laide de nomTableau.length. Par exemple,
for (int i = 0; i < a.length; i++) System.out.println(a[i]);

Une fois un tableau cr, vous ne pouvez pas modier sa taille (mais vous pouvez changer un lment individuel du tableau). Si vous devez modier souvent la taille dun tableau pendant lexcution dun programme, vous pouvez avoir recours une structure de donnes diffrente appele liste de tableaux (voir le Chapitre 5 pour plus dinformations ce sujet).

La boucle "for each"


Java SE 5.0 a introduit une construction de boucle performante qui vous permet de parcourir chaque lment dun tableau (ainsi que dautres collections dlments) sans avoir vous proccuper des valeurs dindice.

98

Au cur de Java 2 - Notions fondamentales

La boucle for amliore


for (variable: collection) instruction

dnit la variable donne sur chaque lment de la collection, puis excute linstruction (qui, bien sr, peut tre un bloc). Lexpression collection doit tre un tableau ou un objet dune classe qui implmente linterface Iterable, comme ArrayList. Nous verrons les listes de tableaux au Chapitre 5 et linterface Iterable au Chapitre 2 du Volume II. Par exemple,
for (int element: a) System.out.println(element);

afche chaque lment du tableau a sur une ligne spare. Il est conseill de lire cette boucle sous la forme "pour chaque lment dans a". Les concepteurs du langage Java ont envisag dutiliser des mots cls comme foreach et in. Mais cette boucle a t ajoute avec un peu de retard au langage Java et, au nal, personne na voulu casser un ancien code qui contenait dj des mthodes ou des variables avec les mmes noms (comme System.in). Bien entendu, vous pourriez obtenir le mme effet avec une boucle for traditionnelle :
for (int i = 0; i < a.length; i++) System.out.println(a[i]);

Toutefois, la boucle "for each" est plus concise et moins sujette erreur (vous navez pas vous inquiter des valeurs dindice de dbut et de n, qui sont souvent pnibles).
INFO
La variable loop de la boucle "for each" parcourt les lments dun tableau, et non les valeurs dindice.

La boucle "for each" est une amlioration agrable de la boucle traditionnelle si vous devez traiter tous les lments dune collection. Il y a toutefois de nombreuses opportunits dutiliser la boucle for traditionnelle. Vous ne voudrez peut-tre pas, par exemple, parcourir la totalit de la collection ou pourriez avoir besoin de la valeur dindice lintrieur de la boucle.
ASTUCE
Il existe une mthode encore plus simple pour afcher toutes les valeurs dun tableau, et ce grce la mthode toString de la classe Arrays. Lappel Arrays.toString(a) renvoie une chane contenant les lments du tableau, insrs entre crochets et spars par des virgules, comme "[2, 3, 5, 7, 11, 13]". Pour afcher le tableau, appelez simplement
System.out.println(Arrays.toString(a));

Initialiseurs de tableaux et tableaux anonymes


Java propose un raccourci pour crer un objet tableau et linitialiser simultanment. Voici un exemple de la syntaxe employer :
int[] smallPrimes = { 2, 3, 5, 7, 11, 13 };

Remarquez quil nest pas ncessaire dappeler new lorsque vous utilisez cette syntaxe.

Chapitre 3

Structures fondamentales de la programmation Java

99

Il est mme possible dinitialiser un tableau anonyme :


new int[] { 17, 19, 23, 29, 31, 37 }

Cette expression alloue un nouveau tableau et le remplit avec les valeurs spcies entre les accolades. Elle dtermine le nombre de valeurs fournies et affecte au tableau le mme nombre dlments. Cette syntaxe est employe pour rinitialiser un tableau sans crer une nouvelle variable. Lexemple
smallPrimes = new int[] { 17, 19, 23, 29, 31, 37 };

est un raccourci pour


int[] anonymous = { 17, 19, 23, 29, 31, 37 }; smallPrimes = anonymous;

INFO
Il est lgal davoir des tableaux de longueur 0. Un tel tableau peut tre utile si vous crivez une mthode qui calcule un rsultat de tableau et que ce rsultat puisse tre vide. Un tableau de longueur 0 se construit de la faon suivante :
new typeElment[0]

Notez quun tableau de longueur 0 est diffrent de null (voir le Chapitre 4 pour plus dinformations concernant null).

Copie des tableaux


Il est possible de copier une variable tableau dans une autre, mais les deux variables feront alors rfrence au mme tableau :
int[] luckyNumbers = smallPrimes; luckyNumbers[5] = 12; // smallPrimes[5] vaut maintenant 12

La Figure 3.14 montre le rsultat. Si vous voulez effectivement copier toutes les valeurs dun tableau dans un autre, il faut employer la mthode copyTo de la classe Arrays.
int[] copiedLuckyNumbers = Arrays.copyOf(luckyNumbers, luckyNumbers.length);

Le second paramtre correspond la longueur du nouveau tableau. Cette mthode est souvent employe pour augmenter la taille dun tableau :
luckyNumbers = Arrays.copyOf(luckyNumbers, 2 * luckyNumbers.length);

Les autres lments sont remplis de 0 si le tableau contient des nombres, false si le tableau contient des valeurs boolennes. A linverse, si la longueur est infrieure celle du tableau initial, seules les valeurs initiales sont copies.
Figure 3.14
Copie dune variable tableau.
smallPrimes = luckyNumbers = 2 3 5 7 11 12

100

Au cur de Java 2 - Notions fondamentales

INFO
Avant Java SE 6, la mthode arraycopy de la classe System servait copier des lments dun tableau dans un autre. Sa syntaxe est la suivante :
System.arraycopy(from, fromIndex, to, toIndex, count);

Le tableau to doit disposer de sufsamment despace pour contenir les lments copis.

Par exemple, les instructions suivantes crent deux tableaux, puis copient les quatre derniers lments du premier tableau dans le second tableau. La copie dbute la position 2 du tableau source ; quatre lments sont copis en partant de la position 3 du tableau cible. Le rsultat est donn la Figure 3.15 :
int[] smallPrimes = {2, 3, 5, 7, 11, 13}; int[] luckyNumbers = {1001, 1002, 1003, 1004, 1005, 1006, 1007}; System.arraycopy(smallPrimes, 2, luckyNumbers, 3, 4); for (int i = 0; i < luckyNumbers.length; i++) System.out.println(i+": "+luckyNumbers[i]);

On obtient en sortie :
0: 1: 2: 3: 4: 5: 6: 1001 1002 1003 5 7 11 13

Figure 3.15
Copie des valeurs vers un tableau.
smallPrimes = 2 3 5 7 11 13

luckyNumbers =

1001 1002 1003 5 7 11 13

INFO C++
Un tableau Java est assez diffrent dun tableau C++ dans la pile (stack). Il peut cependant tre compar un pointeur sur un tableau allou dans le tas (segment heap de la mmoire). Cest--dire que
int[] a = new int[100]; // en Java

Chapitre 3

Structures fondamentales de la programmation Java

101

nest pas la mme chose que


int a[100]; // en C++

mais plutt
int* a = new int[100]; // en C++

En Java, loprateur [] est prdni pour effectuer une vrication de limites. De plus, larithmtique de pointeur nest pas possible vous ne pouvez pas incrmenter a pour quil pointe sur llment suivant du tableau.

Paramtres de ligne de commande


Vous avez dj vu plusieurs exemples de tableaux Java. Chaque programme Java a une mthode main avec un paramtre String[] args. Celui-ci indique que la mthode main reoit un tableau de chanes, qui sont les arguments spcis sur la ligne de commande. Examinez, par exemple, ce programme :
public class Message { public static void main(String[] args) { if (args[0].equals("-h")) System.out.print("Hello,"); else if (args[0].equals("-g")) System.out.print("Goodbye,"); // afficher les autres arguments de ligne de commande for (int i = 1; i < args.length; i++) System.out.print(" "+args[i]); System.out.println("!"); } }

Si le programme est appel de la faon suivante :


java Message -g cruel world

le tableau args a le contenu suivant :


args[0]: "-g" args[1]: "cruel" args[2]: "world"

Le programme afche le message :


Goodbye, cruel world!

INFO C++
Dans la mthode main dun programme Java, le nom du programme nest pas stock dans le tableau args. Si, par exemple, vous lancez le programme ainsi :
java Message -h world

partir de la ligne de commande, args[0] vaudra "-h" et non "Message" ou "java".

102

Au cur de Java 2 - Notions fondamentales

Tri dun tableau


Si vous voulez trier un tableau de nombres, utilisez une des mthodes sort de la classe Arrays :
int[] a = new int[10000]; . . . Arrays.sort(a)

Cette mthode utilise une version adapte de lalgorithme QuickSort qui se rvle trs efcace sur la plupart des ensembles de donnes. La classe Arrays fournit plusieurs autres mthodes de gestion des tableaux ; vous trouverez leur description dans les notes API situes la n de cette section. Le programme du Listing 3.7 montre le fonctionnement des tableaux. Il choisit une combinaison alatoire de nombres pour une loterie. Sil sagit dune loterie o il faut choisir 6 nombres sur 49, le programme peut afcher :
Bet the following combination. Itll make you rich! 4 7 8 19 30 44

Pour slectionner une telle srie alatoire de nombres, il faut dabord remplir un tableau numbers avec les valeurs 1, 2, . . . , n :
int[] numbers = new int[n]; for (int i = 0; i < numbers.length; i++) numbers[i] = i+1;

Un second tableau contient les numros tirer :


int[] result = new int[k];

Nous tirons maintenant k numros. La mthode Math.random renvoie un nombre alatoire virgule ottante entre 0 (inclus) et 1 (exclu). En multipliant le rsultat par n, nous obtenons un nombre alatoire entre 0 et n1 :
int r = (int)(Math.random() * n);

Nous dnissons le ime rsultat comme le numro de cet indice. Au dpart, il sagit simplement de r + 1, mais vous allez voir quen fait, le contenu du tableau numbers change aprs chaque tirage :
result[i] = numbers[r];

Nous devons maintenant nous assurer que nous ne tirerons pas deux fois le mme numro tous les numros dun tirage doivent tre diffrents. Nous crasons donc numbers[r] avec le dernier numro du tableau et dcrmentons n de 1 :
numbers[r] = numbers[n - 1]; n--;

A chaque tirage, nous extrayons en fait un indice, et non la valeur relle. Lindice pointe sur un tableau qui contient les valeurs qui nont pas encore t tires. Aprs avoir tir k numros, le tableau result est tri pour que la sortie soit plus parlante :
Arrays.sort(result); for (int r: result) System.out.println(r);

Chapitre 3

Structures fondamentales de la programmation Java

103

Listing 3.7 : LotteryDrawing.java


import java.util.*; /** * Ce programme prsente la manipulation des tableaux. * @version 1.20 2004-02-10 * @author Cay Horstmann */ public class LotteryDrawing { public static void main(String[] args) { Scanner in = new Scanner(System.in); System.out.print("How many numbers do you need to draw? "); int k = in.nextInt(); System.out.print("What is the highest number you can draw? "); int n = in.nextInt(); // remplir un tableau avec les nombres 12 3 . . . n int[] numbers = new int[n]; for (int i = 0; i < numbers.length; i++) numbers[i] = i+1; // tirer k nombres et les mettre dans un second tableau int[] result = new int[k]; for (int i = 0; i < result.length; i++) { // crer un indice alatoire entre 0 et n - 1 int r = (int)(Math.random() * n); // choisir llment cet emplacement alatoire result[i] = numbers[r]; // dplacer le dernier lment vers lemplac. alatoire numbers[r] = numbers[n - 1]; n--; } // imprimer le tableau tri Arrays.sort(result); System.out.println ("Bet the following combination. Itll make you rich!"); for (int r: result) System.out.println(r); } } java.util.Arrays 1.2 static String toString(type[] a) 5.0

Renvoie une chane avec les lments de a, entre parenthses et dlimits par des virgules. Paramtres :

Un tableau de type int, long, short, char, byte, boolean, float ou double.

static type copyOf(type[] a, int length) 6 static type copyOf(type[] a, int start, int end) 6

104

Au cur de Java 2 - Notions fondamentales

Renvoient un tableau du mme type que a, de longueur length ou end- start, rempli des valeurs de a. Paramtres : a start end un tableau de type int, long, short, char, byte, boolean, float ou double. lindice de dpart (inclus). lindice de n (exclu). Peut tre suprieur a.length, auquel cas le rsultat est complt par des 0 ou des valeurs false. la longueur de la copie. Si length est suprieur a.length, le rsultat est complt par des 0 ou des valeurs false. Sinon, seules les valeurs length initiales sont copies.

length

static void sort(type[] a)

Trie le tableau en utilisant un algorithme QuickSort adapt. Paramtres :


Tableau de type int, long, short, char, byte, float ou double.


6

static int binarySearch(type[] a, type v) static int binarySearch(type[] a, int start, int end type v)

Utilisent lalgorithme de recherche binaire pour rechercher la valeur v. Si elle la trouve, la mthode renvoie lindice de v. Sinon, elle renvoie une valeur r ngative ; -r-1 dsigne la position laquelle v devrait tre insre pour que le tableau reste tri. Paramtres : a start end v

Tableau tri de type int, long, short, char, byte, float ou double. lindice de dpart (inclus) lindice de n (exclu) Valeur de mme type que les lments de a.

static void fill(type[] a, type v)

Affecte la valeur de v tous les lments du tableau. Paramtres : a v

Tableau de type int, long, short, char, byte, boolean, oat ou double. Valeur de mme type que les lments de a.

static boolean equals(type[] a, type[] b)

Renvoie true si les tableaux ont la mme longueur et les lments dindices correspondants possdent la mme valeur. Paramtres : a, b Tableau de type int, long, short, char, byte, boolean, float ou double.

java.lang.System 1.1

static void arraycopy(Object from, int fromIndex, Object to, int toIndex, int count)

Copie les lments du premier tableau dans le second.

Chapitre 3

Structures fondamentales de la programmation Java

105

Paramtres :

from fromIndex to toIndex count

un tableau de nimporte quel type (le Chapitre 5 explique pourquoi il sagit dun paramtre de type Object). lindice de dpart partir duquel copier les lments. un tableau du mme type que from. lindice de dpart auquel copier les lments. le nombre dlments copier.

Tableaux multidimensionnels
Les tableaux multidimensionnels utilisent plusieurs indices pour accder aux lments du tableau. Ils sont utiliss pour les tables et autres organisations plus complexes. Vous pouvez sauter cette section jusqu ce que vous ayez besoin dun tel mcanisme de stockage. Supposons que vous dsiriez crer un tableau de nombres qui montre la manire dont un investissement de 10 000 euros crot selon divers taux dintrt, lorsque les intrts sont pays annuellement et rinvestis. Le Tableau 3.8 illustre ce scnario.
Tableau 3.8 : Accroissement dun investissement en fonction de diffrents taux dintrt

10 % 10 000,00 11 000,00 12 100,00 13 310,00 14 641,00 16 105,10 17 715,61 19 487,17 21 435,89 23 579,48

11 % 10 000,00 11 100,00 12 321,00 13 676,31 15 180,70 16 850,58 18 704,15 20 761,60 23 045,38 25 580,37

12 % 10 000,00 11 200,00 12 544,00 14 049,28 15 735,19 17 623,42 19 738,23 22 106,81 24 759,63 27 730,79

13 % 10 000,00 11 300,00 12 769,00 14 428,97 16 304,74 18 424,35 20 819,52 23 526,05 26 584,44 30 040,42

14 % 10 000,00 11 400,00 12 996,00 14 815,44 16 889,60 19 254,15 21 949,73 25 022,69 28 525,86 32 519,49

15 % 10 000,00 11 500,00 13 225,00 15 208,75 17 490,06 20 113,57 23 130,61 26 600,20 30 590,23 35 178,76

La manire vidente de stocker cette information est un tableau deux dimensions (ou matrice) que nous appellerons balances. Il est trs facile de dclarer une matrice :
double[][] balance;

Comme toujours en Java, vous ne pouvez pas utiliser le tableau avant de lavoir initialis par un appel new. Linitialisation peut se faire par une instruction comme celle-ci :
balances = new double[NYEARS][NRATES];

106

Au cur de Java 2 - Notions fondamentales

Si vous connaissez les lments du tableau, vous pouvez utiliser un raccourci pour initialiser les tableaux plusieurs dimensions sans avoir recours new. Par exemple :
int[][] magicSquare = { {16, 3, 2, 13}, {5, 10, 11, 8}, {9, 6, 7, 12}, {4, 15, 14, 1} };

Lorsque le tableau est initialis, vous pouvez accder ses lments individuellement, laide de deux indices entre crochets, par exemple balance[i][j]. Lexemple de programme stocke un tableau une dimension interest, pour les taux dintrt, et un tableau deux dimensions balance, pour les soldes des comptes, pour chaque anne et chaque taux dintrt. La premire ligne du tableau est initialise avec le solde initial :
for (int j = 0; j < balance[0].length; j++) balances[0][j] = 10000;

Puis les autres lignes sont calcules de la faon suivante :


for (int i = 1; i < balances.length; i++) { for (int j = 0; j < balances[i].length; j++) { double oldBalance = balances[i - 1][j]; double interest = . . .; balance[i][j] = oldBalance+interest; } }

Le Listing 3.8 prsente le programme qui calcule lensemble des valeurs du tableau.
INFO
Une boucle "for each" ne traverse pas automatiquement toutes les entres dun tableau bidimensionnel. Elle parcourt plutt les lignes, qui sont elles-mmes des tableaux une dimension. Pour visiter tous les lments dun tableau bidimensionnel a, imbriquez deux boucles, comme ceci :
for (double[] row: a) for (double b: row) faire quelque chose avec value

ASTUCE
Pour afcher une liste rapide et dsorganise des lments dun tableau bidimensionnel, appelez
System.out.println(Arrays.deepToString(a));

La sortie est mise en forme ainsi :


[[16, 3, 2, 13], [5, 10, 11, 8], [9, 6, 7, 12], [4, 15, 14, 1]]

Chapitre 3

Structures fondamentales de la programmation Java

107

Listing 3.8 : CompoundInterest.java


/** * Ce programme montre comment stocker des donnes tabulaires * dans un tableau 2D. * @version 1.40 2004-02-10 * @author Cay Horstmann */ public class CompoundInterest { public static void main(String[] args) { final double STARTRATE = 10; final int NRATES = 6; final int NYEARS = 10; // dfinir les taux dintrt de 10 15% double[] interestRate = new double[NRATES]; for (int j = 0; j < interestRate.length; j++) interestRate[j] = (STARTRATE+j) / 100.0; double[][] balances = new double[NYEARS][NRATES]; // dfinir les soldes initiaux 10000 for (int j = 0; j < balance[0].length; j++) balance[0][j] = 10000; // calculer lintrt des annes venir for (int i = 1; i < balances.length; i++) { for (int j = 0; j < balances[i].length; j++) { // rcup. solde anne prcdente de la ligne prcdente double oldBalance = balances[i - 1][j]; // calculer lintrt double interest = oldBalance * interestRate[j]; // calculer le solde de lanne balances[i][j] = oldBalance+interest; } } // imprimer une ligne de taux dintrt for (int j = 0; j < interestRate.length; j++) System.out.printf("%9.0f%%", 100 * interestRate[j])); System.out.println(); // imprimer la table des soldes for (double[] row: balances) { // imprimer une ligne de la table for (double b: row) System.out.printf("%10.2f", b); System.out.println(); } } }

108

Au cur de Java 2 - Notions fondamentales

Tableaux irrguliers
Pour linstant, ce que nous avons vu ne sloigne pas trop des autres langages de programmation. Mais en ralit il se passe en coulisse quelque chose de subtil que vous pouvez parfois faire tourner votre avantage : Java ne possde pas de tableaux multidimensionnels, mais uniquement des tableaux unidimensionnels. Les tableaux multidimensionnels sont en ralit des "tableaux de tableaux". Ainsi, dans lexemple prcdent, balances est en fait un tableau de dix lments, chacun deux tant un tableau de six nombres rels (voir Figure 3.16).
Figure 3.16
balances = Un tableau deux dimensions. balances[1] = 10000.0 10000.0 10000.0 10000.0 10000.0 10000.0

balances[1][2] =

11000.0 11100.0 11200.0 11300.0 11400.0 11500.0

. . .
23579.48 25580.37 27730.79 30040.42 32519.49 35178.76

Lexpression balances[i] se rfre au sous-tableau dindice i, autrement dit la range i. Cest en soi un tableau et balances[i] [j] fait rfrence llment j de ce tableau. Comme les ranges de tableau sont accessibles individuellement, vous pouvez aisment les intervertir !
double[] temp = balances[i]; balances[i] = balances[i+1]; balances[i+1] = temp;

De plus, il est facile de crer des tableaux "irrguliers", cest--dire des tableaux dont les diffrentes ranges ont des longueurs diffrentes. Pour illustrer ce mcanisme, crons un tableau dont les ranges i et les colonnes j reprsentent le nombre de tirages possibles pour une loterie o il faut "choisir j nombres parmi i nombres" :

Chapitre 3

Structures fondamentales de la programmation Java

109

1 1 1 1 1 1 1

1 2 3 4 5 6

1 3 6 10 15

1 4 10 20

1 5 15

1 6

Comme j ne peut jamais tre plus grand que i, nous obtenons une matrice triangulaire. La ime range possde i+1 lments (nous admettons un choix de 0 lment et il ny a quune manire deffectuer un tel choix). Pour crer ce tableau irrgulier, commenons par allouer le tableau qui contient les ranges :
int[][] odds = new int[NMAX+1][];

Crons ensuite les ranges elles-mmes :


for (int n = 0; n <= NMAX; n++) odds[n] = new int[n+1];

Une fois le tableau allou, nous pouvons accder normalement ses lments, condition de ne pas dpasser les limites de chaque sous-tableau :
for (int n = 0; n < odds.length; n++) for (int k = 0; k < odds[n].length; k++) { // calculer lotteryOdds . . . odds[n][k] = lotteryOdds; }

Le Listing 3.9 vous montre le programme complet.


INFO C++
La dclaration Java
double[][] balances = new double[10][6]; // Java

nest pas quivalente


double balances[10][6]; // C++

ni mme
double (*balances)[6] = new double[10][6]; // C++

En fait, en C++, un tableau de 10 pointeurs est allou :


double** balances = new double*[10]; // C++

A chaque lment du tableau de pointeurs est ensuite affect un tableau de 6 nombres :


for (i = 0; i < 10; i++) balances[i] = new double[6];

Cette boucle est heureusement excute automatiquement par linstruction new double[10][6]. Si vous dsirez crer un tableau irrgulier, il faut allouer les ranges sparment.

110

Au cur de Java 2 - Notions fondamentales

Listing 3.9 : LotteryArray.java


/** * Ce programme montre un tableau triangulaire. * @version 1.20 2004-02-10 * @author Cay Horstmann */ public class LotteryArray { public static void main(String[] args) { final int NMAX = 10; // allouer un tableau triangulaire int[][] odds = new int[NMAX+1][]; for (int n = 0; n <= NMAX; n++) odds[n] = new int[n+1]; // remplir le tableau triangulaire for (int n = 0; n < odds.length; n++) for (int k = 0; k < odds[n].length; k++) { /* * calculer le binme * n * (n - 1) * (n - 2) * . . . * (n - k+1) * ------------------------------------------* 1 * 2 * 3 * . . . * k */ int lotteryOdds = 1; for (int i = 1; i <= k; i++) lotteryOdds = lotteryOdds * (n - i+1) / i; odds[n][k] = lotteryOdds; } // imprimer le tableau triangulaire for (int[] row: odds) { for (int odd: row) System.out.printf("%4d", odd); System.out.println(); } } }

Vous connaissez maintenant les structures de programmation de base du langage Java. Le chapitre suivant traite de la programmation oriente objet en Java.

4
Objets et classes
Au sommaire de ce chapitre

Introduction la programmation oriente objet Utilisation des classes prdnies Construction de vos propres classes Champs et mthodes statiques Paramtres des mthodes Construction dun objet Packages Le chemin de classe Commentaires pour la documentation Conseils pour la conception de classes
Lobjectif de ce chapitre est :
m m

de vous prsenter la programmation oriente objet ; de vous montrer comment crer des objets appartenant des classes de la bibliothque Java standard ; de vous montrer comment rdiger vos propres classes.

Si vous navez pas dexprience en matire de programmation oriente objet, nous vous conseillons de lire attentivement ce chapitre. La POO (programmation oriente objet) demande une approche diffrente de celle des langages procduraux. La transition nest pas toujours facile, mais il est ncessaire de vous accoutumer au concept dobjet avant dapprofondir Java. Pour les programmeurs C++ expriments, ce chapitre, comme le prcdent, prsentera des informations familires ; malgr tout, il existe des diffrences entre les deux langages, et nous vous conseillons de lire les dernires sections de ce chapitre (en vous concentrant sur les infos relatives C++).

112

Au cur de Java 2 - Notions fondamentales

Introduction la programmation oriente objet


De nos jours, la programmation oriente objet constitue le principal paradigme de la programmation ; elle a remplac les techniques de programmation procdurale, "structure", qui ont t dveloppes au dbut des annes 1970. Java est totalement orient objet et vous devez tre familiaris avec la POO (voir Figure 4.1) pour devenir productif en Java. Un programme est constitu dobjets possdant certaines proprits prsentes aux utilisateurs, ainsi quune implmentation cache. De nombreux objets de vos programmes seront extraits tels quels dune bibliothque, dautres seront personnaliss. Cest votre budget et votre temps disponible qui dcideront du fait que vous construirez un objet ou que vous lachterez. Cependant, tant que les objets en question satisfont vos spcications, vous ne vous proccupez pas de savoir comment ils ont t implments. En POO, vous ne vous proccupez pas de savoir comment un objet est implment, pourvu quil excute ce que vous souhaitez. La programmation structure traditionnelle consiste concevoir un ensemble de fonctions (ou algorithmes) permettant de rsoudre un problme. Une fois ces fonctions dtermines, ltape suivante consistait traditionnellement trouver la manire approprie de stocker des donnes. Cest la raison pour laquelle le concepteur du langage Pascal, Niklaus Wirth, a intitul son fameux ouvrage de programmation Algorithmes + Structures de donnes = Programmes. Remarquez que le terme algorithmes est plac en tte dans ce titre, devant lexpression structures de donnes. Cela montre bien la manire dont les programmeurs travaillaient cette poque. Dabord, vous dcidiez de la manire dont vous alliez manipuler les donnes ; ensuite seulement, vous choisissiez le genre de structures que vous imposeriez aux donnes an de faciliter cette manipulation. La POO inverse cet ordre et place les donnes au premier plan avant de dterminer lalgorithme qui sera employ pour oprer sur ces donnes. Pour les problmes mineurs, la dcomposition en procdures fonctionne trs bien. Mais les objets fonctionnent mieux pour les gros problmes. Imaginez un navigateur Web simple. Son implmentation pourrait ncessiter jusqu 2 000 procdures, toutes manipulant un ensemble de donnes globales. Dans le style orient objet, il pourrait y avoir 100 classes, avec une moyenne de 20 mthodes par classe (voir la comparaison entre programmation procdurale et POO). Cette dernire structure est plus simple saisir pour un programmeur. La dcouverte des bogues est facilite. Supposons que les donnes dun objet particulier se trouvent dans un tat incorrect. Il sera plus facile de rechercher le coupable parmi les 20 mthodes ayant eu accs aux donnes que parmi 2 000 procdures.
Figure 4.1
Programmation procdurale ou oriente objet.
procdure procdure procdure procdure procdure mthode mthode Donnes dobjet mthode mthode mthode mthode Donnes dobjet

Donnes globales

Donnes dobjet

Chapitre 4

Objets et classes

113

Les classes
Une classe est le modle ou la matrice de lobjet effectif. Cela nous amne comparer les classes des moules biscuits, les objets reprsentant les biscuits proprement dits. Lorsquon construit un objet partir dune classe, on dit que lon cre une instance de cette classe. Comme vous avez pu le constater, tout le code que vous crivez en Java se trouve dans une classe. La bibliothque Java standard fournit plusieurs milliers de classes rpondant de multiples besoins comme la conception de linterface utilisateur, les dates et les calendriers, ou la programmation rseau. Quoi quil en soit, il vous faut quand mme crer vos propres classes Java et dcrire les objets des domaines de problmes appartenant vos applications. Lencapsulation (appele parfois dissimulation des donnes, ou isolement des donnes) est un concept cl pour lutilisation des objets. Lencapsulation consiste tout bonnement combiner des donnes et un comportement dans un emballage et dissimuler limplmentation des donnes aux utilisateurs de lobjet. Les donnes dun objet sont appeles ses champs dinstance ; les fonctions qui agissent sur les donnes sont appeles ses mthodes. Un objet spcique, qui est une instance dune classe, a des valeurs spciques dans ses champs dinstance. Le jeu de ces valeurs est ltat actuel de lobjet. Chaque fois que vous appelez une mthode sur un objet, son tat peut changer. Lencapsulation ne fonctionne correctement que si les mthodes nont jamais accs directement aux champs dinstance dans une classe autre que la leur propre. Les programmes doivent interagir avec les donnes dun objet uniquement par lintermdiaire des mthodes de lobjet. Lencapsulation reprsente le moyen de donner lobjet son comportement de "bote noire" ; cest sur elle que reposent la rutilisation et la scurit de lobjet. Cela signie quune classe peut compltement modier la manire dont elle stocke ses donnes, mais tant quelle continue utiliser les mmes mthodes pour les manipuler, les autres objets nen sauront rien et ne sen proccuperont pas. Lorsque vous commencez rellement crire vos propres classes en Java, un autre principe de la POO facilite cette opration : les classes peuvent tre construites sur les autres classes. On dit quune classe construite partir dune autre ltend. Java est en fait fourni avec une "superclasse cosmique" appele Object. Toutes les autres classes tendent cette classe. Vous en apprendrez plus concernant la classe Object dans le prochain chapitre. Lorsque vous tendez une classe existante, la nouvelle classe possde toutes les proprits et mthodes de la classe que vous tendez. Vous fournissez les nouvelles mthodes et les champs de donnes qui sappliquent uniquement votre nouvelle classe. Le concept dextension dune classe pour en obtenir une nouvelle est appel hritage. Reportez-vous au prochain chapitre pour plus de dtails concernant la notion dhritage.

Les objets
Pour bien travailler en POO, vous devez tre capable didentier trois caractristiques essentielles des objets. Ce sont :
m

Le comportement de lobjet. Que pouvez-vous faire avec cet objet ou quelles mthodes pouvezvous lui appliquer ? Ltat de lobjet. Comment lobjet ragit-il lorsque vous appliquez ces mthodes ? Lidentit de lobjet. Comment lobjet se distingue-t-il des autres qui peuvent avoir le mme comportement et le mme tat ?

m m

114

Au cur de Java 2 - Notions fondamentales

Tous les objets qui sont des instances dune mme classe partagent le mme comportement. Celui-ci est dtermin par les mthodes que lobjet peut appeler. Ensuite, chaque objet stocke des informations sur son aspect actuel. Cest ltat de lobjet. Ltat dun objet peut changer dans le temps, mais pas spontanment. Une modication dans ltat dun objet doit tre la consquence dappels de mthodes (si ltat de lobjet change sans quun appel de mthode soit intervenu, cela signie que la rgle de lencapsulation a t viole). Nanmoins, ltat dun objet ne dcrit pas compltement celui-ci, car chaque objet possde une identit spcique. Par exemple, dans un systme de traitement de commandes, deux commandes sont distinctes mme si elles dsignent des produits identiques. Remarquez que des objets individuels instances dune mme classe ont toujours une identit distincte et gnralement un tat distinct. Chacune de ces caractristiques essentielles peut avoir une inuence sur les autres. Par exemple, ltat dun objet peut altrer son comportement. Si une commande est "expdie" ou "paye", elle peut refuser un appel de mthode qui demanderait dajouter ou de supprimer un lment. Inversement, si une commande est "vide" autrement dit, si aucun produit na encore t command , elle ne doit pas pouvoir tre expdie.

Identication des classes


Dans un programme procdural traditionnel, le processus commence par une fonction principale main. Lorsquon travaille dans un systme orient objet, il ny a pas de "dbut", et les programmeurs dbutants en POO se demandent souvent par o commencer. La rponse est la suivante : trouvez dabord les classes appropries, puis ajoutez des mthodes ces classes. Une rgle simple dans lidentication des classes consiste rechercher des noms quand vous analysez le problme. En revanche, les mthodes sont symbolises par des verbes. Par exemple, voici quelques noms dans un systme de gestion de commandes :
m m m m m

produit ; commande ; adresse de livraison ; rglement ; compte.

Ces noms permettent de rechercher les classes Item, Order, et ainsi de suite. On cherche ensuite les verbes. Les produits (ou articles) sont ajouts aux commandes. Les commandes sont expdies ou annules. Les rglements sont appliqus aux commandes. Tous ces verbes, "ajouter", "expdier", "annuler" et "appliquer", permettent didentier lobjet qui aura la principale responsabilit de leur excution. Par exemple, lorsquun nouveau produit est ajout une commande, cest lobjet Order (commande) qui doit tre responsable de cet ajout, car il sait comment stocker et trier ses propres lments. Autrement dit, dans la classe Order, add (ajouter) doit tre une mthode qui reoit un objet Item (produit) comme paramtre. Bien entendu, la "rgle des noms et des verbes" nest quune rgle mnmonique, et seule lexprience vous apprendra dterminer les noms et les verbes qui sont importants pour le dveloppement de vos propres classes.

Chapitre 4

Objets et classes

115

Relations entre les classes


Les relations les plus courantes entre les classes sont :
m m m

dpendance ("utilise") ; agrgation ("possde") ; hritage ("est").

La relation de dpendance ou "utilise" est la plus vidente et la plus courante. Par exemple, la classe Order utilise la classe Account, car les objets Order doivent pouvoir accder aux objets Account pour vrier que le compte est crdit. Mais la classe Item ne dpend pas de la classe Account, car les objets Item nont pas se proccuper de ltat du compte dun client. Une classe dpend dune autre classe si ses mthodes manipulent des objets de cette classe. Efforcez-vous de rduire le nombre de classes qui dpendent mutuellement les unes des autres. Lavantage est le suivant : si une classe A ignore lexistence dune classe B, elle naura pas se proccuper des modications apportes ventuellement B! (Et cela signie quune modication dans la classe B nintroduira pas de bogues dans la classe A.) Dans la terminologie logicielle, on dit vouloir rduire le couplage entre les classes. La relation dagrgation ou "possde" est facile comprendre, car elle est concrte ; par exemple, un objet Order contient des objets Item. Cette relation signie que des objets dune classe A contiennent des objets dune classe B.
INFO
Certains ddaignent le concept dagrgation et prfrent parler de relation "dassociation". Du point de vue de la modlisation, cela peut se comprendre. Mais pour les programmeurs, la relation "possde" semble vidente. Nous prfrons personnellement le terme agrgation pour une seconde raison : la notation standard pour les associations est moins claire. Voir le Tableau 4.1.

La relation dhritage ou "est" exprime une relation entre une classe plus spcique et une, plus gnrale. Par exemple, une classe RushOrder (commande urgente) hrite dune classe Order. La classe spcialise RushOrder dispose de mthodes particulires pour grer la priorit et dune mthode diffrente pour calculer le cot de livraison, mais ses autres mthodes par exemple, ajouter des lments ou facturer sont hrites de la classe Order. En gnral, si la classe A tend la classe B, la classe A hrite des mthodes de la classe B, mais possde des fonctionnalits supplmentaires (lhritage sera dcrit plus en dtail au chapitre suivant). De nombreux programmeurs ont recours la notation UML (Unied Modeling Language) pour reprsenter les diagrammes de classe qui dcrivent la relation entre les classes. Un exemple est montr la Figure 4.2. Vous dessinez les classes sous la forme de rectangles et les relations sont reprsentes par des ches ayant diffrents aspects. Le Tableau 4.1 montre les styles de ches les plus couramment utiliss.

116

Au cur de Java 2 - Notions fondamentales

Figure 4.2
Diagramme dune classe.

INFO
Plusieurs outils permettent de dessiner ce type de diagramme. Les fournisseurs proposent souvent des outils performants (et chers) censs tre le point central de la procdure de dveloppement. On trouve entre autres Rational Rose (http://www.ibm.com/software/awdtools/developer/modeler/rose) et Together (http://www.borland.com/us/products/ together). Vous pouvez galement opter pour le programme source libre ArgoUML (http://argouml.tigris.org). Une version commerciale est disponible chez GentleWare (http://gentleware.com). Pour dessiner des diagrammes simples sans trop de problmes, testez Violet (http://violet.sourceforge.net).

Tableau 4.1 : Notation UML pour reprsenter la relation entre classes

Relation Hritage Hritage dinterface Dpendance Agrgation Association Association dirige

Connecteur UML

Utilisation des classes existantes


On ne peut rien faire en Java sans les classes et vous avez dj vu plusieurs classes dans les chapitres prcdents. Malheureusement, la plupart dentre elles ne correspondent pas lesprit de Java. Un bon exemple de cette anomalie est constitu par la classe Math. Vous avez vu que lon peut utiliser

Chapitre 4

Objets et classes

117

les mthodes de la classe Math, telles que Math.random, sans avoir besoin de savoir comment elles sont implmentes il suft den connatre le nom et les paramtres (sil y en a). Cest la caractristique de lencapsulation, et ce sera vrai pour toutes les classes. Malheureusement, la classe Math encapsule seulement une fonctionnalit ; elle na pas besoin de manipuler ou de cacher des donnes. Comme il ny a pas de donnes, vous navez pas vous proccuper de la cration des objets et de linitialisation de leurs champs dinstance il ny en a pas ! Dans la prochaine section, nous allons tudier une classe plus typique, la classe Date. Vous verrez comment construire les objets et appeler les mthodes de cette classe.

Objets et variables objet


Pour travailler avec les objets, le processus consiste crer des objets et spcier leur tat initial. Vous appliquez ensuite les mthodes aux objets. Dans le langage Java, on utilise des constructeurs pour construire de nouvelles instances. Un constructeur est une mthode spciale dont le but est de construire et dinitialiser les objets. Prenons un exemple. La bibliothque Java standard contient une classe Date. Ses objets dcrivent des moments prcis dans le temps, tels que "31 dcembre 1999, 23:59:59 GMT".
INFO
Vous vous demandez peut-tre pourquoi utiliser des classes pour reprsenter des dates au lieu (comme dans certains langages) dun type intgr. Visual Basic, par exemple, possde un type de donnes intgr et les programmeurs peuvent spcier les dates au format #6/1/1995#. Cela semble apparemment pratique ; les programmeurs utilisent simplement ce type sans se proccuper des classes. Mais en ralit, cette conception de Visual Basic convient-elle dans tous les cas ? Avec certains paramtres locaux, les dates sont spcies sous la forme mois/jour/anne, dans dautres sous la forme jour/mois/anne. Les concepteurs du langage sont-ils vraiment arms pour prvoir tous ces cas de gure ? Sils font un travail incomplet, le langage devient dsagrablement confus et le programmeur frustr est impuissant. Avec les classes, la tche de conception est dlgue un concepteur de bibliothque. Si une classe nest pas parfaite, les programmeurs peuvent facilement crire la leur pour amliorer ou remplacer les classes du systme. Sachez dailleurs que la bibliothque Java est un peu embrouille et quune grosse rorganisation est en cours ; voir ladresse http://jcp.org/en/jsr/detail?id=310.

Les constructeurs ont toujours le mme nom que la classe. Par consquent, le constructeur pour la classe Date est appel Date. Pour construire un objet Date, vous combinez le constructeur avec loprateur new de la faon suivante :
new Date()

Cette expression construit un nouvel objet. Lobjet est initialis avec lheure et la date courantes. Vous pouvez aussi passer lobjet une mthode :
System.out.println(new Date());

Une autre possibilit consiste appliquer une mthode lobjet que vous venez de construire. Lune des mthodes de la classe Date est la mthode toString. Cette mthode permet dobtenir une reprsentation au format chane de la date. Voici comment appliquer la mthode toString un objet Date nouvellement construit :
String s = new Date().toString();

118

Au cur de Java 2 - Notions fondamentales

Dans ces deux exemples, lobjet construit nest utilis quune seule fois. Gnralement, vous voulez conserver les objets que vous construisez pour pouvoir continuer les utiliser. Stockez simplement lobjet dans une variable :
Date birthday = new Date();

La Figure 4.3 montre la variable objet birthday (anniversaire) faisant rfrence lobjet qui vient dtre construit.
Figure 4.3
Cration dun nouvel objet.
birthday = Date

Il existe une diffrence importante entre les objets et les variables objet. Par exemple, linstruction
Date deadline; // deadline ne dsigne pas un objet

dnit une variable objet, deadline (date limite) qui peut rfrencer des objets de type Date. Il est important de comprendre que la variable deadline nest pas un objet et quen fait elle ne rfrence encore aucun objet. Pour le moment, vous ne pouvez employer aucune mthode Date avec cette variable. Linstruction
s = deadline.toString(); // pas encore

provoquerait une erreur de compilation. Vous devez dabord initialiser la variable deadline. Pour cela, deux possibilits. Vous pouvez, bien entendu, initialiser la variable avec lobjet nouvellement construit :
deadline = new Date();

Ou bien dnir la variable pour quelle fasse rfrence un objet existant :


deadline = birthday;

Maintenant, les deux variables font rfrence au mme objet (voir Figure 4.4). Il est important de comprendre quune variable objet ne contient pas rellement un objet. Elle fait seulement rfrence un objet. En Java, la valeur de toute variable objet est une rfrence un objet qui est stock ailleurs. La valeur renvoye par loprateur new est aussi une rfrence. Une instruction telle que
Date deadline = new Date();

comprend deux parties. Lexpression new Date() cre un objet du type Date et sa valeur est une rfrence cet objet qui vient dtre cr. Cette rfrence est ensuite stocke dans la variable deadline.

Chapitre 4

Objets et classes

119

Figure 4.4
Variables objet faisant rfrence au mme objet.

birthday = birthday =

Date

Il est possible de donner explicitement la valeur null une variable objet an dindiquer quelle ne rfrence actuellement aucun objet :
deadline = null; . . . if (deadline!= null) System.out.println(deadline);

Une erreur dexcution se produit si vous appliquez une mthode une variable ayant la valeur null :
birthday = null; String s = birthday.toString(); // erreur lexcution!

Les variables locales ne sont pas initialises automatiquement null. Vous devez les initialiser, soit en appelant new, soit en leur affectant null.
INFO C++
De nombreuses personnes pensent tort que les variables objet de Java se comportent comme les rfrences de C++. Mais en C++ il ny a pas de rfrences nulles et les rfrences ne peuvent pas tre affectes. Il faut plutt penser aux variables objet de Java comme aux pointeurs sur objets de C++. Par exemple,
Date birthday; // Java

est en fait identique :


Date* birthday; // C++

Lorsque lon fait cette association, tout redevient clair. Bien entendu, un pointeur Date* nest pas initialis tant que lon nappelle pas new. La syntaxe est presque la mme en C++ et en Java :
Date* birthday = new Date(); // C++

Si lon copie une variable dans une autre, les deux variables font rfrence la mme date : ce sont des pointeurs sur le mme objet. Lquivalent dune rfrence null de Java est le pointeur null de C++. Tous les objets Java rsident dans le tas (heap). Lorsquun objet contient une autre variable objet, cette variable ne contient elle-mme quun pointeur sur un autre objet qui rside dans le tas. En C++, les pointeurs vous rendent nerveux, car ils sont responsables de nombreuses erreurs. Il est trs facile de crer des pointeurs incorrects ou daltrer la gestion de la mmoire. En Java, ces problmes ont tout bonnement disparu. Si vous utilisez un pointeur qui nest pas initialis, le systme dexcution dclenchera une erreur dexcution au lieu de produire des rsultats alatoires. Vous navez pas vous proccuper de la gestion de la mmoire, car le rcuprateur de mmoire (ou ramasse-miettes) sen charge. En prenant en charge les constructeurs de copie et les oprateurs daffectation, le C++ a fait un bel effort pour permettre limplmentation dobjets qui se copient automatiquement. Par exemple, une copie dune liste lie est une nouvelle liste lie ayant un contenu identique, mais des liens indpendants. Ce mcanisme autorise la conception de classes ayant le mme comportement que les classes prdnies. En Java, il faut utiliser la mthode clone pour obtenir une copie complte dun objet.

120

Au cur de Java 2 - Notions fondamentales

La classe GregorianCalendar de la bibliothque Java


Dans les exemples prcdents, nous avons employ la classe Date qui fait partie de la bibliothque Java standard. Une instance de cette classe possde un tat une position dans le temps. Bien quil ne soit pas indispensable de connatre ces dtails pour utiliser la classe Date, lheure est reprsente par le nombre de millimes de seconde (positif ou ngatif) partir dun point xe (appel epoch ou poque), qui est le 1er janvier 1970 00:00:00 UTC. UTC est le temps universel (Coordinated Universal Time), le standard scientique qui est, pour des raisons pratiques, le mme que lheure GMT (Greenwich Mean Time). En ralit, la classe Date nest pas trs pratique pour manipuler les dates. Les concepteurs de la bibliothque Java considrent quune description de date telle que "31 dcembre 1999, 23:59:59" est une convention arbitraire dtermine par un calendrier. Cette description correspond celle du calendrier grgorien utilis dans de nombreux pays. Ce mme repre temporel pourrait tre dcrit diffremment dans le calendrier chinois ou le calendrier lunaire hbreu, sans parler du calendrier de nos clients martiens.
INFO
Au cours de lhistoire de lhumanit, les civilisations se sont dbattues avec la conception de calendriers attribuant des noms aux dates et ont mis de lordre dans les cycles solaires et lunaires. Louvrage Calendrical Calculations, Second Edition, de Nachum Dershowitz et Edward M. Reingold (Cambridge University Press, 2001), fournit une explication fascinante des calendriers dans le monde, du calendrier rvolutionnaire franais celui des Mayas.

Les concepteurs de la bibliothque ont dcid de sparer le fait de conserver le temps et dattacher des noms des points temporels. La bibliothque Java standard contient donc deux classes distinctes : la classe Date, qui reprsente un point temporel, et la classe GregorianCalendar, qui exprime les dates par rapport au calendrier. En fait, la classe GregorianCalendar tend une classe Calendar plus gnrique, qui dcrit les proprits des calendriers en gnral. Thoriquement, vous pouvez tendre la classe Calendar et implmenter le calendrier lunaire chinois ou un calendrier martien. Quoi quil en soit, la bibliothque standard ne contient pas dautre implmentation de calendrier que le calendrier grgorien. Distinguer la mesure du temps de la notion de calendriers relve tout fait de la conception oriente objet. Il est gnralement souhaitable dutiliser des classes spares pour exprimer des concepts diffrents. La classe Date ne dispose que dun petit nombre de mthodes pour comparer deux points dans le temps. Par exemple, les mthodes before et after vous indiquent si un moment donn vient avant ou aprs un autre :
if (today.before(birthday)) System.out.println("Still time to shop for a gift.");

INFO
En ralit, la classe Date dispose de mthodes telles que getDay, getMonth et getYear, mais ces mthodes sont dprcies. Cela signie que le concepteur de la bibliothque a admis quelles nauraient jamais d y gurer.

Chapitre 4

Objets et classes

121

Ces mthodes faisaient partie de la classe Date avant que les concepteurs ralisent quil tait plus logique de fournir des classes de calendrier spares. Lors de lintroduction de ces classes, les mthodes Date ont t dprcies. Vous pouvez toujours les employer dans vos programmes, mais vous obtiendrez alors des avertissements disgracieux du compilateur. Il est prfrable dviter lutilisation de mthodes dprcies, car elles peuvent trs bien tre supprimes dans une version future de la bibliothque.

La classe GregorianCalendar propose beaucoup plus de mthodes que la classe Date. Elle possde en particulier plusieurs constructeurs utiles. Lexpression
new GregorianCalendar()

construit un nouvel objet qui reprsente la date et lheure de construction de lobjet. Vous pouvez construire un objet calendrier pour minuit une date spcique en fournissant lanne, le mois et le jour :
new GregorianCalendar(1999, 11, 31)

Assez curieusement, les mois sont compts partir de 0. Ainsi, le mois 11 est dcembre. Pour simplier ces manipulations, il existe des constantes comme Calendar.DECEMBER :
new GregorianCalendar(1999, Calendar.DECEMBER, 31)

Vous pouvez aussi dnir lheure :


new GregorianCalendar(1999, Calendar.DECEMBER, 31, 23, 59, 59)

Vous stockez bien entendu lobjet une fois construit dans une variable objet :
GregorianCalendar deadline = new GregorianCalendar(. . .);

La classe GregorianCalendar a des champs dinstance encapsuls pour conserver la date sa valeur dnie. Si lon nexamine pas le code source, il est impossible de connatre la reprsentation interne de ces donnes dans la classe. Evidemment, cest justement l lintrt de la chose. Ce qui importe, ce sont les mthodes mises disposition par la classe.

Les mthodes daltration et les mthodes daccs


Vous vous demandez probablement comment obtenir le jour, le mois ou lanne de la date encapsule dans un objet GregorianCalendar. Et comment modier les valeurs si elles ne vous conviennent pas. Vous trouverez les rponses ces questions en consultant la documentation en ligne ou les infos API la n de cette section. Nous allons tudier ici les mthodes les plus importantes. Le rle dun calendrier est de calculer les attributs, tels que la date, le jour de la semaine, le mois ou lanne, dun point donn dans le temps. La mthode get de la classe GregorianCalendar permet dextraire ces donnes. Pour slectionner llment que vous souhaitez connatre, vous passez la mthode une des constantes dnies dans la classe Calendar, comme Calendar.MONTH pour le mois, ou Calendar.DAY_OF_WEEK pour le jour de la semaine :
GregorianCalendar now = new GregorianCalendar(); int month = now.get(Calendar.MONTH); int weekday = now.get(Calendar.DAY_OF_WEEK);

Les infos API donnent la liste de toutes les constantes disponibles.

122

Au cur de Java 2 - Notions fondamentales

Il est possible de changer ltat en appelant la mthode set :


deadline.set(Calendar.YEAR, 2001); deadline.set(Calendar.MONTH, Calendar.APRIL); deadline.set(Calendar.DAY_OF_MONTH, 15);

Il existe aussi une mthode pour dnir lanne, le mois et le jour en un seul appel :
deadline.set(2001, Calendar.APRIL, 15);

Vous pouvez enn ajouter un certain nombre de jours, de semaines, de mois, etc. un objet de calendrier donn :
deadline.add(Calendar.MONTH, 3); // dcaler la date limite de 3 mois

Si vous ajoutez un nombre ngatif, le dplacement dans le calendrier se fait en arrire. Il existe une diffrence conceptuelle entre, dune part, la mthode get et, dautre part, les mthodes set et add. La mthode get se contente dexaminer ltat de lobjet et de renvoyer une information. En revanche, les mthodes set et add modient ltat de lobjet. Les mthodes qui modient des champs dinstance sont appeles mthodes daltration (mutator), celles qui se contentent daccder aux champs de linstance, sans les modier, sont appeles mthodes daccs (accessor).
INFO C++
En C++, le sufxe const est utilis pour dsigner les mthodes daccs. Une mthode qui nest pas dclare comme const est suppose tre une mthode daltration. Dans le langage Java, il nexiste cependant pas de syntaxe particulire pour distinguer les mthodes daccs de celles daltration.

Une convention couramment employe consiste prxer les mthodes daccs laide de get et les mthodes daltration laide de set. Par exemple, la classe GregorianCalendar dispose des mthodes getTime et setTime qui rcuprent et dnissent le moment dans le temps quun objet calendrier reprsente :
Date time = calendar.getTime(); calendar.setTime(time);

Ces mthodes sont particulirement utiles pour raliser des conversions entre les classes Date et GregorianCalendar. Supposons, par exemple, que vous connaissiez lanne, le mois et le jour et que vous vouliez dnir un objet Date avec ces informations. Puisque la classe Date ne sait rien des calendriers, nous allons dabord construire un objet GregorianCalendar, puis appeler la mthode getTime pour obtenir une date :
GregorianCalendar calendar = new GregorianCalendar(year, month, day); Date hireDay = calendar.getTime();

Inversement, si vous voulez trouver lanne, le mois ou le jour dun objet Date, vous construisez un objet GregorianCalendar, dnissez lheure, puis appelez la mthode get :
GregorianCalendar calendar = new GregorianCalendar(); calendar.setTime(hireDay); int year = calendar.get(Calendar.YEAR);

Chapitre 4

Objets et classes

123

Nous allons conclure cette section avec un programme qui tire parti de la classe GregorianCalendar. Le programme afche un calendrier pour le mois en cours de la faon suivante :
Sun Mon Tue Wed Thu Fri Sat 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19* 20 21 22 23 24 25 26 27 28 29 30 31

La date du jour est signale par un astrisque (*). Vous le voyez, le programme sait comment calculer les dures dun mois et dun jour de la semaine. Examinons les tapes cls du programme. Tout dabord, nous construisons un objet calendrier qui est initialis avec la date courante :
GregorianCalendar d = new GregorianCalendar();

Nous capturons le jour et le mois courants en appelant deux fois la mthode get :
int today = d.get(Calendar.DAY_OF_MONTH); int month = d.get(Calendar.MONTH);

Nous affectons ensuite d le premier jour du mois et rcuprons le jour de la semaine correspondant cette date :
d.set(Calendar.DAY_OF_MONTH, 1); int weekday = d.get(Calendar.DAY_OF_WEEK);

La variable weekday est dnie Calendar.SUNDAY si le premier jour du mois est un dimanche, Calendar.MONDAY sil sagit dun lundi, etc. Ces valeurs correspondent en fait aux entiers 1, 2, 7 mais mieux vaut ne pas crire de code reposant sur ces connaissances. Remarquez que la premire ligne du calendrier est indente de manire que le 1 er du mois corresponde au jour de la semaine qui convient. Cest un peu difcile puisquil existe diverses conventions dnissant le jour qui commence la semaine. Aux Etats-Unis, il sagit du dimanche, la semaine se terminant le samedi, alors quen Europe elle dmarre le lundi et se termine le dimanche. La machine virtuelle Java connat les paramtres rgionaux de lutilisateur courant. Ils dterminent les conventions de mise en forme locales, notamment le dbut de la semaine et le nom des jours.
ASTUCE
Pour voir la sortie du programme avec un autre paramtre rgional, ajoutez une ligne de la sorte au dbut de la mthode main:
Locale.setDefault(Locale.ITALY);

La mthode getFirstDayOfWeek rcupre le premier jour de la semaine dans les paramtres rgionaux actuels. Pour dterminer lindentation requise, nous soustrayons 1 du jour de lobjet de calendrier, jusqu atteindre le premier jour de la semaine.
int firstDayOfWeek = d.getFirstDayOfWeek(); int indent = 0;

124

Au cur de Java 2 - Notions fondamentales

while (weekday != firstDayOfWeek) { indent++; d.add(Calendar.DAY_OF_MONTH, -1); weekday = d.get(Calendar.DAY_OF_WEEK); }

Nous afchons ensuite len-tte avec les noms des jours. Ils sont disponibles dans la classe DateFormatSymbols.
String [] weekdayNames = new DateFormatSymbols().getShortWeekdays();

La mthode getShortWeekdays renvoie une chane avant les noms des jours en version courte dans la langue de lutilisateur (par ex "Sun", "Mon", etc., en anglais). Le tableau est index en fonction des valeurs des jours. Voici la boucle permettant dafcher len-tte :
do { System.out.printf("%4s", weekdayNames[weekday]); d.add(Calendar.DAY_OF_MONTH, 1); weekday = d.get(Calendar.DAY_OF_WEEK); } while (weekday != firstDayOfWeek); System.out.println();

Vous tes maintenant prt afcher le corps du calendrier. Nous indentons la premire ligne et dnissons lobjet date sur le dbut du mois. Nous entrons une boucle dans laquelle d parcourt les jours du mois. Dans chaque itration, nous afchons la valeur de la date. Si d correspond la date du jour, la date est indique avec un *. Si nous atteignons le dbut de chaque nouvelle semaine, nous afchons une nouvelle ligne. Puis nous avanons d de un jour :
d.add(Calendar.DAY_OF_MONTH, 1);

Quand allons-nous nous arrter ? Nous ne savons pas si le mois a 31, 30, 29 ou 28 jours. Nous poursuivons tant que d est dans le mois en cours :
do { . . . } while (d.get(Calendar.MONTH) == month);

Lorsque d se trouve dans le mois suivant, le programme se termine. Le Listing 4.1 montre le programme complet. Vous pouvez constater que la classe GregorianCalendar permet lcriture dun programme de calendrier qui prend en charge toute la complexit relative aux jours de la semaine et aux diffrentes longueurs des mois. Vous navez pas besoin de savoir comment la classe GregorianCalendar calcule les mois et les jours de la semaine. Vous utilisez simplement linterface de la classe les mthodes get, set et add. Lintrt de cet exemple de programme est de dmontrer comment vous pouvez utiliser linterface dune classe pour raliser des tches assez sophistiques, sans jamais avoir vous proccuper des dtails de son implmentation.

Chapitre 4

Objets et classes

125

Listing 4.1 : CalendarTest.java


import java.text.DateFormatSymbols; import java.util.*; /** * @version 1.4 2007-04-07 * @author Cay Horstmann */ public class CalendarTest { public static void main(String[] args) { // construire d comme la date courante GregorianCalendar d = new GregorianCalendar(); int today = d.get(Calendar.DAY_OF_MONTH); int month = d.get(Calendar.MONTH); // attribuer d le premier jour du mois d.set(Calendar.DAY_OF_MONTH, 1); int weekday = d.get(Calendar.DAY_OF_WEEK); // rcuprer le premier jour de la semaine (dimanche aux US) int firstDayOfWeek = d.getFirstDayOfWeek(); // dterminer lindentation requise pour la premire ligne int indent = 0; while (weekday != firstDayOfWeek) { indent++; d.add(Calendar.DAY_OF_MONTH, -1); weekday = d.get(Calendar.DAY_OF_WEEK); } // afficher les noms des jours String[] weekdayNames = new DateFormatSymbols().getShortWeekdays(); do { System.out.printf("%4s", weekdayNames[weekday]); d.add(Calendar.DAY_OF_MONTH, 1); weekday = d.get(Calendar.DAY_OF_WEEK); } while (weekday != firstDayOfWeek); System.out.println(); for (int i = 1; i <= indent; i++) System.out.print(" "); d.set(Calendar.DAY_OF_MONTH, 1); do { // imprimer la date int day = d.get(Calendar.DAY_OF_MONTH); System.out.printf("%3d", day); // marquer la date du jour avec un *

126

Au cur de Java 2 - Notions fondamentales

if (day == today) System.out.print("*"); else System.out.print(" "); // incrmenter d d.add(Calendar.DAY_OF_MONTH, 1); weekday = d.get(Calendar.DAY_OF_WEEK); // dmarrer une nouvelle ligne au dbut de la semaine if (weekday == firstDayOfWeek) System.out.println(); } while (d.get(Calendar.MONTH) == month); // sortir de la boucle si d est le premier jour du mois suivant // imprimer dernire fin de ligne si ncessaire if (weekday!= firstDayOfWeek) System.out.println(); } } java.util.GregorianCalendar 1.1

GregorianCalendar()

Construit un objet calendrier reprsentant lheure courante, dans la zone horaire par dfaut et avec les paramtres locaux par dfaut.

GregorianCalendar(int year, int month, int day) GregorianCalendar(int year, int month, int day, int hour, int minutes, int seconds)

Construisent un calendrier grgorien la date et lheure spcies. Paramtres : year month day hour minutes seconds

int get(int field)

Lanne de la date. Le mois de la date, base 0 (autrement dit : 0 pour janvier). Le jour du mois. Lheure (de 0 23). Les minutes (de 0 59). Les secondes (de 0 59).

Extrait la valeur du champ spci. Paramtres : field Une des valeurs suivantes : Calendar.ERA Calendar.YEAR Calendar.MONTH Calendar.WEEK_OF_YEAR Calendar.WEEK_OF_MONTH Calendar.DAY_OF_MONTH Calendar.DAY_OF_YEAR

Chapitre 4

Objets et classes

127

Calendar.DAY_OF_WEEK Calendar.DAY_OF_WEEK_IN_MONTH Calendar.AM_PM Calendar.HOUR Calendar.HOUR_OF_DAY Calendar.MINUTE Calendar.SECOND Calendar.MILLISECOND Calendar.ZONE_OFFSET Calendar.DST_OFFSET

void set(int field, int value)

Dnit la valeur dun champ particulier. Paramtres :


field

Une des constantes acceptes par get. valueLa nouvelle valeur.

void set(int year, int month, int day) void set(int year, int month, int day, int hour, int minutes, int seconds)

Fournissent de nouvelles valeurs pour les champs. Paramtres : year month day hour minutes seconds

Lanne de la date. Le mois de la date, base 0 (autrement dit : 0 pour janvier). Le jour du mois. Lheure (de 0 23). Les minutes (de 0 59). Les secondes (de 0 59).

void add(int field, int amount)

Est une mthode arithmtique. Elle ajoute la quantit spcie un champ. Par exemple, pour ajouter 7 jours la date courante, utilisez c.add(Calendar.DAY_OF_MONTH, 7). Paramtres : field amount

int getFirstDayOfWeek()

Le champ modier (spci laide dune des constantes acceptes par get). Quantit ajouter au champ (peut tre ngative).

Rcupre le premier jour de la semaine dans le paramtre rgional de lutilisateur en cours, par exemple Calendar.SUNDAY aux Etats-Unis.

void setTime(Date time)

Dnit le calendrier cette position dans le temps. Paramtres :

time

La position dans le temps.

Date getTime()

Dtermine la position dans le temps reprsente par la valeur de cet objet calendrier.

128

Au cur de Java 2 - Notions fondamentales

java.text.DateFormatSymbols 1.1

String[] getShortWeekdays() String[] getShortMonths() String[] getWeekdays() String[] getMonths()

Rcuprent les noms des jours ou des mois dans le paramtre rgional en cours. Utilisent les constantes de jour et de mois de Calendar comme valeurs dindice de tableau.

Dnition de vos propres classes


Vous avez pu voir au Chapitre 3 comment construire des classes simples. Ces classes taient toutes constitues dune unique mthode main. Il est temps maintenant dtudier lcriture de classes plus complexes, ncessaires des applications plus sophistiques. Ces classes nont gnralement pas de mthode main. Elles possdent en revanche leurs propres mthodes et champs dinstance. Pour construire un programme complet, vous combinez plusieurs classes, dont lune possde une mthode main.

Une classe Employee


La syntaxe la plus simple dune classe Java est la suivante :
class NomDeClasse { constructeur1 constructeur2 . . . mthode1 mthode2 . . . champ1 champ2 . . . }

INFO
Nous avons adopt la rgle qui consiste dnir les mthodes au dbut et placer les champs dinstance la n (dune certaine manire, cela encourage peut-tre lide que linterface doit prendre le pas sur limplmentation).

Considrons cette version trs simplie dune classe Employee qui pourrait tre utilise pour le registre du personnel dune entreprise :
class Employee { // constructeur public Employee(String n, double s, int year, int month, int day) { name = n; salary = s;

Chapitre 4

Objets et classes

129

GregorianCalendar calendar = new GregorianCalendar(year, month - 1, day); hireDay = calendar.getTime(); } // une mthode public String getName() { return name; } // autres mthodes . . . // champs dinstance private String name; private double salary; private Date hireDay; }

Nous analyserons limplmentation de cette classe dans les sections suivantes. Examinez dabord le Listing 4.2, qui prsente un programme permettant de voir comment on peut utiliser la classe Employee. Dans ce programme, nous construisons un Tableau Employee et le remplissons avec trois objets employee :
Employee[] staff = new Employee[3]; staff[0] = new Employee("Carl Cracker", . . .); staff[1] = new Employee("Harry Hacker", . . .); staff[2] = new Employee("Tony Tester", . . .);

Nous utilisons ensuite la mthode raiseSalary de la classe Employee pour augmenter de 5 % le salaire de chaque employ :
for (Employee e: staff) e.raiseSalary(5);

Enn, nous imprimons les informations concernant chaque employ, en appelant les mthodes getName, getSalary et getHireDay :
for (Employee e: staff) System.out.println("name="+e.getName() +",salary="+e.getSalary() +",hireDay="+e.getHireDay());

Remarquez que ce programme est constitu de deux classes : la classe Employee et une classe EmployeeTest ayant un modicateur (ou spcicateur daccs) public. La mthode main avec les instructions que nous venons de dcrire est contenue dans la classe EmployeeTest. Le nom du chier source est EmployeeTest.java, puisque le nom du chier doit tre identique celui de la classe public. Vous ne pouvez avoir quune classe publique dans un chier source, mais le nombre de classes non publiques nest pas limit. Quand vous compilez ce code source, le compilateur cre deux chiers classe dans le rpertoire : EmployeeTest.class et Employee.class.

130

Au cur de Java 2 - Notions fondamentales

Vous lancez le programme en donnant linterprteur de bytecode le nom de la classe qui contient la mthode main de votre programme :
java EmployeeTest

Linterprteur de bytecode dmarre lexcution par la mthode main de la classe EmployeeTest. A son tour, le code de cette mthode construit trois nouveaux objets Employee et vous montre leur tat.
Listing 4.2 : EmployeeTest.java
import java.util.*; /** * Ce programme teste la classe Employee * @version 1.11 2004-02-19 * @author Cay Horstmann */ public class EmployeeTest { public static void main(String[] args) { // remplir le tableau staff avec trois objets Employee Employee[] staff = new Employee[3]; staff[0] 1987, staff[1] 1989, staff[2] 1990, = new Employee("Carl Cracker", 75000, 12, 15); = new Employee("Harry Hacker", 50000, 10, 1); = new Employee("Tony Tester", 40000, 3, 15);

// augmenter tous les salaires de 5% for (Employee e: staff) e.raiseSalary(5); // afficher les informations concernant // tous les objets Employee for (Employee e: staff) System.out.println("name="+e.getName() +",salary="+e.getSalary() +",hireDay="+e.getHireDay()); } } class Employee { public Employee(String n, double s, int year, int month, int day) { name = n; salary = s; GregorianCalendar calendar = new GregorianCalendar(year, month - 1, day); // Avec GregorianCalendar 0 dsigne janvier hireDay = calendar.getTime(); }

Chapitre 4

Objets et classes

131

public String getName() { return name; } public double getSalary() { return salary; } public Date getHireDay() { return hireDay; } public void raiseSalary(double byPercent) { double raise = salary * byPercent / 100; salary += raise; } private String name; private double salary; private Date hireDay; }

Travailler avec plusieurs chiers source


Le programme du Listing 4.2 a deux classes dans un seul chier source. Nombre de programmeurs prfrent avoir un chier source pour chaque classe. Vous pouvez, par exemple, placer la classe Employee dans un chier Employee.java et EmployeeTest dans EmployeeTest.java. Si vous prfrez cette organisation, deux possibilits vous sont offertes pour la compilation du programme. Vous pouvez invoquer le compilateur Java par un appel gnrique :
javac Employee*.java

Tous les chiers source correspondants seront compils en chiers de classe. Vous pouvez aussi simplement taper :
javac EmployeeTest.java

Il peut vous paratre surprenant que la seconde possibilit fonctionne, puisque le chier Employee.java nest jamais explicitement compil. Pourtant, lorsque le compilateur Java verra que la classe Employee est utilise dans EmployeeTest.java, il recherchera un chier Employee.class. Sil ne le trouve pas, il recherchera automatiquement Employee.java et le compilera. Mieux encore, si la date de la version de Employee.java quil trouve est plus rcente que celle existant dans le chier Employee.class, le compilateur Java recompilera automatiquement le chier.
INFO
Si vous tes habitu la fonctionnalit make dUNIX (ou lun de ses cousins Windows comme nmake), vous pouvez imaginer le compilateur Java comme possdant la fonctionnalit make intgre.

132

Au cur de Java 2 - Notions fondamentales

Analyser la classe Employee


Nous allons dissquer la classe Employee dans les sections qui suivent. Commenons par les mthodes. Comme vous pouvez le voir en examinant le code source, cette classe possde un constructeur et quatre mthodes :
public public public public public Employee(String n, double s, int year, int month, int day) String getName() double getSalary() Date getHireDay() void raiseSalary(double byPercent)

Toutes les mthodes de cette classe sont publiques. Le mot cl public signie que les mthodes peuvent tre appeles par nimporte quelle mthode de nimporte quelle classe. Il existe quatre niveaux daccs (ou niveaux de visibilit), qui seront dcrits dans une prochaine section ainsi quau chapitre suivant. Remarquez galement que trois champs dinstance contiendront les donnes que nous manipulerons dans une instance de la classe Employee :
private String name; private double salary; private Date hireDay;

Le mot cl private (priv) assure que les seules mthodes pouvant accder ces champs dinstance sont les mthodes de la classe Employee elle-mme. Aucune mthode externe ne peut lire ou crire dans ces champs.
INFO
Il est possible demployer le mot cl public avec vos champs dinstance, mais ce serait une trs mauvaise ide. Si des champs de donnes sont publics, les champs dinstance peuvent tre lus et modis par nimporte quelle partie du programme. Une telle situation irait compltement lencontre du principe dencapsulation. Toute mthode de toute classe peut modier les champs publics (et, notre avis, certaines parties de code proteront de ce privilge daccs au moment o vous vous y attendrez le moins). Nous insistons sur le fait que vos champs dinstance doivent tre privs.

Notez encore que deux des champs dinstance sont eux-mmes des objets. Les champs name et hireDay sont des rfrences des objets String et Date. Il sagit l dune situation courante : les classes contiennent souvent des champs dinstance du type classe.

Premiers pas avec les constructeurs


Examinons le constructeur de la classe Employee :
public Employee(String n, double s, int year, int month, int day) { name = n; salary = s; GregorianCalendar calendar = new GregorianCalendar(year, month - 1, day); hireDay = calendar.getTime(); }

Vous constatez que le nom du constructeur est le mme que le nom de la classe. Ce constructeur sexcute lorsque vous construisez des objets de la classe Employee, et il attribue aux champs dinstance ltat initial que vous voulez leur donner.

Chapitre 4

Objets et classes

133

Par exemple, si vous crez une instance de la classe Employee avec des instructions de ce genre :
new Employee("James Bond", 100000, 1950, 1, 1);

les champs dinstance sont affects de la manire suivante :


name = "James Bond"; salary = 100000; hireDay = January 1, 1950;

Il existe une diffrence importante entre les constructeurs et les autres mthodes. Un constructeur peut seulement tre appel en association avec loprateur new. Vous ne pouvez pas appliquer un constructeur un objet existant pour rednir les champs dinstance. Par exemple,
james.Employee("James Bond", 250000, 1950, 1, 1); // ERREUR

provoquera une erreur de compilation. Nous reparlerons des constructeurs, mais gardez toujours lesprit les points suivants :
m m m m m

Un constructeur porte le mme nom que la classe. Une classe peut avoir plus dun constructeur. Un constructeur peut avoir un ou plusieurs paramtres, ou ventuellement aucun. Un constructeur ne renvoie aucune valeur. Un constructeur est toujours appel laide de loprateur new.
INFO C++

Les constructeurs fonctionnent de la mme manire en Java et en C++. Mais souvenez-vous que tous les objets Java sont construits sur le tas (dans la mmoire heap) et quun constructeur doit tre combin avec new. Les programmeurs C++ oublient facilement loprateur new:
Employee number007("James Bond", 100000, 1950, 11); // OK en C++, incorrect en Java.

Cette instruction fonctionne en C++, mais pas en Java.

ATTENTION
Prenez soin de ne pas dclarer des variables locales ayant le mme nom que des champs dinstance. Par exemple, le constructeur suivant ninitialisera pas le salaire (salary) :
public Employee(String n, double s, . . .) { String name = n; // ERREUR double salary = s; // ERREUR . . . }

Le constructeur dclare les variables locales name et salary. Ces variables ne sont accessibles que dans le constructeur. Elles clipsent les champs dinstance de mme nom. Certains programmeurs comme les auteurs de ce livre crivent ce type de code sils tapent plus vite quils ne pensent, car leurs doigts ont lhabitude dajouter le type de donnes. Cest une erreur sournoise qui peut se rvler difcile dtecter. Il faut donc faire attention, dans toutes les mthodes, ne pas utiliser des variables qui soient homonymes des champs dinstance.

134

Au cur de Java 2 - Notions fondamentales

Paramtres implicites et explicites


Les mthodes agissent sur les objets et accdent leurs champs dinstance. Par exemple, la mthode
public void raiseSalary(double byPercent) { double raise = salary * byPercent / 100; salary += raise; }

affecte une nouvelle valeur au champ dinstance salary de lobjet qui excute cette mthode. Ainsi, linstruction
number007.raiseSalary(5);

accrot le salaire de number007 en augmentant la variable number007.salary de 5 %. Plus prcisment, lappel excute les instructions suivantes :
double raise = number007.salary * 5 / 100; number007.salary += raise;

La mthode raiseSalary a deux paramtres. Le premier, appel paramtre implicite, est lobjet de type Employee qui apparat devant le nom de la mthode lors dun appel. Le second, situ entre parenthses derrire le nom de la mthode, est un paramtre explicite. Comme vous pouvez le voir, les paramtres explicites sont spcis dans la dclaration de la mthode. Par exemple, double byPercent est explicitement dclar. Le paramtre implicite napparat pas dans la dclaration de la mthode. Dans chaque mthode, le mot cl this fait rfrence au paramtre implicite. Si vous prfrez, vous pouvez crire la mthode raiseSalary de la faon suivante :
public void raiseSalary(double byPercent) { double raise = this.salary * byPercent / 100; this.salary += raise; }

Certains programmeurs prfrent ce style dcriture, car il fait clairement la distinction entre les champs dinstance et les variables locales.
INFO C++
En C++, vous dnissez gnralement les mthodes en dehors de la classe :
void Employee::raiseSalary(double byPercent) // en C++, pas en Java { . . . }

Si vous dnissez une mthode au sein dune classe, ce sera automatiquement une mthode en ligne :
class Employee { . . . int getName() { return name; } // en ligne en C++ }

Chapitre 4

Objets et classes

135

Dans le langage Java, toutes les mthodes sont dnies dans la classe elle-mme. Elles ne sont pas pour autant des mthodes en ligne. Cest la responsabilit de la machine virtuelle Java de trouver des opportunits pour le remplacement en ligne. Le compilateur en "juste--temps" surveille les appels de mthodes courtes, souvent appeles, mais pas crases, puis les optimise.

Avantages de lencapsulation
Regardons plus attentivement les mthodes, assez simples, getName, getSalary et getHireDay :
public String getName() { return name; } public double getSalary() { return salary; } public Date getHireDay() { return hireDay; }

Ce sont des exemples manifestes de mthodes daccs. Comme elles renvoient simplement les valeurs des champs dinstance, elles sont appeles parfois mthodes daccs au champ. Ne serait-il pas plus simple de rendre les champs name, salary et hireDay publics, au lieu davoir des mthodes daccs spares ? Il est important de se rappeler que le champ name est "en lecture seule". Une fois quil est dni dans le constructeur, aucune mthode ne peut le modier. Nous savons donc que ce champ ne peut jamais tre corrompu. Le champ salary nest pas en lecture seule, mais il ne peut tre modi que par la mthode raiseSalary. En particulier, si la valeur du champ se rvlait incorrecte, seule cette mthode devrait tre dbogue. Si le champ salary avait t public, le responsable de la corruption de cette valeur pourrait tre nimporte o. Il peut arriver que vous vouliez lire ou modier la valeur dun champ dinstance ; vous devez alors fournir trois lments :
m m m

un champ de donnes priv ; une mthode publique daccs ce champ ; une mthode publique daltration de ce champ.

Le dveloppement de ces lments exige plus de travail que la cration dun simple champ public, mais les bnces sont considrables. Il est tout dabord possible de modier limplmentation interne sans affecter aucun autre code que celui des mthodes de la classe. Par exemple, si le stockage du nom devient :
String firstName; String lastName;

136

Au cur de Java 2 - Notions fondamentales

la mthode getName peut tre modie pour renvoyer :


firstName+" "+lastName

Cette modication est totalement invisible pour le reste du programme. Bien entendu, les mthodes daccs et daltration peuvent parfois exiger un gros travail et une conversion entre les anciennes et les nouvelles donnes. Mais cela nous amne au second avantage de cette technique. Les mthodes daltration peuvent dtecter des erreurs, ce que ne peuvent pas faire de simples instructions daffectation. Par exemple, une mthode setSalary peut sassurer que le salaire nest jamais infrieur 0.
ATTENTION
Prenez soin de ne pas crire des mthodes daccs qui renvoient des rfrences des objets altrables. Nous avons viol cette rgle dans notre classe Employee, dans laquelle la mthode getHireDay renvoie un objet de la classe Date:
class Employee { . . . public Date getHireDay() { return hireDay; } . . . private Date hireDay; }

Le principe dencapsulation est viol ! Considrons le code suivant :


Employee harry = . . .; Date d = harry.getHireDay(); double tenYearsInMilliSeconds = 10 * 365.25 * 24 * 60 * 60 * 1000; d.setTime(d.getTime() (long) tenYearsInMilliSeconds); // ajoutons dix ans danciennet Harry

La cause du problme est subtile. d et harry.hireDay font rfrence au mme objet (voir Figure 4.5). Lapplication de mthodes daltration d change automatiquement ltat priv de lobjet employee! Si vous devez renvoyer une rfrence un objet altrable, vous devez dabord le cloner. Un clone est une copie conforme dun objet et cette copie est stocke un emplacement diffrent de celui de loriginal. Nous tudierons le clonage plus en dtail au Chapitre 6. Voici le code correct :
class Employee { . . . public Date getHireDay() { return (Date)hireDay.clone(); } . . . }

En rsum, souvenez-vous quil faut toujours utiliser clone lorsque vous devez retourner une copie dun champ de donnes altrable.

Chapitre 4

Objets et classes

137

Figure 4.5

Renvoi d'une rfrence 4.5 champ altrable. Figure un

harry = d=

Employee name = salary = hireDay =

Renvoi d'une rfrence un champ altrable.

Date

Privilges daccs fonds sur les classes


Vous savez quune mthode peut accder aux donnes prives de lobjet par lequel elle est invoque. Certaines personnes trouvent surprenant quune mthode puisse accder aux donnes prives de tous les objets de sa classe. Examinons par exemple une mthode equals qui compare deux employs :
class Employee { . . . boolean equals(Employee other) { return name.equals(other.name); } }

Voici un appel typique :


if (harry.equals(boss)) . . .

Cette mthode accde aux champs privs de harry, ce qui nest pas surprenant. Elle accde galement aux champs privs de boss. Cest une opration parfaitement lgale, car boss est un objet de type Employee, et une mthode de la classe Employee a un droit daccs aux champs privs de nimporte quel objet de type Employee.
INFO C++
La mme rgle existe en C++. Une mthode peut accder aux caractristiques prives de nimporte quel objet de sa classe, et pas seulement celles du paramtre implicite.

Mthodes prives
Lorsque nous implmentons une classe, nous spcions que la visibilit de tous les champs de donnes est prive, car les donnes publiques sont dutilisation risque. Mais quen est-il des mthodes ? Bien que la plupart des mthodes soient dclares public, on rencontre frquemment des mthodes private. Vous voudrez parfois dcomposer le code pour calculer diverses mthodes spares. Gnralement, ces mthodes daide (helper) ne doivent pas faire partie de linterface publique : elles peuvent tre trop proches de limplmentation actuelle, exiger un protocole spcial ou un type dappel particulier. Il est prfrable dimplmenter ces mthodes comme prives.

138

Au cur de Java 2 - Notions fondamentales

Pour implmenter une mthode prive en Java, il suft de remplacer le mot cl public par private. En rendant une mthode prive, nous ne sommes plus tenus de la conserver si nous modions limplmentation de la classe. Si la reprsentation interne des donnes est modie, cette mthode pourrait se rvler plus difcile implmenter ou devenir inutile. Peu importe : tant que la mthode est prive, les concepteurs de la classe savent quelle nest jamais employe lextrieur de la classe et quelle peut donc tre supprime. En revanche, si la mthode est publique, nous ne pouvons pas simplement labandonner, car un autre code peut lutiliser.

Champs dinstance nal


Vous pouvez dnir un champ dinstance comme final. Un tel champ doit tre initialis lorsque lobjet est construit. Cest--dire quil doit tre certain que la valeur du champ est dnie aprs la n de chaque constructeur. Ensuite, il ne peut plus tre modi. Par exemple, un champ name de la classe Employee peut tre dclar comme final puisquil ne change jamais aprs la construction de lobjet. Il ny a pas de mthode setName :
class Employee { . . . private final String name; }

Le modicateur final est particulirement utile pour les champs de type primitif ou pour une classe inaltrable (une classe est dite inaltrable lorsque aucune de ses mthodes ne modie ses objets. Par exemple, la classe String est inaltrable). Pour les classes modiables, le modicateur final risque de jeter la confusion dans lesprit du lecteur. Par exemple,
private final Date hiredate;

signie simplement que la rfrence dobjet stocke dans la variable hiredate nest pas modie aprs la construction de lobjet. Cela ne signie pas pour autant que lobjet est constant. Toute mthode est libre dappeler la mthode daltration setTime sur lobjet auquel fait rfrence hiredate.

Champs et mthodes statiques


Dans tous les exemples de programmes que vous avez vus, la mthode main est qualie de static. Nous allons maintenant tudier la signication de ce modicateur.

Champs statiques
Si vous dnissez un champ comme static, il ne peut en exister quun seul par classe. En revanche, chaque objet a sa propre copie de tous les champs dinstance. Supposons, par exemple, que nous voulions affecter un numro unique didentication chaque employ. Nous ajoutons un champ dinstance id et un champ statique nextId la classe Employee :
class Employee { . . . private int id; private static int nextId = 1; }

Chapitre 4

Objets et classes

139

Maintenant, chaque objet employ possde son propre champ id, mais un seul champ nextId est partag entre toutes les instances de la classe. On peut aussi dire quil y a peu prs un millier dobjets de la classe Employee, et par consquent mille champs dinstance id, un pour chaque objet. Mais il ny a quun seul champ statique nextId. Mme sil ny a aucun objet Employee, le champ statique nextId est prsent. Il appartient la classe, pas un objet individuel.
INFO
Dans la plupart des langages de programmation oriente objet, les champs statiques sont appels champs de la classe. Le terme "static" est un reliquat sans signication de C++.

Implmentons une mthode simple :


public void setId() { id = nextId; nextId++; }

Supposons que vous dnissiez le numro didentication demploy pour harry :


harry.setId();

Le champ id de harry est ensuite dni, et la valeur du champ statique nextId est incrmente :
harry.id = Employee.nextId; Employee.nextId++;

Constantes statiques
Les variables statiques sont plutt rares, mais les constantes statiques sont plus courantes. Par exemple, la classe Math dnit une constante statique :
public class Math { . . . public static final double PI = 3.14159265358979323846; . . . }

Vous pouvez accder cette constante dans vos programmes laide de Math.PI. Si le mot cl static avait t omis, PI aurait t un champ dinstance de la classe Math. Vous auriez d avoir recours un objet de la classe Math pour accder PI, et chaque objet Math aurait eu sa propre copie de PI. Une autre constante statique que vous avez souvent utilise est System.out. Elle est dclare dans la classe System :
public class System { . . . public static final PrintStream out = . . .; . . . }

140

Au cur de Java 2 - Notions fondamentales

Comme nous lavons dj mentionn, il nest jamais souhaitable davoir des champs publics, car tout le monde peut les modier. Toutefois, les constantes publiques (cest--dire les champs final) conviennent. Puisque out a t dclar comme final, vous ne pouvez pas lui raffecter un autre ux dimpression :
System.out = new PrintStream(. . .); // ERREUR--out est final

INFO
Si vous examinez la classe System, vous remarquerez une mthode setOut qui vous permet daffecter System.out un ux diffrent. Vous vous demandez peut-tre comment cette mthode peut changer la valeur dune variable final. Quoi quil en soit, setOut est une mthode native, non implmente dans le langage de programmation Java. Les mthodes natives peuvent passer outre les mcanismes de contrle daccs de Java. Cest un moyen trs inhabituel de contourner ce problme et vous ne devez pas lmuler dans vos propres programmes.

Mthodes statiques
Les mthodes statiques sont des mthodes qui noprent pas sur les objets. Par exemple, la mthode pow de la classe Math est une mthode statique. Lexpression
Math.pow(x, a)

calcule la puissance xa. Elle nutilise aucun objet Math pour raliser sa tche. Autrement dit, elle na pas de paramtre implicite. Vous pouvez imaginer les mthodes statiques comme des mthodes nayant pas de paramtre this (dans une mthode non statique, le paramtre this fait rfrence au paramtre implicite de la mthode, voir prcdemment). Puisque les mthodes statiques noprent pas sur les objets, vous ne pouvez pas accder aux champs dinstance partir dune mthode statique. Cependant, les mthodes statiques peuvent accder aux champs statiques dans leur classe. En voici un exemple :
public static int getNextId() { return nextId; // renvoie un champ statique }

Pour appeler cette mthode, vous fournissez le nom de la classe :


int n = Employee.getNextId();

Auriez-vous pu omettre le mot cl static pour cette mthode ? Oui, mais vous auriez alors d avoir une rfrence dobjet du type Employee pour invoquer la mthode.
INFO
Il est lgal dutiliser un objet pour appeler une mthode statique. Par exemple, si harry est un objet Employee, vous pouvez appeler harry.getNextId() au lieu de Employee.getnextId(). Cette criture peut prter confusion. La mthode getNextId ne consulte pas du tout harry pour calculer le rsultat. Nous vous recommandons dutiliser les noms de classes, et non les objets, pour invoquer des mthodes statiques.

Vous utilisez les mthodes statiques dans deux cas :


m

Si une mthode na pas besoin daccder ltat de lobjet, car tous les paramtres ncessaires sont fournis comme paramtres explicites (par exemple, Math.pow).

Chapitre 4

Objets et classes

141

Si une mthode na besoin daccder qu des champs statiques de la classe (par exemple : Employee.getNextId).
INFO C++

Les champs et mthodes statiques ont la mme fonctionnalit en Java et en C++. La syntaxe est toutefois lgrement diffrente. En C++, vous utilisez loprateur "::" pour accder un champ ou une mthode statique hors de sa porte, par exemple Math::PI. Lhistorique du terme "static" est curieux. Le mot cl static a t introduit dabord en C pour indiquer des variables locales qui ne disparaissaient pas lors de la sortie dun bloc. Dans ce contexte, le terme "static" est logique : la variable reste et elle est toujours l lors dune nouvelle entre dans le bloc. Puis static a eu une autre signication en C, il dsignait des variables et fonctions globales non accessibles partir dautres chiers. Le mot cl static a t simplement rutilis pour viter den introduire un nouveau. Enn, C++ a repris le mot cl avec une troisime interprtation, sans aucun rapport, pour indiquer des variables et fonctions appartenant une classe, mais pas un objet particulier de la classe. Cest cette mme signication qua ce terme en Java.

Mthodes "factory"
Voici une autre utilisation courante des mthodes statiques. La classe NumberFormat utilise les mthodes factory, qui produisent des objets de formatage pour divers styles :
NumberFormat currencyFormatter = NumberFormat.getCurrencyInstance(); NumberFormat percentFormatter = NumberFormat.getPercentInstance(); double x = 0.1; System.out.println(currencyFormatter.format(x)); //affiche $0.10 System.out.println(percentFormatter.format(x)); //affiche 10%

Pourquoi ne pas utiliser plutt un constructeur ? Pour deux raisons.


m

Vous ne pouvez pas donner de nom aux constructeurs, le nom dun constructeur est toujours celui de la classe. Mais nous avons besoin de deux noms diffrents pour obtenir linstance currency et linstance percent. Lorsque vous utilisez un constructeur, vous ne pouvez pas modier le type de lobjet construit. Mais la mthode factory peut renvoyer un objet du type DecimalFormat ou une sous-classe qui hrite de NumberFormat (voir le Chapitre 5 pour plus de dtails sur lhritage).

La mthode main
Notez que vous pouvez appeler des mthodes statiques sans avoir aucun objet. Par exemple, vous ne construisez jamais aucun objet de la classe Math pour appeler Math.pow. Pour la mme raison, la mthode main est une mthode statique :
public class Application { public static void main(String[] args) { // construire les objets ici . . . } }

142

Au cur de Java 2 - Notions fondamentales

La mthode main nopre sur aucun objet. En fait, lorsquun programme dmarre, il nexiste encore aucun objet. La mthode statique main sexcute et construit les objets dont le programme a besoin.
ASTUCE
Chaque classe peut avoir une mthode main. Cest une astuce pratique pour le test unitaire de classes. Vous pouvez par exemple ajouter une mthode main la classe Employee:
class Employee { public Employee(String n, double s, int year, int month, int day) { name = n; salary = s; GregorianCalendar calendar = new GregorianCalendar(year, month 1, day); hireDay = calendar.getTime(); } . . . public static void main(String[] args) // test unitaire { Employee e = new Employee("Romeo", 50000, 2003, 3, 31); e.raiseSalary(10); System.out.println(e.getName()+" "+e.getSalary()); } . . . }

Si vous voulez tester isolment la classe Employee, vous excutez simplement :


java Employee

Si la classe Employee fait partie dune plus grande application, vous dmarrez lapplication avec :
java Application

et la mthode main de la classe Employee ne sexcute jamais.

Le programme du Listing 4.3 contient une version simple de la classe Employee avec un champ statique nextId et une mthode statique getNextId. Un tableau est rempli avec trois objets Employee et les informations concernant lemploy sont afches. Enn, nous afchons les numros didentication attribus. Notez que la classe Employee a aussi une mthode main statique pour le test unitaire. Essayez de lancer les deux :
java Employee

et
java StaticTest

pour excuter les deux mthodes main.

Chapitre 4

Objets et classes

143

Listing 4.3 : StaticTest.java


/** * Ce programme prsente les mthodes statiques. * @version 1.01 2004-02-19 * @author Cay Horstmann */ public class StaticTest { public static void main(String[] args) { // remplir le tableau staff avec 3 objets Employee Employee[] staff = new Employee[3]; staff[0] = new Employee("Tom", 40000); staff[1] = new Employee("Dick", 60000); staff[2] = new Employee("Harry", 65000); // imprimer les informations concernant // tous les objets Employee for (Employee e: staff) { e.setId(); System.out.println("name="+e.getName() +",id="+e.getId() +",salary="+e.getSalary()); } int n = Employee.getNextId(); // appel mthode statique System.out.println("Next available id="+n); } } class Employee { public Employee(String n, double s) { name = n; salary = s; id = 0; } public String getName() { return name; } public double getSalary() { return salary; } public int getId() { return id; }

144

Au cur de Java 2 - Notions fondamentales

public void setId() { id = nextId; // dfinir id prochain id disponible nextId++; } public static int getNextId() { return nextId; // renvoie un champ statique } public static void main(String[] args) // test unitaire { Employee e = new Employee("Harry", 50000); System.out.println(e.getName()+" "+e.getSalary()); } private private private private } String name; double salary; int id; static int nextId = 1;

Paramtres des mthodes


Revoyons les termes qui dcrivent la manire dont les paramtres peuvent tre passs une mthode (ou une fonction) dans un langage de programmation. Le terme appel par valeur signie que la mthode rcupre exactement la valeur fournie par lappelant. En revanche, un appel par rfrence signie que la mthode obtient lemplacement de la variable fournie par lappelant. Une mthode peut donc modier la valeur stocke dans une variable passe par rfrence, mais pas celle dune variable passe par valeur. Ces termes "appels par..." sont standard en informatique et dcrivent le comportement des paramtres des mthodes dans les diffrents langages de programmation, pas seulement en Java (en fait, il existe aussi un appel par nom, dont le principal intrt est historique ; il tait employ avec le langage Algol, lun des plus anciens langages de haut niveau). Le langage Java utilise toujours lappel par valeur. Cela signie que la mthode obtient une copie de toutes les valeurs de paramtre. En particulier, la mthode ne peut modier le contenu daucun des paramtres qui lui sont passs. Par exemple, dans lappel suivant :
double percent = 10; harry.raiseSalary(percent);

Peu importe comment la mthode est implmente, nous savons quaprs lappel la mthode, la valeur de percent sera toujours 10. Examinons cette situation dun peu plus prs. Supposons quune mthode tente de tripler la valeur dun paramtre de mthode :
public static void tripleValue(double x) // ne marche pas { x = 3 * x; }

Chapitre 4

Objets et classes

145

Appelons cette mthode :


double percent = 10; tripleValue(percent);

Cela ne marche pas. Aprs lappel la mthode, la valeur de percent est toujours 10. Voici ce qui se passe : 1. x est initialis avec une copie de la valeur de percent (cest--dire 10). 2. x est tripl : il vaut maintenant 30. Mais percent vaut toujours 10 (voir Figure 4.6). 3. La mthode se termine et la variable paramtre x nest plus utilise.
Figure 4.6
La modication dun paramtre numrique na pas deffet durable.
Valeur copie

percent = x=

10 30

Valeur triple

Il existe pourtant deux sortes de paramtres de mthode :


m m

les types primitifs (nombres, valeurs boolennes) ; les rfrences des objets.

Vous avez vu quil tait impossible pour une mthode de modier un paramtre de type primitif. La situation est diffrente pour les paramtres objet. Vous pouvez facilement implmenter une mthode qui triple le salaire dun employ :
public static void tripleSalary(Employee x) // a marche { x.raiseSalary(200); }

Lorsque vous appelez


harry = new Employee(. . .); tripleSalary(harry);

voici ce qui se passe :

146

Au cur de Java 2 - Notions fondamentales

1. x est initialis avec une copie de la valeur de harry, cest--dire une rfrence dobjet. 2. La mthode raiseSalary est applique cette rfrence dobjet. Lobjet Employee, auquel font rfrence la fois x et harry, voit son salaire augment de 200 %. 3. La mthode se termine et la variable paramtre x nest plus utilise. Bien entendu, la variable objet harry continue faire rfrence lobjet dont le salaire a tripl (voir Figure 4.7).
Figure 4.7
La modication dun paramtre objet a un effet durable.
Rfrence copie Salaire tripl

harry = x=

Employee

Vous avez pu voir quil tait facile et en fait trs courant dimplmenter des mthodes qui changent ltat dun paramtre objet. La raison en est simple. La mthode obtient une copie de la rfrence dobjet, et la fois loriginal et la copie font rfrence au mme objet. De nombreux langages de programmation (en particulier, C++ et Pascal) disposent de deux mthodes pour le passage de paramtre : lappel par valeur et lappel par rfrence. Certains programmeurs (et malheureusement aussi certains auteurs de livres) afrment que le langage Java a recours aux appels par rfrence pour les objets. Cest pourtant une erreur. Elle est dailleurs si frquente que cela vaut la peine de prendre le temps dtudier un contre-exemple en dtail. Essayons dcrire une mthode qui change deux objets Employee :
public static void swap(Employee x, Employee y) // ne marche pas { Employee temp = x; x = y; y = temp; }

Si le langage Java utilisait lappel par rfrence pour les objets, cette mthode fonctionnerait :
Employee a = new Employee("Alice", . . .); Employee b = new Employee("Bob", . . .); swap(a, b); // est-ce que a fait maintenant rfrence Bob, et b Alice?

Cependant, la mthode ne modie pas rellement les rfrences dobjet qui sont stockes dans les variables a et b. Les paramtres x et y de la mthode swap sont initialiss avec les copies de ces rfrences. La mthode poursuit lchange de ces copies :

Chapitre 4

Objets et classes

147

// x fait rfrence Alice, y Bob Employee temp = x; x = y; y = temp; // maintenant x fait rfrence Bob, y Alice

Mais, en n de compte, cest une perte de temps. Lorsque la mthode se termine, les variables paramtres x et y sont abandonnes. Les variables originales a et b font toujours rfrence aux mmes objets, comme avant lappel la mthode (voir Figure 4.8).
Figure 4.8
Lchange de paramtres objet na pas deffet durable.
Rfrences copies Employee

alice = bob = x=
y=

Employee

Rfrences changes

Cet exemple montre que le langage Java nutilise pas lappel par rfrence pour les objets. Les rfrences dobjet sont passes par valeur. Voici un rcapitulatif de ce que vous pouvez faire et ne pas faire, avec les paramtres dune mthode, en langage Java :
m

Une mthode ne peut pas modier un paramtre de type primitif (cest--dire des nombres ou des valeurs boolennes). Une mthode peut modier ltat dun paramtre objet. Une mthode ne peut pas modier un paramtre objet pour quil fasse rfrence un nouvel objet.

m m

Le programme du Listing 4.4 en fait la dmonstration. Il essaie dabord de tripler la valeur dun paramtre numrique et ny parvient pas :
Testing tripleValue: Before: percent=10.0 End of method: x=30.0 After: percent=10.0

148

Au cur de Java 2 - Notions fondamentales

Il parvient ensuite tripler le salaire dun employ :


Testing tripleSalary: Before: salary=50000.0 End of method: salary=150000.0 After: salary=150000.0

Aprs appel la mthode, ltat de lobjet auquel harry fait rfrence a chang. Cela est possible, car la mthode en a modi ltat par lintermdiaire dune copie de la rfrence dobjet. Enn, le programme met en vidence lchec de la mthode swap :
Testing swap: Before: a=Alice Before: b=Bob End of method: x=Bob End of method: y=Alice After: a=Alice After: b=Bob

Vous pouvez voir que les variables paramtres x et y sont changes, mais que les variables a et b ne sont pas affectes.
INFO C++
C++ ralise des appels la fois par valeur et par rfrence. Les paramtres par rfrence sont baliss par &. Par exemple, vous pouvez facilement implmenter des mthodes void tripleValue(double& x) ou void swap (Employee& x, Employee& y) qui modient leurs paramtres de rfrence.

Listing 4.4 : ParamTest.java


/** * Ce programme montre le transfert de paramtres en Java. * @version 1.00 2000-01-27 * @author Cay Horstmann */ public class ParamTest { public static void main(String[] args) { /* * Test 1: les mthodes ne peuvent pas modifier * des paramtres numriques */ System.out.println("Testing tripleValue:"); double percent = 10; System.out.println("Before: percent="+percent); tripleValue(percent); System.out.println("After: percent="+percent); /* * Test 2: les mthodes peuvent changer ltat * des paramtres objets */ System.out.println("\nTesting tripleSalary:"); Employee harry = new Employee("Harry", 50000); System.out.println("Before: salary="+harry.getSalary());

Chapitre 4

Objets et classes

149

tripleSalary(harry); System.out.println("After: salary="+harry.getSalary()); /* * Test 3: les mthodes ne peuvent pas attacher de * nouveaux objets aux paramtres objet */ System.out.println("\nTesting swap:"); Employee a = new Employee("Alice", 70000); Employee b = new Employee("Bob", 60000); System.out.println("Before: a="+a.getName()); System.out.println("Before: b="+b.getName()); swap(a, b); System.out.println("After: a="+a.getName()); System.out.println("After: b="+b.getName()); } public static void tripleValue(double x) // ne marche pas { x = 3 * x; System.out.println("End of method: x="+x); } public static void tripleSalary(Employee x) // a marche { x.raiseSalary(200); System.out.println("End of method: salary=" +x.getSalary()); } public static void swap(Employee x, Employee y) { Employee temp = x; x = y; y = temp; System.out.println("End of method: x="+x.getName()); System.out.println("End of method: y="+y.getName()); } } class Employee // classe Employee simplifie { public Employee(String n, double s) { name = n; salary = s; } public String getName() { return name; } public double getSalary() { return salary; }

150

Au cur de Java 2 - Notions fondamentales

public void raiseSalary(double byPercent) { double raise = salary * byPercent / 100; salary += raise; } private String name; private double salary; }

Construction dun objet


Nous avons vu comment crire des constructeurs simples qui dnissent ltat initial de nos objets. Cependant, comme la construction dun objet est une opration primordiale, Java offre une grande diversit de mcanismes permettant dcrire des constructeurs. Nous allons maintenant tudier ces mcanismes.

Surcharge
Nous avons vu que la classe GregorianCalendar possdait plusieurs constructeurs. Nous pouvons employer :
GregorianCalendar today = new GregorianCalendar();

ou :
GregorianCalendar deadline = new GregorianCalendar(2099, Calendar.DECEMBER, 31);

Cette fonctionnalit sappelle surcharge. La surcharge seffectue si plusieurs mthodes possdent le mme nom (dans ce cas prcis, la mthode du constructeur GregorianCalendar), mais des paramtres diffrents. Le compilateur Java se charge de dterminer laquelle il va employer. Il choisit la mthode correcte en comparant le type des paramtres des diffrentes dclarations avec celui des valeurs transmises lors de lappel. Une erreur de compilation se produit si le compilateur se rvle incapable dapparier les paramtres ou si plusieurs correspondances sont possibles. Ce processus est appel rsolution de surcharge.
INFO
Java permet de surcharger nimporte quelle mthode pas seulement les constructeurs. Par consquent, pour dcrire compltement une mthode, vous devez spcier le nom de la mthode ainsi que les types de ses paramtres. Cela sappelle la signature de la mthode. Par exemple, la classe String a quatre mthodes publiques appeles indexOf. Leurs signatures sont :
indexOf(int) indexOf(int, int) indexOf(String) indexOf(String, int)

Le type renvoy ne fait pas partie de la signature de la mthode. Cest--dire que vous ne pouvez pas avoir deux mthodes avec le mme nom et les mmes types de paramtres, et seulement des types renvoys qui diffrent.

Chapitre 4

Objets et classes

151

Initialisation des champs par dfaut


Si vous ne dnissez pas un champ explicitement dans un constructeur, une valeur par dfaut lui est automatiquement attribue : 0 pour les nombres, false pour les valeurs boolennes et null pour les rfrences dobjet. On considre gnralement que ce nest pas une bonne pratique de compter aveuglment sur ce mcanisme. Il est bien videmment plus difcile un tiers de comprendre votre code si les variables sont initialises de manire invisible.
INFO
Il existe une diffrence importante entre les champs et les variables locales. Vous devez toujours explicitement initialiser les variables locales dans une mthode, mais si vous ninitialisez pas un champ dans une classe, il prend automatiquement la valeur par dfaut (0, false ou null).

Prenez, par exemple, la classe Employee. Supposons que vous ne prcisiez pas comment initialiser certains des champs dans un constructeur. Par dfaut, le champ salary sera initialis 0 et les champs name et hireDay auront la valeur null. Cela nest toutefois pas souhaitable, car si quelquun appelle la mthode getName ou getHireDay, il obtiendra une rfrence null quil nattendait certainement pas :
Date h = harry.getHireDay(); calendar.setTime(h); // lance une exception si h vaut null

Constructeurs par dfaut


Un constructeur par dfaut est un constructeur sans paramtres. Par exemple, voici un constructeur par dfaut pour la classe Employee :
public Employee() { name = ""; salary = 0; hireDay = new Date(); }

Si vous crivez une classe sans fournir de constructeur, Java en fournit automatiquement un par dfaut. Ce dernier affecte une valeur par dfaut tous les champs dinstance. Ainsi, toutes les donnes numriques des champs prennent la valeur 0, tous les boolens reoivent la valeur false et toutes les variables objet sont initialises avec la valeur null. Si une classe fournit au moins un constructeur, mais ne fournit pas de constructeur par dfaut, il est illgal de construire des objets sans paramtres de construction. Par exemple, notre classe dorigine Employee dans le Listing 4.2 fournissait un unique constructeur :
Employee(String name, double salary, int y, int m, int d)

Il nest pas lgal, avec cette classe, de construire des employs par dfaut. Cest--dire que lappel
e = new Employee();

provoquerait une erreur.

152

Au cur de Java 2 - Notions fondamentales

ATTENTION
Retenez bien quun constructeur par dfaut est fourni uniquement si votre classe ne possde pas dautre constructeur. Si vous crivez mme un seul constructeur pour votre classe et que vous vouliez que les utilisateurs de votre classe puissent en crer une instance par un appel
new NomDeClasse()

vous devez fournir un constructeur par dfaut explicite (sans arguments). Bien entendu, si les valeurs par dfaut vous conviennent pour tous les champs, vous pouvez simplement crire :
public NomDeClasse() { }

Initialisation explicite de champ


Puisque vous pouvez surcharger les mthodes du constructeur dans une classe, la construction peut se faire de plusieurs faons pour dnir ltat initial des champs dinstance de vos classes. Il est toujours souhaitable de sassurer que, indpendamment de lappel au constructeur, chaque champ dinstance est dni une valeur signicative. Vous pouvez simplement affecter une valeur tous les champs dans la dnition de classe. Par exemple :
class Employee { . . . private String name = ""; }

Cette affectation est ralise avant lexcution du constructeur. Cette syntaxe est particulirement utile si tous les constructeurs dune classe ont besoin de dnir un champ dinstance particulier la mme valeur. Linitialisation nest pas ncessairement une valeur constante. Voici un exemple de champ initialis laide dun appel de mthode. Il sagit dune classe Employee o chaque employ a un champ id. Vous pouvez linitialiser de la faon suivante :
class Employee { . . . static int assignId() { int r = nextId; nextId++; return r; } . . . private int id = assignId(); }

INFO C++
En C++, il nest pas possible dinitialiser directement les champs dinstance dune classe. Tous les champs doivent tre dnis dans un constructeur. Toutefois, C++ dispose dune syntaxe spciale, la liste dinitialisation :
Employee::Employee(String n, double s, int y, int m, int d) // C++

Chapitre 4

Objets et classes

153

name(n), salary(s), hireDay(y, m, d)

{ }

C++ utilise cette syntaxe spciale pour appeler des constructeurs de champ. En Java, cest inutile, car les objets nont pas de sous-objets, seulement des pointeurs sur dautres objets.

Noms de paramtres
Si vous crivez des constructeurs trs triviaux (et vous en crirez beaucoup), la question des noms de paramtres peut devenir fastidieuse. Nous avons, en gnral, opt pour des noms de paramtres dun seul caractre :
public Employee(String n, double s) { name = n; salary = s; }

Linconvnient est quil faut lire le code pour savoir ce que signient les paramtres n et s. Certains programmeurs prxent chaque paramtre avec un "a" :
public Employee(String aName, double aSalary) { name = aName; salary = aSalary; }

Cest trs clair, nimporte quel lecteur peut immdiatement savoir ce que les paramtres dsignent. Une autre astuce est couramment utilise. Elle sappuie sur le fait que les variables paramtres clipsent les champs dinstance de mme nom. Si vous appelez un paramtre salary, ce nom salary fait rfrence au paramtre, et non au champ dinstance. Mais vous pouvez toujours accder au champ dinstance laide de this.salary. Souvenez-vous que this dsigne le paramtre implicite, cest-dire lobjet qui est en train dtre construit. Voici un exemple :
public Employee(String name, double salary) { this.name = name; this.salary = salary; }

INFO C++
En C++, il est courant de prxer les champs dinstance avec un caractre de soulignement (_) ou une lettre xe. Les lettres "m" et "x" sont frquemment choisies. Par exemple, le champ salary peut tre appel _salary, mSalary ou xSalary. Ce nest pas une pratique courante en langage Java.

Appel dun autre constructeur


Le mot cl this fait rfrence au paramtre implicite dune mthode. Il existe cependant une autre signication pour ce mot cl.

154

Au cur de Java 2 - Notions fondamentales

Si la premire instruction dun constructeur a la forme this(...), ce constructeur appelle un autre constructeur de la mme classe. Voici un exemple caractristique :
public Employee(double s) { // appelle Employee(String, double) this("Employee #"+nextId, s); nextId++; }

Lorsque vous appelez new Employee(60000), le constructeur Employee(double) appelle le constructeur Employee(String, double). Cet emploi du mot cl this est pratique, vous navez besoin dcrire le code de construction commun quune seule fois.
INFO C++
Lobjet this de Java est identique au pointeur this de C++. Nanmoins, en C++, il nest pas possible pour un constructeur den appeler un autre. Si vous souhaitez partager du code dinitialisation en C++, vous devez crire une mthode spare.

Blocs dinitialisation
Nous avons dj vu deux faons dinitialiser un champ de donnes :
m m

en spciant une valeur dans un constructeur ; en affectant une valeur initiale dans la dclaration.

Il existe en fait un troisime mcanisme, appel bloc dinitialisation. Une dclaration de classe peut contenir des blocs de code arbitraires qui sont excuts chaque fois quun objet de cette classe est construit. Par exemple :
class Employee { public Employee(String n, double s) { name = n; salary = s; } public Employee() { name = ""; salary = 0; } . . . private static int nextId; private private private ... // bloc { int id; String name; double salary; dinitialisation dobjet

Chapitre 4

Objets et classes

155

id = nextId; nextId++; } }

Dans cet exemple, le champ id est initialis dans le bloc dinitialisation dobjet, peu importe le constructeur utilis pour construire un objet. Le bloc dinitialisation sexcute en premier, avant le corps du constructeur. Ce mcanisme nest jamais ncessaire et il nest pas lgant. Il est gnralement plus clair de placer le code dinitialisation lintrieur dun constructeur.
INFO
La dnition des champs dans les blocs dinitialisation est autorise, mme sils ne sont dnis que plus loin dans la classe. Certaines versions du compilateur Java de Sun graient incorrectement cette situation (bogue n 4459133). Ce bogue avait t rsolu dans Java SE 1.4.1. Or, pour viter des dnitions circulaires, vous ne pouvez pas lire partir de champs qui ne soient initialiss que par la suite. Les rgles exactes sont numres dans la section 8.3.2.3 des caractristiques du langage Java (http://java.sun.com/docs/books/jls). Ces rgles tant sufsamment complexes pour tromper limplmenteur, nous vous conseillons de placer les blocs dinitialisation aprs les dnitions de champs.

Avec toutes ces techniques dinitialisation, il est difcile dindiquer toutes les manires deffectuer une construction dobjet. Voici en dtail ce qui se passe lorsquun constructeur est appel : 1. Tous les champs de donnes sont initialiss leurs valeurs par dfaut (0, false ou null). 2. Tous les initialiseurs de champ et les blocs dinitialisation sont excuts, dans lordre de leur apparition lintrieur de la dclaration de classe. 3. Si la premire ligne du constructeur appelle un second constructeur, le corps du second constructeur est excut. 4. Le corps du constructeur est excut. Naturellement, il est toujours judicieux dorganiser le code dinitialisation de sorte que lon puisse aisment le comprendre sans tre un thoricien du langage. Par exemple, il serait curieux et dangereux de crer une classe dont les constructeurs dpendent de lordre dans lequel sont dclars les champs de donnes. Un champ statique est initialis, soit en spciant une valeur initiale, soit en utilisant un bloc dinitialisation statique. Vous avez dj vu le premier de ces mcanismes :
static int nextId = 1;

Si les champs statiques de votre classe requirent un code dinitialisation complexe, utilisez un bloc dinitialisation statique. Placez le code dans un bloc balis laide du mot cl static. Voici un exemple. Les numros dID demploys doivent dbuter un nombre entier infrieur 10 000 :
// bloc dinitialisation statique static { Random generator = new Random(); nextId = generator.nextInt(10000); }

156

Au cur de Java 2 - Notions fondamentales

Linitialisation statique est excute au premier chargement de la classe. Comme les champs dinstance, les champs statiques valent 0, false ou null moins que vous ne les ayez explicitement dnis une autre valeur. Tous les initialiseurs de champs statiques et les blocs dinitialisation statiques sont excuts dans lordre de leur apparition dans la dclaration de classe.
INFO
Voici une petite fantaisie de Java qui pourra tonner vos collgues programmeurs : il est possible de crer un programme "Hello, World" en Java sans mme crire une mthode main:
public class Hello { static { System.out.println("Hello, World"); } }

Lorsque vous invoquez la classe avec linstruction java Hello, la classe est charge, le bloc dinitialisation statique afche "Hello, World", et cest seulement ensuite que vous obtenez un affreux message derreur vous signalant que main nest pas dnie. Vous pouvez lviter en appelant System.exit(0) la n du bloc dinitialisation statique.

Le programme du Listing 4.5 montre plusieurs des fonctionnalits abordes dans cette section :
m m m m m m

la surcharge de constructeurs ; lappel dun autre constructeur laide de this(...); un constructeur par dfaut ; un bloc dinitialisation dobjet ; un bloc dinitialisation statique ; linitialisation de champ dinstance.

Listing 4.5 : ConstructorTest.java


import java.util.*; /** * Ce programme prsente la construction dobjet. * @version 1.01 2004-02-19 * @author Cay Horstmann */ public class ConstructorTest { public static void main(String[] args) { // remplir le tableau staff avec 3 objets Employee Employee[] staff = new Employee[3]; staff[0] = new Employee("Harry", 40000); staff[1] = new Employee(60000); staff[2] = new Employee(); // afficher les informations concernant // tous les objets Employee

Chapitre 4

Objets et classes

157

for (Employee e: staff) System.out.println("name="+e.getName() +",id="+e.getId() +",salary="+e.getSalary()); } } class Employee { // trois constructeurs surchargs public Employee(String n, double s) { name = n; salary = s; } public Employee(double s) { // appelle le constructeur Employee(String, double) this("Employee #"+nextId, s); } // le constructeur par dfaut public Employee() { // name initialis ""--voir plus bas // salary non dfini explicitement--initialis 0 // id initialis dans le bloc dinitialisation } public String getName() { return name; } public double getSalary() { return salary; } public int getId() { return id; } private static int nextId; private int id; private String name = ""; // initialisation champ dinstance private double salary; // bloc dinitialisation statique static { Random generator = new Random(); // dfinir nextId un nombre alatoire // entre 0 et 9999 nextId = generator.nextInt(10000); }

158

Au cur de Java 2 - Notions fondamentales

// bloc dinitialisation dobjet { id = nextId; nextId++; } } java.util.Random 1.0

Random()

Construit un nouveau gnrateur de nombres alatoires.


int nextInt(int n) 1.2

Renvoie un nombre alatoire entre 0 et n-1.

Destruction des objets et mthode nalize


De nombreux langages, tels que C++, disposent de destructeurs explicites permettant daccomplir les oprations de nettoyage ncessaires la libration de la mmoire alloue aux objets. Puisque Java, par lintermdiaire du garbage collector (ou ramasse-miettes), effectue un nettoyage automatique, la rcupration manuelle de la mmoire nest pas ncessaire et, par consquent, Java ne prend pas en charge les destructeurs. Bien entendu, certains objets utilisent dautres ressources que la mmoire, par exemple un chier ou un handle sur un autre objet qui opre sur des ressources systme. Dans ce cas, il est important de rcuprer et de librer ces ressources lorsquelles ne sont plus utilises. Java permet dajouter une mthode finalize nimporte quelle classe. Cette mthode sera appele avant que le ramasse-miettes ne dtruise lobjet. En pratique, ne comptez pas sur la mthode finalize pour rcuprer les ressources qui sont en quantit limite, car vous ne pouvez pas savoir exactement quel moment elle est appele.
INFO
Il existe une mthode System.runFinalizersOnExit(true) pour vous assurer que les mthodes de nalisation sont appeles avant la fermeture de Java. Cette mthode nest toutefois pas sre et est maintenant dprcie. A la place, vous pouvez ajouter des "crochets de fermeture" avec la mthode Runtime.addShutdownHook (voir la documentation API pour en savoir plus).

Si une ressource doit tre libre ds que vous avez ni de lutiliser, vous devez effectuer cette libration manuellement. Ajoutez une mthode dispose ou close que vous appellerez pour nettoyer ce qui doit ltre. De mme, si vous utilisez une classe qui possde une de ces mthodes, appelez cette mthode ds que vous navez plus besoin de lobjet.

Packages
Java permet de regrouper des classes dans un ensemble (ou un paquet) appel package. Les packages se rvlent pratiques pour lorganisation de votre travail et pour effectuer une sparation entre vos crations et les bibliothques fournies par des tiers.

Chapitre 4

Objets et classes

159

La bibliothque standard de Java est distribue dans un certain nombre de packages, y compris java.lang, java.util, java.net, etc. Les packages standard de Java constituent des exemples de packages hirarchiques. Tout comme les rpertoires dun disque dur, les packages peuvent tre organiss suivant plusieurs niveaux dimbrication. Tous les packages standard de Java se trouvent au sein des hirarchies de package java et javax. Lutilisation des packages permet de sassurer que le nom de chaque classe est unique. Supposons que deux programmeurs aient la brillante ide de fournir une classe Employee. Tant quils placent leurs classes dans des packages diffrents, il ny a pas de conit. En fait, pour sassurer vraiment que le nom dun package est unique, Sun recommande dutiliser comme prxe le nom du domaine Internet de votre socit (a priori unique), crit dans lordre inverse de ses lments. Vous utilisez ensuite des sous-packages pour les diffrents projets. Par exemple, horstmann.com est un domaine enregistr par lun des auteurs. Invers, il devient le package com.horstmann. Ce package peut encore tre subdivis en sous-packages tels que com.horstmann.corejava. Du point de vue du compilateur, il ny a absolument aucune relation entre les packages imbriqus. Par exemple, les packages java.util et java.util.jar nont rien voir lun avec lautre. Chacun reprsente sa propre collection indpendante de classes.

Importation des classes


Une classe peut utiliser toutes les classes de son propre package et toutes les classes publiques des autres packages. Vous pouvez accder aux classes publiques dun autre package de deux faons. La premire consiste simplement ajouter le nom complet du package devant chaque nom de classe. Par exemple :
java.util.Date today = new java.util.Date();

Cest une technique plutt contraignante. Il est plus simple davoir recours import. La directive import constitue un raccourci permettant de faire rfrence aux classes du package. Une fois que cette directive est spcie, il nest plus ncessaire de donner aux classes leur nom complet. Vous pouvez importer une classe spcique ou lensemble dun package. Vous placez les instructions import en tte de vos chiers source (mais au-dessous de toutes les instructions package). Par exemple, vous pouvez importer toutes les classes du package java.util avec linstruction :
import java.util.*;

Vous pouvez alors crire


Date today = new Date();

sans le prxe du package. Il est galement possible dimporter une classe spcique dun package :
import java.util.Date;

La syntaxe java.util.* est moins complique. Cela nentrane aucun effet ngatif sur la taille du code. Si vous importez explicitement des classes, le lecteur de votre code connat exactement les classes utilises.

160

Au cur de Java 2 - Notions fondamentales

ASTUCE
Dans Eclipse, vous pouvez slectionner loption de menu Source/Organize Imports. Les instructions des packages comme import java.util.* sont automatiquement tendues en une liste dimportations spciques comme :
import java.util.ArrayList; import java.util.Date;

Cest une fonctionnalit trs commode.

Sachez toutefois que vous ne pouvez utiliser la notation * que pour importer un seul package. Vous ne pouvez pas utiliser import java.* ni import java.*.* pour importer tous les packages ayant le prxe java. Le plus souvent, vous importez simplement les packages dont vous avez besoin, sans autre proccupation. La seule circonstance demandant une attention particulire est le conit de noms. Par exemple, la fois les packages java.util et java.sql ont une classe Date. Supposons que vous criviez un programme qui importe les deux packages :
import java.util.*; import java.sql.*;

Si vous utilisez maintenant la classe Date, vous obtiendrez une erreur de compilation :
Date today; // ERREUR--java.util.Date ou java.sql.Date?

Le compilateur ne peut dterminer de quelle classe Date vous avez besoin. Ce problme peut tre rsolu par lajout dune instruction import spcique :
import java.util.*; import java.sql.*; import java.util.Date;

Mais si vous avez rellement besoin des deux classes Date? Vous devez alors utiliser le nom complet du package avec chaque nom de classe :
java.util.Date deadline = new java.util.Date(); java.sql.Date today = new java.sql.Date(...);

La localisation des classes dans les packages est le rle du compilateur. Les bytecodes dans les chiers de classe utilisent toujours les noms complets de packages pour faire rfrence aux autres classes.
INFO C++
Les programmeurs C++ font souvent une confusion entre import et #include. Ces deux directives nont rien de commun. En C++, il faut employer #include pour inclure les dclarations des composants externes parce que le compilateur C++ ne consulte aucun chier part celui quil compile et les chiers den-ttes explicitement spcis. Le compilateur Java, pour sa part, cherchera dans dautres chiers condition que vous lui fournissiez une directive de recherche. En Java, il est possible dviter compltement le mcanisme import en nommant explicitement tous les packages, comme java.util.Date. En C++, vous ne pouvez pas viter les directives #include. La directive import est purement une facilit du langage permettant de se rfrer une classe en lui donnant un nom plus court que celui complet du package. Par exemple, aprs une instruction import java.util.* (ou import java.util.Date), vous pouvez faire rfrence la classe java.util.Date en lappelant simplement Date. En C++, une construction analogue au package est la fonctionnalit despace de nom (namespace). Songez aux mots cls package et import de Java comme des quivalents des directives namespace et using de C++.

Chapitre 4

Objets et classes

161

Imports statiques
Depuis Java SE 5.0, linstruction import a t amliore de manire permettre limportation de mthodes et de champs statiques, et non plus simplement des classes. Par exemple, si vous ajoutez la directive
import static java.lang.System.*;

en haut de votre chier source, vous pouvez utiliser les mthodes et les champs statiques de la classe System, sans prxe du nom de classe :
out.println("Goodbye, World!"); // cest--dire System.out exit(0); // cest--dire System.exit

Vous pouvez galement importer une mthode ou un champ spcique :


import static java.lang.System.out;

Dans la pratique, il semble douteux que de nombreux programmeurs souhaitent abrger System.out ou System.exit. Le rsultat est moins clair. Mais il existe deux utilisations pratiques des imports statiques.
m

Les fonctions mathmatiques. Si vous utilisez un import statique pour la classe Math, vous pouvez utiliser des fonctions mathmatiques dune manire naturelle. Par exemple,
sqrt(pow(x, 2) + pow(y, 2))

semble plus clair que


Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2))
m

Des constantes encombrantes. Si vous utilisez de nombreuses constantes avec des noms compliqus, limport statique vous ravira. Par exemple,
if (d.get(DAY_OF_WEEK) == MONDAY)

est plus agrable lil que


if (d.get(Calendar.DAY_OF_WEEK) == Calendar.MONDAY)

Ajout dune classe dans un package


Pour placer des classes dans un package, vous devez mettre le nom du package en haut de votre chier source, avant le code qui dnit les classes dans le package. Par exemple, le chier Employee.java dans le Listing 4.7 commence ainsi :
package com.horstmann.corejava; public class Employee { . . . }

Si vous ne mettez pas une instruction package dans le chier source, les classes dans ce chier source appartiendront au package par dfaut qui na pas de nom de package. Jusqu prsent, tous nos exemples de classes se trouvaient dans le package par dfaut. Vous placez les chiers source dun package dans un sous-rpertoire correspondant au nom complet du package. Par exemple, tous les chiers source dans le package com.horstmann.corejava

162

Au cur de Java 2 - Notions fondamentales

doivent se trouver dans le sous-rpertoire com/horstmann/corejava (com\horstmann\corejava sous Windows). Le compilateur place les chiers de classe dans la mme structure de rpertoire. Le programme des Listings 4.6 et 4.7 est rparti sur deux packages : la classe PackageTest appartient au package par dfaut et la classe Employee au package com.horstmann.corejava. Le chier Employee.java doit donc se trouver dans un sous-rpertoire com/horstmann/corejava. En dautres termes, la structure de rpertoire est la suivante :
. (base directory) PackageTest.java PackageTest.class com/ horstmann/ corejava/ Employee.java Employee.class

Pour compiler ce programme, positionnez-vous dans le rpertoire de base et lancez la commande :


javac PackageTest.java

Le compilateur recherche automatiquement le chier com/horstmann/corejava/Employee.java et le compile. Etudions un exemple plus raliste, dans lequel nous nutilisons pas le package par dfaut, mais dont les classes sont distribues sur plusieurs packages (com.horstmann.corejava et com.mycompany) :
. (base directory) com/ horstmann/ corejava/ Employee.java Employee.class mycompany/ PayrollApp.java PayrollApp.class

Dans cette situation, vous devez toujours compiler et excuter des classes depuis le rpertoire de base, cest--dire le rpertoire contenant le rpertoire com :
javac com/mycompany/PayrollApp.java java com.mycompany.PayrollApp

Noubliez pas que le compilateur fonctionne sur les chiers (avec les sparateurs de chiers et une extension .java), tandis que linterprteur Java charge une classe (avec des sparateurs par point).
ATTENTION
Le compilateur ne vrie pas les rpertoires lorsquil compile les chiers source. Supposons, par exemple, que vous ayez un chier source commenant par la directive :
package com.mycompany;

Vous pouvez compiler le chier, mme sil ne se trouve pas dans un sous-rpertoire com/mycompany. La compilation se droulera sans erreurs, sil ne dpend pas des autres packages. Toutefois, la machine virtuelle ne trouvera pas les classes rsultantes lorsque vous tenterez dexcuter le programme.

Chapitre 4

Objets et classes

163

Listing 4.6 : PackageTest.java


import com.horstmann.corejava.*; // La classe Employee est dfinie dans ce package import static java.lang.System.*; /** * Ce programme montre lutilisation des packages. * @author Cay * @version 1.11 2004-02-19 * @author Cay Horstmann */ public class PackageTest { public static void main(String[] args) { // du fait de linstruction import, nous nutilisons pas // com.horstmann.corejava.Employee ici Employee harry = new Employee("Harry Hacker", 50000, 1989, 10, 1); harry.raiseSalary(5); // du fait de linstruction import, nous navons pas // besoin dutiliser System.out ici out.println("name="+harry.getName() +",salary="+harry.getSalary()); } }

Listing 4.7 : Employee.java


package com.horstmann.corejava; // les classes de ce fichier font partie de ce package import java.util.*; // les instructions import viennent aprs linstruction package /* * @version 1.10 1999-12-18 * @author Cay Horstmann */ public class Employee { public Employee(String n, double s, int year, int month, int day) { name = n; salary = s; GregorianCalendar calendar = new GregorianCalendar(year, month - 1, day); // GregorianCalendar utilise 0 pour janvier hireDay = calendar.getTime(); }

164

Au cur de Java 2 - Notions fondamentales

public String getName() { return name; } public double getSalary() { return salary; } public Date getHireDay() { return hireDay; } public void raiseSalary(double byPercent) { double raise = salary * byPercent / 100; salary += raise; } private String name; private double salary; private Date hireDay; }

Visibilit dans un package


Nous avons dj rencontr les modicateurs daccs public et private. Les composants logiciels dclars public sont utilisables par nimporte quelle classe. Les lments private ne sont accessibles que dans la classe qui les dnit. Si vous ne spciez pas de modicateur public ou private, un composant (classe, mthode ou variable) est accessible toutes les mthodes du mme package. Revenons au Listing 4.2. La classe Employee ntait pas dnie en tant que classe publique. Par consquent, seules les autres classes de son package en loccurrence, le package par dfaut, comme EmployeeTest peuvent y accder. Pour les classes, il sagit dune situation par dfaut assez raisonnable. En revanche, ce fut un choix malheureux en ce qui concerne les variables. Cellesci doivent maintenant tre explicitement spcies private si lon ne souhaite pas que leur visibilit stende par dfaut tout le package (ce qui serait en contradiction avec la rgle de lencapsulation). Le problme nat du fait quil est trs facile doublier de taper le mot cl private. Voici un exemple de la classe Window du package java.awt, qui fait partie du code source fourni avec le JDK :
public class Window extends Container { String warningString; . . . }

Notez que la variable warningString nest pas prive ! Cela signie que les mthodes de toutes les classes du package java.awt ont accs cette variable et peuvent lui affecter nimporte quelle chane. En fait, les seules mthodes qui accdent cette variable se trouvent dans la classe Window, et une dclaration private aurait donc t parfaitement approprie. Nous pouvons supposer que le programmeur tait press en tapant le code et quil a tout bonnement oubli le modicateur private.

Chapitre 4

Objets et classes

165

INFO
Il est surprenant de constater que ce problme na jamais t corrig, bien que nous layons signal dans huit ditions de ce livre il semble que les implmenteurs de bibliothque ne lisent pas cet ouvrage. Par ailleurs, de nouveaux champs ont t ajouts la classe au cours des annes, et environ la moiti dentre eux ne sont pas privs non plus.

Est-ce rellement un problme ? Cela dpend. Par dfaut, les packages ne sont pas des entits fermes. Cest--dire que nimporte qui peut y ajouter des lments. Des programmeurs malintentionns peuvent donc ajouter du code qui modie les variables dont la visibilit stend la totalit du package. Par exemple, dans des versions prcdentes du langage de programmation Java, il tait trs facile de pntrer dans une autre classe du package java.awt, simplement en dmarrant la classe avec
package java.awt;

puis de placer le chier de classe rsultant dans un sous-rpertoire java/awt quelque part dans le chemin de classe, et vous pouviez avoir accs aux structures internes du package java.awt. Grce ce subterfuge, il tait possible de rednir le message davertissement (voir Figure 4.9).
Figure 4.9
Changement du message davertissement dans la fentre dun applet.

A partir de la version 1.2, les concepteurs du JDK ont modi le chargeur de classe pour explicitement dsactiver le chargement de classes dnies par lutilisateur, et dont le nom de package commencerait par "java."! Bien entendu, vos propres classes ne bncient pas de cette protection. Vous pouvez utiliser la place un autre mcanisme, le package sealing (plombage de package), qui rsout le problme de laccs trop libre au package. Si vous plombez un package, aucune autre classe ne peut lui tre ajoute. Vous verrez au Chapitre 10 comment produire un chier JAR qui contient des packages plombs.

Le chemin de classe
Vous avez vu que les classes taient stockes dans des sous-rpertoires du systme de chiers. Le chemin daccs de la classe doit correspondre au nom du package. Vous pouvez aussi employer lutilitaire JAR pour ajouter des chiers de classe une archive. Une archive contient plusieurs chiers de classe et des sous-rpertoires dans un chier compress, ce qui conomise lespace et rduit le temps daccs (nous tudierons les chiers JAR plus en dtail au

166

Au cur de Java 2 - Notions fondamentales

Chapitre 10). Lorsque vous utilisez une bibliothque tierce dans vos programmes, vous recevez gnralement un ou plusieurs chiers JAR inclure. Le JDK propose galement plusieurs chiers JAR, comme jre/lib/rt.jar, qui contient des milliers de classes de bibliothque.
ASTUCE
Les chiers JAR ont recours au format ZIP pour lorganisation des chiers et sous-rpertoires. Vous pouvez utiliser nimporte quel utilitaire ZIP pour explorer rt.jar et les autres chiers JAR.

Pour rendre vos classes accessibles aux programmes, vous devez : 1. Placer vos classes lintrieur dun rpertoire, disons /home/user/classdir. Notez que ce rpertoire est celui de base pour larborescence de package. Si vous ajoutez la classe com.horstmann.corejava.Employee, le chier Employee.class doit tre localis dans le sous-rpertoire /home/user/classdir/com/horstmann/corejava. 2. Placer les chiers JAR dans un rpertoire, par exemple, /home/user/archives. 3. Dnir le chemin de classe (classpath). Ce chemin est la collection de tous les emplacements pouvant contenir des chiers de classe. Sous UNIX, les lments dans le chemin de classe sont spars par des caractres deux-points :
/home/user/classdir:.:/home/user/archives/archive.jar

Sous Windows, ils sont spars par des points-virgules :


c:\classes;.;c:\archives\archive.jar

Dans les deux cas, le point dsigne le rpertoire courant. Ce chemin de classe contient :
m m m

le rpertoire de base /home/user/classdir ou c:\classdir; le rpertoire courant (.) ; le chier JAR /home/user/archives/archive.jar ou c:\archives\archive.jar.

Depuis Java SE 6, vous pouvez utiliser un caractre gnrique pour un rpertoire de chier JAR, comme :
/home/user/classdir:.:/home/user/archives/*

ou
C:\classdir;.;c:\archives\*

Sous Unix, le * doit tre chapp pour viter lexpansion du shell. Tous les chiers JAR ( lexception des chiers .class) du rpertoire archives gurent dans ce chemin de classe. Les classes recherchent toujours les chiers de bibliothque dexcution (rt.jar et les autres chiers JAR dans les rpertoires jre/lib et jre/lib/ext, mais pas les chiers .class) ; vous ne les incluez pas explicitement dans le chemin de classe.

Chapitre 4

Objets et classes

167

ATTENTION
Le compilateur javac recherche toujours les chiers dans le rpertoire courant, mais linterprteur java nexamine le rpertoire courant que si le rpertoire "." fait partie du chemin daccs de classe. En labsence de dnition de chemin de classe, cela ne pose pas de problme, le chemin de classe par dfaut est le rpertoire ".". Si vous avez dni le chemin de classe et oubli dinclure le rpertoire ".", vos programmes seront compils sans erreur, mais ils ne pourront pas sexcuter.

Le chemin de classe recense tous les rpertoires et les chiers archives constituant des points de dpart pour retrouver les classes. Voici un exemple de chemin de classe :
/home/user/classdir:.:/home/user/archives/archive.jar

Supposons que la machine virtuelle recherche le chier de la classe com.horstmann.corejava.Employee. Elle va dabord chercher dans les chiers de classe systme qui sont stocks dans les rpertoires jre/lib et jre/lib/ext. Elle ny trouvera pas le chier de classes et va donc se tourner vers le chemin de classe et rechercher les chiers suivants :
m m m

/home/user/classdir/com/horstmann/corejava/Employee.class; com/horstmann/corejava/Employee.class en commenant par le rpertoire courant ; com/horstmann/corejava/Employee.class dans /home/user/archives/archive.jar.

La tche du compilateur est plus difcile que celle de la machine virtuelle en ce qui concerne la localisation de chiers. Si vous faites rfrence une classe sans spcier son package, le compilateur doit dabord trouver le package qui contient la classe. Il consulte toutes les directives import en tant que sources possibles pour la classe. Supposons par exemple que le chier source contienne les directives
import java.util.*; import com.horstmann.corejava.*;

et que le code source fasse rfrence une classe Employee. Le compilateur essaiera alors de trouver java.lang.Employee (car le package java.lang est toujours import par dfaut), java.util. Employee, com.horstmann.corejava.Employee et Employee dans le package courant. Il recherche chacune de ces classes dans tous les emplacements du chemin de classe. Une erreur de compilation se produit si plus dune classe est trouve (les classes devant tre uniques, lordre des instructions import na pas dimportance). Ltape suivante pour le compilateur consiste consulter les chiers source pour voir si la source est plus rcente que le chier de classe. Si oui, le chier source est recompil automatiquement. Souvenez-vous que vous ne pouvez quimporter des classes publiques des autres packages. Un chier source peut seulement contenir une classe publique, et les noms du chier et de la classe publique doivent correspondre. Le compilateur peut donc facilement localiser les chiers source pour les classes publiques. Vous pouvez importer des classes non publiques partir des packages courants. Ces classes peuvent tre dnies dans des chiers source avec des noms diffrents. Si vous importez une classe partir du package courant, le compilateur examine tous les chiers source de ce package pour vrier celui qui la dnit.

168

Au cur de Java 2 - Notions fondamentales

Dnition du chemin de classe


Mieux vaut spcier le chemin de classe avec loption -classpath (ou -cp) :
java classpath /home/user/classdir:.:/home/user archives/archive.jar MyProg.java

ou
java classpath c:\classdir;.;\archives\archive.jar MyProg.java

Lensemble de la commande doit tre tap sur une seule ligne. Mieux vaut galement placer cette longue ligne de commande dans un script de shell ou un chier de lot. La mthode prfre pour dnir le chemin de classe consiste employer loption -classpath. Vous pouvez aussi dnir la variable denvironnement CLASSPATH, selon votre shell. Avec le shell Bourne Again (bash), utilisez la commande :
export CLASSPATH=/home/user/classdir:.:/home/user/archives/archive.jar

Avec le shell C, utilisez la commande :


setenv CLASSPATH /home/user/classdir:.:/home/user/archives/archive.jar

Avec le shell Windows, utilisez :


set CLASSPATH=c:\classdir;.;c:\archives\archive.jar

Le chemin de classe est dni tant que le shell existe.


ATTENTION
Certains conseillent de dnir la variable denvironnement CLASSPATH de manire permanente. Or, cest peu recommand. Les programmeurs oublient le paramtre global, puis sont surpris du fait que leurs classes ne se chargent pas correctement. Un exemple particulirement fcheux concerne linstallateur QuickTime dApple sous Windows. Il dnit CLASSPATH globalement de manire quil pointe vers un chier JAR dont il a besoin mais il ninclut pas le rpertoire courant dans le chemin de classe. En consquence, de nombreux programmeurs Java stonnent de voir leurs programmes se compiler mais refuser de sexcuter.

ATTENTION
Certains recommandent de contourner totalement le chemin de classe, en dposant tous les chiers JAR dans le rpertoire jre/lib/ext. Le conseil est vritablement inoprant, et ce pour deux raisons. Les archives qui chargent manuellement dautres classes ne fonctionnent pas correctement lorsquelles sont places dans le rpertoire dextension (voir le Chapitre 9 du Volume II pour en savoir plus sur les chargeurs de classe). De plus, les programmeurs ont tendance oublier les chiers quils y ont placs quelques mois auparavant. Ils sinterrogent alors lorsque le chargeur de classe semble ignorer un chemin particulirement bien conu, chargeant en fait des classes oublies depuis longtemps dans le rpertoire dextension.

Commentaires pour la documentation


Le JDK contient un outil trs utile, appel javadoc, qui gnre une documentation au format HTML partir de vos chiers source. En fait, la documentation API que nous avons dcrite au Chapitre 3 est simplement le rsultat de lexcution de javadoc sur le code source de la bibliothque Java standard.

Chapitre 4

Objets et classes

169

Si vous ajoutez des commentaires commenant par le dlimiteur spcial /** votre code source, vous pouvez gnrer facilement une documentation daspect trs professionnel. Ce systme est astucieux, car il vous permet de conserver votre code et votre documentation au mme endroit. Si votre documentation se trouve dans un chier spar, le code et les commentaires divergeront fatalement un moment donn. Mais puisque les commentaires de documentation sont dans le mme chier que le code source, il est facile de les mettre jour en mme temps et dexcuter javadoc.

Insertion des commentaires


Lutilitaire javadoc extrait les informations concernant les lments suivants :
m m m m

les packages ; les classes publiques et les interfaces ; les mthodes publiques et protges ; les champs publics et protgs.

Les fonctionnalits protges seront examines au Chapitre 5 ; les interfaces, au Chapitre 6. Vous pouvez (et devez) fournir un commentaire pour chacune de ces fonctionnalits. Chaque commentaire est plac immdiatement au-dessus de la fonctionnalit quil dcrit. Un commentaire commence par /** et se termine par */. Chaque squence /** . . . */ contient un texte au format libre suivi par des balises. Une balise commence par un @, comme par exemple, @author ou @param. La premire phrase du commentaire au format libre doit tre une instruction de sommaire. Lutilitaire javadoc gnre automatiquement les pages de sommaire qui extraient ces phrases. Dans le texte libre, vous pouvez utiliser des balises HTML telles que <em>...</em> pour insister, <code>...</code> pour une police espacement xe, <strong>...</strong> pour des caractres gras, et mme <img ...> pour inclure une image. Evitez cependant les balises <h1> ou <hr> qui peuvent interfrer avec la mise en forme du document.
INFO
Si vos commentaires contiennent des liens vers dautres chiers comme des images (par exemple des diagrammes ou des images de composants de linterface utilisateur), placez ces chiers dans des sous-rpertoires du rpertoire contenant le chier source appel doc-files. Lutilitaire javadoc copiera ces rpertoires doc-files et leur contenu du rpertoire source vers le rpertoire documentation. Vous devez utiliser le rpertoire doc-files dans votre lien, comme <img src="doc-files/uml.png" alt="UML diagram"/>.

Commentaires de classe
Un commentaire de classe doit tre plac aprs les instructions import, juste avant la dnition class. Voici un exemple :
/** * Un objet <code>Card</code> reprsente une carte jouer, telle * que "Dame de coeur". Une carte a une couleur (Pique, Coeur,

170

Au cur de Java 2 - Notions fondamentales

* Trfle ou Carreau) et une valeur (1 = As, 2 . . . 10, 11 = Valet, * 12 = Dame, 13 = Roi). */ public class Card { . . . }

INFO
Il nest pas ncessaire de commencer chaque ligne par un astrisque, comme ci-aprs :
/** Un objet <code>Card</code> reprsente une carte jouer, telle que "Dame de cur". Une carte a une couleur (Pique, Cur, Trfle ou Carreau) et une valeur (1 = As, 2 . . . 10, 11 = Valet, 12 = Dame, 13 = Roi) */

Toutefois, la plupart des diteurs de texte ajoutent automatiquement les astrisques et les placent aux sauts de ligne.

Commentaires de mthode
Chaque commentaire de mthode doit immdiatement prcder la mthode quil dcrit. En plus des balises gnrales, vous pouvez utiliser les balises suivantes : @param description de variable Cette balise ajoute une entre la section des paramtres (parameters) de la mthode courante. La description peut occuper plusieurs lignes et avoir recours aux balises HTML. Toutes les balises @param pour une mthode doivent tre regroupes. @return description Cette balise ajoute une section de renvoi (returns) la mthode courante. La description peut occuper plusieurs lignes et avoir recours aux balises HTML. @throws description de classe Cette balise ajoute une note concernant le dclenchement possible dune exception par la mthode. Les exceptions sont traites au Chapitre 11. Voici un exemple de commentaire de mthode :
/** * Augmente le salaire dun employ. * @param byPercent le pourcentage daugmentation du salaire * (par ex. 10 = 10%) * @return le montant de laugmentation */ public double raiseSalary(double byPercent) { double raise = salary * byPercent / 100; salary += raise; return raise; }

Chapitre 4

Objets et classes

171

Commentaires de champ
Seuls les champs publics doivent tre documents cest--dire gnralement des constantes statiques. Par exemple :
/** * La couleur "Coeur" */ public static final int HEARTS = 1;

Commentaires gnraux
Les balises suivantes peuvent tre employes dans les commentaires de documentation de classe : @author nom Cette balise cre une mention dauteur (author). Il peut y avoir plusieurs balises @author, une pour chaque auteur : @version texte Cette balise cre une entre "version". Le texte dcrit la version courante. Les balises suivantes peuvent tre employes dans tous les commentaires de documentation : @since texte Cette balise cre une entre "depuis" (since). Le texte peut tre toute description de la version ayant introduit cette fonctionnalit. Par exemple, @since version1.7.1. @deprecated texte Cette balise ajoute un commentaire signalant que la classe, la mthode ou la variable est dprcie et ne doit plus tre utilise. Le texte doit suggrer une solution de remplacement. Par exemple :
@deprecated Utiliser <code>setVisible(true)</code> la place

Vous pouvez ajouter des liens hypertexte vers dautres parties connexes de la documentation javadoc ou vers dautres documents externes, laide des balises @see et @link : @see rfrence Cette balise ajoute un lien hypertexte dans la section "see also" (voir aussi). Elle peut tre employe avec les classes et les mthodes. Ici, rfrence peut tre lun des choix suivants :
package.classe#fonctionnalit label <a href="...">label</a> "texte"

La premire possibilit est la plus utile. Vous fournissez le nom dune classe, dune mthode ou dune variable et javadoc insre un lien hypertexte vers la documentation. Par exemple,
@see com.horstmann.corejava.Employee#raiseSalary(double)

cre un lien vers la mthode raiseSalary(double) dans la classe com.horstmann.corejava.Employee. Vous pouvez omettre le nom du package ou la fois les noms de package et de classe. La fonctionnalit est alors recherche dans le package ou la classe courants. Notez que vous devez utiliser un #, et non un point, pour sparer la classe du nom de la mthode ou de la variable. Le compilateur Java lui-mme est assez fut pour dterminer la signication du caractre point, comme sparateur entre les packages, les sous-packages, les classes, les classes internes et les mthodes ou variables. Lutilitaire javadoc, lui, nest pas aussi pointu, et vous devez laider.

172

Au cur de Java 2 - Notions fondamentales

Si la balise @see est suivie dun caractre <, vous devez spcier un lien hypertexte. Le lien peut concerner nimporte quelle adresse URL. Par exemple :
@see <a href="www.horstmann.com/corejava.html">The Core Java home page</a>

Dans chaque cas, vous pouvez spcier un label facultatif, qui apparatra en tant quancre du lien. Si vous omettez le label, le nom du code cible ou lURL apparatront lutilisateur en tant quancre. Si la balise @see est suivie dun caractre ", le texte safche dans la section "see also". Par exemple :
@see "Core Java 2 volume 2"

Vous pouvez ajouter plusieurs balises @see pour une fonctionnalit, mais vous devez les regrouper ensemble. Vous pouvez aussi placer les liens hypertexte vers dautres classes ou mthodes, nimporte o dans vos commentaires. Vous insrez une balise spciale sous la forme
{@link package.classe#fonctionnalit label}

nimporte o dans un commentaire. La description de la fonctionnalit suit les mmes rgles que pour la balise @see.

Commentaires de package et densemble


Vous placez les commentaires de classe, de mthode et de variable directement dans les chiers source Java, dlimits par les commentaires de documentation /** . . . */. Toutefois, pour gnrer des commentaires de package, vous devez ajouter un chier spar dans chaque rpertoire de package. Deux choix soffrent vous : 1. Fournir un chier HTML intitul package.html. Tout texte entre les balises <body>...</body> est extrait. 2. Fournir un chier Java nomm package-info.java. Le chier doit contenir un commentaire Javadoc initial, dlimit par /** et */ et suivi dune instruction package. Il ne doit contenir aucun autre code ou commentaire. Vous pouvez aussi fournir un commentaire densemble pour tous les chiers source. Placez-le dans un chier appel overview.html, situ dans le rpertoire parent qui contient tous les chiers source. Tout le texte entre les balises <body>...</body> est extrait. Ce commentaire de vue densemble safche lorsque lutilisateur slectionne "Overview" dans la barre de navigation.

Extraction des commentaires


Ici, docDirectory est le nom du rpertoire o vous voulez stocker les chiers HTML. Voici les tapes suivre : 1. Positionnez-vous dans le rpertoire contenant les chiers source documenter. Si vous devez documenter des packages imbriqus, tels que com.horstmann.corejava, vous devez vous trouver dans le rpertoire qui contient le sous-rpertoire com (le rpertoire qui contient le chier overview.html, le cas chant). 2. Excutez la commande
javadoc -d docDirectory nomDuPackage

pour un package simple.

Chapitre 4

Objets et classes

173

Ou excutez
javadoc -d docDirectory nomDuPackage1 nomDuPackage2...

pour documenter plusieurs packages. Si vos chiers se trouvent dans le package par dfaut, excutez plutt
javadoc -d docDirectory *.java

Si vous avez omis loption -d docDirectory, les chiers HTML sont extraits du rpertoire courant. Cela peut devenir compliqu et nest pas recommand. Le programme javadoc peut tre personnalis par de nombreuses options de ligne de commande. Vous pouvez, par exemple, employer les options -author et -version pour inclure les balises @author et @version dans la documentation (elles sont omises par dfaut). Une autre option utile est -link, pour inclure des liens hypertexte vers les classes standard. Par exemple, si vous utilisez la commande
javadoc link http://java.sun.com/javase/6/docs/api *.java

toutes les classes de la bibliothque standard sont automatiquement lies la documentation du site Web de Sun. Avec loption -linksource, chaque chier source est converti au format HTML (sans codage couleur, mais avec des numros de ligne) et chaque nom de classe et de mthode se transforme en lien hypertexte pointant vers la source. Pour dcouvrir dautres options, vous pouvez consulter la documentation en ligne de lutilitaire javadoc ladresse http://java.sun.com/javase/javadoc/.
INFO
Si vous avez besoin de personnalisation supplmentaire, par exemple pour produire une documentation sous un format autre que HTML, vous pouvez fournir votre propre doclet pour gnrer la sortie sous la forme que vous voulez. Il sagit l dune requte trs particulire et vous devez vous reporter la documentation en ligne spcique ladresse suivante : http://java.sun.com/j2se/javadoc.

ASTUCE
DocCheck est un doclet utile disponible ladresse http://java.sun.com/j2se/javadoc/doccheck/. Il analyse un jeu de chiers source la recherche des commentaires de documentation manquants.

Conseils pour la conception de classes


Sans vouloir tre exhaustif ou ennuyeux, nous allons terminer ce chapitre par quelques conseils qui permettront vos classes de faire bonne gure dans les cercles lgants de la POO. 1. Les donnes doivent tre prives. Cest une rgle absolue : toute exception viole le principe dencapsulation. Vous pouvez crire en cas de besoin une mthode daccs ou une mthode daltration, mais les champs proprement dits doivent rester privs. Lexprience a montr que la reprsentation des donnes peut changer, mais que la manire dont on les utilise change plus rarement. Lorsque les donnes sont prives,

174

Au cur de Java 2 - Notions fondamentales

une modication de leur reprsentation naffecte pas lutilisateur de la classe et les bogues sont plus facilement dtectables. 2. Initialisez toujours les donnes. Java ninitialise pas les variables locales votre place, mais il initialise les champs dinstance des objets. Ne vous ez pas aveuglment aux valeurs par dfaut ; initialisez explicitement les variables en spciant leur valeur par dfaut, soit dans la classe, soit dans tous les constructeurs. 3. Nabusez pas des types de base dans une classe. Le principe consiste remplacer plusieurs champs (apparents) par une autre classe. Vos classes seront ainsi plus aises comprendre et modier. Par exemple, dans une classe Customer, remplacez
private private private private String String String String street; city; state; zip;

par une nouvelle classe nomme Address. De cette manire, vous pourrez facilement faire face une ventuelle modication de la prsentation des adresses (pour le courrier international, par exemple). 4. Tous les champs nont pas besoin de mthodes daccs et de mthodes daltration. Vous pouvez avoir besoin de connatre et de modier le salaire dun employ. En revanche, une fois lobjet construit, il nest pas ncessaire de modier sa date dembauche. Bien souvent, les objets contiennent des champs dinstance qui ne doivent pas tre lus ni modis par dautres par exemple, le tableau des codes postaux dans une classe Address. 5. Utilisez un format standard pour la dnition de vos classes. Nous prsentons toujours le contenu des classes dans lordre suivant : lments publics ; lments accessibles au package ; lments privs. Chaque section est organise ainsi : mthodes dinstance ; mthodes statiques ; champs dinstance ; champs statiques. Aprs tout, les utilisateurs de votre classe sont davantage concerns par linterface publique que par les dtails de limplmentation prive. Et ils sintressent plus aux mthodes quaux donnes. En fait, il nexiste pas de convention universelle concernant le meilleur style. Le guide Sun du langage de programmation Java recommande de lister dabord les champs, puis les mthodes. Quel que soit le style que vous adopterez, lessentiel est de rester cohrent.

Chapitre 4

Objets et classes

175

6. Subdivisez les classes ayant trop de responsabilits. Ce conseil peut sembler vague, car le terme "trop" est relatif. En bref, chaque fois quil existe une possibilit manifeste de diviser une classe complexe en deux classes conceptuellement plus simples, protez de loccasion (mais nexagrez pas, la complexit renatra si vous crez trop de petites sous-classes). Voici un exemple de conception maladroite :
public class CardDeck // conception maladroite { public public public public public CardDeck() { . . . } void shuffle() { . . . } int getTopValue() { . . . } int getTopSuit() { . . . } void draw() { . . . }

private int[] value; private int[] suit; }

Cette classe implmente en ralit deux concepts spars : un jeu de cartes, avec ses mthodes shuffle et draw, et une carte avec les mthodes permettant dinspecter la valeur et la couleur dune carte. Il est logique ici dintroduire une classe Card reprsentant une carte individuelle. Vous avez maintenant deux classes, avec chacune ses propres responsabilits :
public class CardDeck { public CardDeck() { . . . } public void shuffle() { . . . } public Card getTop() { . . . } public void draw() { . . . } private Card[] cards; } public class Card { public Card(int aValue, int aSuit) { . . . } public int getValue() { . . . } public int getSuit() { . . . } private int value; private int suit; }

7. Donnez des noms signicatifs vos classes et vos mthodes. Les variables doivent toujours avoir un nom reprsentatif de leur contenu. Il en va de mme pour les classes (il est vrai que la bibliothque standard contient quelques exemples contestables, comme la classe Date qui concerne lheure). Un bon principe consiste nommer les classes avec un substantif (Order) ou un substantif associ un adjectif (RushOrder). Pour les mthodes, respectez la convention standard en faisant dbuter leur nom par un prxe en minuscules : get pour les mthodes daccs (getSalary) et set pour les mthodes daltration (setSalary).

176

Au cur de Java 2 - Notions fondamentales

Ce chapitre a trait des bases des objets et des classes, faisant de Java un langage "bas sur les objets". Pour quil soit vritablement orient objet, un langage de programmation doit aussi prendre en charge lhritage et le polymorphisme. La prise en charge de ces caractristiques par Java fait lobjet du prochain chapitre.

5
Lhritage
Au sommaire de ce chapitre

Classes, superclasses et sous-classes Object : la superclasse cosmique Listes de tableaux gnriques Enveloppes dobjets et autoboxing Mthodes ayant un nombre variable de paramtres Enumration de classes Rexion Conseils pour lutilisation de lhritage
Le chapitre prcdent tudiait les classes et les objets. Celui-ci traite de lhritage, essentiel dans la programmation oriente objet. Lide qui sous-tend ce concept est que vous pouvez crer de nouvelles classes bases sur des classes existantes. Lorsque vous hritez dune classe, vous rutilisez (ou hritez de) ses mthodes et champs et ajoutez de nouveaux champs pour adapter votre classe de nouvelles situations. Cette technique est essentielle en programmation Java. Si votre exprience concerne essentiellement les langages procduraux, comme C, Visual Basic ou COBOL, nous vous conseillons de lire attentivement ce chapitre. Les programmeurs C++ chevronns, ainsi que ceux qui ont dj expriment un langage orient objet comme Smalltalk, seront ici en territoire connu ; il existe nanmoins de nombreuses diffrences entre limplmentation de lhritage en Java et son implmentation dans les autres langages orients objet. La dernire partie de ce chapitre couvre la rexion, la capacit den savoir plus au sujet des classes et de leurs proprits dans un programme en cours dexcution. La rexion est une fonctionnalit puissante, mais indniablement complexe. Elle concerne plus les concepteurs doutils que les programmeurs dapplication, et vous pouvez donc vous contenter de survoler cette section dans un premier temps, pour y revenir plus tard.

178

Au cur de Java 2 - Notions fondamentales

Classes, superclasses et sous-classes


Revenons la classe Employee, dj prsente au Chapitre 4. Supposons que vous travailliez pour une entreprise au sein de laquelle les directeurs (managers) sont traits diffremment des autres employs. Les directeurs sont aussi des employs sous bien des aspects. Tout comme les employs, ils reoivent un salaire. Toutefois, tandis que les employs sont censs excuter leurs tches en change de leur salaire, les dirigeants reoivent un bonus sils atteignent leurs objectifs. Cest le genre de situation qui appelle fortement lhritage. Pour quelle raison ? Parce quil faut dnir une nouvelle classe, Manager, et y ajouter des fonctionnalits. Vous pouvez cependant conserver une partie de ce que vous avez dj programm dans la classe Employee, et tous les champs de la classe dorigine seront prservs. Dune faon plus abstraite, disons quil existe une relation "est" entre Manager et Employee. Tout directeur est un employ : cette relation dtat (ou dappartenance) est le ambeau de lhritage. Voici comment dnir une classe Manager qui hrite de la classe Employee. Le mot cl extends est employ en Java pour signier lhritage.
class Manager extends Employee { mthodes et champs ajouts }

INFO C++
Lhritage est comparable en Java et en C++. Java utilise le mot cl extends au lieu de :. Tout hritage en Java est public ; il nexiste pas danalogie avec les fonctionnalits C++ dhritage priv et protg.

Le mot cl extends signie que vous crez une nouvelle classe qui drive dune classe existante. La classe existante est appele superclasse, classe de base ou encore classe parent (voire classe anctre). La nouvelle classe est appele sous-classe, classe drive ou classe enfant. Les termes superclasse et sous-classe sont les plus courants en programmation Java, bien que certains programmeurs prfrent lanalogie parent/enfant, qui convient bien la notion dhritage. La classe Employee est une superclasse, mais ce nest pas parce quelle serait suprieure une sousclasse ou contiendrait plus de fonctionnalits. En fait, cest le contraire : les sous-classes offrent plus de fonctionnalits que leur superclasse. Par exemple, comme vous le verrez lors de lexamen du reste de la classe Manager, cette dernire encapsule plus de donnes et possde plus de fonctionnalits que sa superclasse Employee.
INFO
En anglais, les prxes super et sub (sous) sont issus du langage des ensembles employ en informatique thorique et en mathmatiques. Lensemble de tous les employs contient lensemble de tous les directeurs ; on dit quil sagit dun superensemble de lensemble des directeurs. En dautres termes, lensemble de tous les directeurs est un sousensemble de lensemble de tous les employs.

Chapitre 5

Lhritage

179

Notre classe Manager a un nouveau champ pour stocker le bonus et une nouvelle mthode pour le dnir :
class Manager extends Employee { . . . public void setBonus(double b) { bonus = b; } private double bonus; }

Ces mthodes et champs nont rien de particulier. Si vous avez un objet Manager, vous pouvez simplement appliquer la mthode setBonus :
Manager boss = . . .; boss.setBonus(5000);

Vous ne pouvez pas appliquer la mthode setBonus un objet Employee, elle ne fait pas partie des mthodes dnies dans la classe Employee. Vous pouvez cependant utiliser des mthodes telles que getName et getHireDay avec les objets Manager. Mme si ces mthodes ne sont pas explicitement dnies dans la classe Manager, celle-ci en hrite automatiquement de la superclasse Employee. De mme, les champs name, salary et hireDay sont hrits de la superclasse. Chaque objet Manager a quatre champs : name, salary, hireDay et bonus. Lors de la dnition dune sous-classe par extension de sa superclasse, il vous suft dindiquer les diffrences entre les sous-classes et la superclasse. Lors de la conception de classes, vous placez les mthodes les plus gnrales dans la superclasse, et celles plus spcialises dans la sous-classe. La mise en commun de fonctionnalits dans une superclasse est trs frquente en programmation oriente objet. Cependant, certaines des mthodes de la superclasse ne conviennent pas pour la sous-classe Manager. En particulier, la mthode getSalary, qui doit renvoyer la somme du salaire de base et du bonus. Vous devez fournir une nouvelle mthode pour remplacer celle de la superclasse :
class Manager extends Employee { . . . public double getSalary() { . . . } . . . }

Comment pouvez-vous implmenter cette mthode ? Au premier abord, cela parat simple : renvoyez simplement la somme des champs salary et bonus :
public double getSalary() { return salary+bonus; // ne marche pas }

180

Au cur de Java 2 - Notions fondamentales

Cela ne peut pas marcher. La mthode getSalary de la classe Manager na pas daccs direct aux champs privs de la superclasse. Cela signie que la mthode getSalary de la classe Manager ne peut pas accder directement au champ salary, mme si chaque objet Manager a un champ appel salary. Seules les mthodes de la classe Employee ont accs aux champs privs. Si les mthodes de Manager veulent accder ces champs privs, elles doivent procder comme les autres mthodes : utiliser linterface publique, cest--dire la mthode publique getSalary de la classe Employee. Essayons nouveau. Nous voulons donc appeler getSalary au lieu daccder simplement au champ salary :
public double getSalary() { double baseSalary = getSalary(); // ne marche toujours pas return baseSalary+bonus; }

Le problme est que lappel getSalary ralise simplement un appel lui-mme, puisque la classe Manager a une mthode getSalary (prcisment la mthode que nous essayons dimplmenter). Il sensuit une srie innie dappels la mme mthode, ce qui entrane un plantage du programme. Nous devons prciser que nous voulons appeler la mthode getSalary de la superclasse Employee, et non celle de la classe courante. Vous devez pour cela utiliser le mot cl super, lappel
super.getSalary()

appelle la mthode getSalary de la classe Employee. Voici la version correcte de la mthode getSalary pour la classe Manager :
public double getSalary() { double baseSalary = super.getSalary(); return baseSalary+bonus; }

INFO
Certains pensent que super est analogue la rfrence this. Cette analogie nest toutefois pas exacte super nest pas une rfrence un objet. Par exemple, vous ne pouvez pas affecter la valeur super une autre variable objet. super est un mot cl spcial qui demande au compilateur dinvoquer la mthode de la superclasse.

Vous avez vu quune sous-classe pouvait ajouter des champs et quelle pouvait ajouter ou remplacer des mthodes de la superclasse. Cependant, lhritage ne peut pas ter des champs ou des mthodes.
INFO C++
Java utilise le mot cl super pour appeler une mthode de superclasse. En C++, vous utilisez le nom de la superclasse avec loprateur ::. Par exemple, la mthode getSalary de la classe Manager appellera Employee::getSalary au lieu de super.getSalary.

Chapitre 5

Lhritage

181

Enn, nous allons fournir un constructeur :


public Manager(String n, double s, int year, int month, int day) { super(n, s, year, month, day); bonus = 0; }

Ici, le mot cl super a une signication diffrente. Linstruction


super(n, s, year, month, day);

est un raccourci pour dire "appeler le constructeur de la superclasse Employee avec n, s, year, month et day comme paramtres". Puisque le constructeur de Manager ne peut pas accder aux champs privs de la classe Employee, il doit les initialiser par lintermdiaire dun constructeur. Le constructeur est invoqu laide de la syntaxe spciale super. Lappel utilisant super doit tre la premire instruction dans le constructeur pour la sous-classe. Si le constructeur de la sous-classe nappelle pas explicitement un constructeur de la superclasse, le constructeur par dfaut est appel (sans paramtre). Au cas o la superclasse ne possderait pas de constructeur par dfaut et o aucun autre constructeur nest appel explicitement partir du constructeur de la sous-classe le compilateur Java indique une erreur.
INFO
Souvenez-vous que le mot cl this a deux signications : dnir une rfrence au paramtre implicite, et appeler un autre constructeur de la mme classe. Le mot cl super a galement deux signications : invoquer une mthode de superclasse et invoquer un constructeur de superclasse. Lorsquils sont utiliss pour invoquer des constructeurs, les mots cls this et super sont trs proches. Les appels de constructeur ne peuvent qutre la premire instruction dans un autre constructeur. Les paramtres de construction sont passs, soit un autre constructeur de la mme classe (this), soit un constructeur de la superclasse (super).

INFO C++
Dans un constructeur C++, vous nappelez pas super, mais vous utilisez la syntaxe de liste dinitialisation pour construire la superclasse. Le constructeur Manager a laspect suivant en C++ :
Manager::Manager(String n, double s, int year, int month, int day) // C++ { bonus = 0; }

La consquence de la rednition de la mthode getSalary pour les objets Manager est que, pour les directeurs, le bonus est automatiquement ajout au salaire. Pour comprendre ce fonctionnement, crons un nouveau directeur et dnissons son bonus :
Manager boss = new Manager("Carl Cracker", 80000, 1987, 12, 15); boss.setBonus(5000);

182

Au cur de Java 2 - Notions fondamentales

Crons un tableau de trois employs :


Employee[] staff = new Employee[3];

Affectons ce tableau le personnel de lentreprise (employs et directeurs) :


staff[0] staff[1] 1989, staff[2] 1990, = boss; = new Employee("Harry Hacker", 50000, 10, 1); = new Employee("Tony Tester", 40000, 3, 15);

Afchons le salaire de chacun :


for (Employee e: staff) System.out.println(e.getName()+" " +e.getSalary());;

Cette boucle afche les donnes suivantes :


Carl Cracker 85000.0 Harry Hacker 50000.0 Tommy Tester 40000.0

staff[1] et staff[2] afchent leur salaire de base, car ce sont des objets de la classe Employee. En revanche, staff[0] est un objet Manager et sa mthode getSalary ajoute le bonus au salaire de base. Ce qui est important, cest que lappel
e.getSalary()

slectionne la mthode getSalary correcte. Notez que le type dclar de e est Employee, mais que le type rel de lobjet auquel e fait rfrence peut tre soit Employee, soit Manager. Lorsque e fait rfrence un objet Employee, lappel e.getSalary() appelle la mthode getSalary de la classe Employee. Si toutefois, e fait rfrence un objet Manager, cest la mthode getSalary de la classe Manager qui est appele la place. La machine virtuelle connat le type rel de lobjet auquel e fait rfrence et invoque par consquent la mthode qui convient. Cette possibilit pour une variable objet (comme la variable e) de pouvoir faire rfrence plusieurs types est appele polymorphisme. La slection automatique de la mthode approprie lors de lexcution est appele liaison dynamique. Nous reviendrons sur ces deux concepts plus en dtail dans ce chapitre.
INFO C++
En Java, vous navez pas besoin de dclarer une mthode comme virtuelle. La liaison dynamique est le comportement par dfaut. Si vous ne voulez pas quune mthode soit virtuelle, attribuez-lui le mot cl final (nous y reviendrons dans ce chapitre).

Le Listing 5.1 contient un programme qui montre la faon dont diffre le calcul du salaire pour les objets Employee et Manager.

Chapitre 5

Lhritage

183

Listing 5.1 : ManagerTest.java


import java.util.*; /** * Ce programme prsente lhritage. * @version 1.21 2004-02-21 * @author Cay Horstmann */ public class ManagerTest { public static void main(String[] args) { // construire un objet Manager Manager boss = new Manager("Carl Cracker", 80000, 1987, 12, 15); boss.setBonus(5000); Employee[] staff = new Employee[3]; // remplir le tableau staff avec des objets Manager et Employee staff[0] staff[1] 1989, staff[2] 1990, = boss; = new Employee("Harry Hacker", 50000, 10, 1); = new Employee("Tommy Tester", 40000, 3, 15);

// imprimer les infos concernant tous les objets Employee for (Employee e: staff) System.out.println("name="+e.getName() +",salary="+e.getSalary()); } } class Employee { public Employee(String n, double s, int year, int month, int day) { name = n; salary = s; GregorianCalendar calendar = new GregorianCalendar(year, month - 1, day); hireDay = calendar.getTime(); } public String getName() { return name; } public double getSalary() { return salary; } public Date getHireDay() {

184

Au cur de Java 2 - Notions fondamentales

return hireDay; } public void raiseSalary(double byPercent) { double raise = salary * byPercent / 100; salary += raise; } private String name; private double salary; private Date hireDay; } class Manager extends Employee { /** * @param n Nom de lemploy * @param s Le salaire * @param year Lanne dembauche * @param month Le mois dembauche * @param day Le jour dembauche */ public Manager(String n, double s, int year, int month, int day) { super(n, s, year, month, day); bonus = 0; } public double getSalary() { double baseSalary = super.getSalary(); return baseSalary+bonus; } public void setBonus(double b) { bonus = b; } private double bonus; }

Hirarchie dhritage
Lhritage nest pas oblig de se limiter une seule couche de classes drives. Nous pourrions, par exemple, crer une classe Executive (Cadre) qui prolonge Manager. Lensemble de toutes les classes drives dune superclasse commune est appel hirarchie dhritage ou hirarchie des classes (voir Figure 5.1). Le chemin daccs une classe particulire vers ses anctres, dans la hirarchie dhritage, se nomme la chane dhritage. Il existe gnralement plusieurs chanes descendant dune mme classe anctre. Vous pouvez former une sous-classe Programmer ou Secretary qui prolonge la classe Employee : elles nauront aucune relation avec la classe Manager (ni entre elles). Le processus de cration de classes drives nest pas limit.

Chapitre 5

Lhritage

185

INFO C++
Java ne prend pas en charge lhritage multiple (pour savoir comment rcuprer la majeure partie des fonctionnalits de lhritage multiple, voir la section sur les interfaces au prochain chapitre).

Figure 5.1
Hirarchie dhritage de Employee.
Employee

Manager

Secretary

Programmer

Executive

Polymorphisme
Il existe une rgle simple pour savoir si lhritage est ou non le concept envisager pour vos donnes. La relation "est" dit que tout objet de la sous-classe est un objet de la superclasse. Par exemple, chaque directeur est un employ. Il est par consquent logique que la classe Manager soit une sous-classe de la classe Employee. La rciproque nest videmment pas vraie chaque employ nest pas un directeur. Une autre faon de formuler cette relation est le principe de substitution. Il prcise que vous pouvez utiliser un objet dune sous-classe chaque fois que le programme attend un objet dune superclasse. Vous pouvez ainsi affecter un objet dune sous-classe une variable de la superclasse :
Employee e; e = new Employee(. . .); e = new Manager(. . .); // objet Employee attendu // OK, Manager peut aussi tre utilis

Dans le langage Java, les variables objet sont polymorphes. Une variable du type Employee peut faire rfrence un objet du type Employee ou un objet de toute sous-classe de la classe Employee (tel que Manager, Executive, Secretary, etc.). Nous tirons parti de ce principe dans le Listing 5.1 :
Manager boss = new Manager(. . .); Employee[] staff = new Employee[3]; staff[0] = boss;

Dans ce cas, les variables staff[0] et boss font rfrence au mme objet. Cependant, staff[0] est considr par le compilateur seulement comme un objet Employee. Cela signie que vous pouvez appeler
boss.setBonus(5000); // OK

186

Au cur de Java 2 - Notions fondamentales

mais pas
staff[0].setBonus(5000); // ERREUR

Le type dclar de staff[0] est Employee et la mthode setBonus nest pas une mthode de la classe Employee. Vous ne pouvez toutefois pas affecter une rfrence de superclasse une variable de sous-classe. Par exemple, laffectation suivante est incorrecte :
Manager m = staff[i]; // ERREUR

La raison de cette interdiction est simple : tous les employs ne sont pas directeurs. Si cette affectation russissait et que m puisse faire rfrence un objet Employee qui ne soit pas un directeur, il serait possible dappeler ultrieurement m.setBonus(...) et une erreur dexcution sensuivrait.
ATTENTION
En Java, il est possible de transformer des tableaux de rfrences de sous-classes en tableaux de rfrences de superclasses, sans transtypage. Envisageons par exemple un tableau de directeurs
Manager[] managers = new Manager[10];

La conversion de ce tableau en tableau Employee[] est autorise :


Employee[] staff = managers; // OK

Pourquoi pas, aprs tout ? Si manager[i] est un objet Manager, cest galement un objet Employee. En fait, un vnement surprenant survient. Noubliez pas que managers et staff font rfrence au mme tableau. Etudiez maintenant linstruction
staff[0] = new Employee("Harry Hacker", ...);

Le compilateur autorisera cette instruction avec plaisir. Mais staff[0] et manager[0] constituent la mme rfrence, on dirait donc que nous avons russi faire passer clandestinement un employ dans les rangs de la direction. Cela serait trs dconseill : lappel managers[0].setBonus(1000) tenterait daccder une instance inexistante et corromprait la mmoire voisine. Pour viter toute corruption, tous les tableaux se souviennent du type dlment cr et ils surveillent que seules les rfrences compatibles y soient stockes. Par exemple, le tableau cr sous la forme new Manager[10] se souvient quil sagit uniquement dun tableau de directeurs. Tenter de stocker une rfrence Employee entrane une exception de type ArrayStoreException.

Liaison dynamique
Il importe de bien comprendre ce qui se passe lorsquun appel de mthode est appliqu un objet. Voici les dtails : 1. Le compilateur examine le type dclar de lobjet et le nom de la mthode. Supposons que nous appelions x.f(param) et que le paramtre implicite x soit dclar comme tant un objet de la classe C. Notez quil peut y avoir plusieurs mthodes, toutes avec le mme nom f, mais avec des types de paramtres diffrents. Il peut, par exemple, y avoir une mthode f(int) et une mthode f(String). Le compilateur numre toutes les mthodes appeles f dans la classe C et toutes les mthodes public appeles f dans les superclasses de C. Maintenant, le compilateur connat tous les candidats possibles pour la mthode appeler.

Chapitre 5

Lhritage

187

2. Le compilateur dtermine ensuite les types des paramtres qui sont fournis dans lappel de la mthode. Si, parmi toutes les mthodes nommes f, il en existe une seule dont les types de paramtres correspondent exactement aux paramtres fournis, cette mthode est choisie pour lappel. Ce processus est appel rsolution de surcharge. Par exemple, dans un appel x.f("Hello"), le compilateur choisira f(String) et non f(int). La situation peut devenir complexe du fait des conversions de type (int vers double, Manager vers Employee, etc.). Si le compilateur ne peut pas trouver de mthode avec les types de paramtres qui correspondent, ou sil existe plusieurs mthodes pouvant convenir aprs application des conversions, le compilateur renvoie une erreur. Maintenant, le compilateur connat le nom et le type des paramtres de la mthode devant tre appele.
INFO
Souvenez-vous que le nom et la liste des types de paramtres pour une mthode sont appels signature de la mthode. Par exemple, f(int) et f(String) sont deux mthodes de mme nom, mais prsentant des signatures diffrentes. Si vous dnissez une mthode dans une sous-classe avec la mme signature quune mthode dune superclasse, vous remplacez cette mthode. Le type renvoy ne fait pas partie de la signature. Toutefois, lorsque vous remplacez une mthode, vous devez conserver un type de retour compatible. Avant Java SE 5.0, les types de retours devaient tre identiques. Mais la sous-classe peut maintenant modier le type de retour dune mthode remplace en sous-type du type dorigine. Supposons par exemple que la classe Employee ait un
public Employee getBuddy() { ... }

la sous-classe Manager peut alors remplacer cette mthode par


public Manager getBuddy() { ... } // OK avec Java SE 5.0

On dit que les deux mthodes getBuddy ont des types de retours covariants.

3. Si la mthode est private, static, final ou un constructeur, le compilateur sait exactement quelle mthode appeler (le modicateur final est expliqu dans la section suivante). Cela sappelle une liaison statique. Sinon, la mthode appeler dpend du type rel du paramtre implicite et la liaison dynamique doit tre utilise au moment de lexcution. Dans notre exemple, le compilateur gnrerait une instruction pour appeler f(String) avec liaison dynamique. 4. Lorsque le programme sexcute et quil utilise la liaison dynamique pour appeler une mthode, la machine virtuelle doit appeler la version de la mthode approprie pour le type rel de lobjet auquel x fait rfrence. Supposons que le type rel soit D, une sous-classe de C. Si la classe D dnit une mthode f(String), celle-ci est appele. Sinon, la superclasse de D est examine pour rechercher une mthode f(String), etc. Excuter cette recherche chaque fois quune mthode est appele serait une perte de temps. La machine virtuelle prcalcule donc pour chaque classe une table de mthodes, qui liste toutes les signatures de mthodes et les mthodes relles appeler. Lorsquune mthode est rellement appele, la machine virtuelle fait une simple recherche dans la table. Dans notre exemple, elle consulte la table de mthodes pour la classe D et recherche la mthode appeler pour f(String). Il peut sagir de D.f(String) ou de X.f(String), o X est une superclasse quelconque de D. Il existe une variante ce scnario. Si lappel est super.f(param), le compilateur consulte la table des mthodes de la superclasse du paramtre implicite.

188

Au cur de Java 2 - Notions fondamentales

Nous allons examiner ce processus en dtail dans lappel e.getSalary() du Listing 5.1. Le type dclar de e est Employee. La classe Employee possde une seule mthode appele getSalary, et elle na pas de paramtre. Dans ce cas, nous nallons pas nous proccuper de rsolution de surcharge. Puisque la mthode getSalary nest pas private, static ni final, elle est lie dynamiquement. La machine virtuelle produit des tables de mthodes pour les classes Employee et Manager. La table Employee montre que toutes les mthodes sont dnies dans la classe Employee elle-mme :
Employee: getName() -> Employee.getName() getSalary() -> Employee.getSalary() getHireDay() -> Employee.getHireDay() raiseSalary(double) -> Employee.raiseSalary(double)

En ralit, ce nest pas tout ; comme vous verrez plus loin dans ce chapitre, la classe Employee a une superclasse Object de laquelle elle hrite un certain nombre de mthodes. Les mthodes de Object sont ignores pour linstant. La table de mthodes Manager est lgrement diffrente. Trois mthodes font partie de lhritage, une est rednie et une est ajoute :
Manager: getName() -> Employee.getName() getSalary() -> Manager.getSalary() getHireDay() -> Employee.getHireDay() raiseSalary(double) -> Employee.raiseSalary(double) setBonus(double) -> Manager.setBonus(double)

A lexcution, lappel e.getSalary() est rsolu de la faon suivante : 1. Tout dabord, la machine virtuelle consulte la table de mthodes pour le type rel de e. Il peut sagir de la table des mthodes pour Employee, Manager ou une autre sous-classe de Employee. 2. Puis la machine virtuelle recherche dans la classe dtermine la signature de getSalary(). Elle sait maintenant quelle mthode appeler. 3. Enn, la machine virtuelle appelle la mthode. La liaison dynamique possde une proprit trs importante : elle rend les programmes extensibles sans quil soit ncessaire de modier le code existant. Supposons quune nouvelle classe Executive soit ajoute, et quil soit possible que la variable e fasse rfrence un objet de cette classe. Le code contenant lappel e.getSalary() na pas besoin dtre recompil. La mthode Executive.getSalary() est appele automatiquement si e fait rfrence un objet du type Executive.
ATTENTION
Lorsque vous remplacez une mthode, la mthode de la sous-classe doit tre au moins aussi visible que celle de la superclasse. En particulier, si la mthode de la superclasse est public, la mthode de la sous-classe doit aussi tre dclare comme public. Il est courant domettre accidentellement le spcicateur public pour la mthode de la sous-classe. Le compilateur proteste alors et signale que vous essayez de fournir un privilge daccs plus faible.

Chapitre 5

Lhritage

189

Empcher lhritage : les classes et les mthodes nal


Dans certaines circonstances, vous souhaiterez interdire dautres programmeurs de former une sous-classe partir dune des classes que vous avez cres. On emploie le modicateur final pour spcier quune classe ne peut pas tre tendue (une telle classe est aussi appele classe final). Supposons que nous voulions empcher la cration de sous-classes partir de la classe Executive. Il suft pour cela dutiliser le modicateur final dans sa dclaration, de la faon suivante :
final class Executive extends Manager { . . . }

Une mthode peut galement tre dclare final. Dans ce cas, aucune sous-classe ne pourra remplacer cette mthode (toutes les mthodes dune classe final sont automatiquement des mthodes final). Par exemple :
class Employee { . . . public final String getName() { return name; } . . . }

INFO
Souvenez-vous que les champs peuvent galement tre qualis de final. Un champ nal ne peut pas tre modi une fois que lobjet a t construit. Toutefois, si une classe est dclare comme final, seules les mthodes, et non les champs, sont automatiquement final.

Il nexiste quune bonne raison de rendre une mthode ou une classe final : vrier que la smantique ne peut pas tre transforme en sous-classe. Par exemple, les mthodes getTime et setTime de la classe Calendar sont final. Ceci montre que les concepteurs de la classe Calendar ont pris la responsabilit de la conversion entre la classe Date et ltat du calendrier. Aucune sous-classe ne doit tre autorise bouleverser cet arrangement. De mme, la classe String est une classe final. Cela signie que personne ne peut dnir de sous-classe de String. En dautres termes, si vous voyez une rfrence String, vous savez quelle fait rfrence une chane et rien dautre. Certains programmeurs considrent que vous devez dclarer toutes les mthodes sous la forme final, moins davoir une bonne raison pour vouloir utiliser le polymorphisme. En fait, en C++ et C#, les mthodes nutilisent le polymorphisme que si vous le demandez prcisment. Ceci peut vous sembler un peu extrme, mais il vaut certainement mieux penser soigneusement aux mthodes et aux classes final lorsque vous concevez une hirarchie de classe. Aux premiers temps de Java, certains programmeurs utilisaient le mot cl final dans lespoir dviter les liaisons dynamiques. Lorsquune mthode nest pas remplace et quelle est courte, un compilateur peut optimiser lappel de mthode, une procdure appele inlining. Par exemple, appliquer linlining lappel e.getName() le remplace par laccs de champ e.name. Cette amlioration est valable : les units centrales dtestent la drivation, qui interfre avec leur stratgie de prlecture

190

Au cur de Java 2 - Notions fondamentales

des instructions lors du traitement de linstruction actuelle. Toutefois, si getName peut tre remplac dans une autre classe, le compilateur ne peut pas procder linlining car il ne sait pas ce que peut faire le code de remplacement. Heureusement, le compilateur JIT de la machine virtuelle peut se rvler bien meilleur quun compilateur traditionnel. Il connat exactement les classes qui tendent une classe donne et peut vrier si une classe remplace rellement une mthode donne. Si une mthode est courte, frquemment appele et quelle nest pas rellement remplace, le compilateur JIT peut procder linlining de la mthode. Que se passe-t-il si la machine virtuelle charge une autre sous-classe qui surcharge une mthode en ligne ? Loptimiseur doit annuler linlining. Lopration est lente mais narrive que rarement.
INFO C++
En C++, une mthode nest pas lie dynamiquement par dfaut ; de plus, il est possible de spcier la directive inline pour que les appels la mthode soient remplacs par le code source de la mthode. Cependant, aucun mcanisme nempche une sous-classe de remplacer une mthode de superclasse. On peut crire des classes C++ incapables davoir de descendance, mais cela exige une ruse complexe qui se justie trs rarement (cette astuce mystrieuse est laisse au lecteur en guise dexercice. Indice : utilisez une classe de base virtuelle).

Transtypage
Nous avons appris, au Chapitre 3, que la conversion force dun type en un autre sappelle le transtypage, et que Java utilise une syntaxe spcique pour dsigner ce mcanisme. Par exemple,
double x = 3.405; int nx = (int)x;

convertit la valeur de lexpression x en un entier (int), en supprimant la partie dcimale. Il est parfois ncessaire de convertir un nombre rel en nombre entier. Il peut aussi tre ncessaire de convertir un objet dune classe en objet dune autre classe. Pour effectuer un transtypage dune rfrence dobjet, on emploie une syntaxe comparable celle du transtypage dune expression numrique. Le nom de classe cible est mis entre parenthses et plac devant la rfrence dobjet que lon souhaite convertir. Voici un exemple :
Manager boss = (Manager)staff[0];

Il ny a quune seule raison deffectuer un transtypage dobjet utiliser ce dernier pleine capacit lorsque son type rel a t temporairement occult. Par exemple, dans la classe ManagerTest, le tableau staff devait tre un tableau dobjets Employee, car certains des lments du tableau taient des employs rguliers (de type Employee). Les objets directeurs de ce tableau doivent tre transtyps en Manager (leur type rel) si lon souhaite accder leurs variables spciques. Remarquez que dans lexemple de la premire section, nous nous sommes efforcs dviter le transtypage. Nous avons initialis la variable boss avec un objet Manager avant de la stocker dans le tableau. Il nous fallait utiliser le type correct pour dnir le bonus du directeur. En Java, comme vous le savez, chaque variable objet appartient un type donn, qui dcrit le genre dobjet auquel se rfre la variable et en dtermine les capacits. Par exemple, staff[i] fait rfrence un objet Employee (et peut donc rfrencer un objet Manager).

Chapitre 5

Lhritage

191

Le compilateur sassure que lon ne promet pas plus que lon ne peut tenir lorsque vous stockez une valeur dans une variable. Si vous affectez une rfrence dune sous-classe une variable de la superclasse, vous promettez moins et le compilateur vous laisse faire. En revanche, si vous affectez une rfrence de la superclasse une variable dune sous-classe, vous promettez plus. Vous devez donc utiliser un transtypage pour que votre intention puisse tre vrie au moment de lexcution. Que se passe-t-il si vous tentez de transtyper un objet dans un type driv et que vous mentiez par consquent sur le contenu de cet objet ?
Manager boss = (Manager) staff[1]; // ERREUR

Lorsque le programme sexcute, Java remarque que le type est inappropri et gnre une exception de type ClassCastException. Si vous ninterceptez pas cette exception, le programme se termine. Il est par consquent souhaitable de dterminer si un transtypage russira avant de lentreprendre. A cette n, employez loprateur instanceof, de la faon suivante :
if (staff[1] instanceof Manager) { boss = (Manager) staff[1]; . . . }

Prcisons que le compilateur ne vous laissera pas effectuer un transtypage si celui-ci est invitablement vou lchec. A titre dexemple, le transtypage
Date c = (Date) staff[1];

provoque une erreur de compilation, car Date nest pas une sous-classe de Employee. En rsum :
m m

Un transtypage dobjet ne peut sappliquer qu des objets de la mme hirarchie de classes. Utilisez instanceof avant de procder un transtypage dune superclasse vers une sous-classe.
INFO

Le test
x instanceof C

ne gnre pas dexception si x vaut null. Il renvoie simplement la valeur false. Cela est logique, car null ne fait pas rfrence un objet, en tout cas pas un objet du type C.

En rgle gnrale, il est prfrable de ne pas convertir le type dun objet par transtypage. Dans nos exemples, il est rarement ncessaire de transtyper un objet Employee en objet Manager. La mthode getSalary fonctionnera correctement avec les deux objets des deux classes. La rpartition dynamique permet de slectionner automatiquement la mthode correcte (par polymorphisme). Lunique raison davoir recours au transtypage est lemploi dune mthode spcique aux directeurs, comme setBonus. Si, pour quelque raison que ce soit, il apparat important dappeler setBonus pour un objet de type Employee, demandez-vous si cela rvle une faille dans la conception de la superclasse. Il peut tre indiqu de revoir la conception de la superclasse et dajouter une mthode setBonus. Noubliez pas ceci : il suft dune exception ClassCastException non dtourne pour interrompre lexcution du programme. En gnral, il est prfrable de limiter autant que possible lutilisation de transtypages et de loprateur instanceof.

192

Au cur de Java 2 - Notions fondamentales

INFO C++
Java emploie une syntaxe de transtypage issue du C, mais elle sutilise comme lopration dynamic_cast de C++. Par exemple :
Manager boss = (Manager) staff[1]; // Java

correspond :
Manager* boss = dynamic_cast<Manager*>(staff[1]); // C++

avec nanmoins une diffrence importante : si le transtypage choue, il ne produit pas un objet null, mais dclenche une exception. Dans ce sens, il ressemble un transtypage de rfrences en C++. Cest dommage, car en C++ vous pouvez effectuer la vrication de type et le transtypage en une seule opration.
Manager* boss = dynamic_cast<Manager*>(staff[1]); // C++ if (boss!= NULL) . . .

En Java, il faut employer une combinaison de loprateur instanceof et du transtypage :


if (staff[1] instanceof Manager) { Manager boss = (Manager) staff[1]; . . . }

Classes abstraites
A mesure que lon remonte dans la hirarchie des classes, celles-ci deviennent plus gnrales et souvent plus abstraites. A un certain moment, la classe anctre devient tellement gnrale quon la considre surtout comme un moule pour des classes drives et non plus comme une vritable classe dote dinstances. Prenons lexemple dune extension de la hirarchie de notre classe Employee. Un employ est une personne, tout comme lest un tudiant. Etendons notre hirarchie de classe pour inclure les classes Person et Student. La Figure 5.2 montre les relations dhritage entre ces classes.
Figure 5.2
Schma de lhritage pour Person et ses sous-classes.
Person

Employee

Student

Pourquoi construire une classe dote dun tel niveau dabstraction ? Certains attributs, comme le nom, concernent tout le monde. Les tudiants et les employs ont un nom, et le fait dintroduire une superclasse commune permet de "factoriser" la mthode getName un plus haut niveau dans la hirarchie dhritage.

Chapitre 5

Lhritage

193

Ajoutons prsent une autre mthode, getDescription, dont le but est de renvoyer une brve description de la personne, par exemple :
an employee with a salary of $50,000.00 a student majoring in computer science

Il est facile dimplmenter cette mthode pour les classes Employee et Student. Mais quelle information pouvez-vous fournir dans la classe Person? Cette classe ne sait rien de la personne, except son nom. Bien entendu, vous pouvez implmenter Person.getDescription() pour renvoyer une chane vide. Mais il existe un meilleur moyen. Si vous employez le mot cl abstract, vous navez pas besoin dimplmenter la mthode du tout :
public abstract String getDescription(); // aucune implmentation requise

Pour plus de clart, une classe possdant une ou plusieurs mthodes abstraites doit elle-mme tre dclare abstraite :
abstract class Person { . . . public abstract String getDescription(); }

En plus des mthodes abstraites, les classes abstraites peuvent possder des donnes et des mthodes concrtes. Par exemple, la classe Person peut stocker le nom de la personne et disposer dune mthode qui renvoie ce nom :
abstract class Person { public Person(String n) { name = n; } public abstract String getDescription(); public String getName() { return name; } private String name; }

ASTUCE
Certains programmeurs ne pensent pas que les classes abstraites peuvent avoir des mthodes concrtes. Il est toujours prfrable de dplacer les champs et mthodes communs (abstraits ou non) dans la superclasse (abstraite ou non).

Les mthodes abstraites reprsentent en quelque sorte des emplacements pour les mthodes qui seront implmentes dans les sous-classes. Lorsque vous tendez une classe abstraite, vous avez deux possibilits. Vous pouvez laisser certaines ou toutes les mthodes abstraites indnies. La sous-classe doit ensuite tre qualie dabstraite galement. Vous pouvez aussi dnir toutes les mthodes, la sous-classe nest alors plus abstraite.

194

Au cur de Java 2 - Notions fondamentales

Nous allons par exemple, dnir une classe Student qui tend la classe abstraite Person et implmente la mthode getDescription. Puisque aucune des mthodes de la classe Student nest abstraite, il nest pas ncessaire de la dclarer comme classe abstraite. Une classe peut tre dclare abstract mme si elle ne possde pas de mthodes abstraites. Les classes abstraites ne peuvent pas tre instancies. Autrement dit, si une classe est dclare abstract, il est impossible de crer un objet de cette classe. Par exemple, lexpression
new Person("Vince Vu")

est une erreur. Vous pouvez nanmoins crer des objets de sous-classes concrtes. Notez cependant que vous pouvez toujours crer une variable objet dune classe abstraite, mais cette variable doit rfrencer un objet dune sous-classe non abstraite. Par exemple :
Person p = new Student("Vince Vu", "Economics");

La variable p est ici une variable du type abstrait Person qui fait rfrence une instance de la sousclasse non abstraite Student.
INFO C++
En C++, une mthode abstraite est appele "fonction virtuelle pure" (pure virtual function) et sa dclaration est termine par = 0, de la faon suivante :
class Person // C++ { public: virtual string getDescription() = 0; . . . };

Une classe C++ est abstraite si elle possde au moins une fonction virtuelle pure. Il nexiste pas de mot cl particulier en C++ pour dsigner une classe abstraite.

Nous allons dnir une sous-classe concrte Student qui tend la classe abstraite Person :
class Student extends Person { public Student(String n, String m) { super(n); major = m; } public String getDescription() { return "a student majoring in "+major; } private String major; }

La classe Student dnit la mthode getDescription. Toutes les mthodes de la classe Student sont donc concrtes et la classe nest plus dsormais abstraite.

Chapitre 5

Lhritage

195

Le programme du Listing 5.2 dnit la superclasse abstraite Person et deux sous-classes concrtes Employee et Student. Un tableau de rfrences Person est rempli avec les objets employ et tudiant :
Person[] people = new Person[2]; people[0] = new Employee(. . .); people[1] = new Student(. . .);

Nous afchons ensuite les noms et les descriptions de ces objets :


for (Person p = people) System.out.println(p.getName()+", "+p.getDescription());

Certains sont dconcerts par lappel :


p.getDescription()

Nest-ce pas l un appel de mthode indnie ? Noubliez pas que la variable p ne fait jamais rfrence un objet Person puisquil est impossible de construire un objet de la classe abstraite Person. La variable p fait toujours rfrence un objet dune sous-classe concrte comme Employee ou Student. Pour ces objets, la mthode getDescription est dnie. Auriez-vous pu omettre totalement la mthode abstraite pour la superclasse Person et simplement dnir les mthodes getDescription dans les sous-classes Employee et Student? Si vous laviez fait, vous nauriez alors pas pu invoquer la mthode getDescription sur la variable p. Le compilateur sassure que vous ninvoquez que les mthodes qui sont dclares dans la classe. Les mthodes abstraites sont un concept important dans le langage de programmation Java. Vous les rencontrerez le plus souvent au sein des interfaces. Pour plus dinformations concernant les interfaces, reportez-vous au Chapitre 6.
Listing 5.2 : PersonTest.java
import java.util.*; /** * Ce programme prsente les classes abstraites * @version 1.01 2004-02-21 * @author Cay Horstmann */ public class PersonTest { public static void main(String[] args) { Person[] people = new Person[2]; // remplir le tableau people avec des objets Student et Employee people[0] = new Employee("Harry Hacker", 50000, 1989, 10, 1); people[1] = new Student("Maria Morris", "computer science"); // afficher les noms et descriptions de tous les objets Person for (Person p: people) System.out.println(p.getName()+", " +p.getDescription()); } }

196

Au cur de Java 2 - Notions fondamentales

abstract class Person { public Person(String n) { name = n; } public abstract String getDescription(); public String getName() { return name; } private String name; } class Employee extends Person { public Employee(String n, double s, int year, int month, int day) { super(n); salary = s; GregorianCalendar calendar = new GregorianCalendar(year, month - 1, day); hireDay = calendar.getTime(); } public double getSalary() { return salary; } public Date getHireDay() { return hireDay; } public String getDescription() { return String.format("an employee with a salary of $%.2f, salary); } public void raiseSalary(double byPercent) { double raise = salary * byPercent / 100; salary += raise; } private double salary; private Date hireDay; } class Student extends Person { /** * @param n Nom de ltudiant * @param m La spcialit de ltudiant */

Chapitre 5

Lhritage

197

public Student(String n, String m) { // passer n au constructeur de la superclasse super(n); major = m; } public String getDescription() { return "a student majoring in "+major; } private String major; }

Accs protg
Comme vous le savez, les champs dune classe sont gnralement dclars private et les mthodes public. Tout lment private est invisible pour les autres classes. Nous avons expliqu au dbut de ce chapitre que cette ccit slective sapplique galement aux sous-classes : une sous-classe na pas accs aux champs privs de sa superclasse. Il existe nanmoins des circonstances dans lesquelles vous voudrez limiter une mthode aux sousclasses seulement ou, plus gnralement, permettre aux mthodes dune sous-classe davoir accs un champ de superclasse. Dans ce cas, il faut dclarer cet lment protected (protg). Par exemple, si la superclasse Employee dclare le champ hireDay comme protected plutt que private, les mthodes de la classe Manager pourront y accder directement. Cependant, les mthodes de la classe Manager ne pourront quaccder au champ hireDay des objets Manager, et non des autres objets Employee. Cette restriction vite la violation du mcanisme de protection et le risque que des sous-classes soient simplement cres pour obtenir laccs aux champs protgs. Dans la pratique, les champs protected doivent tre utiliss avec une grande prudence. Supposons que votre classe soit utilise par dautres programmeurs et que vous layez dote de champs protgs. A votre insu, dautres programmeurs peuvent faire hriter de votre classe des sous-classes qui accderont vos champs protgs. Ils risquent donc dtre contraris si vous modiez ensuite limplmentation de votre classe. Tout cela va lencontre de lesprit de la programmation oriente objet, qui recommande lencapsulation des donnes. En revanche, les mthodes protges sont plus courantes. Une classe peut dclarer une mthode protected si celle-ci est dun usage dlicat. Cela autorise les sous-classes (qui, a priori, connaissent bien leur anctre) utiliser cette mthode dlicate, mais les autres classes sans lien de parent nen ont pas le droit. Un bon exemple de ce genre de mthode est la mthode clone de la classe Object voir le Chapitre 6 pour plus de dtails.
INFO C++
En fait, les lments protected de Java sont visibles par toutes les sous-classes, mais aussi par toutes les autres classes qui se trouvent dans le mme package. La signication de protected est lgrement diffrente en C++, et la notion de protection en Java est encore moins sre quen C++.

198

Au cur de Java 2 - Notions fondamentales

Rsumons les caractristiques des quatre modicateurs de visibilit de Java : 1. Private (priv). Visible uniquement par la classe. 2. Public. Visible par toutes les classes. 3. Protected (protg). Visible par le package et toutes les sous-classes. 4. Par dfaut (hlas !) aucun modicateur nest spci. Visible par tout le package.

Object : la superclasse cosmique


La classe Object reprsente lanctre ultime toutes les classes Java tendent Object. Nanmoins, vous navez jamais crire :
class Employee extends Object

La superclasse Object est prise en compte par dfaut si aucune superclasse nest explicitement spcie. Comme chaque classe Java tend Object, il importe de se familiariser avec les fonctionnalits de cette classe anctre. Nous en examinerons ici les caractristiques de base ; les autres aspects sont traits aux chapitres suivants et dans la documentation en ligne (plusieurs mthodes de Object sont ddies aux threads voir le Volume II pour plus dinformations sur les threads). Une variable de type Object peut rfrencer un objet de nimporte quel type :
Object obj = new Employee("Harry Hacker", 35000);

Bien entendu, une variable de type Object nest utile quen tant que conteneur gnrique pour des valeurs arbitraires. Pour pouvoir rellement en utiliser la valeur courante, il faut connatre le type original (effectif) et appliquer un transtypage :
Employee e = (Employee)obj;

En Java, seuls les types primitifs (nombres, caractres et valeurs boolennes) ne sont pas des objets. Tous les types de tableaux, quils soient dobjets ou de types primitifs, sont des types de classes qui tendent la classe Object :
Employee[] staff = new Employee[10]; obj = staff; // OK obj = new int[10]; // OK

INFO C++
Il nexiste pas de classe racine cosmique en C++. Toutefois, en C++, tout pointeur peut tre converti en pointeur void*.

La mthode equals
La mthode equals de la classe Object dtermine si deux objets sont gaux ou non. Telle quelle est implmente dans la classe Object, cette mthode vrie que les deux rfrences dobjet sont identiques. Cest un dfaut assez raisonnable : si deux objets sont identiques, ils doivent certainement tre gaux. Cela suft pour certaines classes. Il est peu logique, par exemple, de comparer deux objets PrintStream des ns dgalit. Vous voudrez pourtant souvent implmenter le test dgalit dans lequel deux objets sont considrs gaux lorsquils ont le mme tat.

Chapitre 5

Lhritage

199

Envisageons par exemple deux employs gaux sils ont le mme nom, le mme salaire et la mme date dembauche (dans une vraie base de donnes demploys, il serait plus logique de comparer les identiants. Nous prenons cet exemple pour montrer le mcanisme de la mise en place de la mthode equals) :
class Employee { . . . public boolean equals(Object otherObject) { // tester rapidement si les objets sont identiques if (this == otherObject) return true; // doit renvoyer false si le paramtre explicite vaut null if (otherObject == null) return false; // si les classes ne correspondent pas, // elles ne peuvent pas tre gales if (getClass()!= otherObject.getClass()) return false; // nous savons maintenant que otherObject // est un objet Employee non null Employee other = (Employee) otherObject; // tester si les champs ont des valeurs identiques return name.equals(other.name) && salary == other.salary && hireDay.equals(other.hireDay); } }

La mthode getClass renvoie la classe dun objet nous verrons cette mthode en dtail plus loin dans ce chapitre. Dans notre test, deux objets ne peuvent tre gaux que sils sont de la mme classe. Lorsque vous dnissez la mthode equals pour une sous-classe, appelez dabord equals sur la superclasse. Si ce test ne russit pas, les objets ne peuvent pas tre gaux. Si les champs de superclasse sont gaux, vous tes prt comparer les champs dinstance de la sous-classe :
class Manager extends Employee { . . . public boolean equals(Object otherObject) { if (!super.equals(otherObject)) return false; // super.equals vrifie que this et otherObject appartiennent // la mme classe Manager other = (Manager) otherObject; return bonus == other.bonus; } }

Test dgalit et hritage


Comment devrait se comporter la mthode equals si les paramtres implicites et explicites nappartiennent pas la mme classe ? Ce domaine a fait lobjet dune certaine controverse. Dans lexemple

200

Au cur de Java 2 - Notions fondamentales

prcdent, la mthode equals renvoie false si les classes ne correspondent pas exactement. Mais de nombreux programmeurs utilisent plutt un test instanceof :
if (!(otherObject instanceof Employee)) return false;

Cela autorise lventualit que otherObject puisse appartenir une sous-classe. Toutefois, cette approche peut vous poser problme. Voici pourquoi. La spcication du langage Java requiert que la mthode equals ait les proprits suivantes : 1. Quelle soit rective : pour toute rfrence x non nulle, x.equals(x) doit renvoyer true. 2. Quelle soit symtrique : pour toutes rfrences x et y, x.equals(y) doit renvoyer true, si et seulement si y.equals(x) renvoie true. 3. Quelle soit transitive : pour toutes rfrences x, y et z, si x.equals(y) renvoie true et y.equals(z) renvoie true, alors x.equals(z) doit renvoyer true. 4. Quelle soit cohrente : si les objets auxquels x et y font rfrence nont pas chang, des appels successifs x.equals(y) renvoient la mme valeur. 5. Pour toute rfrence x non nulle, x.equals(null) doit renvoyer false. Ces rgles sont certainement raisonnables. Vous ne voudriez pas quun implmenteur de bibliothque pondre sil faut appeler x.equals(y) ou y.equals(x) lorsque vous localisez un lment dans une structure de donnes. Toutefois, la rgle de symtrie implique des consquences subtiles lorsque les paramtres appartiennent diffrentes classes. Envisageons un appel
e.equals(m)

o e est un objet Employee et m, un objet Manager, tous les deux se trouvant avoir les mmes name, salary et hireDate. Si Employee.equals utilise un test instanceof, cet appel renvoie true. Mais cela signie que lappel inverse
m.equals(e)

doit aussi renvoyer true la rgle de symtrie ne permet pas de renvoyer false ni de lancer une exception. La classe Manager est ennuye. Sa mthode equals doit tre prte se comparer tout Employee, sans prendre en compte les informations spciques aux directeurs ! Tout coup, le test instanceof apparat moins attirant ! Certains auteurs ont prtendu que le test getClass tait inadapt car il viole le principe de substitution. On cite souvent cet gard la mthode equals de la classe AbstractSet, qui teste si deux ensembles possdent les mmes lments. La classe AbstractSet possde deux sous-classes concrtes, TreeSet et HashSet, qui utilisent diffrents algorithmes pour localiser des lments de lensemble. Il est important de pouvoir comparer deux ensembles, quelle que soit leur implmentation. Toutefois, lexemple des ensembles est plutt spcialis. Il serait logique de dclarer AbstractSet.equals sous la forme final, car personne ne doit rednir la smantique de lgalit du jeu (la mthode nest pas rellement final. Ceci permet une sous-classe dimplmenter un algorithme plus efcace pour le test dgalit).

Chapitre 5

Lhritage

201

Comme nous le voyons, il existe deux scnarios distincts :


m

Si les sous-classes peuvent avoir leur propre notion de lgalit, lexigence de symtrie vous oblige utiliser le test getClass. Si la notion dgalit est xe dans la superclasse, vous pouvez utiliser le test instanceof et permettre aux objets de diffrentes sous-classes dtre gaux entre eux.

Dans lexemple des employs et des directeurs, nous considrons que deux objets sont gaux lorsquils ont des champs concordants. Si nous avons deux objets Manager avec le mme nom, salaire et date dembauche, mais avec des bonus diffrents, ils doivent donc tre diffrents. Nous avons par consquent utilis le test getClass. Mais supposons que nous ayons utilis un ID demploy pour le test dgalit. Cette notion de lgalit est logique pour toutes les sous-classes. Nous pourrions alors utiliser le test instanceof, et nous dclarerions Employee.equals sous la forme final.
INFO
La bibliothque Java standard contient plus de 150 implmentations des mthodes equals, avec un mli-mlo dutilisations dinstanceof, dappels getClass, dinterceptions de ClassCastException ou de rien du tout.

Notre recette pour lcriture dune mthode equals parfaite : 1. Nommez le paramtre explicite otherObject vous devrez ultrieurement le transtyper en une autre variable que vous appellerez other. 2. Vriez si this se trouve tre identique otherObject :
if (this == otherObject) return true;

Cette instruction est une simple optimisation. Dans la pratique, cest trs courant. Il est plus conomique de vrier lidentit que de comparer les champs. 3. Testez si otherObject vaut null et renvoyez false dans ce cas. Ce test est obligatoire.
if (otherObject == null) return false;

4. Comparez les classes de this et otherObject. Si la smantique de equals risque de changer dans les sous-classes, utilisez le test getClass :
if (getClass()!= otherObject.getClass()) return false;

Si la mme smantique vaut pour toutes les sous-classes, vous pouvez utiliser un test instanceof :
if (!(otherObject instanceof NomClasse)) return false;

5. Convertissez otherObject en une variable du type de votre classe :


nomDeClasse other = (nomDeClasse)otherObject

6. Comparez maintenant les champs, comme lexige votre notion dgalit. Utilisez == pour les champs de type primitif, equals pour les champs dobjet. Renvoyez true si tous les champs correspondent, false sinon :
return champ1 == other.champ1 && champ2.equals(other.champ2) && . . .;

202

Au cur de Java 2 - Notions fondamentales

Si vous rednissez equals dans une sous-classe, incluez un appel super.equals(other).


ASTUCE
Si vous disposez de champs de type tableau, vous pouvez utiliser la mthode Arrays.equals pour vrier que les lments de tableau correspondants sont gaux.

ATTENTION
Il arrive souvent de se mprendre sur le moment o implmenter la mthode equals. Retrouverez-vous le problme ?
public class Employee { public boolean equals(Employee other) { return name.equals(other.name) && salary == other.salary && hireDay.equals(other.hireDay); } ... }

Cette mthode dclare le type de paramtre explicite sous la forme Employee. En consquence, il ne remplace pas la mthode equals de la classe Object mais dnit une mthode nayant aucun rapport. Depuis Java SE 5.0, vous pouvez vous protger de ce type derreur en balisant les mthodes qui remplacent les mthodes de la superclasse avec @Override:
@Override public boolean equals(Object other)

Si vous avez fait une erreur et que vous dnissiez une nouvelle mthode, le compilateur signale lerreur. Supposons par exemple que vous ajoutiez la dclaration suivante la classe Employee.
@Override public boolean equals(Employee other)

Lerreur est signale car cette mthode ne remplace aucune mthode de la superclasse Object.

java.util.Arrays 1.2

static boolean equals(type[] a, type[] b) 5.0

Renvoie true si les tableaux ont des longueurs gales et des lments gaux des positions correspondantes. Les tableaux peuvent avoir des types de composants Object, int, long, short, char, byte, boolean, float ou double.

La mthode hashCode
Un code de hachage est un entier driv dun objet. Les codes de hachage doivent tre brouills : si x et y sont deux objets distincts, la probabilit devrait tre forte que x.hashCode() et y.hashCode() soient diffrents. Le Tableau 5.1 reprend quelques exemples de codes de hachage tirs de la mthode hashCode de la classe String.

Chapitre 5

Lhritage

203

La classe String utilise lalgorithme suivant pour calculer le code de hachage :


int hash = 0; for (int i = 0; i < length(); i++) hash = 31 * hash + charAt(i);

Tableau 5.1 : Codes de hachage tirs de la fonction hashCode

Chane Hello Harry Hacker

Code de hachage 69609650 69496448 2141031506

La mthode hashCode est dnie dans la classe Object. Chaque objet possde donc un code de hachage par dfaut, extrait de ladresse mmoire de lobjet. Etudiez lexemple suivant :
String s = "Ok"; StringBuffer sb = new StringBuilder(s); System.out.println(s.hashCode() + " " + sb.hashCode()); String t = new String("Ok"); StringBuilder tb = new StringBuilder(t); System.out.println(t.hashCode() + " " + tb.hashCode());

Le Tableau 5.2 en prsente le rsultat.


Tableau 5.2 : Codes de hachage des chanes et constructeurs de chanes

Objet s sb t tb

Code de hachage 2556 20526976 2556 20527144

Sachez que les chanes s et t prsentent le mme code de hachage car, pour les chanes, ces codes sont drivs de leur contenu. Les constructeurs de chanes sb et tb disposent de codes de hachage diffrents car aucune mthode hashCode na t dnie pour la classe StringBuilder et la mthode hashCode par dfaut de la classe Object drive le code de hachage de ladresse mmoire de lobjet. Si vous rednissez la mthode equals, vous devrez aussi rednir la mthode hashCode pour les objets que les utilisateurs pourraient insrer dans une table de hachage (nous avons trait des tables de hachage au Chapitre 2 du Volume II). La mthode hashCode devrait renvoyer un entier (qui peut tre ngatif). Associez simplement les codes de hachage des champs dinstance, de sorte que les codes des diffrents objets soient plus largement rpartis.

204

Au cur de Java 2 - Notions fondamentales

Il existe, par exemple, une mthode hashCode pour la classe Employee :


class Employee { public int hashCode() { return 7 * name.hashCode() + 11 * new Double(salary).hashCode() + 13 * hireDay.hashCode(); } . . . }

Vos dnitions de equals et hashCode doivent tre compatibles : si x.equals(y) est vrai, alors x.hashCode() doit avoir la mme valeur que y.hashCode(). Si, par exemple, vous dnissez Employee.equals, de manire comparer les ID des employs, la mthode hashCode devra hacher les ID, et non les noms des employs ou les adresses mmoire.
ASTUCE
Si vous disposez de champs de type tableau, vous pouvez utiliser la mthode Arrays.hashCode pour calculer un code de hachage compos des codes de hachage des lments du tableau.

java.lang.Object 1.0

int hashCode()

Renvoie un code de hachage pour cet objet. Il peut sagir dun entier, positif ou ngatif. Les objets gaux doivent renvoyer des codes de hachage identiques.
java.util.Arrays 1.2

static int hashCode(type[] a) 5.0

Calcule le code de hachage du tableau a, pouvant avoir un type de composant Object, int, long, short, char, byte, boolean, float ou double.

La mthode toString
Une autre mthode importante de la classe Object est toString, qui renvoie une chane reprsentant la valeur de lobjet. Voici un exemple typique. La mthode toString de la classe Point renvoie une chane comme celle-ci :
java.awt.Point[x=10,y=20]

La plupart des mthodes toString ont ce format : le nom de la classe, suivi des valeurs de champs incluses entre crochets. Voici une implmentation de la mthode toString pour la classe Employee :
public String toString() { return "Employee[name="+name +",salary="+salary +",hireDay="+hireDay +"]"; }

Chapitre 5

Lhritage

205

En ralit, vous pouvez faire mieux. Au lieu de coder en dur le nom de la classe dans la mthode toString, appelez getClass().getName() pour obtenir une chane avec le nom de la classe :
public String toString() { return getClass().getName() +"[name="+name +",salary="+salary +",hireDay="+hireDay +"]"; }

La mthode toString sapplique alors galement aux sous-classes. Bien entendu, le programmeur de la sous-classe doit dnir sa propre mthode toString et ajouter les champs de la sous-classe. Si la superclasse utilise getClass().getName(), la sous-classe peut simplement appeler super.toString(). Voici, par exemple, une mthode toString pour la classe Manager :
class Manager extends Employee { . . . public String toString() { return super.toString() +"[bonus="+bonus +"]"; } }

Maintenant, un objet Manager safche de la faon suivante :


Manager[name=...,salary=...,hireDay=...][bonus=...]

La mthode toString est omniprsente pour une raison essentielle : chaque fois quun objet est concatn avec une chane laide de loprateur "+", le compilateur Java invoque automatiquement la mthode toString pour obtenir une reprsentation de lobjet sous forme de chane. Voici un exemple :
Point p = new Point(10, 20); String message = "La position actuelle est "+p; // invoque automatiquement p.toString()

ASTUCE
Au lieu dcrire x.toString(), vous pouvez crire ""+x. Cette instruction concatne la chane vide avec la reprsentation de chane de x, qui correspond exactement la reprsentation obtenue par x.toString(). A la diffrence de toString, cette instruction fonctionne, mme si x est de type primitif.

Si x est un objet quelconque et que vous appeliez


System.out.println(x);

la mthode println appelle simplement x.toString() et afche la chane rsultante.

206

Au cur de Java 2 - Notions fondamentales

La classe Object dnit la mthode toString pour afcher le nom de classe et le code de hachage de lobjet. Par exemple, lappel
System.out.println(System.out)

produit une sortie ressemblant ceci :


java.io.PrintStream@2f6684

La raison en est que limplmenteur de la classe PrintStream na pas pris la peine de remplacer la mthode toString.
ATTENTION
Malheureusement, les tableaux hritent la mthode toString de Object; en outre, le type de tableau qui safche dans un format archaque, par exemple :
int[] luckyNumbers = { 2, 3, 5, 7, 11, 13 }; String s = "" + luckyNumbers;

donne la chane "[I@1a46e30" (le prxe [I indique un tableau dentiers). La solution consiste appeler la mthode Arrays.toString statique la place. Le code
String s = Arrays.toString(luckyNumbers);

produit la chane "[2, 3, 5, 7, 11, 13]". Pour afcher correctement les tableaux multidimensionnels (cest--dire des tableaux de tableaux), utilisez

Arrays.deepToString.

La mthode toString est un outil prcieux de consignation. De nombreuses classes de la bibliothque de classes standard dnissent la mthode toString comme fournisseur dinformations utiles sur ltat dun objet. Cela est particulirement utile pour consigner des messages, par exemple :
System.out.println("Current position = "+position);

Comme nous lexpliquons au Chapitre 11, une solution encore meilleure consisterait indiquer :
Logger.global.info("Current position = " + position);

ASTUCE
Il est fortement recommand dajouter une mthode toString chacune des classes que vous crivez. Vous et tous les programmeurs utilisant vos classes apprcierez le support de consignation.

Le programme du Listing 5.3 implmente les mthodes equals, hashCode et toString pour les classes Employee et Manager.
Listing 5.3 : EqualsTest.java
import java.util.*; /** * Ce programme prsente la mthode equals. * @version 1.11 2004-02-21 * @author Cay Horstmann */

Chapitre 5

Lhritage

207

public class EqualsTest { public static void main(String[] args) { Employee alice1 = new Employee("Alice Adams", 75000, 1987, 12, 15); Employee alice2 = alice1; Employee alice3 = new Employee("Alice Adams", 75000, 1987, 12, 15); Employee bob = new Employee("Bob Brandson", 50000, 1989, 10, 1); System.out.println("alice1 == alice2: " +(alice1 == alice2)); System.out.println("alice1 == alice3: " +(alice1 == alice3)); System.out.println("alice1.equals(alice3): " +alice1.equals(alice3)); System.out.println("alice1.equals(bob): " +alice1.equals(bob)); System.out.println("bob.toString(): "+bob); Manager carl = new Manager("Carl Cracker", 80000, 1987, 12, 15); Manager boss = new Manager("Carl Cracker", 80000, 1987, 12, 15); boss.setBonus(5000); System.out.println("boss.toString(): "+boss); System.out.println("carl.equals(boss): " +carl.equals(boss)); System.out.println("alice1.hashCode(): " + alice1.hashCode()); System.out.println("alice3.hashCode(): " + alice3.hashCode()); System.out.println("bob.hashCode(): " + bob.hashCode()); System.out.println("carl.hashCode(): " + carl.hashCode()); } } class Employee { public Employee(String n, double s, int year, int month, int day) { name = n; salary = s; GregorianCalendar calendar = new GregorianCalendar(year, month - 1, day); hireDay = calendar.getTime(); } public String getName() { return name; } public double getSalary() { return salary; } public Date getHireDay() { return hireDay; }

208

Au cur de Java 2 - Notions fondamentales

public void raiseSalary(double byPercent) { double raise = salary * byPercent / 100; salary += raise; } public boolean equals(Object otherObject) { // test pour vrifier si les objets sont identiques if (this == otherObject) return true; // doit renvoyer false si le paramtre explicite vaut null if (otherObject == null) return false; // si les classes ne correspondent pas, // elles ne peuvent pas tre gales if (getClass()!= otherObject.getClass()) return false; // nous savons maintenant que otherObject est // un objet Employee non null Employee other = (Employee) otherObject; // tester si les valeurs de champs sont identiques return name.equals(other.name) && salary == other.salary && hireDay.equals(other.hireDay); } public int hashCode() { return 7 * name.hashCode() + 11 * new Double(salary).hashCode() + 13 * hireDay.hashCode(); } public String toString() { return getClass().getName() +"[name="+name +",salary="+salary +",hireDay="+hireDay +"]"; } private String name; private double salary; private Date hireDay; } class Manager extends Employee { public Manager(String n, double s, int year, int month, int day) { super(n, s, year, month, day); bonus = 0; }

Chapitre 5

Lhritage

209

public double getSalary() { double baseSalary = super.getSalary(); return baseSalary+bonus; } public void setBonus(double b) { bonus = b; } public boolean equals(Object otherObject) { if (!super.equals(otherObject)) return false; Manager other = (Manager) otherObject; // super.equals a vrifi que this et other // appartenaient la mme classe return bonus == other.bonus; } public int hashCode() { return super.hashCode() + 17 * new Double(bonus).hashCode(); } public String toString() { return super.toString() +"[bonus="+bonus +"]"; } private double bonus; } java.lang.Object 1.0

Class getClass()

Renvoie un objet class contenant des informations sur lobjet. Comme vous le verrez dans ce chapitre, la reprsentation des classes lexcution est encapsule dans la classe Class.

boolean equals(Object otherObject)

Compare deux objets ; renvoie true si les objets pointent vers la mme zone mmoire et false dans le cas contraire. Vous devez remplacer cette mthode dans vos propres classes.

String toString()

Renvoie une chane dcrivant la valeur de lobjet. Vous devez remplacer cette mthode dans vos propres classes.

Object clone()

Cre un clone de lobjet. Le systme dexcution de Java alloue de la mmoire pour la nouvelle instance et y recopie la mmoire alloue lobjet courant.

210

Au cur de Java 2 - Notions fondamentales

INFO
Le clonage constitue une opration importante, mais il sagit aussi dun processus assez dlicat qui peut rserver de mauvaises surprises aux imprudents. Nous y reviendrons lors de lexamen de la mthode clone, au Chapitre 6.

java.lang.Class 1.0

String getName()

Renvoie le nom de cette classe.


Class getSuperclass()

Renvoie la superclasse de cette classe en tant quobjet Class.

Listes de tableaux gnriques


Dans de nombreux langages de programmation en particulier C , la taille des tableaux doit tre xe au moment de la compilation. Les programmeurs dtestent cette contrainte : elle les oblige de pnibles acrobaties de conception. Combien demploys y aura-t-il dans un service ? Probablement pas plus de 100. Et si un service important a 150 employs ? Allons-nous gcher 90 entres pour chaque service nayant que 10 employs ? La situation est bien plus simple en Java, car il est possible de spcier la taille dun tableau au moment de lexcution :
int actualSize = . . .; Employee[] staff = new Employee[actualSize];

Bien entendu, ce genre de code ne rsout pas compltement le problme de la modication dynamique des tableaux. Une fois que la taille dun tableau est spcie, il nest pas facile de la changer. La manire la plus simple de grer cette situation courante consiste utiliser une autre classe Java, appele ArrayList. La classe ArrayList est semblable un tableau, mais elle ajuste automatiquement sa capacit mesure que vous ajoutez et supprimez des lments, sans que vous nayez rien faire. Depuis Java SE 5.0, ArrayList est une classe gnrique avec un paramtre de type. Pour spcier le type des objets dlments inclus dans la liste de tableau, vous annexez un nom de classe entre les signes suprieur et infrieur , sous la forme ArrayList<Employee>. Vous verrez au Chapitre 13 comment dnir votre propre classe gnrique, mais il est inutile de connatre les caractristiques techniques du type ArrayList pour lutiliser. Nous dclarons et construisons ici une liste de tableau qui contient des objets Employee :
ArrayList<Employee> staff = new ArrayList<Employee>();

INFO
Les classes gnriques nexistaient pas avant Java SE 5.0. Il ny avait quune seule classe ArrayList, une collection "fourre-tout" dont les lments taient de type Object. Si vous devez utiliser une ancienne version de Java, abandonnez simplement tous les sufxes de type <...>. Vous pouvez continuer utiliser ArrayList sans sufxe <...> dans Java SE 5.0 et versions ultrieures. On le considre comme un type "brut", dont le paramtre de type est effac.

Chapitre 5

Lhritage

211

INFO
Dans les versions encore antrieures du langage Java, les programmeurs utilisaient la classe Vector pour les tableaux dynamiques. La classe ArrayList est toutefois plus efcace, et il nexiste plus de raison valable dutiliser la classe Vector.

Employez la mthode add pour ajouter de nouveaux lments une liste de tableaux. Voici par exemple comment remplir une liste de tableaux avec des objets Employee :
staff.add(new Employee("Harry Hacker", . . .)); staff.add(new Employee("Tony Tester", ...));

La classe ArrayList gre un tableau interne de rfrences Object. Ce tableau nira par tre satur. Cest l que les listes de tableaux sont magiques : si vous appelez add et que le tableau interne soit plein, la liste de tableaux cre automatiquement un tableau plus grand et recopie tous les objets du plus petit tableau vers le plus grand. Si vous connaissez dj le nombre dlments que contiendra le tableau, ou que vous puissiez lvaluer assez prcisment, appelez la mthode ensureCapacity avant de remplir la liste de tableaux :
staff.ensureCapacity(100);

Cet appel alloue un tableau interne de 100 objets. Les 100 premiers appels add nimpliquent aucune raffectation coteuse. Vous pouvez aussi passer une capacit initiale au constructeur de ArrayList :
ArrayList<Employee> staff = new ArrayList<Employee>(100);

ATTENTION
Lallocation dune liste de tableaux de la faon suivante :
new ArrayList<Employee>(100) // la capacit est de 100

nest pas la mme chose que lallocation dun nouveau tableau :


new Employee[100] // la taille est de 100

Il faut tablir une distinction entre la capacit dune liste de tableaux et la taille dun tableau. Si vous allouez un tableau de 100 lments, le tableau disposera de 100 emplacements, prts lemploi. Une liste de tableaux dont la capacit est de 100 lments peut potentiellement contenir 100 lments (et, en ralit, plus de 100, au prix de rallocations supplmentaires). Mais au dpart, juste aprs sa construction, une liste de tableaux ne contient en fait aucun lment.

La mthode size renvoie le nombre rel dlments dans la liste de tableaux. Par exemple,
staff.size()

renvoie le nombre actuel dlments dans la liste de tableaux staff. Ce qui est lquivalent de
a.length

pour un tableau a. Une fois que vous tes quasiment certain que la liste de tableaux a atteint sa taille dnitive, vous pouvez appeler la mthode trimToSize. Elle ajuste la taille du bloc de mmoire pour que la quantit

212

Au cur de Java 2 - Notions fondamentales

exacte ncessaire pour le nombre actuel dlments soit rserve. Le "ramasse-miettes" rcuprera toute mmoire en excs. Une fois que vous avez ajust la taille dune liste de tableaux, lajout de nouveaux lments va dplacer de nouveau le bloc, ce qui prend du temps. Nutilisez trimToSize que si vous tes certain de ne plus ajouter dlments la liste de tableaux.
INFO C++
La classe ArrayList est identique au modle de vecteur C++. ArrayList et vector sont tous deux des types gnriques. Mais le modle vector de C++ surcharge loprateur [] pour faciliter laccs aux lments. Comme Java nautorise pas la surcharge des oprateurs, une opration quivalente exige un appel de mthode explicite. De plus, les vecteurs de C++ sont copis par valeur. Si a et b sont deux vecteurs, laffectation a = b; fait de a un nouveau vecteur, de la mme longueur que b, et tous les lments de b sont copis vers a. En Java, la mme affectation amnera a et b rfrencer la mme liste de tableaux.

java.util.ArrayList<T> 1.2

ArrayList<T>()

Construit une liste de tableaux vide.


ArrayList<T>(int initialCapacity)

Construit une liste de tableaux vide ayant la capacit spcie. Paramtres :

initialCapacity La capacit de stockage initiale de la liste de tableaux.

boolean add(T obj)

Ajoute un lment la n de la liste de tableaux. Renvoie toujours true. Paramtres :

int size()

obj

Llment ajouter.

Renvoie le nombre dlments actuellement contenus dans la liste de tableaux (bien entendu, elle est toujours infrieure ou gale la capacit de la liste de tableaux).

void ensureCapacity(int capacity)

Vrie que la liste de tableaux a la capacit ncessaire pour contenir le nombre dlments donn, sans ncessiter la rallocation de son tableau de stockage interne. Paramtres :

capacity

La capacit de stockage souhaite.

void trimToSize()

Rduit la capacit de stockage de la liste de tableaux sa taille actuelle.

Accder aux lments dune liste de tableaux


Malheureusement, rien nest gratuit ; les avantages que procure laccroissement automatique de la taille dune liste de tableaux exigent une syntaxe plus complexe pour accder aux lments, car la classe ArrayList ne fait pas partie du langage Java ; il sagit dune classe utilitaire tiers ajoute la bibliothque standard. Au lieu demployer la syntaxe [], bien pratique pour accder un lment dun tableau ou le modier, il faut appeler les mthodes get et set.

Chapitre 5

Lhritage

213

Par exemple, pour dnir le ime lment, crivez


staff.set(i, harry);

ce qui est quivalent


a[i] = harry;

pour un tableau a (comme pour les tableaux, les valeurs dindex sont bases sur zro).
ATTENTION
Nappelez pas list.set(i, x) tant que la taille de la liste de tableaux nest pas suprieure i. Lexemple suivant est incorrect :
ArrayList<Employee> list = new ArrayList<Employee>(100); // capacit 100, taille 0 list.set(0, x); // pas encore dlment 0

Appelez la mthode add au lieu de set pour remplir un tableau et nemployez set que pour remplacer un lment dj ajout.

Pour obtenir un lment de liste de tableau, utilisez


Employee e = staff.get(i);

ce qui est quivalent :


Employee e = a[i];

INFO
Avant Java SE 5.0, les classes gnriques nexistaient pas et la mthode get de la classe brute ArrayList navait dautre choix que de renvoyer un Object. Les lments qui appelaient get devaient donc transtyper la valeur renvoye sur le type souhait :
Employee e = (Employee) staff.get(i);

Le ArrayList brut est aussi un peu dangereux. Ses mthodes add et set acceptent des objets de nimporte quel type. Un appel
staff.set(i, new Date());

se compile quasiment sans aucun avertissement et ne pose problme que lorsque vous rcuprez lobjet et que vous essayez de le transtyper. Si vous utilisez plutt un ArrayList<Employee>, le compilateur dtecte cette erreur.

Une petite astuce permet de proter dun double avantage une croissance exible et un accs pratique aux lments. Crez dabord une liste de tableaux et ajoutez-y tous ses lments :
ArrayList<X> list = new ArrayList<X>(); while (. . .) { x = . . .; list.add(x); }

Utilisez ensuite la mthode toArray pour copier les lments dans un tableau :
X[] a = new X[list.size()]; list.toArray(a);

214

Au cur de Java 2 - Notions fondamentales

Au lieu dajouter des lments la n dune liste de tableaux, vous pouvez aussi les insrer au milieu. Utilisez la mthode add avec un paramtre dindice :
int n = staff.size() / 2; staff.add(n, e);

Les lments situs aux emplacements n et suprieurs sont dcals pour faire place au nouvel lment. Si la nouvelle taille de la liste de tableaux aprs linsertion excde la capacit, il est procd une rallocation. Il est galement possible de supprimer un lment au milieu dune liste de tableaux :
Employee e = staff.remove(n);

Les lments situs au-dessus de llment supprim sont dcals vers le bas, et la taille du tableau est rduite de 1. Linsertion et la suppression dlments ne sont pas trs efcaces. On ne sen proccupe gure dans le cas de petites listes de tableaux. Si vous devez ajouter ou supprimer frquemment des lments au milieu dune collection, il est prfrable dutiliser une liste lie. La programmation avec les listes lies est tudie au Chapitre 13. Depuis Java SE 5.0, vous pouvez utiliser la boucle "for each" pour parcourir le contenu dune liste de tableau :
for (Employee e : staff) faire quelque chose avec e

Cette boucle a le mme effet que


for (int i = 0; i < staff.size(); i++) { Employee e = staff.get(i); faire quelque chose avec e }

Le Listing 5.4 est une modication du programme EmployeeTest du Chapitre 4. Le Tableau Employee[] est remplac par un ArrayList<Employee>. Remarquez les changements suivants :
m m m m

Il nest pas ncessaire de spcier la taille du tableau. Vous appelez add pour ajouter autant dlments que vous voulez. Vous utilisez size() au lieu de length pour compter le nombre dlments. Vous accdez un lment laide de a.get(i) au lieu de a[i].

Listing 5.4 : ArrayListTest.java


import java.util.*; /** * Ce programme prsente la classe ArrayList. * @version 1.1 2004-02-21 * @author Cay Horstmann */ public class ArrayListTest { public static void main(String[] args) { // remplir le tableau staff avec trois objets Employee

Chapitre 5

Lhritage

215

ArrayList<employee> staff = new ArrayList<employee>(); staff.add(new Employee("Carl Cracker", 75000, 1987, 12, 15)); staff.add(new Employee("Harry Hacker", 50000, 1989, 10, 1)); staff.add(new Employee("Tony Tester", 40000, 1990, 3, 15)); // augmenter le salaire de tout le monde de 5% for (Employee e: staff) e.raiseSalary(5); // afficher les informations concernant tous les objets Employee for (Employee e: staff) System.out.println("name="+e.getName() +",salary="+e.getSalary() +",hireDay="+e.getHireDay()); } } class Employee { public Employee(String n, double s, int year, int month, int day) { name = n; salary = s; GregorianCalendar calendar = new GregorianCalendar(year, month - 1, day); hireDay = calendar.getTime(); } public String getName() { return name; } public double getSalary() { return salary; } public Date getHireDay() { return hireDay; } public void raiseSalary(double byPercent) { double raise = salary * byPercent / 100; salary += raise; } private String name; private double salary; private Date hireDay; } java.util.ArrayList<T> 1.2 void set(int index, T obj)

Place une valeur dans la liste de tableau lindex spci, ce qui remplace le contenu prcdent.

216

Au cur de Java 2 - Notions fondamentales

Paramtres :

index obj

La position dinsertion, qui doit tre comprise entre 0 et size() -1. La nouvelle valeur

T get(int index)

Rcupre la valeur stocke lindex spci. Paramtres :

index

Lindex de llment rcuprer, qui doit tre compris entre 0 et size() -1.

void add(int index, T obj)

Place un nouvel lment la position spcie, en dcalant les autres lments vers le haut. Paramtres : index obj

T remove(int index)

La position dinsertion, qui doit tre comprise entre 0 et size() -1. Le nouvel lment.

Supprime un lment et dcale vers le bas les lments dindice suprieur. Llment supprim est renvoy. Paramtres : index Lindice de llment supprimer. Il doit tre compris entre 0 et size() -1.

Compatibilit entre les listes de tableaux brutes et types


Lorsque vous crivez un nouveau code avec Java SE 5.0 et versions ultrieures, utilisez des paramtres de type, comme ArrayList<Employee>, pour les listes de tableau. Vous pouvez toutefois avoir besoin dinteragir avec le code existant, qui utilise le type ArrayList brut. Supposons que vous disposiez de la classe existante suivante :
public class EmployeeDB { public void update(ArrayList list) { ... } public ArrayList find(String query) { ... } }

Vous pouvez transfrer une liste de tableau type la mthode update sans autre transtypage :
ArrayList<Employee> staff = ...; employeeDB.update(staff);

Lobjet staff est simplement transfr la mthode update.


ATTENTION
Mme si vous ne recevez pas derreur ou davertissement du compilateur, cet appel nest pas tout fait sr. La mthode update pourrait ajouter des lments dans la liste de tableau de type Employee. Une exception survient lors de la rcupration de ces lments. Ceci peut paratre effrayant mais, en y rchissant bien, le comportement est simplement le mme que celui avant Java SE 5.0. Lintgrit de la machine nest donc jamais remise en cause. Dans ce cas, vous ne perdez pas en scurit, mais vous ne protez pas non plus des vrications de compilation.

Chapitre 5

Lhritage

217

A linverse, lorsque vous attribuez un ArrayList brut un ArrayList typ, vous obtenez un avertissement :
ArrayList<Employee> result = employeeDB.find(query); // produit un avertissement

INFO
Pour voir le texte de lavertissement, vous devez procder la compilation avec loption -Xlint:unchecked.

Utiliser un transtypage ne permet pas de se dbarrasser de lavertissement :


ArrayList<Employee> result = (ArrayList<Employee>) employeeDB.find(query); // produit un autre avertissement

Vous obtenez un autre avertissement, vous indiquant que le transtypage est erron. Ceci est la consquence dune limitation assez malheureuse des types gnriques dans Java. Pour des raisons de compatibilit, le compilateur traduit toutes les listes de tableaux tapes en objets ArrayList bruts, aprs avoir vri que les rgles de type nont pas t violes. Dans un programme en cours dexcution, toutes les listes de tableaux sont identiques, il ny a pas de paramtres de types dans la machine virtuelle. Ainsi, les transtypages (ArrayList) et (ArrayList<Employee>) transportent des vrications dexcution identiques. Vous ne pouvez pas faire grand-chose face cette situation. Lorsque vous interagissez avec du code existant, tudiez les avertissements du compilateur et persuadez-vous que les avertissements ne sont pas srieux.

Enveloppes dobjets et autoboxing


Il est parfois ncessaire de convertir un type primitif comme int en un objet. Tous les types primitifs ont une contrepartie sous forme de classe. Par exemple, il existe une classe Integer correspondant au type primitif int. Une classe de cette catgorie est gnralement appele classe enveloppe (object wrapper). Les classes enveloppes portent des noms correspondant aux types (en anglais) : Integer, Long, Float, Double, Short, Byte, Character, Void et Boolean (les six premires hritent de la superclasse Number). Les classes enveloppes sont inaltrables : vous ne pouvez pas modier une valeur enveloppe, une fois lenveloppe construite. Elles sont aussi final, vous ne pouvez donc pas en faire des sous-classes. Supposons que nous voulions travailler sur une liste dentiers. Malheureusement, le paramtre de type entre les signes ne peut pas tre un type primitif. Il nest pas possible de former un ArrayList<int>. Cest ici que la classe enveloppe Integer fait son apparition. Vous pouvez dclarer une liste de tableaux dobjets Integer :
ArrayList<Integer> list = new ArrayList<Integer>();

ATTENTION
Un ArrayList<Integer> est bien moins efcace quun tableau int[] car chaque valeur est enveloppe sparment dans un objet. Vous ne voudriez utiliser cette construction que pour les petites collections lorsque la commodit du programmeur est plus importante que lefcacit.

218

Au cur de Java 2 - Notions fondamentales

Une autre innovation de Java SE 5.0 facilite lajout et la rcupration dlments de tableau. Lappel
list.add(3);

est automatiquement traduit en


list.add(new Integer(3));

Cette conversion est appele autoboxing.


INFO
Vous pourriez penser que lenveloppement automatique est plus cohrent, mais la mtaphore "boxing" (emballage) provient du C#.

A linverse, lorsque vous attribuez un objet Integer une valeur int, il est automatiquement dball (unboxing). En fait, le compilateur traduit
int n = list.get(i);

en
int n = list.get(i).intValue();

Les oprations dautoboxing et dunboxing fonctionnent mme avec les expressions arithmtiques. Vous pouvez par exemple appliquer lopration dincrmentation en une rfrence denveloppe :
Integer n = 3; n++;

Le compilateur insre automatiquement des instructions pour dballer lobjet, augmenter la valeur du rsultat et le remballer. Dans la plupart des cas, vous avez lillusion que les types primitifs et leurs enveloppes ne sont quun seul et mme lment. Ils ne diffrent considrablement quen un seul point : lidentit. Comme vous le savez, loprateur ==, appliqu des objets denveloppe, ne teste que si les objets ont des emplacements mmoire identiques. La comparaison suivante chouerait donc probablement :
Integer a = 1000; Integer b = 1000; if (a == b) ...

Toutefois, une implmentation Java pourrait, si elle le choisit, envelopper des valeurs communes dans des objets identiques, la comparaison pourrait donc russir. Cette ambigut nest pas souhaitable. La solution consiste appeler la mthode equals lorsque lon compare des objets denveloppe.
INFO
La caractristique dautoboxing exige que boolean, byte, char = 127 et que short et int compris entre 128 et 127 soient envelopps dans des objets xes. Par exemple, si a et b avaient t initialiss avec 100 dans lexemple prcdent, la comparaison aurait russi.

Enn, soulignons que le boxing et lunboxing sont proposs par le compilateur et non par la machine virtuelle. Le compilateur insre les appels ncessaires lorsquil gnre les bytecodes dune classe. La machine virtuelle excute simplement ces bytecodes.

Chapitre 5

Lhritage

219

Vous verrez souvent les enveloppes de nombres pour une autre raison. Les concepteurs de Java ont dcouvert que les enveloppes constituent un endroit pratique pour y stocker certaines mthodes de base, comme celles qui permettent de convertir des chanes de chiffres en nombres. Pour transformer une chane en entier, vous devez utiliser linstruction suivante :
int x = Integer.parseInt(s);

Ceci na rien voir avec les objets Integer; parseInt est une mthode statique. Mais la classe Integer constitue un bon endroit pour ly placer. Les notes API montrent quelques mthodes parmi les plus importantes de la classe Integer. Les autres classes de nombre implmentent les mthodes correspondantes.
ATTENTION
Certains pensent, tort, que les classes enveloppes peuvent tre employes pour implmenter des mthodes capables de modier les paramtres numriques. Nous avons vu au Chapitre 4 quil tait impossible dcrire une mthode Java qui incrmente un paramtre entier, car les paramtres des mthodes Java sont toujours passs par valeur.
public static void triple(int x) // ne marchera pas { X = 3 * x; // modifie la variable locale }

Mais ne pourrait-on pas contourner ce problme en substituant un Integer un int?


public static void triple(Integer x) // ne marchera pas { . . . }

Le problme vient du fait que les objets Integer sont inaltrables : linformation contenue dans lenveloppe ne peut pas changer. Vous ne pouvez pas employer les classes enveloppes pour crer une mthode qui modie les paramtres numriques. Si vous dsirez crire une mthode qui modie des paramtres numriques, vous pouvez utiliser un des types holder dnis dans le package org.omg.CORBA. Il existe des types IntHolder, BooleanHolder, etc. Chaque type holder a un champ public value grce auquel vous pouvez accder la valeur stocke :
public static void triple(IntHolder x) { x.value = 3 * x.value; }

java.lang.Integer 1.0

int intValue()

Renvoie la valeur de cet objet Integer dans un rsultat de type int (surcharge la mthode intValue de la classe Number).

static String toString(int i)

Renvoie un nouvel objet chane reprsentant le nombre i, en base 10.


static String toString(int i, int radix)

Permet de renvoyer une reprsentation du nombre i dans la base spcie par le paramtre radix.

220

Au cur de Java 2 - Notions fondamentales

static int parseInt(String s) static int parseInt(String s, int radix)

Renvoient la valeur dentier dont les chiffres sont contenus dans la chane s. La chane doit reprsenter un nombre entier exprim en base 10 (pour la premire mthode) ou dans la base spcie par le paramtre radix (dans la seconde).

static Integer valueOf(String s) static Integer valueOf(String s, int radix)

Renvoient un nouvel objet Integer initialis avec lentier dont les chiffres sont contenus dans la chane s. La chane doit reprsenter un nombre entier exprim en base 10 (pour la premire mthode) ou dans la base spcie par le paramtre radix (pour la seconde).
java.text.NumberFormat 1.1

Number parse(String s)

Renvoie la valeur numrique, en supposant que la chane spcie reprsente un nombre.

Mthodes ayant un nombre variable de paramtres


Avant Java SE 5.0, chaque mthode Java disposait dun nombre xe de paramtres. Il est toutefois maintenant possible de fournir des mthodes qui peuvent tre appeles avec un nombre variable de paramtres (quelquefois appels mthodes "varargs"). Vous avez dj vu une telle mthode, la mthode printf. Par exemple, les appels
System.out.printf("%d", n);

et
System.out.printf("%d %s", n, "widgets");

appellent tous deux la mme mthode, mme si lun a deux paramtres et lautre, trois. La mthode printf est dnie comme ceci :
public class PrintStream { public PrintStream printf(String fmt, Object... args) { return format(fmt, args); } }

Ici, lellipse... fait partie du code Java. Elle montre que la mthode peut recevoir un nombre arbitraire dobjets (en plus du paramtre fmt). La mthode printf reoit en fait deux paramtres, la chane format et un tableau Object[] qui contient tous les autres paramtres (si lappelant fournit des entiers ou autres valeurs de type primitif, lautoboxing les transforme en objets). Elle dispose maintenant de la tche non enviable danalyser la chane fmt et de se conformer au spcicateur du format ith avec la valeur args[i]. Autrement dit, pour limplmenteur de printf, le type de paramtre Object... est exactement le mme que Object[]. Le compilateur doit transformer chaque appel printf, en regroupant les paramtres dans un tableau et en procdant lautoboxing en fonction des besoins :
System.out.printf("%d %s", new Object[] { new Integer(n), "widgets" } );

Chapitre 5

Lhritage

221

Vous pouvez dnir vos propres mthodes avec des paramtres variables et spcier tout type pour les paramtres, mme un type primitif. Voici un exemple simple : une fonction qui calcule le maximum dun nombre variable de valeurs :
public static double max(double... values) { double largest = Double.MIN_VALUE; for (double v: values) if (v > largest) largest = v; return largest; }

Appelez simplement la fonction comme ceci :


double m = max(3.1, 40.4, -5);

Le compilateur transfre un nouveau double[] { 3.1, 40.4, -5 } la fonction max.


INFO
Le transfert dun tableau comme dernier paramtre dune mthode avec des paramtres variables est autoris, par exemple :
System.out.printf("%d %s", new Object[] { new Integer(1), "widgets" } );

Vous pouvez donc rednir une fonction existante dont le dernier paramtre est un tableau par une mthode disposant de paramtres variables, sans casser un code existant. MessageFormat.format, par exemple, a t amlior dans ce sens dans Java SE 5.0. Si vous le souhaitez, vous pouvez mme dclarer la mthode main sous la forme :
public static void main(String... args)

Classes dnumration
Vous avez vu au Chapitre 3 comment dnir les types numrs dans Java SE 5.0 et versions ultrieures. Voici un exemple typique :
public enum Size { SMALL, MEDIUM, LARGE, EXTRA_LARGE };

Le type dni par cette dclaration est, en fait, une classe. La classe possde exactement quatre instances ; il nest pas possible de construire de nouveaux objets. Vous navez donc jamais besoin dutiliser equals pour les valeurs de types numrs. Utilisez simplement == pour les comparer. Vous pouvez, si vous le souhaitez, ajouter des constructeurs, des mthodes et des champs un type numr. Bien entendu, les constructeurs ne sont invoqus que lorsque les constantes numres sont construites. En voici un exemple :
enum Size { SMALL("S"), MEDIUM("M"), LARGE("L"), EXTRA_LARGE("XL"); private Size(String abbreviation) { this.abbreviation = abbreviation; } public String getAbbreviation() { return abbreviation; }

222

Au cur de Java 2 - Notions fondamentales

private String abbreviation; }

Tous les types numrs sont des sous-classes de la classe Enum, dont ils hritent de plusieurs mthodes. La mthode la plus utile est toString, qui renvoie le nom de la constante numre. Ainsi, Size.SMALL.toString() renvoie la chane "SMALL". Linverse de toString est la mthode statique valueOf. Par exemple, linstruction
Size s = (Size) Enum.valueOf(Size.class, "SMALL");

dnit s sur Size.SMALL. Chaque type numr possde une mthode de valeurs statique qui renvoie un tableau de toutes les valeurs de lnumration. Par exemple, lappel :
Size[] values = Size.values();

renvoie le tableau avec les lments Size.SMALL, Size.MEDIUM, Size.LARGE et Size.EXTRA_LARGE. La mthode ordinal donne la position dune constante numre dans la dclaration enum, en partant de zro. Ainsi, Size.MEDIUM.ordinal() renvoie 1. Le petit programme du Listing 5.5 montre comment fonctionnent les types numrs.
INFO
La classe Enum dispose dun paramtre de type que nous avons ignor pour des raisons de simplicit. Par exemple, le type numr Size tend en fait Enum<Size>. Le paramtre de type est utilis dans la mthode compareTo. Nous verrons cette mthode au Chapitre 6 et les paramtres de type au Chapitre 12.

Listing 5.5 : EnumTest.java


import java.util.*; /** * Ce programme prsente les types numrs. * @version 1.0 2004-05-24 * @author Cay Horstmann */ public class EnumTest { public static void main(String[] args) { Scanner in = new Scanner(System.in); System.out.print("Enter a size: (SMALL, MEDIUM, LARGE, EXTRA_LARGE) "); String input = in.next().toUpperCase(); Size size = Enum.valueOf(Size.class, input); System.out.println("size=" + size); System.out.println("abbreviation=" + size.getAbbreviation()); if (size == Size.EXTRA_LARGE) System.out.println("Good job--you paid attention to the _."); } }

Chapitre 5

Lhritage

223

enum Size { SMALL("S"), MEDIUM("M"), LARGE("L"), EXTRA_LARGE("XL"); private Size(String abbreviation) { this.abbreviation = abbreviation; } public String getAbbreviation() { return abbreviation; } private String abbreviation; } java.lang.Enum<E> 5.0

static Enum valueOf(Class enumClass, String name)

Renvoie la constante numre de la classe donne, avec le nom donn.


String toString()

Renvoie le nom de cette constante numre.


int ordinal()

Renvoie la position base sur zro de cette constante numre dans la dclaration enum.
int compareTo(E other)

Renvoie un entier ngatif si cette constante numre arrive avant, zro si this == other, et un entier positif le reste du temps. Lordre des constantes est donn par la dclaration enum.

Rexion
La bibliothque de rexion constitue une bote outils riche et labore pour crire des programmes qui manipulent dynamiquement du code Java. Cette fonctionnalit est trs largement utilise dans JavaBeans, larchitecture des composants Java (voir le Volume II pour en savoir plus sur JavaBeans). Au moyen de la rexion, Java est capable de supporter des outils comme ceux auxquels les utilisateurs de Visual Basic sont habitus. Prcisment, lorsque de nouvelles classes sont ajoutes au moment de la conception ou de lexcution, des outils de dveloppement dapplication rapide peuvent se renseigner dynamiquement sur les capacits des classes qui ont t ajoutes. Un programme qui peut analyser les capacits des classes est appel recteur. Le mcanisme de rexion est extrmement puissant. Comme vous le montrent les sections suivantes, il peut tre employ pour :
m m

analyser les capacits des classes au moment de lexcution ; tudier les objets au moment de lexcution, par exemple pour crire une seule mthode toString qui fonctionne pour toutes les classes ; implmenter un code gnrique pour la manipulation des tableaux ; proter des objets Method qui se comportent comme les pointeurs de fonction des langages tels que C++.

m m

La rexion est un mcanisme puissant et complexe ; il intresse toutefois principalement les concepteurs doutils, et non les programmeurs dapplications. Si vous vous intressez la programmation des applications plutt quaux outils pour les programmeurs Java, vous pouvez ignorer sans problme le reste de ce chapitre et y revenir par la suite.

224

Au cur de Java 2 - Notions fondamentales

La classe Class
Lorsque le programme est lanc, le systme dexcution de Java gre, pour tous les objets, ce que lon appelle "lidentication de type lexcution". Cette information mmorise la classe laquelle chaque objet appartient. Linformation de type au moment de lexcution est employe par la machine virtuelle pour slectionner les mthodes correctes excuter. Vous pouvez accder cette information en travaillant avec une classe Java particulire, baptise singulirement Class. La mthode getClass() de la classe Object renvoie une instance de type Class :
Employee e; . . . Class cl = e.getClass();

Tout comme un objet Employee dcrit les proprits dun employ particulier, un objet Class dcrit les proprits dune classe particulire. La mthode la plus utilise de Class est sans conteste getName, qui renvoie le nom de la classe. Par exemple, linstruction
System.out.println(e.getClass().getName()+" "+e.getName());

afche
Employee Harry Hacker

si e est un employ ou
Manager Harry Hacker

si e est un directeur. Si la classe se trouve dans un package, le nom du package fait partie du nom de la classe :
Date d = new Date(); Class cl = d.getClass(); String name = cl.getName(); // nom rgl sur "java.util.Date"

Vous pouvez aussi obtenir un objet Class correspondant un nom de classe, laide de la mthode statique forName :
String className = "java.util.Date"; Class cl = Class.forName(className);

Cette technique est utilise si le nom de classe est stock dans une chane qui peut changer lexcution. Cela fonctionne lorsque className est bien le nom dune classe ou dune interface. Dans le cas contraire, la mthode forName lance une exception vrie. Consultez la section "Introduction linterception des exceptions" plus loin pour voir comment fournir un gestionnaire dexception lorsque vous utilisez cette mthode.
ASTUCE
Au dmarrage, la classe contenant votre mthode main est charge. Elle charge toutes les classes dont elle a besoin. Chacune de ces classes charge les classes qui lui sont ncessaires, et ainsi de suite. Cela peut demander un certain temps pour une application importante, ce qui est frustrant pour lutilisateur. Vous pouvez donner aux utilisateurs de votre programme lillusion dun dmarrage plus rapide, laide de lastuce suivante. Assurez-vous que la classe contenant la mthode main ne fait pas explicitement rfrence aux autres classes. Afchez dabord un cran splash, puis forcez manuellement le chargement des autres classes en appelant Class.forName.

Chapitre 5

Lhritage

225

Une troisime technique emploie un raccourci pratique pour obtenir un objet de type Class. En effet, si T est dun type Java quelconque, alors T.class reprsente lobjet classe correspondant. Voici quelques exemples :
Class cl1 = Date.class; // si vous importez java.util.*; Class cl2 = int.class; Class cl3 = Double[].class;

Remarquez quun objet Class dsigne en ralit un type, qui nest pas ncessairement une classe. Par exemple, int nest pas une classe, mais int.class est pourtant un objet du type Class.
INFO
Depuis Java SE 5.0, la classe Class possde des paramtres. Par exemple, Employee.class est de type Class<Employee>. Nous ne traiterons pas de ce problme car il compliquerait encore un concept dj abstrait. Dans un but pratique, vous pouvez ignorer le paramtre de type et travailler avec le type Class brut. Voyez le Chapitre 13 pour en savoir plus sur ce problme.

ATTENTION
Pour des raisons historiques, la mthode getName renvoie des noms tranges pour les types tableaux : Double[].class.getName() renvoie "[Ljava.lang.Double;".

int[].class.getName() renvoie "[I".

La machine virtuelle gre un unique objet Class pour chaque type. Vous pouvez donc utiliser loprateur == pour comparer les objets class, par exemple :
if (e.getClass() == Employee.class) . . .

Il existe une autre mthode utile qui permet de crer une instance de classe la vole. Cette mthode se nomme, assez naturellement, newInstance(). Par exemple,
e.getClass().newInstance();

cre une nouvelle instance du mme type de classe que e. La mthode newInstance appelle le constructeur par dfaut (celui qui ne reoit aucun paramtre) pour initialiser lobjet nouvellement cr. Une exception est dclenche si la classe ne dispose pas de constructeur par dfaut. Une combinaison de forName et de newInstance permet de crer un objet partir dun nom de classe stock dans une chane :
String s = "java.util.Date"; Object m = Class.forName(s).newInstance();

INFO
Vous ne pourrez pas employer cette technique si vous devez fournir des paramtres au constructeur dune classe que vous souhaitez instancier la vole. Vous devrez recourir la mthode newInstance de la classe Constructor.

226

Au cur de Java 2 - Notions fondamentales

INFO C++
La mthode newInstance correspond un constructeur virtuel en C++. Cependant, la construction virtuelle en C++ nest pas une caractristique du langage, mais une simple tournure idiomatique qui doit tre prise en charge par une bibliothque spcialise. Class est comparable la classe type_info de C++ et la mthode getClass quivaut loprateur typeid. Nanmoins, la classe Class de Java est plus souple que type_info qui peut seulement fournir le nom dun type, et non crer de nouveaux objets de ce type.

Introduction linterception dexceptions


La gestion des exceptions est traite en dtail au Chapitre 11 mais, en attendant, vous pouvez rencontrer des cas o les mthodes menacent de lancer des exceptions. Lorsquune erreur se produit au moment de lexcution, un programme peut "lancer une exception". Le lancement dune exception est plus souple que la terminaison du programme, car vous pouvez fournir un gestionnaire qui "intercepte" lexception et la traite. Si vous ne fournissez pas de gestionnaire, le programme se termine pourtant et afche la console un message donnant le type de lexception. Vous avez peut-tre dj vu un compte-rendu dexception si vous employez tort une rfrence null ou que vous dpassiez les limites dun tableau. Il existe deux sortes dexceptions : vries et non vries (checked/unchecked). Dans le cas dexceptions vries, le compilateur vrie que vous avez fourni un gestionnaire. Cependant, de nombreuses exceptions communes, telle quune tentative daccder une rfrence null, ne sont pas vries. Le compilateur ne vrie pas si vous fournissez un gestionnaire pour ces erreurs aprs tout, vous tes cens mobiliser votre nergie pour viter ces erreurs plutt que de coder des gestionnaires pour les traiter. Mais toutes les erreurs ne sont pas vitables. Si une exception peut se produire en dpit de vos efforts, le compilateur sattendra ce que vous fournissiez un gestionnaire. Class.forName est un exemple de mthode qui dclenche une exception vrie. Vous tudierez, au Chapitre 11, plusieurs stratgies de gestion dexception. Pour linstant, nous nous contenterons de voir limplmentation du gestionnaire le plus simple. Placez une ou plusieurs instructions pouvant lancer des exceptions vries, dans un bloc dinstructions try, puis fournissez le code du gestionnaire dans la clause catch.
try { instructions pouvant dclencher des exceptions } catch(Exception e) { action du gestionnaire }

En voici un exemple :
try { String name = . . .; // extraire le nom de classe Class cl = Class.forName(name); // peut lancer une exception . . . // faire quelque chose avec cl }

Chapitre 5

Lhritage

227

catch(Exception e) { e.printStackTrace(); }

Si le nom de classe nexiste pas, le reste du code dans le bloc try est saut et le programme se poursuit la clause catch (ici, nous imprimons une trace de pile laide de la mthode printStackTrace de la classe Throwable qui est la superclasse de la classe Exception). Si aucune des mthodes dans le bloc try ne dclenche dexception, le code du gestionnaire dans la clause catch est saut. Vous navez besoin de fournir de gestionnaire dexception que pour les exceptions vries. Il est facile de dterminer les mthodes qui lancent celles-ci le compilateur protestera quand vous appellerez une mthode menaant de lancer une exception vrie et que vous ne fournirez pas de gestionnaire.
java.lang.Class 1.0 static Class forName(String className)

Renvoie lobjet Class qui reprsente la classe ayant pour nom className.

Object newInstance()

Renvoie une nouvelle instance de cette classe.


java.lang.reflect.Constructor 1.1 Object newInstance(Object[] args)

Construit une nouvelle instance de la classe dclarante du constructeur. Paramtres : args Les paramtres fournis au constructeur. Voir la section relative la rexion pour plus dinformations sur la faon de fournir les paramtres.

java.lang.Throwable 1.0 void printStackTrace()

Afche lobjet Throwable et la trace de la pile dans lunit derreur standard.

La rexion pour analyser les caractristiques dune classe


Nous vous proposons un bref aperu des lments les plus importants du mcanisme de rexion, car il permet dexaminer la structure dune classe. Les trois classes Field, Method et Constructor, qui se trouvent dans le package java.lang.reflect, dcrivent respectivement les champs, les mthodes et les constructeurs dune classe. Ces trois classes disposent dune mthode getName qui renvoie le nom de llment. La classe Field possde une mthode getType renvoyant un objet, de type Class, qui dcrit le type du champ. Les classes Method et Constructor possdent des mthodes permettant dobtenir les types des paramtres et la classe Method signale aussi le type de retour. Les trois classes possdent galement une mthode appele getModifiers : elle renvoie un entier dont les bits sont utiliss comme smaphores pour dcrire les modicateurs spcis, tels que public ou static. Vous pouvez alors utiliser les mthodes statiques de la classe Modifier du package java.lang.reflect pour analyser les entiers renvoys par getModifiers. Par exemple, il existe des mthodes telles que isPublic, isPrivate ou isFinal pour dterminer si un constructeur ou une mthode a t dclare public, private ou final.

228

Au cur de Java 2 - Notions fondamentales

Il vous suft dappeler la mthode approprie de Modifier et de lutiliser sur lentier renvoy par getModifiers. Il est galement possible demployer Modifier.toString pour afcher les modicateurs. Les mthodes getFields, getMethods et getConstructors de la classe Class renvoient dans des tableaux les lments publics, les mthodes et les constructeurs grs par la classe. Ces lments sont des objets de la classe correspondante de java.lang.reflect. Cela inclut les membres publics des superclasses. Les mthodes getDeclaredFields, getDeclaredMethods et getDeclaredConstructors de Class renvoient des tableaux constitus de tous les champs, mthodes et constructeurs dclars dans la classe, y compris les membres privs et protgs, mais pas les membres des superclasses. Le Listing 5.6 explique comment afcher toutes les informations relatives une classe. Le programme vous demande le nom dune classe, puis afche la signature de chaque mthode et de chaque constructeur ainsi que le nom de chaque champ de donnes de la classe en question. Par exemple, si vous tapez :
java.lang.Double

Le programme afche :
public class java.lang.Double extends java.lang.Number { public java.lang.Double(java.lang.String); public java.lang.Double(double); public public public public public public public public public public public public public public public public public public public public public int hashCode(); int compareTo(java.lang.Object); int compareTo(java.lang.Double); boolean equals(java.lang.Object); java.lang.String toString(); static java.lang.String toString(double); static java.lang.Double valueOf(java.lang.String); static boolean isNaN(double); boolean isNaN(); static boolean isInfinite(double); boolean isInfinite(); byte byteValue(); short shortValue(); int intValue(); long longValue(); float floatValue(); double doubleValue(); static double parseDouble(java.lang.String); static native long doubleToLongBits(double); static native long doubleToRawLongBits(double); static native double longBitsToDouble(long);

public static final double POSITIVE_INFINITY; public static final double NEGATIVE_INFINITY; public static final double NaN; public static final double MAX_VALUE; public static final double MIN_VALUE; public static final java.lang.Class TYPE; private double value; private static final long serialVersionUID; }

Chapitre 5

Lhritage

229

Ce qui est remarquable dans ce programme, cest quil peut analyser toute classe apte tre charge par linterprteur, et pas seulement les classes disponibles au moment o le programme est compil. Nous le rutiliserons au chapitre suivant pour examiner les classes internes gnres automatiquement par le compilateur Java.
Listing 5.6 : ReectionTest.java
import java.util.*; import java.lang.reflect.*; /** * Ce programme utilise la rflexion pour afficher toutes les * caractristiques dune classe. * @version 1.1 2004-02-01 * @author Cay Horstmann */ public class ReflectionTest { public static void main(String[] args) { // lire le nom de classe dans les args de ligne de commande // ou lentre de lutilisateur String name; if (args.length > 0) name = args[0]; else { Scanner in = new Scanner(System.in); System.out.println("Enter class Name (e.g. java.util.Date): "); name = in.next(); } try { // afficher le nom de classe et de superclasse // (if!= Object) Class cl = Class.forName(name); Class supercl = cl.getSuperclass(); String modifiers = Modifier.toString(cl.getModifiers()); if (modifiers.length() > 0) System.out.print(modifiers + " "); System.out.print("class "+name); if (supercl!= null && supercl!= Object.class) System.out.print(" extends "+supercl.getName()); System.out.print("\n{\n"); printConstructors(cl); System.out.println(); printMethods(cl); System.out.println(); printFields(cl); System.out.println("}"); } catch(ClassNotFoundException e) { e.printStackTrace(); } System.exit(0); }

230

Au cur de Java 2 - Notions fondamentales

/** * affiche tous les constructeurs dune classe * @param cl Une classe */ public static void printConstructors(Class cl) { Constructor[] constructors = cl.getDeclaredConstructors(); for (Constructor c: constructors) { String name = c.getName(); System.out.print(" "); String modifiers = Modifier.toString(c.getModifiers()); If (modifiers.length() > 0) System.out.print(modifiers + " "); System.out.print(name+"("); // afficher les types des paramtres Class[] paramTypes = c.getParameterTypes(); for (int j = 0; j < paramTypes.length; j++) { if (j > 0) System.out.print(", "); System.out.print(paramTypes[j].getName()); } System.out.println(");"); } } /** * imprime toutes les mthodes dune classe * @param cl Une classe */ public static void printMethods(Class cl) { Method[] methods = cl.getDeclaredMethods(); for (Method m: methods) { Class retType = m.getReturnType(); String name = m.getName(); System.out.print(" "); // affiche les modificateurs, le type renvoy // et le nom de la mthode String modifiers = Modifier.toString(m.getModifiers()); if (modifiers.length() > 0) System.out.print( modifiers +" "); System.out.print(retType.getName() + " " + name + "("); // imprime les types des paramtres Class[] paramTypes = m.getParameterTypes(); for (int j = 0; j < paramTypes.length; j++) { if (j > 0) System.out.print(", "); System.out.print(paramTypes[j].getName()); } System.out.println(");"); } }

Chapitre 5

Lhritage

231

/** * Affiche tous les champs dune classe * @param cl Une classe */ public static void printFields(Class cl) { Field[] fields = cl.getDeclaredFields(); for (Field f: fields) { Class type = f.getType(); String name = f.getName(); System.out.print(" "); String modifiers = Modifier.toString(f.getModifiers()); if (modifiers.length() > 0) System.out.println( modifiers + " "); System.out.println(type.getName()+" "+name+";"); } } } java.lang.Class 1.0

Field[] getFields() 1.1 Field[] getDeclaredFields() 1.1

getFields renvoie un tableau contenant les objets Field reprsentant les champs publics de cette classe ou de ses superclasses. getDeclaredFields renvoie un tableau dobjets Field pour tous les champs de cette classe. Ces mthodes renvoient un tableau de longueur 0 sil ny a pas de champ correspondant ou si lobjet Class reprsente un type primitif ou un type tableau.

Method[] getMethods 1.1() Method[] getDeclaredMethods() 1.1

Renvoient un tableau qui contient des objets Method : getMethods renvoie des mthodes publiques et inclut des mthodes hrites ; getDeclaredMethods renvoie toutes les mthodes de cette classe ou interface mais ninclut pas les mthodes hrites.

Constructor[] getConstructors() 1.1 Constructor[] getDeclaredConstructors() 1.1

Renvoient un tableau dobjets Constructor reprsentant tous les constructeurs publics (avec getConstructors) ou tous les constructeurs (avec getDeclaredConstructors) de la classe dsigne par cet objet Class.
java.lang.reflect.Field 1.1 java.lang.reflect.Method 1.1 java.lang.reflect.Constructor 1.1

Class getDeclaringClass()

Renvoie lobjet Class de la classe qui dnit ce constructeur, cette mthode ou ce champ.
Class[] getExceptionTypes()

Dans les classes Constructor et Method, renvoie un tableau dobjets Class qui reprsentent les types dexceptions dclenches par la mthode.

232

Au cur de Java 2 - Notions fondamentales

int getModifiers()

Renvoie un entier dcrivant les modicateurs du constructeur, de la mthode ou du champ. Utilisez les mthodes de la classe Modifier pour analyser le rsultat.

String getName()

Renvoie une chane qui donne le nom du constructeur, de la mthode ou du champ.


Class[] getParameterTypes()

Dans les classes Constructor et Method, renvoie un tableau dobjets Class qui reprsentent les types des paramtres.

Class getReturnType()

Dans les classes Method, renvoie un objet Class qui reprsente le type de retour.
java.lang.reflect.Modifier 1.1

static String toString(int modifiers)

Renvoie une chane avec les modicateurs correspondant aux bits dnis dans modifiers.
static boolean isAbstract(int modifiers) static boolean isFinal(int modifiers) static boolean isInterface(int modifiers) static boolean isNative(int modifiers) static boolean isPrivate(int modifiers) static boolean isProtected(int modifiers) static boolean isPublic(int modifiers) static boolean isStatic(int modifiers) static boolean isStrict(int modifiers) static boolean isSynchronized(int modifiers) static boolean isVolatile(int modifiers)

Ces mthodes testent le bit dans la valeur modifiers qui correspond llment modicateur du nom de la mthode.

La rexion pour lanalyse des objets lexcution


Dans la section prcdente, nous avons vu comment trouver le nom et le type des champs de nimporte quel objet :
m m

obtenir lobjet Class correspondant ; appeler getDeclaredFields sur lobjet Class.

Dans cette section, nous allons franchir une tape supplmentaire et tudier le contenu des champs. Bien entendu, il est facile de lire le contenu dun champ spcique dun objet dont le nom et le type sont connus lors de lcriture du programme. Mais la rexion nous permet de lire les champs des objets qui ntaient pas connus au moment de la compilation. A cet gard, la mthode essentielle est la mthode get de la classe Field. Si f est un objet de type Field (obtenu par exemple grce getDeclaredFields) et obj un objet de la classe dont f est un champ, alors f.get(obj) renvoie un objet dont la valeur est la valeur courante du champ de obj. Tout cela peut paratre un peu abstrait ; nous allons donc prendre un exemple :

Chapitre 5

Lhritage

233

Employee harry = new Employee("Harry Hacker", 35000, 10, 1, 1989); Class cl = harry.getClass(); // lobjet class reprsentant Employee Field f = cl.getDeclaredField("name"); // le champ name de la classe Employee Object v = f.get(harry); // la valeur du champ name de lobjet harry // cest--dire lobjet String "Harry Hacker"

En fait, ce code pose un problme. Comme le champ name est un champ priv, la mthode get dclenche une exception IllegalAccessException (Exception pour accs interdit). Cette mthode ne peut tre employe que pour obtenir les valeurs des champs accessibles. Le mcanisme de scurit de Java vous permet de connatre les champs dun objet, mais pas de lire la valeur de ces champs si vous navez pas une autorisation daccs. Par dfaut, le mcanisme de rexion respecte le contrle des accs. Nanmoins, si un programme Java nest pas contrl par un gestionnaire de scurit qui le lui interdit, il peut outrepasser son droit daccs. Pour cela, il faut invoquer la mthode setAccessible dun objet Field, Method ou Constructor :
f.setAccessible(true); // les appels f.get(harry) sont maintenant autoriss

La mthode setAccessible se trouve dans la classe AccessibleObject, qui est la superclasse commune des classes Field, Method et Constructor. Cette fonctionnalit est destine au dbogage, au stockage permanent et des mcanismes similaires. Nous lemploierons pour tudier une mthode toString gnrique. La mthode get pose un second problme. Le champ name est de type String, il est donc possible de rcuprer la valeur en tant que Object. Mais supposons que nous dsirions tudier le champ salary. Celui-ci est de type double, et les nombres ne sont pas des objets en Java. Il existe deux solutions. La premire consiste utiliser la mthode getDouble de la classe Field; la seconde est un appel get, car le mcanisme de rexion enveloppe automatiquement la valeur du champ dans la classe enveloppe approprie, en loccurrence, Double. Bien entendu, il est possible de modier les valeurs obtenues. Lappel f.set(obj, value) affecte une nouvelle valeur au champ de lobjet obj reprsent par f. Le Listing 5.7 montre comment crire une mthode toString gnrique capable de fonctionner avec nimporte quelle classe. Elle appelle dabord getDeclaredField pour obtenir tous les champs de donnes. Elle utilise ensuite la mthode setAccessible pour rendre tous ces champs accessibles, puis elle rcupre le nom et la valeur de chaque champ. Le Listing 5.7 convertit chaque valeur en chane par un appel sa propre mthode toString :
class ObjectAnalyzer { public String toString(Object obj) { Class cl = obj.getClass(); ... String r = cl.getName(); // inspecter les champs de cette classe et de toutes les superclasses do { r += "["; Field[] fields = cl.getDeclaredFields();

234

Au cur de Java 2 - Notions fondamentales

AccessibleObject.setAccessible(fields, true); // extraire les noms et valeurs de tous les champs for (Field f: fields) { if (!Modifier.isStatic(f.getModifiers())) { if (!r.endsWith("[")) r += "," r += f.getName()+"="; try { Object val = f.get(obj); r += val.toString(val); } catch (Exception e) { e.printStackTrace(); } } } r += "]"; cl = cl.getSuperclass(); } while (cl!= null); return r; } . . . }

La totalit du code du Listing 5.7 doit traiter deux complexits. Les cycles de rfrence pourraient entraner une rcursion innie. ObjectAnalyzer suit donc la trace des objets qui ont dj t visits. De mme, pour regarder dans les tableaux, vous devez adopter une approche diffrente. Vous en saurez plus dans la section suivante. Cette mthode toString peut tre employe pour dissquer nimporte quel objet. Par exemple, lappel
ArrayList<Integer> squares = new ArrayList<Integer>(); for (int i = 1; i <= 5; i++) squares.add(i * i); System.out.println(new ObjectAnalyzer().toString(squares));

produit limpression
java.util.ArrayList[elementData=class java.lang.Object[]{java.lang.Integer[value=1][][], java.lang.Integer[value=4][][], java.lang.Integer[value=9][][],java.lang.Integer[value=16][][], java.lang.Integer[value=25][][], null,null,null,null,null},size=5][modCount=5][][]

La mthode gnrique toString est utilise pour implmenter les mthodes toString de vos propres classes, comme ceci :
public String toString() { return new ObjectAnalyzer().toString(this); }

Il sagit dune technique sre pour crer une mthode toString, qui pourra vous tre utile dans vos programmes.

Chapitre 5

Lhritage

235

Listing 5.7 : ObjectAnalyzerTest.java


import java.lang.reflect.*; import java.util.*; /** * Ce programme utilise une rflexion pour espionner les objets. * @version 1.11 2004-02-21 * @author Cay Horstmann */ public class ObjectAnalyzerTest { public static void main(String[] args) { ArrayList<Integer> squares = new ArrayList<Integer>(); for (int i = 1; i <= 5; i++) squares.add(i * i); System.out.println(new ObjectAnalyzer().toString(squares)); } } class ObjectAnalyzer { /** * Convertit un objet en une reprsentation chane * qui liste tous les champs. * @param obj Un objet * @return une chane avec le nom de classe de lobjet * et tous les noms et valeurs de champs */ public String toString(Object obj) { if (obj == null) return "null"; if (visited.contains(obj)) return "..."; visited.add(obj); Class cl = obj.getClass(); if (cl == String.class) return (String) obj; if (cl.isArray()) { String r = cl.getComponentType() + "[]{"; for (int i = 0; i < Array.getLength(obj); i++) { if (i > 0) r += ","; Object val = Array.get(obj, i); if (cl.getComponentType().isPrimitive()) r += val; else r += toString(val); } return r + "}"; } String r = cl.getName(); // inspecter les champs de cette classe et de // toutes les superclasses do { r += "["; Field[] fields = cl.getDeclaredFields(); AccessibleObject.setAccessible(fields, true);

236

Au cur de Java 2 - Notions fondamentales

// extraire les noms et valeurs de tous les champs for (Field f: fields) { if (!Modifier.isStatic(f.getModifiers())) { if (!r.endsWith("[")) r += ","; r += f.getName()+"="; try { Class t = f.getType(); Object val = f.get(obj); if (t.isPrimitive()) r += val; else r += toString(val); } catch (Exception e) { e.printStackTrace(); } } } r += "]"; cl = cl.getSuperclass(); } while (cl!= null); return r; } private ArrayList<Object> visited = new ArrayList<Object>(); } java.lang.reflect.AccessibleObject 1.2 void setAccessible(boolean flag)

Dnit lindicateur daccessibilit de lobjet rexion. La valeur true signie que la vrication daccs de Java est invalide et que les proprits prives de lobjet peuvent tre lues et affectes.

boolean isAccessible()

Rcupre la valeur de lindicateur daccessibilit de cet objet rexion.


static void setAccessible(AccessibleObject[] array,boolean flag)

Permet de spcier ltat de lindicateur daccessibilit pour un tableau dobjets.


java.lang.Class 1.1 Field getField(String name) Field[] getFields()

Rcuprent le champ public avec le nom donn ou un tableau de tous les champs.

Field getDeclaredField(String name) Field[] getDeclaredFields()

Rcuprent le champ qui est dclar dans cette classe avec le nom donn ou un tableau de tous les champs.
java.lang.reflect.Field 1.1 Object get(Object obj)

Rcupre la valeur du champ dcrit par cet objet Field dans lobjet obj.

Chapitre 5

Lhritage

237

void set(Object obj, Object newValue)

Dnit le champ dcrit par cet objet Field dans lobjet obj avec une nouvelle valeur.

La rexion pour crer un tableau gnrique


La classe Array du package java.lang.reflect permet de crer des tableaux dynamiques. Lorsquon utilise cette caractristique avec la mthode arraycopy, dcrite au Chapitre 3, il est possible dtendre dynamiquement la taille dun tableau existant tout en en prservant le contenu actuel. Le problme que nous souhaitons rsoudre est assez typique. Supposons que nous disposions dun tableau dun type quelconque qui est satur et que nous voulions lagrandir. Comme nous sommes fatigus dcrire systmatiquement le code traditionnel ("augmenter le bloc mmoire et recopier"), nous dcidons dcrire une mthode gnrique permettant dagrandir un tableau :
Employee[] a = new Employee[100]; . . . // le tableau est satur a = (Employee[])arrayGrow(a);

Comment crire cette mthode gnrique ? Heureusement, un tableau Employee[] peut tre converti en tableau Object[]. Cela sannonce bien. Voici une premire mouture de notre mthode gnrique. Nous augmentons le tableau de 10 % + 10 lments (car une simple augmentation de 10 % ne sufrait pas pour de petits tableaux) :
static Object[] badArrayGrow(Object[] a) // sans intrt { int newLength = a.length * 11 / 10+10; Object[] newArray = new Object[newLength]; System.arraycopy(a, 0, newArray, 0, a.length); return newArray; }

La question se pose nanmoins de lutilisation du tableau qui en rsulte. Le type de tableau renvoy par ce code est un tableau dobjets (Object[]), il est en fait cr par cette instruction :
new Object[newLength]

Un tableau dobjets ne peut pas tre transtyp en tableau demploys (Employee[]). Java dclencherait une exception de transtypage ClassCastException lexcution. Comme nous lavons dj expliqu, un tableau Java mmorise le type de ses lments, cest--dire le type dlment utilis dans lexpression new lors de la cration du tableau. Il est valide de transtyper temporairement un tableau Employee[] en tableau Object[], et de le retranstyper ensuite dans son type dorigine. Mais un tableau qui a t initialement cr en tant que tableau Object[] ne peut jamais tre transtyp en tableau Employee[]. Pour dvelopper ce genre de tableau gnrique, nous devons tre capables de crer un nouveau tableau ayant le mme type que le tableau original. Pour cela, il nous faut exploiter les mthodes de la classe Array du package java.lang.reflect. La cl de lopration est la mthode newInstance de la classe Array, qui construit un nouveau tableau. Il faut indiquer cette mthode, en paramtres, le type des lments et la nouvelle taille dsire :
Object newArray = Array.newInstance(componentType, newLength);

Pour parvenir nos ns, il nous faut connatre la taille (ou longueur) et le type du nouveau tableau.

238

Au cur de Java 2 - Notions fondamentales

Cette taille est obtenue par un appel Array.getLength(a). La mthode statique getLength renvoie la taille de nimporte quel tableau. Pour obtenir le type, il faut suivre ces tapes : 1. Obtenir dabord lobjet classe de a. 2. Conrmer quil sagit bien dun tableau. 3. Utiliser ensuite la mthode getComponentType de la classe Class (qui nest dnie que pour les objets classe reprsentant des tableaux) an de connatre le vritable type du tableau. On peut se demander pourquoi getLength est une mthode de Array et getComponentType une mthode de Class. Ce choix a sans doute paru appropri aux concepteurs. Voici la nouvelle version de notre mthode :
static Object goodArrayGrow(Object a) // utile { Class cl = a.getClass(); if (!cl.isArray()) return null; Class componentType = cl.getComponentType(); int length = Array.getLength(a); int newLength = length * 11 / 10+10; Object newArray = Array.newInstance(componentType, newLength); System.arraycopy(a, 0, newArray, 0, length); return newArray; }

Remarquez que notre mthode arrayGrow peut tre employe pour augmenter des tableaux de tout type, et pas seulement des tableaux dobjets :
int[] a = { 1, 2, 3, 4 }; a = (int[]) goodArrayGrow(a);

Pour permettre cela, le paramtre de goodArrayGrow est dclar de type Object, et non comme un tableau dobjets (Object[]). Un tableau de type entier int[] peut tre converti en Object, mais un tableau dobjets ne le peut pas ! Le Listing 5.8 illustre le fonctionnement des deux mthodes daugmentation de tableau. Notez que la conversion de type de la valeur renvoye par badArrayGrow va provoquer une exception.
INFO
Nous prsentons ce programme pour montrer le fonctionnement des tableaux via la rexion. Pour simplement agrandir un tableau, utilisez la mthode copyOf de la classe Arrays.
Employee[] a = new Employee[100]; . . . // le tableau est plein a = Arrays.copyOf(a, a.length * 11 / 10 + 10);

Listing 5.8 : ArrayGrowTest.java


import java.lang.reflect.*; /** * Ce programme prsente lutilisation de la rflexion * pour manipuler des tableaux

Chapitre 5

Lhritage

239

* @version 1.01 2004-02-21 * @author Cay Horstmann */ public class ArrayGrowTest { public static void main(String[] args) { int[] a = { 1, 2, 3 }; a = (int[]) goodArrayGrow(a); arrayPrint(a); String[] b = { "Tom", "Dick", "Harry" }; b = (String[]) goodArrayGrow(b); arrayPrint(b); System.out.println ("The following call will generate an exception."); b = (String[]) badArrayGrow(b); } /** * Cette mthode tente dagrandir un tableau en allouant * un nouveau tableau et en copiant tous les lments. * @param a Le tableau agrandir * @return Un tableau plus grand contenant tous les lments de a. * Toutefois, le tableau renvoy est du type Object[], * et non du mme type que a */ static Object[] badArrayGrow(Object[] a) { int newLength = a.length * 11 / 10+10; Object[] newArray = new Object[newLength]; System.arraycopy(a, 0, newArray, 0, a.length); return newArray; } /** * Cette mthode agrandit un tableau en allouant * un nouveau tableau et en copiant tous les lments. * @param a Le tableau agrandir. Peut tre un tableau object * ou un tableau du type fondamental * @return Un tableau plus grand contenant tous les lments de a. */ static Object goodArrayGrow(Object a) { Class cl = a.getClass(); if (!cl.isArray()) return null; Class componentType = cl.getComponentType(); int length = Array.getLength(a); int newLength = length * 11 / 10+10; Object newArray = Array.newInstance(componentType, newLength); System.arraycopy(a, 0, newArray, 0, length); return newArray; }

240

Au cur de Java 2 - Notions fondamentales

/** * Une mthode permettant dafficher tous les lments * dans un tableau * @param a Le tableau afficher. Peut tre un tableau object * ou un tableau du type primitif */ static void arrayPrint(Object a) { Class cl = a.getClass(); if (!cl.isArray()) return; Class componentType = cl.getComponentType(); int length = Array.getLength(a); System.out.print(componentType.getName() +"["+length+"] = { "); for (int i = 0; i < Array.getLength(a); i++) System.out.print(Array.get(a, i) + " "); System.out.println("}"); } } java.lang.reflect.Array 1.1 static Object get(Object array, int index) static xxx getXxx(Object array, int index)

(xxx est lun des types primitifs boolean, byte, char, double, float, int, long, short). Ces mthodes renvoient la valeur du tableau donn, stocke lindex donn.

static void set(Object array, int index, Object newValue) static setXxx(Object array, int index, xxx newValue)

(xxx est lun des types primitifs boolean, byte, char, double, float, int, long, short). Ces mthodes stockent une nouvelle valeur dans le tableau donn, lindex donn.

static int getLength(Object array)

Renvoie la longueur du tableau donn.


static Object newInstance(Class componentType, int length) static Object newInstance(Class componentType, int[] lengths)

Renvoie un nouveau tableau du type de composant donn avec les dimensions donnes.

Les pointeurs de mthodes


A premire vue, Java ne possde pas de pointeurs de mthodes ils permettent de fournir ladresse dune mthode une autre mthode, an que la seconde puisse appeler la premire. En fait, les concepteurs du langage ont prcis que les pointeurs de mthodes taient dangereux et que les interfaces Java (dont nous parlerons au prochain chapitre) constituaient une meilleure solution. En ralit, depuis Java 1.1, Java dispose de pointeurs de mthodes, qui sont une consquence (peut-tre accidentelle) du dveloppement du package de rexion.
INFO
Parmi les extensions non standard du langage que Microsoft a ajout son driv de Java, J++ (et son successeur, C#), on trouve un autre type de pointeur de mthode, appel dlgu, qui est diffrent de la classe Method que nous avons vue dans cette section. Cependant, les classes internes (que nous verrons au chapitre suivant) constituent une construction plus utile que les dlgus.

Chapitre 5

Lhritage

241

Pour voir luvre les pointeurs de mthode, rappelez-vous que vous pouvez inspecter un champ dun objet laide de la mthode get de la classe Field. Pour sa part, la classe Method dispose dune mthode invoke permettant dappeler la mthode enveloppe dans lobjet Method. La signature de la mthode invoke est la suivante :
Object invoke(Object obj, Object... args)

Le premier paramtre est implicite et les autres objets fournissent les paramtres explicites. Avant la version Java SE 5.0, vous deviez transmettre un tableau dobjets ou null si la mthode ne disposait pas de paramtres explicites. Dans le cas dune mthode statique, le premier paramtre est ignor il peut recevoir la valeur null. Par exemple, si m1 reprsente la mthode getName de la classe Employee, le code suivant vous montre comment lappeler :
String n = (String) m1.invoke(harry);

A limage des mthodes get et set du champ Field, un problme se pose si le paramtre ou le type de rsultat est non pas une classe, mais un type primitif. Il faut soit se reposer sur lautoboxing soit, avant Java SE 5.0, envelopper tout type primitif dans sa classe enveloppe correspondante. Inversement, si le type de retour est un type primitif, la mthode invoke renverra plutt le type enveloppe. Supposons que m2 reprsente la mthode getSalary de la classe Employee. Lobjet renvoy est alors un objet Double, vous devez donc le transtyper en consquence. Depuis Java SE 5.0, lunboxing automatique gre les autres oprations :
double s = (Double) m2.invoke(harry);

Comment obtenir un objet Method? Il est bien videmment possible dappeler getDeclaredMethods, qui renvoie un tableau dobjets Method dans lequel on recherchera la mthode dsire. On peut galement appeler la mthode getMethod de la classe Class. Elle est comparable getField, qui reoit une chane de caractres reprsentant un nom de champ et renvoie un objet Field. Cependant, puisquil peut exister plusieurs mthodes homonymes, il faut tre certain dobtenir la bonne. Cest la raison pour laquelle il faut galement fournir les types de paramtres de la mthode dsire. La signature de getMethod est :
Method getMethod(String name, Class... parameterTypes)

Voici, par exemple, la manire dobtenir des pointeurs sur les mthodes getName et raiseSalary de la classe Employee :
Method m1 = Employee.class.getMethod("getName"); Method m2 = Employee.class.getMethod("raiseSalary", double.class);

Avant Java SE 5.0, vous deviez emballer les objets Class dans un tableau ou fournir null sil ny avait pas de paramtres). Maintenant que vous connaissez les rgles dutilisation des objets Method, nous allons les mettre en application. Le programme du Listing 5.8 afche une table de valeurs pour des fonctions mathmatiques comme Math.sqrt ou Math.sin. Lafchage ressemble ceci :
public static native double java.lang.Math.sqrt(double) 1.0000 | 1.0000 2.0000 | 1.4142 3.0000 | 1.7321 4.0000 | 2.0000 5.0000 | 2.2361

242

Au cur de Java 2 - Notions fondamentales

6.0000 7.0000 8.0000 9.0000 10.0000

| | | | |

2.4495 2.6458 2.8284 3.0000 3.1623

Le code dafchage dune table est bien sr indpendant de la fonction relle qui safche sous cette forme :
double dx = (to - from) / (n - 1); for (double x = from; x <= to; x += dx) { double y = (Double) f.invoke(null,x); System.out.printf("%10.4f | %10.4f%n", x, y); }

Ici, f est un objet de type Method. Le premier paramtre de invoke est null, car nous appelons une mthode statique. Pour tabuler la fonction Math.sqrt, nous dnissons f sur :
Math.class.getMethod("sqrt", double.class)

Cest la mthode de la classe Math dont le nom est sqrt et qui a un seul paramtre de type double. Le Listing 5.9 prsente le code complet du tabulateur gnrique et quelques tests.
Listing 5.9 : MethodPointerTest.java
import java.lang.reflect.*; /** * Ce programme montre comment appeler des mthodes via la rflexion. * @version 1.1 2004-02-21 * @author Cay Horstmann */ public class MethodPointerTest { public static void main(String[] args) throws Exception { // obtenir les pointeurs sur // les mthodes square et sqrt Method square = MethodPointerTest.class.getMethod("square", double.class); Method sqrt = Math.class.getMethod("sqrt", double.class); // afficher les tables de valeurs x- et yprintTable(1, 10, 10, square); printTable(1, 10, 10, sqrt); } /** * Renvoie le carr dun nombre * @param x Un nombre * @return x au carr */ public static double square(double x) { return x * x; }

Chapitre 5

Lhritage

243

/** * Affiche une table avec des valeurs x et y pour une mthode * @param from La limite infrieure des valeurs x * @param to La limite suprieure des valeurs x * @param n Le nombre de ranges dans la table * @param f Une mthode avec un paramtre double et * une valeur renvoye double */ public static void printTable(double from, double to, int n, Method f) { // Affiche la mthode comme en-tte de la table System.out.println(f); double dx = (to - from) / (n - 1); for (double x = from; x <= to; x += dx) { try { double y = (Double) f.invoke(null, x); System.out.printf("%10.4f | %10.4f%n", x, y); } catch (Exception e) { e.printStackTrace(); } } } }

Cet exemple est clair : on peut accomplir avec les objets Method tout ce qui est ralisable avec les pointeurs de fonction du C (ou les dlgus du C#). A cette diffrence prs, toutefois : en langage C, ce style de programmation est gnralement considr comme incongru et dangereux. Que se passet-il ici lorsque vous appelez une mthode en lui passant un paramtre incorrect ? La mthode invoke dclenche une exception. Les paramtres et le rsultat de la mthode invoke sont ncessairement de type Object. Cela signie que lon doit effectuer un bon nombre de transtypages. En consquence, le compilateur na pas loccasion de vrier votre code et les erreurs napparaissent que durant les tests, lorsquelles sont plus difciles corriger. De plus, une routine qui exploite la rexion pour obtenir un pointeur de mthode est sensiblement plus lente quun code qui appelle les mthodes directement. Nous vous recommandons de nemployer les objets Method dans vos programmes quen cas dabsolue ncessit. Lutilisation des interfaces et des classes internes est de loin prfrable. Tout comme les concepteurs de Java, nous vous conseillons de ne pas employer les objets Method pour les fonctions de rappel (callback). Lusage des interfaces pour les fonctions de rappel produit un code plus rapide et plus facile mettre jour (nous reviendrons sur tous ces aspects au prochain chapitre).
java.lang.reflect.Method 1.1

public Object invoke(Object implicitParameter, Object[] explicitParameters)

Appelle la mthode dcrite par cet objet, en transmettant les paramtres donns et en renvoyant la valeur que la mthode renvoie. Pour les mthodes statiques, transfrez null comme paramtre implicite. Transfrez les valeurs des types primitifs en utilisant les enveloppes. Un type primitif renvoie des valeurs qui ne doivent pas tre enveloppes.

244

Au cur de Java 2 - Notions fondamentales

Conseils pour lutilisation de lhritage


Nous terminerons ce chapitre par quelques conseils que nous considrons utiles sur lutilisation de lhritage. 1. Placez les oprations communes et les champs communs dans la superclasse. Cest la raison pour laquelle nous avons plac le champ name dans la classe Person au lieu de le dupliquer dans Employee et Student. 2. Nutilisez pas de champs protgs. Certains programmeurs pensent que le fait de dnir la plupart des champs dinstance comme protected, "au cas o", est une bonne ide, an que les sous-classes puissent accder ces champs en cas de besoin. Pourtant, le mcanisme protected napporte pas une grande protection, pour deux raisons. Tout dabord, le jeu des sous-classes est illimit nimporte qui peut former une sous-classe partir de vos classes, puis crire un code permettant daccder directement aux champs dinstance protgs, rompant ainsi lencapsulation. Et, dans Java, toutes les classes dans le mme package ont accs aux champs protgs, quil sagisse ou non de sous-classes. Les mthodes protected peuvent toutefois tre utiles pour indiquer celles qui ne sont pas prvues pour un usage gnral et doivent tre rednies dans les sous-classes. La mthode clone en est un bon exemple. 3. Utilisez lhritage pour dterminer la relation dappartenance ("est"). Lhritage permet de rduire le code. Il est parfois employ de manire abusive. Supposons que nous ayons besoin dune classe Contractor. Les contractants ont un nom et une date dembauche, mais ils ne peroivent pas de salaire. En revanche, ils sont pays lheure et ne restent pas assez longtemps pour obtenir une augmentation. La tentation est grande de former une sousclasse Contractor de la classe Employee et dy ajouter un champ hourlyWage (tarif horaire).
class Contractor extends Employee { . . . private double hourlyWage; }

Ce nest pourtant pas une bonne ide, car dsormais chaque objet contractant va possder la fois un champ salaire et un champ tarif horaire. Cela vous posera de nombreux problmes lorsque vous implmenterez des mthodes pour lafchage des ches de paie ou des formulaires dimpt. Finalement, vous crirez plus de code que si vous naviez pas utilis lhritage. La relation contractant/employ ne passe pas le test dappartenance ("est"). Un contractant nest pas un type particulier demploy. 4. Nemployez pas lhritage moins que toutes les mthodes hrites ne soient valables. Supposons que vous dsiriez crire une classe Holiday. Chaque jour de cong est un jour et les jours peuvent tre exprims comme des instances de la classe GregorianCalendar, nous pouvons donc employer lhritage.
class Holiday extends GregorianCalendar { . . . }

Malheureusement, le jeu des congs nest pas ferm sous les oprations hrites. Lune des mthodes publiques de GregorianCalendar est add. Et add peut transformer un cong en non-cong :
Holiday christmas; christmas.add(Calendar.DAY_OF_MONTH, 12);

Chapitre 5

Lhritage

245

Lhritage nest donc pas indiqu dans ce cas. 5. Ne modiez pas le comportement attendu lorsque vous remplacez une mthode. Le principe de substitution sapplique non seulement la syntaxe mais surtout au comportement. Lorsque vous remplacez une mthode, vous ne devez pas changer son comportement sans raison valable. Le compilateur ne peut pas vous aider, il ne peut pas vrier si vos nouvelles dnitions sont adaptes. Vous pouvez "rparer" le problme de la mthode add dans la classe Holiday en rednissant add, par exemple pour quil nait aucun rle, pour dclencher une exception ou pour passer au prochain holiday. Toutefois, cette rparation viole le principe de substitution. La suite dinstructions
int d1 = x.get(Calendar.DAY_OF_MONTH); x.add(Calendar.DAY_OF_MONTH, 1); int d2 = x.get(Calendar.DAY_OF_MONTH); System.out.println(d2 - d1);

devrait avoir le comportement attendu, que x soit ou non du type GregorianCalendar ou Holiday. Bien entendu, cest l quest le problme. Les gens raisonnables ou non peuvent argumenter loisir sur le comportement attendu. Certains auteurs avancent, par exemple, que le principe de substitution exige que Manager.equals ignore le champ de bonus car Employee.equals lignore. Ces discussions sont toujours inutiles si elles nont pas dobjectif concret. Au nal, ce qui importe, cest que vous ne dtourniez pas lintention du premier concepteur lorsque vous remplacez des mthodes dans les sous-classes. 6. Utilisez le polymorphisme plutt que linformation de type. Chaque fois que vous rencontrez du code ayant cette forme :
if (x est de type1) action1(x); else if (x est de type2) action2(x);

pensez au polymorphisme. Est-ce que action1 et action2 reprsentent un concept commun ? Si oui, faites-en une mthode dune superclasse ou une interface commune aux deux types. Vous pourrez alors appeler simplement :
x.action();

et excuter laction approprie grce au mcanisme de rpartition dynamique inhrent au polymorphisme. Le code qui emploie les interfaces ou les mthodes polymorphes est plus facile mettre jour et amliorer que le code qui utilise des tests multiples. 7. Nabusez pas de la rexion. Le mcanisme de rexion vous permet dcrire des programmes tonnamment gnralistes, en dtectant les champs et les mthodes au moment de lexcution. Cette capacit peut tre extrmement utile pour la programmation systme, mais elle nest gnralement pas approprie pour les applications. La rexion est fragile le compilateur ne peut pas vous aider dtecter les erreurs de programmation. Toutes les erreurs sont trouves au moment de lexcution et dclenchent des exceptions.

246

Au cur de Java 2 - Notions fondamentales

Vous avez maintenant vu que Java accepte les bases de la programmation oriente objet : classes, hritage et polymorphisme. Au prochain chapitre, nous aborderons deux sujets avancs trs importants pour utiliser efcacement Java : les interfaces et les classes internes.

6
Interfaces et classes internes
Au sommaire de ce chapitre

Interfaces Clonage dobjets Interfaces et callbacks Classes internes Proxies


Vous connaissez maintenant tous les lments de base qui concernent la programmation oriente objet en Java. Ce chapitre prsentera plusieurs techniques avances, qui sont trs couramment utilises. Malgr leur nature peu vidente, il vous faudra les matriser pour complter votre panoplie doutils Java. La premire est appele interface, une faon de dcrire ce que les classes doivent faire, sans prciser comment elles doivent le faire. Une classe peut implmenter une ou plusieurs interfaces. Vous pouvez ensuite utiliser les objets de ces classes "implmentantes" chaque fois que la conformit avec linterface est ncessaire. Aprs les interfaces, nous verrons le clonage (ou copie intgrale) dun objet. Le clone dun objet est un nouvel objet qui a le mme tat que loriginal. En particulier, vous pouvez modier le clone sans affecter loriginal. Nous tudierons ensuite le mcanisme des classes internes. Les classes internes sont techniquement quelque peu complexes elles sont dnies lintrieur dautres classes et leurs mthodes peuvent accder aux champs de la classe qui les contient. Ces classes sont utiles pour la conception de collections de classes devant cooprer. Elles permettent dcrire du code concis, de qualit "professionnelle", qui permet de grer les vnements de linterface utilisateur graphique. Ce chapitre conclura avec une discussion sur les proxies, objets qui implmentent des interfaces arbitraires. Un proxy est une construction trs spcialise, utile pour la cration doutils systme. Vous pouvez sauter cette section dans limmdiat.

248

Au cur de Java 2 - Notions fondamentales

Interfaces
Dans le langage Java, une interface nest pas une classe, mais un jeu de conditions pour les classes qui veulent se conformer linterface. Gnralement, le fournisseur dun service annonce : "Si votre classe se conforme une interface particulire, je vais fournir le service". Examinons un exemple concret. La mthode sort de la classe Arrays promet de trier un tableau dobjets, mais une condition : les objets doivent appartenir des classes qui implmentent linterface Comparable. Voici quoi ressemble linterface Comparable :
public interface Comparable { int compareTo(Object other); }

Cela signie que toute classe qui implmente linterface Comparable doit possder une mthode compareTo et que la mthode doit accepter un paramtre Object et renvoyer un entier.
INFO
Depuis Java SE 5.0, linterface Comparable a t amliore pour devenir un type gnrique :
public interface Comparable<T> { int compareTo(T other); // le paramtre a le type T }

Par exemple, une classe qui implmente Comparable<Employee> doit fournir une mthode :
int compareTo(Employee other)

Vous pouvez toujours utiliser le type "brut" Comparable sans paramtre de type, mais vous devrez alors transtyper manuellement le paramtre de la mthode compareTo sur le type souhait.

Toutes les mthodes dune interface sont automatiquement public. Cest la raison pour laquelle il nest pas ncessaire de fournir le mot cl public lorsque vous dclarez une mthode dans une interface. Il existe une condition supplmentaire : lors de lappel x.compareTo(y), la mthode compareTo doit en ralit tre capable de comparer deux objets et dindiquer lequel entre x ou y est le plus grand. La mthode doit renvoyer un nombre ngatif si x est plus petit que y, zro sils sont gaux, et un nombre positif dans les autres cas. Cette interface particulire a une seule mthode. Certaines interfaces ont plus dune mthode. Comme vous le verrez plus tard, les interfaces peuvent aussi dnir des constantes. Ce qui importe, en fait, est ce que les interfaces ne peuvent pas fournir. Les interfaces nont jamais de champs dinstance, et les mthodes ne sont jamais implmentes dans linterface. La fourniture des champs dinstance et des implmentations de mthode est prise en charge par les classes qui implmentent linterface. Vous pouvez imaginer une interface comme tant semblable une classe abstraite sans champs dinstance. Il y a toutefois certaines diffrences entre ces deux concepts nous les tudierons plus en dtail ultrieurement.

Chapitre 6

Interfaces et classes internes

249

Supposons maintenant que nous voulions utiliser la mthode sort de la classe Arrays pour trier un tableau dobjets Employee. La classe Employee doit donc implmenter linterface Comparable. Pour quune classe implmente une interface, vous devez excuter deux tapes :
m m

dclarer que votre classe a lintention dimplmenter linterface donne ; fournir les dnitions pour toutes les mthodes dans linterface.
class Employee implements Comparable

Pour dclarer quune classe implmente une interface, employez le mot cl implements : Bien entendu, la classe Employee doit maintenant fournir la mthode compareTo. Supposons que nous voulions comparer le salaire des employs. Voici une mthode compareTo qui renvoie 1 si le salaire du premier employ est infrieur celui du second, 0 sils sont gaux et 1 dans les autres cas :
public int compareTo(Object otherObject) { Employee other = (Employee) otherObject; if (salary < other.salary) return -1; if (salary > other.salary) return 1; return 0; }

ATTENTION
Dans la dclaration de linterface, la mthode compareTo na pas t dclare public, car toutes les mthodes dans une interface sont automatiquement publiques. Cependant, lors de limplmentation de linterface, vous devez dclarer la mthode comme public. Sinon, le compilateur suppose que la visibilit de la mthode se situe au niveau du package ce qui est la valeur par dfaut pour une classe. Le compilateur proteste alors, car vous essayez de fournir un privilge daccs plus faible.

Avec Java SE 5.0, nous pouvons faire un peu mieux. Nous dciderons dimplmenter plutt le type dinterface Comparable<Employee> :
class Employee implements Comparable<Employee> { public int compareTo(Employee other) { if (salary < other.salary) return -1; if (salary > other.salary) return 1; return 0; } . . . }

Sachez que le transtypage disgracieux du paramtre Object a disparu.


ASTUCE
La mthode compareTo de linterface Comparable renvoie un entier. Si les objets ne sont pas gaux, la valeur ngative ou positive renvoye na aucune importance. Cette largesse peut tre utile si vous comparez des champs entiers. Supposons, par exemple, que chaque employ ait un unique identicateur entier id, et que vous vouliez trier les employs par numro dID. Vous pouvez simplement renvoyer id - other.id. Cette valeur sera ngative si le premier ID est infrieur au second, gale 0 sils sont identiques et positive dans les autres cas. Lindication est sufsante ; la plage des entiers doit tre sufsamment petite pour quil ne se produise pas un dpassement lors de la

250

Au cur de Java 2 - Notions fondamentales

soustraction. Si vous savez que les ID ont une valeur non ngative ou une valeur absolue qui est au maximum (Integer.MAX_VALUE - 1) / 2, vous ne risquez rien. Cette astuce de soustraction nest videmment pas valable pour les nombres virgule ottante. La diffrence salary - other.salary peut alors tre arrondie 0 si les salaires sont sufsamment proches bien que non identiques.

Vous avez vu ce quune classe doit faire pour tirer parti du service de tri elle doit simplement implmenter une mthode compareTo. Cest tout fait raisonnable. Il doit y avoir un moyen pour que la mthode sort puisse comparer les objets. Mais pourquoi la classe Employee ne peut-elle tout simplement fournir une mthode compareTo sans implmenter linterface Comparable? Les interfaces sont ncessaires, car le langage Java est fortement typ. Lors dun appel de mthode, le compilateur doit pouvoir vrier que la mthode existe rellement. Quelque part dans la mthode sort, on trouvera des instructions comme celles-ci :
if (a[i].compareTo(a[j]) > 0) { // rorganiser a[i] et a[j] . . . }

Le compilateur doit savoir que a[i] possde rellement une mthode compareTo. Si a est un tableau dobjets Comparable, lexistence de la mthode est conrme, car chaque classe qui implmente linterface Comparable doit fournir la mthode.
INFO
Il serait logique que la mthode sort dans la classe Arrays soit dnie pour accepter un tableau Comparable[], an que le compilateur puisse protester si quelquun appelle sort avec un tableau dont le type dlment nimplmente pas linterface Comparable. Malheureusement, il nen est rien. La mthode sort accepte un tableau Object[] et a recours un transtypage bizarre :
// dans la bibliothque standard -- Non recommand if (((Comparable) a[i]).compareTo(a[j]) > 0) { // rorganiser a[i] et a[j] . . . }

Si a[i] nappartient pas une classe qui implmente linterface Comparable, la machine virtuelle dclenche une exception.

Le Listing 6.1 dcrit la totalit du code ncessaire pour le tri dun tableau demploys.
Listing 6.1 : EmployeeSortTest.java
import java.util.*; /** * Ce programme prsente lutilisation de linterface Comparable. * @version 1.30 2004-02-27 * @author Cay Horstmann */

Chapitre 6

Interfaces et classes internes

251

public class EmployeeSortTest { public static void main(String[] args) { Employee[] staff = new Employee[3]; staff[0] = new Employee("Harry Hacker", 35000); staff[1] = new Employee("Carl Cracker", 75000); staff[2] = new Employee("Tony Tester", 38000); Arrays.sort(staff); // afficher les informations pour tous les objets Employee for (Employee e: staff) System.out.println("name="+e.getName() +",salary="+e.getSalary()); } } class Employee implements Comparable<Employee> { public Employee(String n, double s) { name = n; salary = s; } public String getName() { return name; } public double getSalary() { return salary; } public void raiseSalary(double byPercent) { double raise = salary * byPercent / 100; salary += raise; } /** * Compare les salaires des employs * @param other Un autre objet Employee * @return une valeur ngative si cet employ * a un salaire infrieur celui de otherObject, * 0 si les salaires sont identiques, * une valeur positive dans les autres cas */ public int compareTo(Employee other) { if (salary < other.salary) return -1; if (salary > other.salary) return 1; return 0; }

252

Au cur de Java 2 - Notions fondamentales

private String name; private double salary; } java.lang.Comparable<T> 1.0 int compareTo(T other)

Compare cet objet dautres et renvoie un entier ngatif si cet objet est infrieur lautre, 0 sils sont gaux et un entier positif dans les autres cas.
java.util.Arrays 1.2 static void sort(Object[] a)

Trie les lments du tableau a, laide dun algorithme mergesort personnalis. Tous les lments du tableau doivent appartenir des classes qui implmentent linterface Comparable; ils doivent aussi tous tre comparables entre eux.
INFO
Selon les standards du langage : "Limplmenteur doit sassurer que sgn(x.compareTo(y)) = sgn(y.compare To(x)) pour toutes les occurrences de x et y (cela implique que x.compareTo(y) doit dclencher une exception si y.compareTo(x) dclenche une exception)." Ici, "sgn" est le signe dun nombre : sgn(n) vaut 1 si n est ngatif, 0 si n gale 0 et 1 si n est positif. En bon franais, si vous inversez les paramtres de compareTo, le signe (mais pas ncessairement la valeur relle) du rsultat doit aussi tre invers. Comme avec la mthode equals, des problmes peuvent survenir lorsque lon fait appel lhritage. Etant donn que Manager tend Employee, il implmente Comparable<Employee> et non pas Comparable <Manager>. Si Manager choisit de remplacer compareTo, il doit tre prpar comparer les directeurs aux employs. Il ne peut pas simplement transtyper lemploy en directeur :
class Manager extends Employee { public int compareTo(Employee other) { Manager otherManager = (Manager) other; // NON . . . } . . . }

La rgle d"antisymtrie" est viole. Si x est un Employee et y un Manager, lappel x.compareTo(y) ne dclenche pas dexception il compare simplement x et y en tant quemploys. Mais linverse, y.compareTo(x) dclenche une exception ClassCastException. Cest la mme situation quavec la mthode equals vue au Chapitre 5 ; la solution est la mme. Il existe deux scnarios distincts. Si les sous-classes ont des notions diffrentes de la comparaison, vous devez interdire la comparaison des objets appartenant des classes diffrentes. Chaque mthode compareTo devrait commencer par le test
if (getClass()!= other.getClass()) throw new ClassCastException();

Sil existe un algorithme commun pour comparer les objets de sous-classe, fournissez simplement une seule mthode compareTo dans la superclasse et dclarez-la sous la forme final. Supposons par exemple que vous vouliez que les directeurs aient une meilleure situation que les employs ordinaires, en dehors du salaire. Quadviendra-t-il des autres sous-classes comme Executive et Secretary ? Si vous devez tablir un ordre hirarchique, fournissez une mthode dans la classe Employee (par exemple rank). Amenez

Chapitre 6

Interfaces et classes internes

253

chaque sous-classe remplacer rank et implmentez une seule mthode compareTo qui prendra en compte les valeurs du rang.

Proprits des interfaces


Les interfaces ne sont pas des classes. En particulier, vous ne devez jamais utiliser loprateur new pour instancier une interface :
x = new Comparable(. . .); // ERREUR

Cependant, bien que vous ne puissiez pas construire des objets interface, vous pouvez toujours dclarer des variables interface :
Comparable x; // OK

Une variable interface doit faire rfrence un objet dune classe qui implmente linterface :
x = new Employee(. . .); // OK du moment que Employee implmente Comparable

Ensuite, tout comme vous utilisez instanceof pour vrier si un objet appartient une classe spcique, vous pouvez utiliser instanceof pour vrier si un objet implmente une interface :
if (unObjet instanceof Comparable) { . . . }

Tout comme vous pouvez construire des hirarchies de classes, vous pouvez tendre des interfaces. Cela autorise plusieurs chanes dinterfaces allant dun plus large degr de gnralit un plus petit degr de spcialisation. Supposez, par exemple, que vous ayez une interface appele Moveable :
public interface Moveable { void move(double x, double y); }

Vous pourriez alors imaginer une interface appele Powered qui ltendrait :
public interface Powered extends Moveable { double milesPerGallon(); }

Bien que vous ne puissiez pas mettre des champs dinstance ou des mthodes statiques dans une interface, vous pouvez fournir des constantes lintrieur. Par exemple :
public interface Powered extends Moveable { double milesPerGallon(); double SPEED_LIMIT = 95; // une constante public static final }

Tout comme les mthodes dans une interface sont automatiquement public, les champs sont toujours public static final.
INFO
Il est lgal de qualier les mthodes dinterface de public, et les champs de public static final. Certains programmeurs le font, par habitude ou pour une plus grande clart. Cependant, les spcications du langage Java recommandent de ne pas fournir de mots cls redondants et nous respectons cette recommandation.

254

Au cur de Java 2 - Notions fondamentales

Certaines interfaces dnissent simplement les constantes et pas de mthodes. Par exemple, la bibliothque standard contient une interface SwingConstants qui dnit les constantes NORTH, SOUTH, HORIZONTAL, etc. Toute classe qui choisit dimplmenter linterface SwingConstants hrite automatiquement de ces constantes. Ses mthodes peuvent faire simplement rfrence NORTH au lieu du SwingConstants.NORTH, plus encombrant. Toutefois, cette utilisation des interfaces semble plutt dgnre, et nous ne la recommandons pas. Alors que chaque classe ne peut avoir quune superclasse, les classes peuvent implmenter plusieurs interfaces. Cela vous donne un maximum de exibilit pour la dnition du comportement dune classe. Par exemple, le langage Java a une importante interface intgre, appele Cloneable (nous reviendrons en dtail sur cette interface dans la section suivante). Si votre classe implmente Cloneable, la mthode clone dans la classe Object ralisera une copie dle des objets de votre classe. Supposez, par consquent, que vous vouliez disposer des fonctions de clonage et de comparaison ; il vous sufra dimplmenter les deux interfaces :
class Employee implements Cloneable, Comparable

Utilisez des virgules pour sparer les interfaces qui dcrivent les caractristiques que vous voulez fournir.

Interfaces et classes abstraites


Si vous avez lu la section concernant les classes abstraites au Chapitre 5, vous vous demandez peuttre pourquoi les concepteurs du langage Java se sont compliqu la tche en introduisant le concept dinterface. Pourquoi ne pas faire de Comparable une classe abstraite ?
abstract class Comparable // Pourquoi pas? { public abstract int compareTo(Object other); }

La classe Employee pourrait simplement tendre cette classe abstraite et fournir la mthode compareTo :
class Employee extends Comparable // Pourquoi pas? { public int compareTo(Object other) { . . . } }

Il y a, malheureusement, un problme majeur en ce qui concerne lutilisation dune classe de base abstraite pour exprimer une proprit gnrique. Une classe ne peut tendre quune seule classe. Supposons quune autre classe drive dj de la classe Employee, disons Person. La classe Employee ne peut alors tendre de seconde classe :
class Employee extends Person, Comparable // ERREUR

En revanche, chaque classe peut implmenter autant dinterfaces quelle le souhaite :


class Employee extends Person implements Comparable // OK

Les autres langages de programmation, en particulier C++, autorisent une classe avoir plus dune superclasse. Cette fonctionnalit est appele hritage multiple. Les concepteurs de Java ont choisi de ne pas la prendre en charge, car elle rend le langage soit trs complexe (comme dans C++), soit moins efcace (comme dans Eiffel).

Chapitre 6

Interfaces et classes internes

255

Les interfaces, elles, procurent la plupart des avantages de lhritage multiple tout en vitant les risques de complexit et dinefcacit.
INFO C++
Le langage C++ prend en charge lhritage multiple, avec tous les tracas qui laccompagnent, comme les classes de base virtuelles, les rgles de prdominance et les transtypages de pointeurs transversaux. Peu de programmeurs C++ exploitent rellement lhritage multiple et certains afrment quil ne devrait jamais tre utilis. Dautres conseillent de nemployer lhritage multiple que pour lhritage de style "mix-in". Dans ce style, une classe de base primaire dcrit lobjet parent et des classes de base additionnelles (les mix-in) peuvent fournir des caractristiques complmentaires. Ce style est comparable une classe Java ayant une seule classe de base et des interfaces additionnelles. Nanmoins, en C++, les mix-in peuvent ajouter un comportement par dfaut, ce que ne peuvent pas faire les interfaces Java.

Clonage dobjets
Lorsque vous faites une copie dune variable, loriginal et la copie sont des rfrences au mme objet (voir Figure 6.1). Cela signie que tout changement apport lune des variables affecte aussi lautre.
Employee original = new Employee("John Public", 50000); Employee copy = original; copy.raiseSalary(10); // ae--a aussi chang loriginal

Si vous voulez que copy soit un nouvel objet qui, au dpart, est identique original, mais dont ltat peut diverger avec le temps, appelez la mthode clone.
Employee copy = original.clone(); copy.raiseSalary(10); // OK-original inchang

En ralit, les choses ne sont pas si simples. La mthode clone est une mthode protected de Object, ce qui signie que votre code ne peut pas simplement lappeler directement. Seule la classe Employee peut cloner des objets Employee. Et il y a une bonne raison cela. Rchissons la manire dont la classe Object peut implmenter clone. Elle ne sait rien de lobjet et ne peut effectuer quune copie champ champ. Si tous les champs de donnes de lobjet sont des nombres ou appartiennent un autre type de base, la copie des champs ne posera pas de problme. Mais si lobjet contient des rfrences des sous-objets, la copie des champs vous donnera une autre rfrence au sous-objet. En consquence, loriginal et les objets clons continueront partager certaines informations. Pour visualiser ce phnomne, considrons la classe Employee, prsente au Chapitre 4. La Figure 6.2 montre ce qui se passe lorsque vous appelez la mthode clone de la classe Object pour cloner un tel objet Employee. Vous pouvez constater que lopration de clonage par dfaut est "supercielle" (shallow) elle ne clone pas les objets qui sont rfrencs lintrieur dautres objets. Le fait que cette copie soit supercielle est-il important ? Cela dpend. Si le sous-objet qui est partag entre loriginal et le clone superciel est inaltrable, le partage est sr. Cela se produit certainement si le sous-objet appartient une classe inaltrable, telle que String. Le sous-objet peut aussi tout simplement rester constant pendant toute la dure de vie de lobjet, sans que des mthodes daltration ne laffectent, ni quaucune mthode ne produise de rfrence vers lui.

256

Au cur de Java 2 - Notions fondamentales

Figure 6.1
Copie et clonage.
original = copy =

Copie Employee

Clonage original = Employee

copy =

Employee

Figure 6.2
Une copie "shallow".

original =

Employee name = salary = hireDay = 50000.0

String

copy =

Employee name = salary = hireDay = 50000.0

Date

Chapitre 6

Interfaces et classes internes

257

Cependant, assez frquemment, les sous-objets sont altrables et vous devez rednir la mthode clone pour raliser une copie "intgrale" (deep copy) qui clone aussi les sous-objets. Dans notre exemple, le champ hireDay est un objet Date, qui lui est altrable. Pour chaque classe, vous devez dcider si oui ou non : 1. La mthode clone par dfaut convient. 2. La mthode clone par dfaut peut tre corrige par un appel de clone sur les sous-objets altrables. 3. La mthode clone ne doit pas tre tente. La troisime option est celle par dfaut. Pour pouvoir choisir la premire ou la deuxime option, une classe doit : 1. Implmenter linterface Cloneable. 2. Rednir la mthode clone avec le modicateur daccs public.
INFO
La mthode clone est dclare protected dans la classe Object an que votre code ne puisse pas simplement appeler unObjet.clone(). Mais les mthodes protges ne sont-elles pas accessibles partir de nimporte quelle sous-classe et toute classe nest-elle pas une sous-classe de Object? Heureusement, les rgles concernant les accs protgs sont plus subtiles (voir Chapitre 5). Une sous-classe peut appeler une mthode clone protge, seulement pour cloner ses propres objets. Vous devez rednir clone comme publique pour permettre aux objets dtre clons par nimporte quelle mthode.

Dans ce cas, lapparence de linterface Cloneable na rien voir avec lutilisation normale des interfaces. En particulier, elle ne spcie pas la mthode clone qui est hrite de la classe Object. Linterface sert purement de balise, indiquant que le concepteur de la classe comprend le processus de clonage. Les objets sont tellement paranoaques en ce qui concerne le clonage, quils gnrent une exception vrie (checked exception), si un objet requiert le clonage, mais nimplmentent pas cette interface.
INFO
Linterface Cloneable fait partie des quelques interfaces de balisage que fournit Java (certains programmeurs les appellent interfaces marqueur). Souvenez-vous que lintrt habituel dune interface telle que Comparable est dassurer quune classe implmente une mthode ou un jeu de mthodes spcique. Une interface de balisage na pas de mthodes ; son seul objectif est de permettre lutilisation de instanceof dans une requte de type :
if (obj instanceof Cloneable) . . .

Nous vous recommandons de ne pas utiliser les interfaces de balisage dans vos programmes.

Mme si limplmentation par dfaut (copie supercielle) de clone est adquate, vous devez toujours implmenter linterface Cloneable, rednir clone comme public, appeler super.clone(). Voici un exemple :
class Employee implements Cloneable {

258

Au cur de Java 2 - Notions fondamentales

// lever le niveau de visibilit public, // changer le type de retour public Employee clone() throws CloneNotSupportedException { Return (Employee) super.clone(); } . . . }

INFO
Avant Java SE 5.0, la mthode clone avait toujours un type de retour Object. Les types de retours covariants de Java SE 5.0 vous permettent de spcier le type de retour correct pour vos mthodes clone.

La mthode clone que vous venez de voir najoute aucune fonctionnalit la copie "shallow" fournie par Object.clone. Elle se contente de rendre la mthode public. Pour raliser une copie intgrale, vous devrez poursuivre lexercice et cloner les champs dinstance altrables. Voici un exemple de mthode clone qui cre une copie intgrale :
class Employee implements Cloneable { . . . public Employee clone() throws CloneNotSupportedException { // appeler Object.clone() Employee cloned = (Employee) super.clone(); // cloner les champs altrables cloned.hireDay = (Date) hireDay.clone() return cloned; } }

La mthode clone de la classe Object menace de lancer une exception CloneNotSupportedException, et ce ds que clone est appele sur un objet dont la classe nimplmente pas linterface Cloneable. Bien entendu, les classes Employee et Date implmentent linterface Cloneable, lexception ne sera donc pas dclenche. Toutefois, le compilateur ne le sait pas. Nous avons donc dclar lexception :
public Employee clone() throws CloneNotSupportedException

Vaudrait-il mieux lintercepter ?


public Employee clone() { try { return super.clone(); } catch (CloneNotSupportedException e) { return null; } // ceci narriverait pas, puisque nous utilisons Cloneable }

Chapitre 6

Interfaces et classes internes

259

Ceci convient bien pour les classes final. Dans les autres cas, il vaut mieux laisser le spcicateur throws sa place. Les sous-classes ont alors loption de lancer une exception CloneNotSupportedException si elles ne prennent pas en compte le clonage. Vous devez prendre garde lorsque vous clonez des sous-classes. Par exemple, une fois que vous avez dni la mthode clone pour la classe Employee, nimporte qui peut aussi cloner les objets Manager. La mthode clone Employee peut-elle assurer cette tche ? Cela dpend des champs de la classe Manager. Dans notre cas, cela ne pose pas de problme, car le champ bonus est du type primitif. Mais Manager pourrait avoir acquis des champs qui exigent une copie intgrale ou qui ne peuvent pas tre clons. Il nest absolument pas garanti que limplmenteur de la sous-classe dispose dun clone xe pour raliser laction adquate. Vous devez donc vous assurer que la mthode clone est dclare comme protge (protected) dans la classe Object. Mais vous naurez pas ce luxe si vous souhaitez que les utilisateurs de vos classes puissent appeler clone. Devez-vous alors implmenter clone dans vos propres classes ? Si vos clients doivent raliser des copies intgrales, probablement oui. Certains auteurs considrent toutefois quil vaut mieux viter totalement clone et implmenter sa place une autre mthode dans ce but. Nous convenons que clone est plutt trange, mais vous rencontrerez les mmes problmes en utilisant une autre mthode. De toute faon, le clonage est moins commun que vous pourriez le penser. Moins de 5 % des classes de la bibliothque standard implmentent clone. Le programme du Listing 6.2 clone un objet Employee, puis invoque deux mthodes daltration. La mthode raiseSalary change la valeur du champ salary, alors que la mthode setHireDay change ltat du champ hireDay. Aucune de ces altrations naffecte lobjet original, car clone a t dnie pour faire une copie intgrale.
INFO
Tous les types de tableaux possdent une mthode clone qui est public et non protected. Vous pouvez lutiliser pour crer un nouveau tableau qui contiendra des copies de tous les lments. Par exemple,
int[] luckyNumbers = { 2, 3, 5, 7, 11, 13 }; int[] cloned = (int[]) luckyNumbers.clone(); cloned[5] = 12; // ne modifie pas luckyNumbers[5]

INFO
Le Chapitre 1 du Volume II montre un autre mcanisme pour cloner les objets laide de la fonction de srialisation dobjet de Java. Ce mcanisme est facile implmenter et sr, mais il nest pas trs efcace.

Listing 6.2 : CloneTest.java


import java.util.*; /** * Ce programme prsente le clonage. * @version 1.10 2002-07-01 * @author Cay Horstmann */ public class CloneTest {

260

Au cur de Java 2 - Notions fondamentales

public static void main(String[] args) { try { Employee original = new Employee("John Q. Public", 50000); original.setHireDay(2000, 1, 1); Employee copy = original.clone(); copy.raiseSalary(10); copy.setHireDay(2002, 12, 31); System.out.println("original="+original); System.out.println("copy="+copy); } catch (CloneNotSupportedException e) { e.printStackTrace(); } } } class Employee implements Cloneable { public Employee(String n, double s) { name = n; salary = s; hireDay = new Date(); } public Employee clone() throws CloneNotSupportedException { // appeler Object.clone() Employee cloned = (Employee) super.clone(); // cloner les champs altrables cloned.hireDay = (Date) hireDay.clone(); return cloned; } /** * Affecte une date donne au jour dembauche (hireday) * @param year Lanne du jour dembauche * @param month Le mois du jour dembauche * @param day Le jour dembauche */ public void setHireDay(int year, int month, int day) { Date newHireDay = new GregorianCalendar(year, month - 1, day).getTime(); // Exemple de mutation de champ dinstance hireDay.setTime(newHireDay.getTime()); } public void raiseSalary(double byPercent) { double raise = salary * byPercent / 100; salary += raise; }

Chapitre 6

Interfaces et classes internes

261

public String toString() { return "Employee[name="+name +",salary="+salary +",hireDay="+hireDay() +"]"; } private String name; private double salary; private Date hireDay; }

Interfaces et callbacks
Un pattern habituel de la programmation est celui des callbacks. Dans ce pattern, vous indiquez laction raliser en cas de survenue dun vnement particulier. Vous pourriez vouloir, par exemple, quune action particulire survienne lorsque lon clique sur un bouton ou que lon slectionne un lment de menu. Toutefois, comme vous navez pas encore vu comment implmenter les interfaces utilisateur, nous envisagerons une situation similaire, mais plus simple. Le package javax.swing contient une classe Timer, utile pour tre averti de lexpiration dun dlai imparti. Par exemple, si une partie de votre programme contient une horloge, vous pouvez demander tre averti chaque seconde, de manire pouvoir mettre jour lafchage de lhorloge. Lorsque vous construisez un minuteur, vous dnissez lintervalle de temps et lui indiquez ce quil doit faire lorsque le dlai est coul. Comment indiquer au minuteur ce quil doit faire ? Dans de nombreux langages de programmation, vous fournissez le nom dune fonction que le minuteur doit appeler priodiquement. Toutefois, les classes de la bibliothque Java adoptent une approche oriente objet. Vous transfrez un objet dune classe quelconque. Le minuteur appelle alors lune des mthodes de cet objet. Transfrer un objet est une opration plus exible que transfrer une fonction car lobjet peut transporter des informations complmentaires. Bien entendu, le minuteur doit savoir quelle mthode appeler. Vous devez spcier un objet dune classe qui implmente linterface ActionListener du package java.awt.event. Voici cette interface :
public interface ActionListener { void actionPerformed(ActionEvent event); }

Le minuteur appelle la mthode actionPerformed lorsque le dlai a expir.


INFO C++
Comme vous lavez vu au Chapitre 5, Java possde lquivalent des pointeurs de fonction, savoir les objets Method. Ils sont toutefois difciles utiliser, plus lents et la scurit des types ne peut pas tre vrie au moment de la compilation. Ds que vous utilisez un pointeur de fonction en C++, envisagez dutiliser une interface en Java.

262

Au cur de Java 2 - Notions fondamentales

Supposons que vous souhaitiez afcher le message "At the tone, the time is " ( la tonalit, il sera), suivi dun bip, et ce toutes les 10 secondes. Dnissez une classe qui implmente linterface ActionListener. Vous placerez alors toutes les instructions que vous voulez voir excuter dans la mthode actionPerformed :
class TimePrinter implements ActionListener { public void actionPerformed(ActionEvent event) { Date now = new Date(); System.out.println("At the tone, the time is " + now); Toolkit.getDefaultToolkit().beep(); } }

Remarquez le paramtre ActionEvent de la mthode actionPerformed. Il apporte des informations sur lvnement, comme lobjet source qui la gnr (voir le Chapitre 8 pour en savoir plus). Toutefois, il nest pas important dobtenir des informations dtailles sur ce programme et vous pouvez ignorer le paramtre en toute scurit. Construisez ensuite un objet de cette classe et transmettez-le au constructeur Timer :
ActionListener listener = new TimePrinter(); Timer t = new Timer(10000, listener);

Le premier paramtre du constructeur Timer correspond au dlai qui doit scouler entre les notications, mesur en millimes de seconde. Nous voulons tre avertis toutes les 10 secondes. Le deuxime paramtre est lobjet couteur. Enn, vous dmarrez le minuteur :
t.start();

Toutes les 10 secondes, un message du type


At the tone, the time is Thu Apr 13 23:29:08 PDT 2000

safche, suivi dun bip. Le Listing 6.3 fait fonctionner le minuteur et son couteur. Une fois le minuteur dmarr, le programme afche un message et attend que lutilisateur clique sur OK pour sarrter. Entre-temps, lheure actuelle safche par intervalles de 10 secondes. Soyez patient lorsque vous excutez le programme. La bote de dialogue "Quit program?" (fermer le programme ?) safche immdiatement, mais le premier message du minuteur napparat quaprs 10 secondes. Sachez que le programme importe la classe javax.swing.Timer par nom, en plus dimporter javax.swing.* et java.util.*. Ceci annule lambigut qui existait entre javax.swing.Timer et java.util.Timer, une classe non lie pour programmer les tches darrire-plan.
Listing 6.3 : TimerTest.java
/** * @version 1.00 2000-04-13 * @author Cay Horstmann */ import java.awt.*;

Chapitre 6

Interfaces et classes internes

263

import java.awt.event.*; import java.util.*; import javax.swing.*; import javax.swing.Timer; // rsoudre le conflit avec java.util.Timer public class TimerTest { public static void main(String[] args) { ActionListener listener = new TimePrinter(); // construit un minuteur qui appelle lcouteur // toutes les 10 secondes Timer t = new Timer(10000, listener); t.start(); JOptionPane.showMessageDialog(null, "Quit program?"); System.exit(0); } } class TimePrinter implements ActionListener { public void actionPerformed(ActionEvent event) { Date now = new Date(); System.out.println("At the tone, the time is " + now); Toolkit.getDefaultToolkit().beep(); } } javax.swing.JOptionPane 1.2

static void showMessageDialog(Component parent, Objectmessage)

Afche une bote de dialogue avec une invite et un bouton OK. La bote de dialogue est centre sur le composant parent. Si parent est null, la bote de dialogue est centre lcran.
javax.swing.Timer 1.2 Timer(int interval, ActionListener listener)

Construit un minuteur qui avertit lcouteur lorsque les millimes de seconde de lintervalle se sont couls.

void start()

Dmarre le minuteur. Une fois lanc, il appelle actionPerformed sur ses couteurs.
void stop()

Arrte le minuteur. Une fois arrt, il nappelle plus actionPerformed sur ses couteurs.
javax.awt.Toolkit 1.0 static Toolkit getDefaultToolkit()

Rcupre la bote outils par dfaut. Une bote outils contient des informations sur lenvironnement de linterface graphique utilisateur.

void beep()

Emet un bip.

264

Au cur de Java 2 - Notions fondamentales

Classes internes
Une classe interne est une classe qui est dnie lintrieur dune autre classe. Trois raisons justient lemploi de classes internes :
m

Les mthodes de classe internes peuvent accder aux donnes, partir de la porte o elles sont dnies, y compris les donnes qui pourraient tre des donnes prives. Les classes internes peuvent tre caches aux autres classes du mme package.

m m

Les classes internes anonymes sont utiles pour dnir des callbacks sans crire beaucoup de code. Nous allons diviser ce sujet assez complexe en plusieurs tapes :
m

A partir de la section suivante, vous verrez une classe interne simple qui accde un champ dinstance de sa classe externe. A la section "Rgles particulires de syntaxe pour les classes internes", nous verrons les rgles de syntaxe spciales pour les classes internes. A la section "Utilit, ncessit et scurit des classes internes", nous tudierons les classes internes pour voir comment les traduire en classes ordinaires. Les lecteurs dlicats pourront sauter cette section. A la section "Classes internes locales", nous discuterons des classes internes locales qui peuvent accder aux variables locales dans la porte qui les englobe. A la section "Classes internes anonymes", nous prsenterons les classes internes anonymes et montrerons comment les utiliser habituellement pour implmenter les callbacks. Enn, partir de la section "Classes internes statiques", vous verrez comment utiliser les classes internes statiques pour les classes daide imbriques.
INFO C++

Le langage C++ permet lemploi de classes imbriques. Une classe imbrique se situe dans la porte de la classe qui lenglobe. Voici un exemple typique ; une classe de liste lie dnit une classe permettant de stocker les liens et une classe qui dtermine une position ditration :
class LinkedList { public: class Iterator // une classe imbrique { public: void insert(int x); int erase(); . . . }; . . . private: class Link // une classe imbrique { public: Link* next; int data; }; . . . };

Chapitre 6

Interfaces et classes internes

265

Limbrication est une relation entre les classes, non entre les objets. Un objet LinkedList na pas de sous-objets de types Iterator ou Link. Cela procure deux avantages : le contrle de nom et le contrle daccs. Puisque le nom Iterator est imbriqu dans la classe LinkedList, il est connu lextrieur sous la forme LinkedList::Iterator et ne peut pas entrer en conit avec une autre classe baptise Iterator. En Java, cet avantage est moins important, car les packages Java procurent un contrle de nom quivalent. Remarquez que la classe Link se trouve dans la partie private de la classe LinkedList. Elle est absolument invisible au reste du code. Pour cette raison, il ny a aucun risque dclarer ses champs public. Ils ne sont accessibles quaux mthodes de la classe LinkedList (qui a le besoin lgitime dy accder). Ils sont invisibles lextrieur de LinkedList. En Java, ce genre de contrle ntait pas possible avant larrive des classes internes. Les classes internes de Java possdent cependant une caractristique supplmentaire qui les rend plus riches, et donc plus utiles que les classes imbriques de C++. Un objet dune classe interne possde une rfrence implicite lobjet de la classe externe qui la instanci. Grce ce pointeur, il peut accder tous les champs de lobjet externe. Nous verrons dans ce chapitre les dtails de ce mcanisme. En Java, les classes internes static ne sont pas dotes de ce pointeur. Elles sont exactement quivalentes aux classes imbriques de C++.

Accder ltat dun objet laide dune classe interne


La syntaxe des classes internes est assez complexe. Cest pourquoi nous utiliserons un exemple simple, bien que peu raliste, pour dmontrer lusage des classes internes. Nous allons refactoriser lexemple TimerTest et extraire une classe TalkingClock. Une horloge parlante se construit avec deux paramtres : lintervalle entre les annonces et une balise pour activer ou dsactiver le bip :
public class TalkingClock { public TalkingClock(int interval, boolean beep) public void start() { . . . } private int interval; private boolean beep; public class TimePrinter implements ActionListener // une classe interne { . . . } }

{ . . . }

Remarquez la classe TimePrinter, qui se trouve lintrieur de la classe TalkingClock. Cela ne signie pas que chaque TalkingClock possde un champ dinstance TimePrinter. Comme vous le verrez, les objets TimePrinter sont construits par les mthodes de la classe TalkingClock. Voici la classe TimePrinter plus en dtail. Sachez que la mthode actionPerformed vrie la balise du bip avant dmettre le son :
private class TimePrinter implements ActionListener { public void actionPerformed(ActionEvent event) { Date now = new Date(); System.out.println("At the tone, the time is " + now); if (beep) Toolkit.getDefaultToolkit().beep(); } }

266

Au cur de Java 2 - Notions fondamentales

Une chose surprenante se passe. La classe TimePrinter na pas de champ dinstance ni de variable nomme beep. En fait, beep fait rfrence au champ de lobjet TalkingClock qui a cr TimePrinter. Cest une innovation. Traditionnellement, une mthode pouvait rfrencer les champs de lobjet qui linvoquait. Une mthode de la classe interne a accs la fois ses propres champs et ceux de lobjet externe crateur. Pour que tout cela fonctionne, un objet dune classe interne obtient toujours une rfrence implicite lobjet qui la cr (voir Figure 6.3). Cette rfrence est invisible dans la dnition de la classe interne. Pour faire la lumire sur ce concept, nous allons appeler la rfrence lobjet externe outer. La mthode actionPerformed est alors quivalente ce qui suit :
public void actionPerformed(ActionEvent event) { Date now = new Date(); System.out.println("At the tone, the time is " + now); if (outer.beep) Toolkit.getDefaultToolkit().beep(); }

La rfrence de classe externe est dnie dans le constructeur. Le compilateur modie tous les constructeurs de classe interne, en ajoutant un paramtre pour la rfrence de classe externe. Etant donn que TimePrinter ne dnit aucun constructeur, le compilateur synthtise un constructeur par dfaut, gnrant un code comme celui-ci :
public TimePrinter(TalkingClock clock) // Code gnr automatiquement { outer = clock; }

Figure 6.3
Un objet dune classe interne possde une rfrence un objet dune classe externe.

TimePrinter outer =

TalkingClock interval = beep = 1000 true

Notez bien que outer nest pas un mot cl du langage Java. Nous lavons uniquement utilis pour illustrer le mcanisme mis en uvre dans une classe interne. Lorsquun objet TimePrinter est construit dans la mthode start, le compilateur passe la rfrence this au constructeur dans lhorloge parlante courante :
ActionListener listener = new TimePrinter( this); // paramtre ajout automatiquement

Chapitre 6

Interfaces et classes internes

267

Le Listing 6.4 dcrit le programme complet qui teste la classe interne. Examinez de nouveau le contrle daccs. Si la classe TimePrinter avait t une classe rgulire, il aurait fallu accder la balise beep par lintermdiaire dune mthode publique de la classe TalkingClock. Lemploi dune classe interne constitue une amlioration. Il nest pas ncessaire de fournir des mthodes daccs qui nintressent quune seule autre classe.
INFO
Nous aurions pu dclarer la classe TimePrinter comme private. Dans ce cas, seules les mthodes TalkingClock auraient pu construire les objets TimePrinter. Seules les classes internes peuvent tre prives. Les classes ordinaires ont toujours une visibilit soit publique, soit sur le package.

Listing 6.4 : InnerClassTest.java


import import import import import java.awt.*; java.awt.event.*; java.util.*; javax.swing.*; javax.swing.Timer;

/** * Ce programme prsente lutilisation des classes internes. * @version 1.10 2004-02-27 * @author Cay Horstmann */ public class InnerClassTest { public static void main(String[] args) { TalkingClock clock = new TalkingClock(1000, true); clock.start(); // laisser le programme fonctionner jusqu ce que lutilisateur // clique sur "OK" JOptionPane.showMessageDialog(null, "Quit program?"); System.exit(0); } } /** * Une horloge qui affiche lheure intervalles rguliers. */ class TalkingClock { /** * Construit une horloge parlante * @param interval Lintervalle entre les messages * (en millimes de seconde) * @param beep true si lhorloge doit sonner */ public TalkingClock(int interval, boolean beep) { this.interval = interval; this.beep = beep; }

268

Au cur de Java 2 - Notions fondamentales

/** * Lance lhorloge. */ public void start() { ActionListener listener = new TimePrinter(); Timer t = new Timer(interval, listener); t.start(); } private int interval; private boolean beep; public class TimePrinter implements ActionListener { public void actionPerformed(ActionEvent event) { Date now = new Date(); System.out.println("At the tone, the time is " + now); if (beep) Toolkit.getDefaultToolkit().beep(); } } }

Rgles particulires de syntaxe pour les classes internes


Dans la section prcdente, nous avons explicit la rfrence de classe externe dune classe interne en la baptisant outer. En ralit, la syntaxe correcte pour la rfrence externe est un peu plus complexe. Lexpression
ClasseExterne.this

indique la rfrence de classe externe. Vous pouvez par exemple, crire la mthode actionPerformed de la classe interne TimePrinter de la faon suivante :
public void actionPerformed(ActionEvent event) { ... if (TalkingClock.this.beep) Toolkit.getDefaultToolkit().beep(); }

Inversement, vous pouvez crire le constructeur de lobjet interne plus explicitement, laide de la syntaxe :
objetExterne.new ClasseInterne(paramtres de construction)

Par exemple,
ActionListener listener = this.new TimePrinter();

Ici, la rfrence la classe externe de lobjet TimePrinter nouvellement construit est dnie par la rfrence this de la mthode qui cre lobjet de la classe interne. Cest le cas le plus courant. Comme toujours, lindication this. est redondante. Nanmoins, il est galement possible de donner la rfrence de la classe externe une autre valeur en la nommant explicitement. Par exemple, si TimePrinter est une classe interne publique, vous pouvez construire un objet TimePrinter pour nimporte quelle horloge parlante :
TalkingClock jabberer = new TalkingClock(1000, true); TalkingClock.TimePrinter listener = jabberer.new TimePrinter();

Chapitre 6

Interfaces et classes internes

269

Notez que vous faites rfrence une classe interne de la faon suivante
ClasseExterne.ClasseInterne

lorsquelle se trouve tre hors de la porte de la classe externe.

Utilit, ncessit et scurit des classes internes


Lorsque les classes internes ont t ajoutes au langage Java dans Java 1.1, de nombreux programmeurs les ont considres comme une nouvelle fonctionnalit majeure qui tait hors de propos dans la philosophie Java. La syntaxe est complexe (nous le verrons en examinant les classes internes anonymes dans la suite de ce chapitre). Il nest pas vident de saisir linteraction des classes internes avec dautres caractristiques du langage comme le contrle daccs et la scurit. En ajoutant une fonctionnalit lgante et intressante plutt que ncessaire, Java a-t-il commenc suivre la voie funeste de tant dautres langages, en se dotant dune caractristique lgante et intressante, mais pas ncessaire ? Nous ne donnerons pas une rponse dnitive, mais notez que les classes internes constituent un phnomne qui est li au compilateur et non la machine virtuelle. Les classes internes sont traduites en chiers de classe rguliers, avec des signes $ dlimitant les noms des classes externes et internes et la machine virtuelle Java ne les reconnat pas comme une particularit. Par exemple, la classe TimePrinter lintrieur de la classe TalkingClock est traduite en un chier de classe TalkingClock$TimePrinter.class. Pour le constater, faites cette exprience : excutez le programme ReflectionTest du Chapitre 5 et donnez-lui comme classe de rexion la classe TalkingClock$TimePrinter. Vous pouvez aussi simplement employer lutilitaire javap :
javap private nomClasse

INFO
Si vous utilisez UNIX, noubliez pas dchapper le caractre $ si vous fournissez le nom de classe sur la ligne de commande. En fait, excutez le programme ReflectionTest ou javap sous la forme :
java ReflectionTest TalkingClock\$TimePrinter

ou
javap private TalkingClock\$TimePrinter

Vous obtenez donc lafchage suivant :


public class TalkingClock$TimePrinter { public TalkingClock$TimePrinter(TalkingClock); public void actionPerformed(java.awt.event.ActionEvent); final TalkingClock this$0; }

Vous pouvez voir que le compilateur a gnr un champ dinstance supplmentaire, this$0, pour la rfrence la classe externe (le nom this$0 est synthtis par le compilateur vous ne pouvez pas y faire rfrence dans le code source). Vous pouvez aussi voir le paramtre TalkingClock ajout pour le constructeur.

270

Au cur de Java 2 - Notions fondamentales

Si le compilateur peut effectuer automatiquement cette transformation, ne pourrait-on pas simplement programmer manuellement le mme mcanisme ? Essayons. Nous allons faire de TimePrinter une classe rgulire, extrieure la classe TalkingClock. Lors de la construction dun objet TimePrinter, nous lui passerons la rfrence this de lobjet qui le cre :
class TalkingClock { . . . public void start() { ActionListener listener = new TimePrinter(this); Timer t = new Timer(interval, listener); t.start(); } } class TimePrinter implements ActionListener { public TimePrinter(TalkingClock clock) { outer = clock; } . . . private TalkingClock outer; }

Examinons maintenant la mthode actionPerformed. Elle doit pouvoir accder outer.beep.


if (outer.beep) . . . // ERREUR

Nous venons de rencontrer un problme. La classe interne peut accder aux donnes prives de la classe externe, mais notre classe externe TimePrinter ne le peut pas. Nous voyons bien que les classes internes sont intrinsquement plus puissantes que les classes rgulires, puisquelles ont un privilge daccs suprieur. Il est lgitime de se demander comment les classes internes peuvent acqurir ce privilge daccs suprieur, alors quelles sont traduites en classes rgulires simplement dotes de noms particuliers la machine virtuelle ne les distingue pas. Pour rsoudre ce mystre, utilisons de nouveau le programme ReflectionTest an despionner la classe TalkingClock :
class TalkingClock { public TalkingClock(int, boolean); static boolean access$0(TalkingClock); public void start(); private int interval; private boolean beep; }

Remarquez la mthode statique access$0 ajoute par le compilateur la classe externe. Elle renvoie le champ beep de lobjet transfr en tant que paramtre. Elle est appele par les mthodes de la classe interne. Linstruction
if (beep)

Chapitre 6

Interfaces et classes internes

271

dans la mthode actionPerformed de la classe TimePrinter ralise, en fait, lappel suivant :


if (access$0(outer));

Y a-t-il un risque pour la scurit ? Bien sr. Il est facile un tiers dinvoquer la mthode access$0 pour lire le champ priv beep. Bien entendu, access$0 nest pas un nom autoris pour une mthode Java. Nanmoins, pour des pirates familiers de la structure des chiers de classe, il est facile de produire un chier de classe avec des instructions (machine virtuelle) qui appellent cette mthode, par exemple en utilisant un diteur hexadcimal. Les mthodes daccs secrtes ayant une visibilit au niveau du package, le code dattaque devrait tre plac dans le mme package que la classe attaque. En rsum, si une classe interne accde un champ priv, il est possible daccder ce champ par le biais de classes ajoutes au package de la classe externe, mais une telle opration exige du talent et de la dtermination. Un programmeur ne peut pas obtenir accidentellement un tel privilge daccs ; pour y parvenir, il doit intentionnellement crer ou modier un chier de classe.
INFO
Les constructeurs et mthodes synthtiss peuvent tre assez complexes (passez cette info si vous navez pas le courage daborder ce sujet). Supposons que nous transformions TimePrinter en classe interne prive. Il nexiste pas de classe prive dans la machine virtuelle, le compilateur produit donc une chose formidable, une classe visible par le package avec un constructeur priv
private TalkingClock$TimePrinter(TalkingClock);

Bien entendu, personne ne peut appeler le constructeur. Il existe donc un second constructeur visible par le package :
TalkingClock$TimePrinter(TalkingClock, TalkingClock$1);

qui appelle le premier. Le compilateur traduit le constructeur dans la mthode start de la classe TalkingClock en
new TalkingClock$TimePrinter(this, null)

Classes internes locales


Si vous examinez attentivement le code de lexemple TalkingClock, vous constaterez que vous navez besoin quune seule fois du nom du type TimePrinter : lorsque vous crez un objet de ce type dans la mthode start. Dans une telle situation, vous pouvez dnir les classes localement lintrieur dune seule mthode :
public void start() { class TimePrinter implements ActionListener { public void actionPerformed(ActionEvent event) { Date now = new Date(); System.out.println("At the tone, the time is " + now); if (beep) Toolkit.getDefaultToolkit().beep(); } }

272

Au cur de Java 2 - Notions fondamentales

ActionListener listener = new TimePrinter(); Timer t = new Timer(interval, listener); t.start(); }

Les classes locales ne sont jamais dclares avec un spcicateur daccs (cest--dire public ou private). Leur porte est toujours restreinte au bloc dans lequel elles sont dclares. Les classes locales prsentent limmense avantage dtre compltement caches au monde extrieur, et mme au reste du code de la classe TalkingClock. A part start, aucune mthode ne connat lexistence de la classe TimePrinter.

Accs aux variables nal partir de mthodes externes


Les classes locales ont un autre avantage sur les autres classes internes. Elles peuvent non seulement accder aux champs de leurs classes externes, mais galement aux variables locales ! Ces variables locales doivent tre dclares final. Voici un exemple typique. Dplaons les paramtres interval et beep du constructeur TalkingClock la mthode start :
public void start(int interval, final boolean beep) { class TimePrinter implements ActionListener { public void actionPerformed(ActionEvent event) { Date now = new Date(); System.out.println("At the tone, the time is " + now); if (beep) Toolkit.getDefaultToolkit().beep(); } } ActionListener listener = new TimePrinter(); Timer t = new Timer(interval, listener); t.start(); }

Notez que la classe TalkingClock na plus besoin de stocker une variable dinstance beep. Elle fait simplement rfrence la variable paramtre beep de la mthode start. Cela nest peut-tre pas si surprenant. La ligne
if (beep) . . .

est situe au plus profond de la mthode start, alors pourquoi ne pourrait-elle pas avoir accs la variable beep? An de comprendre ce problme dlicat, examinons de plus prs le ux dexcution : 1. La mthode start est appele. 2. La variable objet listener est initialise par un appel au constructeur de la classe interne TimePrinter. 3. La rfrence listener est passe au constructeur de Timer, le temporisateur est dmarr et la mthode start se termine. A ce moment, la variable paramtre beep de la mthode start nexiste plus. 4. Une seconde plus tard, la mthode actionPerformed excute if (beep) . . .

Chapitre 6

Interfaces et classes internes

273

Pour que le code de la mthode actionPerformed fonctionne, la classe TimePrinter doit avoir fait une copie de la valeur de paramtre beep avant quil ne disparaisse en tant que variable locale de la mthode start. Et cest exactement ce qui se passe. Dans notre exemple, le compilateur synthtise le nom TalkingClock$1TimePrinter pour la classe interne locale. Si vous utilisez le programme ReflectionTest pour espionner la classe TalkingClock$1TimePrinter, vous obtiendrez ce qui suit :
class TalkingClock$1TimePrinter { TalkingClock$1TimePrinter(TalkingClock, boolean); public void actionPerformed(java.awt.event.ActionEvent); final boolean val$beep; final TalkingClock this$0; }

Remarquez le paramtre boolean du constructeur et la variable dinstance val$beep. Lorsquun objet est cr, la valeur beep est passe au constructeur et stocke dans le champ val$beep. Le compilateur dtecte laccs des variables locales, cre des champs dinstance pour chacune delles et copie les variables locales dans le constructeur an que les champs dinstance soient initialiss. Du point de vue du programmeur, laccs aux variables locales est assez plaisant. Il simplie vos classes internes en rduisant le nombre des champs dinstance que vous devez programmer explicitement. Comme nous lavons dj signal, les mthodes dune classe locale peuvent uniquement faire rfrence des variables locales dclares final. Cest la raison pour laquelle le paramtre beep a t dclar final dans notre exemple. Une variable locale dclare final ne peut pas tre modie aprs avoir t initialise. Ainsi, nous avons la garantie que la variable locale et sa copie dans la classe locale auront bien la mme valeur.
INFO
Vous avez dj vu des variables final utilises en tant que constantes, limage de ce qui suit :
public static final double SPEED_LIMIT = 55;

Le mot cl final peut tre appliqu aux variables locales, aux variables dinstance et aux variables statiques. Dans tous les cas, cela signie la mme chose : cette variable ne peut tre affecte quune seule fois aprs sa cration. Il nest pas possible den modier ultrieurement la valeur elle est dnitive. Cela dit, il nest pas obligatoire dinitialiser une variable final lors de sa dnition. Par exemple, la variable paramtre final beep est initialise une fois aprs sa cration, lorsque la mthode start est appele (si elle est appele plusieurs fois, chaque appel cre son propre paramtre beep). La variable dinstance val$beep, que nous avons vue dans la classe interne TalkingClock$1TimePrinter, est dnie une seule fois, dans le constructeur de la classe interne. Une variable final qui nest pas initialise lors de sa dnition est souvent appele variable nale vide.

La restriction final est assez peu pratique. Supposons, par exemple, que vous souhaitiez actualiser un compteur dans la porte. Ici, nous voulons compter la frquence dappel de la mthode compareTo pendant le tri.
int counter = 0; Date[] dates = new Date[100];

274

Au cur de Java 2 - Notions fondamentales

for (int i = 0; i < dates.length; i++) dates[i] = new Date() { public int compareTo(Date other) { counter++; // ERREUR return super.compareTo(other); } }; Arrays.sort(dates); System.out.println(counter + " comparisons.");

Vous ne pouvez pas dclarer counter comme final car il est vident que vous devez lactualiser. Vous ne pouvez pas non plus le remplacer par un Integer car les objets Integer sont inaltrables. Le recours consisterait utiliser un tableau de longueur 1 :
final int[] counter = new int[1]; for (int i = 0; i < dates.length; i++) dates[i] = new Date() { public int compareTo(Date other) { counter[0]++; return super.compareTo(other); } };

La variable de tableau est toujours dclare final, mais cela signie simplement que vous ne pouvez pas lui faire rfrencer un autre tableau. Libre vous daltrer les lments du tableau. Lorsque les classes internes ont t inventes, le compilateur de prototype a automatiquement effectu cette transformation pour toutes les variables locales modies dans la classe interne. Certains programmeurs craignaient malgr tout que le compilateur produise des tas dobjets dans leur dos ; la restriction final a donc t adopte la place. Une prochaine version du langage Java pourrait dailleurs bien inverser cette dcision.

Classes internes anonymes


Lors de lutilisation de classes internes locales, vous pouvez souvent aller plus loin. Si vous ne souhaitez crer quun seul objet de cette classe, il nest mme pas ncessaire de donner un nom la classe. Une telle classe est appele classe interne anonyme :
public void start(int interval, final boolean beep) { ActionListener listener = new ActionListener() { public void actionPerformed(ActionEvent event) { Date now = new Date(); System.out.println("At the tone, the time is " + now); if (beep) Toolkit.getDefaultToolkit().beep(); } }; Timer t = new Timer(interval, listener); t.start(); }

Chapitre 6

Interfaces et classes internes

275

Il sagit l dune syntaxe assez obscure qui signie : crer un nouvel objet dune classe qui implmente linterface ActionListener, o la mthode actionPerformed requise est celle dnie entre les accolades { }. En gnral, la syntaxe est :
new SuperType(paramtres de construction) { mthodes et donnes de la classe interne }

SuperType peut tre ici une interface telle que ActionListener; la classe interne implmente alors cette interface. SuperType peut galement tre une classe, et dans ce cas la classe interne tend cette classe. Une classe interne anonyme ne peut pas avoir de constructeurs, car le nom dun constructeur doit tre identique celui de la classe (et celle-ci na pas de nom). Au lieu de cela, les paramtres de construction sont donns au constructeur de la superclasse. En particulier, chaque fois quune classe interne implmente une interface, elle ne peut pas avoir de paramtres de construction. Il faut nanmoins toujours fournir les parenthses, comme dans cet exemple :
new TypeInterface() { mthodes et donnes }

Il faut y regarder deux fois pour faire la diffrence entre la construction dun nouvel objet dune classe rgulire et la construction dun objet dune classe interne anonyme qui tend cette classe.
Person queen = // un objet Person count = // un objet new Person("Mary"); Person new Person("Dracula") { ... }; dune classe interne tendant Person

Si la parenthse fermante de la liste de paramtres du constructeur est suivie dune accolade ouvrante, cela dnit une classe interne anonyme. Les classes internes anonymes sont-elles une brillante ide, ou plutt un excellent moyen dcrire du code hermtique ? Sans doute les deux. Lorsque le code dune classe interne est trs court quelques lignes de code simple cela peut faire gagner un peu de temps. Mais cest exactement le genre dconomie qui vous amne concourir pour le prix du code Java le plus obscur. Le Listing 6.5 contient le code source complet du programme dhorloge parlante avec une classe interne anonyme. Si vous comparez ce programme celui du Listing 6.4, vous verrez que le code avec la classe interne anonyme est plus court, et, heureusement, avec un peu de pratique, aussi facile comprendre.
Listing 6.5 : AnonymousInnerClassTest.java
import import import import import java.awt.*; java.awt.event.*; java.util.*; javax.swing.*; javax.swing.Timer;

/** * Ce programme prsente les classes internes anonymes. * @version 1.10 2004-02-27 * @author Cay Horstmann */

276

Au cur de Java 2 - Notions fondamentales

public class AnonymousInnerClassTest { public static void main(String[] args) { TalkingClock clock = new TalkingClock(); clock.start(1000, true); // laisse le programme fonctionner jusqu ce que lutilisateur // clique sur "OK" JOptionPane.showMessageDialog(null, "Quit program?"); System.exit(0); } } /** * Une horloge qui affiche lheure intervalles rguliers. */ class TalkingClock { /** * Dmarre lhorloge. * @param interval Lintervalle entre les messages * (en millimes de seconde) * @param beep true si lhorloge doit mettre un bip */ public void start(int interval, final boolean beep) { ActionListener listener = new ActionListener() { public void actionPerformed(ActionEvent event) { Date now = new Date(); System.out.println("At the tone, the time is " + now); if (beep) Toolkit.getDefaultToolkit().beep(); } }; Timer t = new Timer(interval, listener); t.start(); } }

Classes internes statiques


On dsire parfois utiliser une classe interne pour cacher simplement une classe dans une autre, sans avoir besoin de fournir la classe interne une rfrence un objet de la classe externe. A cette n, la classe interne est dclare static. Voici un exemple typique qui montre les raisons dun tel choix. Prenons le calcul des valeurs minimale et maximale dun tableau. Bien entendu, on crit normalement une mthode pour calculer le minimum et une autre pour calculer le maximum. Lorsque ces deux mthodes sont appeles, le tableau est parcouru deux fois. Il serait plus efcace de parcourir le tableau une seule fois et de calculer simultanment les deux valeurs :
double min = Double.MAX_VALUE; double max = Double.MIN_VALUE; for (double v: values)

Chapitre 6

Interfaces et classes internes

277

{ if (min > v) min = v; if (max < v) max = v; }

Cependant, la mthode doit renvoyer deux nombres. Pour ce faire, nous dclarons une classe Pair qui stocke deux valeurs numriques :
class Pair { public Pair(double f, double s) { first = f; second = s; } public double getFirst() { return first; } public double getSecond() { return second; } private double first; private double second; }

La fonction minmax peut alors renvoyer un objet de type Pair :


class ArrayAlg { public static Pair minmax(double[] values) { . . . return new Pair(min, max); } }

Lappelant de la fonction utilise ensuite les mthodes getFirst et getSecond pour rcuprer les rponses :
Pair p = ArrayAlg.minmax(d); System.out.println("min = "+p.getFirst()); System.out.println("max = "+p.getSecond());

Bien sr, le nom Pair est un terme trs courant et, dans un grand projet, il se peut quun autre programmeur ait eu la mme ide brillante, mais sa classe Pair contient une paire de chanes de caractres. Ce conit potentiel de noms peut tre vit en faisant de Pair une classe interne publique dans ArrayAlg. La classe sera alors connue du public sous le nom ArrayAlg.Pair :
ArrayAlg.Pair p = ArrayAlg.minmax(d);

Quoi quil en soit, et contrairement aux autres classes internes employes dans les exemples prcdents, nous ne voulons pas avoir une rfrence un autre objet au sein dun objet Pair. Cette rfrence peut tre supprime en dclarant la classe interne comme static :
class ArrayAlg { public static class Pair { . . . } . . . }

278

Au cur de Java 2 - Notions fondamentales

Seules les classes internes peuvent videmment tre dclares static. Une classe interne static ressemble exactement nimporte quelle autre classe interne, avec cette diffrence, cependant, quun objet dune classe interne statique ne possde pas de rfrence lobjet externe qui la cr. Dans notre exemple, nous devons utiliser une classe interne statique, car lobjet de la classe interne est construit dans une mthode statique :
public static Pair minmax(double[] d) { . . . return new Pair(min, max); }

Si Pair navait pas t dclar static, le compilateur aurait indiqu quaucun objet implicite de type ArrayAlg ntait disponible pour initialiser lobjet de la classe interne.
INFO
On utilise une classe interne statique lorsque la classe interne na pas besoin daccder un objet de la classe externe. On emploie aussi le terme classe imbrique pour dsigner les classes internes statiques.

INFO
Les classes internes qui sont dclares dans une interface sont automatiquement statiques et publiques.

Le Listing 6.6 contient le code source complet de la classe ArrayAlg et de la classe imbrique Pair.
Listing 6.6 : StaticInnerClassTest.java
/** * Ce programme prsente lutilisation de classes internes statiques. * @version 1.01 2004-02-27 * @author Cay Horstmann */ public class StaticInnerClassTest { public static void main(String[] args) { double[] d = new double[20]; for (int i = 0; i < d.length; i++) d[i] = 100 * Math.random(); ArrayAlg.Pair p = ArrayAlg.minmax(d); System.out.println("min = "+p.getFirst()); System.out.println("max = "+p.getSecond()); } } class ArrayAlg { /** * Une paire de nombres virgule flottante */ public static class Pair {

Chapitre 6

Interfaces et classes internes

279

/** * Construit une paire partir de * deux nombres virgule flottante * @param f Le premier nombre * @param s Le second nombre */ public Pair(double f, double s) { first = f; second = s; } /** * Renvoie le premier nombre de la paire * @return le premier nombre */ public double getFirst() { return first; } /** * Renvoie le second nombre de la paire * @return le second nombre */ public double getSecond() { return second; } private double first; private double second; } /** * Calcule la fois le minimum et le maximum dun tableau * @param values Un tableau de nombres virgule flottante * @return une paire dont le premier lment est le minimum et * dont le second lment est le maximum */ public static Pair minmax(double[] values) { double min = Double.MAX_VALUE; double max = Double.MIN_VALUE; for (double v: values) { if (min > v) min = v; if (max < v) max = v; } return new Pair(min, max); } }

280

Au cur de Java 2 - Notions fondamentales

Proxies
Dans cette dernire section du chapitre, nous allons tudier les proxies, une fonctionnalit devenue disponible avec la version 1.3 de Java SE. Vous utilisez un proxy pour crer, au moment de lexcution, de nouvelles classes qui implmentent un jeu donn dinterfaces. Les proxies ne sont ncessaires que si vous ne savez pas, au moment de la compilation, les interfaces que vous devez implmenter. Cette situation se produit rarement pour les programmeurs dapplication, mais nhsitez pas passer cette section si les difcults ne vous intressent pas. Cependant, pour certaines applications systme, la souplesse que procurent les proxies peut avoir une importance capitale. Supposons que vous vouliez construire un objet dune classe qui implmente une ou plusieurs interfaces dont vous ne connaissez peut-tre pas la nature exacte au moment de la compilation. Cest l un problme ardu. Pour construire une classe relle, vous pouvez simplement utiliser la mthode newInstance ou la rexion pour trouver un constructeur. Mais vous ne pouvez pas instancier une interface. Vous devez dnir une nouvelle classe dans un programme qui sexcute. Pour rsoudre ce problme, certains programmes gnrent du code, le placent dans un chier, invoquent le compilateur puis chargent le chier de classe rsultant. Cette procdure est lente, bien entendu, et demande aussi le dploiement du compilateur avec le programme. Le mcanisme de proxy est une meilleure solution. La classe proxy peut crer des classes entirement nouvelles au moment de lexcution. Une telle classe proxy implmente les interfaces que vous spciez. En particulier, elle possde les mthodes suivantes :
m m

toutes les mthodes requises par les interfaces spcies ; toutes les mthodes dnies dans la classe Object (toString, equals, etc.).

Nanmoins, vous ne pouvez pas dnir de nouveau code pour ces mthodes au moment de lexcution. A la place, vous devez fournir un gestionnaire dinvocation. Ce gestionnaire est un objet de toute classe implmentant linterface InvocationHandler. Cette interface possde une seule mthode :
Object invoke(Object proxy, Method method, Object[] args)

Chaque fois quune mthode est appele sur lobjet proxy, la mthode invoke du gestionnaire dinvocation est appele, avec lobjet Method et les paramtres de lappel original. Le gestionnaire dinvocation doit alors trouver comment grer lappel. Pour crer un objet proxy, vous appelez la mthode newProxyInstance de la classe Proxy. Cette mthode a trois paramtres :
m

Un chargeur de classe. Dans le cadre du modle de scurit Java, il est possible dutiliser diffrents chargeurs de classe pour les classes systme, les classes qui sont tlcharges partir dInternet, etc. Nous verrons les chargeurs de classes au Chapitre 9 du Volume II. Pour linstant, nous spcierons null pour utiliser le chargeur de classe par dfaut. Un tableau dobjets Class, un pour chaque interface implmenter. Un gestionnaire dinvocation.

m m

Deux questions restent en suspens. Comment dnissons-nous le gestionnaire ? Et que pouvonsnous faire de lobjet proxy rsultant ? Les rponses dpendent videmment du problme que nous voulons rsoudre avec le mcanisme de proxy. Les proxies peuvent tre employs dans bien des cas, par exemple :
m

le routage dappels de mthode vers des serveurs distants ;

Chapitre 6

Interfaces et classes internes

281

lassociation dvnements de linterface utilisateur avec des actions, dans un programme qui sexcute ; la trace des appels de mthode des ns de dbogage.

Dans notre exemple de programme, nous allons utiliser les proxies et les gestionnaires dinvocation pour tracer les appels de mthode. Nous dnissons une classe enveloppe TraceHandler qui stocke un objet envelopp (wrapped). Sa mthode invoke afche simplement le nom et les paramtres de la mthode appeler, puis appelle la mthode avec lobjet envelopp en tant que paramtre implicite :
class TraceHandler implements InvocationHandler { public TraceHandler(Object t) { target = t; } public Object invoke(Object proxy, Method m, Object[] args) throws Throwable { // afficher le nom et les paramtres de la mthode . . . // invoquer la mthode return m.invoke(target, args); } private Object target; }

Voici comment vous construisez un objet proxy qui dclenche lactivit de trace chaque fois que lune de ses mthodes est appele :
Object value = . . .; // construire lenveloppe (wrapper) InvocationHandler handler = new TraceHandler(value); // construire le proxy pour une ou plusieurs interfaces Class[] interfaces = new Class[] { Comparable.class }; Object proxy = Proxy.newProxyInstance(null, interfaces, handler);

Maintenant, chaque fois quune mthode de lune des interfaces sera appele sur le proxy, le nom de la mthode et ses paramtres seront afchs, puis la mthode sera invoque sur value. Dans le programme du Listing 6.7, les objets proxy sont employs pour raliser la trace dune recherche binaire. Un tableau est rempli avec les valeurs entires de proxies de 1 1 000. Puis la mthode binarySearch de la classe Arrays est invoque pour rechercher un entier alatoire dans le tableau. Enn, llment correspondant safche :
Object[] elements = new Object[1000]; // remplir les lments avec des valeurs de proxies de 1 1000 for (int i = 0; i < elements.length; i++) { Integer value = i+1; elements[i] = Proxy.newInstance(. . .); // proxy pour la valeur; } // construire un entier alatoire Integer key = new Random().nextInt(elements.length) + 1;

282

Au cur de Java 2 - Notions fondamentales

// rechercher la cl (key) int result = Arrays.binarySearch(elements, key); // afficher la correspondance si trouve if (result >= 0) System.out.println(elements[result]);

La classe Integer implmente linterface Comparable. Les objets proxy appartiennent une classe qui est dnie au moment de lexcution (son nom est du style $Proxy0). Cette classe implmente galement linterface Comparable. Sa mthode compareTo appelle la mthode invoke du gestionnaire de lobjet proxy.
INFO
Comme vous lavez vu plus tt dans ce chapitre, depuis Java SE 5.0 la classe Integer implmente en fait Comparable<Integer>. Toutefois, au moment de lexcution, tous les types gnriques sont effacs et le proxy est construit avec lobjet de classe, pour la classe brute Comparable.

La mthode binarySearch ralise des appels du style :


if (elements[i].compareTo(key) < 0) . . .

Puisque nous avons complt le tableau avec des objets proxy, les appels compareTo appellent la mthode invoke de la classe TraceHandler. Cette mthode afche le nom de la mthode et ses paramtres, puis invoque compareTo sur lobjet Integer envelopp. Enn, la n du programme, nous appelons :
System.out.println(elements[result]);

La mthode println appelle toString sur lobjet proxy, et cet appel est aussi redirig vers le gestionnaire dinvocation. Voici la trace complte dune excution du programme :
500.compareTo(288) 250.compareTo(288) 375.compareTo(288) 312.compareTo(288) 281.compareTo(288) 296.compareTo(288) 288.compareTo(288) 288.toString()

Remarquez lalgorithme de recherche dichotomique qui coupe en deux lintervalle de recherche chaque tape. Vous voyez que la mthode toString est mise en proxy, mme si elle nappartient pas linterface Comparable. Vous le dcouvrirez la prochaine section, certaines mthodes Object sont toujours mises en proxy.
Listing 6.7 : ProxyTest.java
import java.lang.reflect.*; import java.util.*; /** * Ce programme prsente lutilisation des proxies.

Chapitre 6

Interfaces et classes internes

283

* @version 1.00 2000-04-13 * @author Cay Horstmann */ public class ProxyTest { public static void main(String[] args) { Object[] elements = new Object[1000]; // remplir les lments avec des proxies de 1 1000 for (int i = 0; i < elements.length; i++) { Integer value = i + 1; InvocationHandler handler = new TraceHandler(value); Object proxy = Proxy.newProxyInstance(null, new Class[] { Comparable.class } , handler); elements[i] = proxy; } // construit un entier alatoire Integer key = new Random().nextInt(elements.length) + 1; // recherche la cl int result = Arrays.binarySearch(elements, key); // affiche la correspondance le cas chant if (result >= 0) System.out.println(elements[result]); } } /** * Un gestionnaire dinvocation qui affiche le nom et les paramtres * de la mthode, puis appelle la mthode initiale */ class TraceHandler implements InvocationHandler { /** * Construit un TraceHandler * @param t le paramtre implicite de lappel de mthode */ public TraceHandler(Object t) { target = t; } public Object invoke(Object proxy, Method m, Object[] args) throws Throwable { // affiche largument implicite System.out.print(target); // affiche le nom de la mthode System.out.print("." + m.getName() + "("); // affiche les arguments explicites if (args!= null) { for (int i = 0; i < args.length; i++) { System.out.print(args[i]);

284

Au cur de Java 2 - Notions fondamentales

if (i < args.length - 1) System.out.print(", "); } } System.out.println(")"); // appelle la mthode relle return m.invoke(target, args); } private Object target; }

Proprits des classes proxy


Maintenant que vous avez vu les classes proxy luvre, nous allons tudier certaines de leurs proprits. Souvenez-vous que les classes proxy sont cres la vole, dans un programme en cours dexcution. Cependant, une fois cres, ce sont des classes rgulires, comme nimporte quelle autre classe dans la machine virtuelle. Toutes les classes proxy tendent la classe Proxy. Une classe proxy na quune variable dinstance le gestionnaire dinvocation qui est dni dans la superclasse Proxy. Toutes les donnes supplmentaires ncessaires pour excuter les tches des objets proxy doivent tre stockes dans le gestionnaire dinvocation. Par exemple, lorsque les objets Comparable ont t transforms en proxies dans le programme du Listing 6.7, la classe TraceHandler a envelopp les objets rels. Toutes les classes proxy remplacent les mthodes toString, equals et hashCode de la classe Object. Comme toutes les mthodes proxy, ces mthodes appellent simplement invoke sur le gestionnaire dinvocation. Les autres mthodes de la classe Object (comme clone et getClass) ne sont pas rednies. Les noms des classes proxy ne sont pas dnis. La classe Proxy dans la machine virtuelle de Sun gnre des noms de classes commenant par la chane $Proxy. Il ny a quune classe proxy pour un chargeur de classe et un jeu ordonn dinterfaces particuliers. Cest--dire que si vous appelez deux fois la mthode newProxyInstance avec le mme chargeur de classe et le mme tableau dinterface, vous obtenez deux objets de la mme classe. Vous pouvez aussi obtenir cette classe laide de la mthode getProxyClass :
Class proxyClass = Proxy.getProxyClass(null, interfaces);

Une classe proxy est toujours public et final. Si toutes les interfaces quimplmente la classe proxy sont public, alors la classe proxy nappartient pas un package particulier. Sinon, toutes les interfaces non publiques doivent appartenir au mme package, et la classe proxy appartient alors aussi ce package. Vous pouvez dterminer si un objet Class particulier reprsente une classe proxy en appelant la mthode isProxyClass de la classe Proxy.
java.lang.reflect.InvocationHandler 1.3

Object invoke(Object proxy, Method method, Object[] args)

Dnit cette mthode pour quelle contienne laction que vous voulez excuter chaque fois quune mthode a t invoque sur lobjet proxy.

Chapitre 6

Interfaces et classes internes

285

java.lang.reflect.Proxy 1.3 static Class getProxyClass(ClassLoader loader, Class[] interfaces)

Renvoie la classe proxy qui implmente les interfaces donnes.

static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler handler)

Construit une nouvelle instance de la classe proxy qui implmente les interfaces donnes. Toutes les mthodes appellent la mthode invoke de lobjet gestionnaire donn.

static boolean isProxyClass(Class c)

Renvoie true si c est une classe proxy. Cela clt notre dernier chapitre sur les bases du langage de programmation Java. Les interfaces et les classes internes sont des concepts que vous rencontrerez frquemment. Comme nous lavons mentionn toutefois, les proxies constituent une technique pointue principalement destine aux concepteurs doutils, et non aux programmeurs dapplication. Vous tes maintenant prt aborder lapprentissage des techniques de programmation graphique et des interfaces utilisateur, qui commence au Chapitre 7.

7
Programmation graphique
Au sommaire de ce chapitre

Introduction Swing Cration dun cadre Positionnement dun cadre Afchage des informations dans un composant Formes 2D Couleurs Texte et polices Afchage dimages
Vous avez tudi jusqu prsent lcriture de programmes qui nacceptaient que des caractres taps au clavier, les traitaient et afchaient le rsultat dans une console. De nos jours, ce nest pas ce que dsirent la plupart des utilisateurs. Les programmes modernes et les pages Web ne fonctionnent pas de cette manire. Ce chapitre vous ouvrira la voie qui permet dcrire des programmes utilisant une interface utilisateur graphique (GUI, Graphic User Interface). Vous apprendrez en particulier crire des programmes qui permettent de spcier la taille et la position des fentres, dy afcher du texte avec diverses polices de caractres, de dessiner des images, et ainsi de suite. Vous obtiendrez ainsi un ventail de comptences que nous mettrons en uvre dans les prochains chapitres. Les deux chapitres suivants vous montreront comment grer des vnements tels que les frappes au clavier et les clics de la souris et comment ajouter des lments dinterfaces : menus, boutons, etc. Aprs avoir lu ces trois chapitres, vous saurez crire des programmes graphiques autonomes. Des techniques plus sophistiques de programmation graphique sont tudies au Volume II. Si, au contraire, vous souhaitez utiliser Java pour la programmation ct serveur uniquement et que la GUI ne vous intresse pas, vous pouvez aisment sauter ces chapitres.

288

Au cur de Java 2 - Notions fondamentales

Introduction Swing
Lors de lintroduction de Java 1.0, le programme contenait une bibliothque de classe que Sun appelait Abstract Window Toolkit (AWT) pour la programmation de base de linterface graphique utilisateur (GUI). La manire dont la bibliothque AWT de base gre les lments de linterface utilisateur se fait par la dlgation de leur cration et de leur comportement la bote outils native du GUI sur chaque plate-forme cible (Windows, Solaris, Macintosh, etc.). Si vous avez par exemple utilis la version originale dAWT pour placer une bote de texte sur une fentre Java, une case de texte "pair" sous-jacente a gr la saisie du texte. Le programme qui en rsulte pourrait alors, en thorie, sexcuter sur lune de ces plates-formes, avec "laspect" de la plate-forme cible, do le slogan de Sun, "Ecrire une fois, excuter partout". Lapproche fonde sur les pairs fonctionnait bien pour les applications simples, mais il est rapidement devenu vident quil tait trs difcile dcrire une bibliothque graphique portable de haute qualit qui dpendait des lments dune interface utilisateur native. Linterface utilisateur comme les menus, les barres de dlement et les champs de texte peuvent avoir des diffrences subtiles de comportement sur diffrentes plates-formes. Il tait donc difcile doffrir aux utilisateurs une exprience cohrente et prvisible. De plus, certains environnements graphiques (comme X11/Motif) ne disposent pas dune large collection de composants dinterface utilisateur comme lont Windows ou Macintosh. Ceci restreint ensuite une bibliothque portable fonde sur des pairs adopter lapproche du "plus petit dnominateur commun". En consquence, les applications GUI labores avec AWT ntaient pas aussi jolies que les applications Windows ou Macintosh natives et navaient pas non plus le type de fonctionnalit que les utilisateurs de ces plates-formes attendaient. Plus triste encore, il existait diffrents bogues dans la bibliothque de linterface utilisateur AWT sur les diffrentes plates-formes. Les dveloppeurs se sont plaints quils devaient tester leurs applications sur chaque plate-forme, une pratique qui sest appele, avec un peu de drision, "Ecrire une fois, dboguer partout". En 1996, Netscape a cr une bibliothque de GUI dnomme IFC (Internet Foundation Classes) qui utilisait une approche totalement diffrente. Les lments de linterface utilisateur, comme les boutons, les menus, etc., taient dessins sur des fentres vierges. La seule fonctionnalit requise par le systme de fentres sous-jacent ncessitait une mthode pour faire apparatre les fentres et dessiner dedans. Ainsi, les lments IFC de Netscape avaient le mme aspect et se comportaient de la mme manire, quelle que soit la plate-forme dexcution du programme. Sun a collabor avec Netscape pour parfaire cette approche, avec pour rsultat une bibliothque dinterfaces utilisateur portant le nom de code "Swing". Swing constituait une extension de Java 1.1 et sest nalement intgr dans la bibliothque standard de Java SE 1.2. Comme la dit Duke Ellington, "Tout a nest rien si je nai pas le swing." Et Swing est maintenant le nom ofciel du kit de dveloppement dinterface graphique. Swing fait partie des classes JFC (Java Foundation Classes). Lensemble des JFC est vaste et ne se limite pas Swing. Elles incluent non seulement les composants Swing, mais galement des API daccessibilit, de dessin 2D et de fonctionnalits de glisser-dplacer (ou drag and drop).

Chapitre 7

Programmation graphique

289

INFO
Swing ne remplace pas compltement AWT, il est fond sur larchitecture AWT. Swing fournit simplement des composants dinterface plus performants. Vous utilisez larchitecture de base dAWT, en particulier la gestion des vnements, lorsque vous crivez un programme Swing. A partir de maintenant, nous emploierons le terme "Swing" pour dsigner les classes allges de linterface utilisateur "dessine" et "AWT" pour dsigner les mcanismes sousjacents du kit de fentrage (tels que la gestion dvnements).

Bien entendu, les lments dinterface Swing seront un peu plus lents safcher sur lcran que les composants lourds employs par AWT. Prcisons que cette diffrence de vitesse ne pose pas de problme sur les machines rcentes. En revanche, il existe dexcellentes raisons dopter pour Swing :
m m

Swing propose un ensemble dlments dinterface plus tendu et plus pratique. Swing dpend peu de la plate-forme dexcution ; en consquence, il est moins sensible aux bogues spciques dune plate-forme. Swing procure une bonne exprience lutilisateur qui travaille sur plusieurs plates-formes.

Cependant, le troisime avantage cit reprsente galement un recul : si les lments de linterface utilisateur se ressemblent sur toutes les plates-formes, leur aspect ("look and feel") est quand mme diffrent de celui des contrles natifs et les utilisateurs vont devoir sadapter cette nouvelle prsentation. Swing rsout ce problme dune manire trs lgante. Les programmeurs dune application Swing peuvent donner leur interface un look and feel spcique. Les Figures 7.1 et 7.2 montrent le mme programme en cours dexcution, lun sous Windows, lautre sous GTK (pour des raisons de copyright, le look and feel Windows nest disponible que pour les programmes Java excuts sur des plates-formes Windows).
Figure 7.1
Application Swing avec le look and feel de Windows.

Sun a dvelopp un look and feel indpendant de la plate-forme, baptis "Metal", jusqu ce que les spcialistes du marketing le renomment "Java look and feel". Or la plupart des programmeurs continuent utiliser le terme "Metal", et cest ce que nous ferons dans ce livre.

290

Au cur de Java 2 - Notions fondamentales

Figure 7.2
Application Swing avec le look and feel de GTK.

Certains ont fait la critique que Metal tait un peu indigeste et son aspect a t modernis pour la version 5.0 (voir Figure 7.3). Sachez que laspect Metal prend en charge plusieurs thmes, des variations mineures des couleurs et des polices. Le thme par dfaut sappelle "Ocean".
Figure 7.3
Le look and feel "Metal" de Swing, avec le thme Ocean.

Dans Java SE 6, Sun a amlior la prise en charge du look and feel natif pour Windows et GTK. Une application Swing choisit dsormais des personnalisations des schmas de couleurs et rend dlement les boutons et barres doutils modernes. Certains prfrent que les applications Java emploient le look and feel de leurs plates-formes, dautres optent pour Metal ou pour un look and feel indpendant. Vous le verrez au Chapitre 8, il est trs facile de laisser les utilisateurs choisir leur look and feel prfr.

Chapitre 7

Programmation graphique

291

INFO
Bien que cela dpasse le cadre de cet ouvrage, sachez que le programmeur Java peut amliorer un look and feel existant ou en concevoir un qui soit entirement nouveau. Cest un travail fastidieux qui exige de spcier la mthode de dessin des divers composants Swing. Certains dveloppeurs ont dj accompli cette tche en portant Java sur des plates-formes non traditionnelles (telles que des terminaux de borne ou des ordinateurs de poche). Consultez le site http://javootoo.com pour dcouvrir toute une srie dimplmentations look and feel intressantes. Java SE 5.0 a introduit un nouveau look and feel, appel Synth, qui facilite ce processus. Synth vous permet de dnir un nouveau look and feel en fournissant des chiers image et des descripteurs XML, sans effectuer aucune programmation.

INFO
La plus grande de la programmation de linterface utilisateur Java seffectue aujourdhui en Swing, une notable exception prs. Lenvironnement de dveloppement intgr Eclipse utilise une bote outils graphique appele SWT qui est identique AWT, faisant concorder des composants natifs sur diverses plates-formes. Vous trouverez des articles dcrivant SWT ladresse http://www.eclipse.org/articles/.

Si vous avez dj programm des applications pour Windows avec Visual Basic ou C#, vous connaissez la simplicit demploi des outils graphiques et des diteurs de ressources de ces produits. Ces logiciels permettent de concevoir laspect dune application et gnrent une bonne partie (ou la totalit) du code de linterface. Il existe des outils de dveloppement dinterface pour Java, mais, pour les utiliser efcacement, vous devez savoir construire linterface manuellement. Le reste du chapitre donne les bases pour afcher une fentre et dessiner son contenu.

Cration dun cadre


En Java, une fentre de haut niveau cest--dire une fentre qui nest pas contenue dans une autre fentre est appele frame (cadre ou fentre dencadrement). Pour reprsenter ce niveau suprieur, la bibliothque AWT possde une classe nomme Frame. La version Swing de cette classe est baptise JFrame, qui tend la classe Frame et dsigne lun des rares composants Swing qui ne soient pas dessins sur un canevas (grille). Les lments de dcoration (boutons, barre de titre, icnes, etc.) ne sont pas dessins par Swing, mais par le systme de fentrage de lutilisateur.
ATTENTION
La plupart des classes de composant Swing commencent par la lettre "J" : JButton, JFrame, etc. Ce sont des classes comme Button et Frame, mais il sagit de composants AWT. Si vous omettez par inadvertance la lettre "J", votre programme peut toujours se compiler et sexcuter, mais le mlange de Swing et de composants AWT peut amener des incohrences visuelles et de comportement.

Nous allons maintenant tudier les mthodes employes le plus frquemment lorsque nous travaillons avec un composant JFrame. Le Listing 7.1 prsente un programme simple qui afche une fentre vide sur lcran, comme le montre la Figure 7.4.

292

Au cur de Java 2 - Notions fondamentales

Figure 7.4
Le plus simple des cadres visibles.

Listing 7.1 : SimpleFrameTest.java


import javax.swing.*; /** * @version 1.32 2007-06-12 * @author Cay Horstmann */ public class SimpleFrameTest { public static void main(String[] args) { SimpleFrame frame = new SimpleFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } } class SimpleFrame extends JFrame { public SimpleFrame() { setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); } public static final int DEFAULT_WIDTH = 300; public static final int DEFAULT_HEIGHT = 200; }

Examinons ce programme ligne par ligne. Les classes Swing se trouvent dans le package javax.swing. Le terme javax dsigne un package dextension (pour le distinguer dun package standard). Pour des raisons historiques, les classes Swing constituent une extension mais elles font partie de chaque implmentation de Java SE depuis la version 1.2. Par dfaut, un cadre a une taille assez peu utile de 0 0 pixel. Nous dnissons une sous-classe SimpleFrame dont le constructeur dnit la taille 300 200 pixels. Cest la seule diffrence entre un SimpleFrame et un JFrame. Dans la mthode main de la classe SimpleFrameTest, nous commenons par construire un objet SimpleFrame et nous lafchons. Nous devons traiter deux problmes techniques dans chaque programme Swing. Tout dabord, tous les composants Swing doivent tre congurs partir du thread de rpartition des vnements, le thread de contrle qui transfre des vnements comme des clics de souris et des

Chapitre 7

Programmation graphique

293

frappes de touche sur les composants de linterface utilisateur. Le fragment de code suivant sert excuter des instructions dans le thread de rpartition des vnements :
EventQueue.invokeLater(new Runnable() { public void run() { Instructions } });

Nous verrons les dtails au Chapitre 14. Pour lheure, considrez simplement quil sagit dune incantation magique servant lancer un programme Swing.
INFO
Vous verrez que de nombreux programmes Swing ninitialisent pas linterface utilisateur dans le thread de rpartition des vnements. Cette situation tait parfaitement acceptable pour raliser linitialisation dans le thread principal. Malheureusement, les composants Swing devenant plus complexes, les programmeurs de Sun ne pouvaient plus garantir la scurit de cette mthode. La probabilit derreur est extrmement faible et vous ne voudrez srement pas faire partie des malchanceux qui rencontrent un problme intermittent. Mieux vaut procder dans les rgles, mme si le code semble plutt mystrieux.

Nous indiquons ensuite ce qui doit se passer lorsque lutilisateur ferme ce cadre. Dans ce cas prcis, le programme doit sortir. Utilisez pour cela linstruction :
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

Dans dautres programmes comprenant plusieurs cadres, vous ne souhaiterez pas la sortie du programme si lutilisateur ferme seulement lun des cadres. Par dfaut, un cadre est masqu lorsque lutilisateur le ferme, mais le programme ne se termine pas pour autant. Il aurait pu tre intressant que le programme se termine aprs que le dernier cadre est devenu invisible, mais il nen est pas ainsi. Le simple fait de construire un cadre ne lafche pas automatiquement. Les cadres sont au dpart invisibles. Cela permet au programmeur dy ajouter des composants avant lafchage. Pour afcher le cadre, la mthode main appelle la mthode setVisible du cadre.
INFO
Avant Java SE 5.0, il tait possible dutiliser la mthode show, dont la classe JFrame hritait dans la superclasse Window. Cette dernire avait elle-mme une superclasse Component qui disposait galement dune mthode show. La mthode Component.show tait une mthode dprcie dans Java SE 1.2. Vous tes cens appeler setVisible(true) pour afcher un composant. Nanmoins, jusqu Java SE 1.4, la mthode Window.show ntait pas dprcie. En fait, elle tait assez utile, pour afcher la fentre et la faire apparatre au premier plan. Malheureusement, cet avantage a disparu avec la politique de dprciation et Java SE 5.0 dprciait la mthode show galement pour les fentres.

Aprs avoir plani les instructions dinitialisation, la mthode main se termine. Sachez quelle ne met pas n au programme, mais simplement au thread principal. Le thread de rpartition des vnements maintient le programme en activit jusqu son achvement, soit par fermeture du cadre, soit par appel la mthode System.exit.

294

Au cur de Java 2 - Notions fondamentales

Le rsultat du programme est prsent la Figure 7.4 : il sagit dun cadre parfaitement vide et sans intrt. La barre de titre et les lments, tels que les boutons droite, sont dessins par le systme dexploitation et non par la bibliothque Swing. Si vous excutez le mme programme sous Windows, GTK ou Mac, les oritures du cadre seront diffrentes. La bibliothque Swing dessine tout lintrieur du cadre. Dans ce programme, elle emplit simplement le cadre avec une couleur darrire-plan par dfaut.
INFO
Depuis Java SE 1.4, vous pouvez dsactiver toutes les dcorations de cadre en appelant frame.setUndecorated(true).

Positionnement dun cadre


La classe JFrame ne fournit que peu de mthodes capables de modier laspect dun cadre. Cependant, grce lhritage, les diverses superclasses de JFrame proposent la plupart des mthodes permettant dagir sur la taille et la position dun cadre. Les plus importantes sont les suivantes :
m m

Les mthodes setLocation et setBounds dnissent la position du cadre. La mthode setIconImage indique au systme de fentres licne afcher dans la barre de titre, la fentre de commutation des tches, etc. La mthode setTitle spcie le texte afch dans la barre de titre. La mthode setResizable reoit un paramtre boolen pour dterminer si la taille dun cadre peut tre modie par lutilisateur.

m m

La Figure 7.5 illustre la chane dhritage de la classe JFrame.


ASTUCE
Les notes API de cette section proposent les mthodes les plus importantes, selon nous, pour donner un aspect correct aux cadres. Certaines sont dnies dans la classe JFrame. Dautres proviennent des diverses superclasses de JFrame. A un certain point, vous devrez peut-tre faire des recherches dans la documentation API pour retrouver des mthodes usage spcique. Malheureusement, cela est un peu fastidieux avec les mthodes hrites. Ainsi, par exemple, la mthode toFront sapplique aux objets du type JFrame, mais puisquelle est simplement hrite de la classe Window, la documentation de JFrame ne lexplique pas. Sil vous semble quune mthode serait ncessaire et quelle ne soit pas explique dans la documentation de la classe pour laquelle vous travaillez, essayez de parcourir la documentation API des mthodes des superclasses de cette classe. Le haut de chaque page API possde des liens hypertexte vers les superclasses et les mthodes hrites sont rpertories sous le rsum des mthodes nouvelles et remplaces.

Comme lindiquent les notes API, cest gnralement dans la classe Component (anctre de tous les objets dinterface utilisateur graphique) ou dans la classe Window (superclasse du parent de la classe Frame) que lon recherche les mthodes permettant de modier la taille et la position des cadres. Par exemple, la mthode setLocation de la classe Component permet de repositionner un composant. Si vous appelez :
setLocation(x, y)

Chapitre 7

Programmation graphique

295

le coin suprieur gauche du cadre est plac x pixels du bord gauche et y pixels du sommet de lcran (0, 0) reprsente le coin suprieur gauche de lcran. De mme, la mthode setBounds de Component permet de modier simultanment la taille et la position dun composant (en particulier JFrame), de la faon suivante :
setBounds(x, y, width, height)

Figure 7.5
La hirarchie dhritage des classes JFrame et JPanel dans AWT et Swing.
Objet

Composant

Conteneur

JComponent

Fentre

JPanel

Cadre

JFrame

Vous pouvez aussi donner le contrle du placement au systme de fentres. Si vous appelez
setLocationByPlatform(true);

avant dafcher le cadre, le systme de fentres choisit lemplacement (mais non la taille), gnralement avec un lger dcalage par rapport la dernire fentre.

296

Au cur de Java 2 - Notions fondamentales

INFO
Pour un cadre, les coordonnes de setLocation et setBounds sont relatives lcran. Pour dautres composants, placs dans un conteneur, les coordonnes sont relatives au conteneur, comme vous le verrez au Chapitre 9.

Proprits des cadres


De nombreuses mthodes de classes de composants sont fournies par les paires get/set, notamment les mthodes de la classe Frame :
public String getTitle() public void setTitle(String title)

Cette paire get/set est appele une proprit, elle possde un nom et un type. Le nom sobtient en modiant la premire lettre venant aprs get ou set. Par exemple, la classe Frame possde une proprit de nom title et de type String. Conceptuellement, title est une proprit du cadre. Lorsque nous la dnissons, nous nous attendons ce que le titre change lcran. Lorsque nous obtenons la proprit, nous nous attendons recevoir la valeur dnie. Peu importe comment la classe Frame implmente cette proprit. Utilise-t-elle simplement un cadre pair pour stocker le titre ? Ou dispose-t-elle dun champ dinstance
private String title; // non requis pour la proprit

Si la classe ne possde pas de champ dinstance correspondant, nous ne savons pas comment sont implmentes les mthodes get et set (et cela nous est gal). Elles se contentent peut-tre de lire et dcrire le champ dinstance ? Ou font-elles plus, par exemple en avertissant le systme de fentres que le titre a chang. Il existe toutefois une exception la convention get/set : pour les proprits de type boolean, la mthode get dmarre par is. Ainsi, par exemple, les deux mthodes suivantes dnissent la proprit locationByPlatform :
public boolean isLocationByPlatform() public void setLocationByPlatform(boolean b)

Nous verrons les proprits plus en dtail au Chapitre 8 du Volume II.


INFO
De nombreux langages de programmation, notamment Visual Basic et C#, disposent dune prise en charge intgre pour les proprits. Il est possible quune prochaine version de Java possde galement une construction de langage allant dans ce sens.

Dterminer une taille de cadre adquate


Noubliez pas que si vous ne spciez pas explicitement la taille dun cadre, celui-ci aura par dfaut une largeur et une hauteur de 0 pixel. Pour simplier notre programme, nous avons donn au cadre une taille qui devrait tre accepte par la plupart des systmes dafchage. Cependant, dans une

Chapitre 7

Programmation graphique

297

application professionnelle, vous devez dabord dterminer la rsolution de lcran an dadapter la taille du cadre : une fentre qui peut sembler raisonnablement grande sur lcran dun portable aura la taille dun timbre-poste sur un cran en haute rsolution. Pour connatre la taille de lcran, procdez aux tapes suivantes. Appelez la mthode statique getDefaultToolkit de la classe Toolkit pour rcuprer lobjet Toolkit (la classe Toolkit est un dpotoir pour diverses mthodes qui interfacent avec le systme de fentrage natif). Appelez ensuite la mthode getScreenSize, qui renvoie la taille de lcran sous la forme dun objet Dimension (un objet Dimension stocke simultanment une largeur et une hauteur dans des variables dinstance publiques appeles respectivement width et height). Voici le code :
Toolkit kit = Toolkit.getDefaultToolkit(); Dimension screenSize = kit.getScreenSize(); int screenWidth = screenSize.width; int screenHeight = screenSize.height;

Nous utilisons 50 % de ces valeurs pour la taille du cadre et indiquons au systme de fentrage de placer le cadre :
setSize(screenWidth / 2, screenHeight / 2); setLocationByPlatform(true);

Nous fournissons galement une icne notre cadre. Comme la reprsentation des images dpend aussi du systme, nous employons la bote outils pour charger une image. Ensuite, nous affectons limage licne du cadre :
Image img = kit.getImage("icon.gif"); setIconImage(img);

La position de licne dpendra du systme dexploitation. Sous Windows, par exemple, licne safche dans le coin suprieur gauche de la fentre, et elle apparat galement dans la liste des tches lorsque vous appuyez sur la combinaison de touches Alt+Tab. Le Listing 7.2 prsente le programme complet. Lorsque vous excuterez le programme, remarquez licne "Core Java". Voici quelques conseils supplmentaires pour grer les cadres :
m

Si votre cadre ne contient que des composants standard comme des boutons et des champs de texte, il vous suft dappeler la mthode pack pour rgler sa taille. Le cadre sera rgl sur la plus petite taille qui permet de loger tous les composants. Il est assez frquent de dnir le cadre principal dun programme sur la taille maximale. Depuis Java SE 1.4, vous pouvez maximiser un cadre en appelant :
frame.setExtendedState(Frame.MAXIMIZED_BOTH);

Vous pouvez aussi retenir la manire dont lutilisateur place et dimensionne le cadre de votre application et restaurer ces limites lorsque vous relancez lapplication. Vous verrez au Chapitre 10 comment utiliser lAPI Preferences dans ce but. Si vous crivez une application qui prote de lafchage multiple, utilisez les classes GraphicsEnvironment et GraphicsDevice pour obtenir les dimensions des crans. La classe GraphicsDevice vous permet aussi dexcuter votre application en mode plein cran.

298

Au cur de Java 2 - Notions fondamentales

Listing 7.2 : SizedFrameTest.java


import java.awt.*; import javax.swing.*; /** * @version 1.32 2007-04-14 * @author Cay Horstmann */ public class SizedFrameTest { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { SizedFrame frame = new SizedFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); } } class SizedFrame extends JFrame { public SizedFrame() { // extraire les dimensions de lcran Toolkit kit = Toolkit.getDefaultToolkit(); Dimension screenSize = kit.getScreenSize(); int screenHeight = screenSize.height; int screenWidth = screenSize.width; // dfinir la largeur et la hauteur du cadre et // laisser la plate-forme choisir lemplacement lcran setSize(screenWidth / 2, screenHeight / 2); setLocationByPlatform(true); // dfinir licne et le titre du cadre Image img = kit.getImage("icon.gif"); setIconImage(img); setTitle("SizedFrame"); } } java.awt.Component 1.0

boolean isVisible() void setVisible(boolean b)

Rcuprent ou dnissent la proprit visible. A lorigine, les composants sont visibles, lexception des composants de haut niveau, comme JFrame.

Chapitre 7

Programmation graphique

299

void setSize(int width, int height) 1.1

Redimensionne le composant sur la largeur et la hauteur spcies.


void setLocation(int x, int y) 1.1

Dplace le composant vers un nouvel emplacement. Les coordonnes x et y utilisent les coordonnes du conteneur si le composant nest pas de haut niveau ou les coordonnes de lcran si le composant est de haut niveau (par exemple, a JFrame).

void setBounds(int x, int y, int width, int height) 1.1

Dplace et redimensionne le composant.


Dimension getSize() 1.1 void setSize(Dimension d) 1.1

Rcuprent ou dnissent la proprit size de ce composant.


java.awt.Window 1.0

void toFront()

Afche cette fentre par-dessus toutes les autres.


void toBack()

Place cette fentre au-dessous de la pile des fentres du bureau et rorganise les autres fentres visibles.

boolean isLocationByPlatform() 5.0 void setLocationByPlatform(boolean b) 5.0

Rcuprent ou dnissent la proprit locationByPlatform. Lorsque la proprit est dnie avant que cette fentre ne soit afche, la plate-forme choisit un emplacement adquat.
java.awt.Frame 1.0

boolean isResizable() void setResizable(boolean b)

Rcuprent ou dnissent la proprit resizable. Lorsque la proprit est dnie, lutilisateur peut modier la taille du cadre.

String getTitle() void setTitle(String s)

Rcuprent ou dnissent la proprit title qui dtermine le texte de la barre de titre du cadre.
Image getIconImage() void setIconImage(Image image)

Rcuprent ou dnissent la proprit iconImage qui dtermine licne du cadre. Le systme de fentrage peut afcher licne dans le cadre de la dcoration ou dautres endroits.

boolean isUndecorated() 1.4 void setUndecorated(boolean b) 1.4

Rcuprent ou dnissent la proprit undecorated. Lorsque la proprit est dnie, le cadre est afch sans dcorations, comme une barre de titre ou un bouton de fermeture. Cette mthode doit tre appele avant que le cadre soit afch.

int getExtendedState() 1.4

300

Au cur de Java 2 - Notions fondamentales

void setExtendedState(int state) 1.4

Rcuprent ou dnissent ltat tendu de la fentre. Ltat est lun de ceux-ci :


Frame.NORMAL Frame.ICONIFIED Frame.MAXIMIZED_HORIZ Frame.MAXIMIZED_VERT Frame.MAXIMIZED_BOTH java.awt.Toolkit 1.0 static Toolkit getDefaultToolkit()

Renvoie la bote outils par dfaut.


Dimension getScreenSize()

Rcupre la taille de lcran.


Image getImage(String filename)

Charge une image partir du chier spci par filename.

Afchage des informations dans un composant


Nous allons voir maintenant comment afcher des informations lintrieur dun cadre. Par exemple, au lieu dafcher "Not a Hello, World program" en mode texte dans une fentre de console, comme nous lavons fait au Chapitre 3, nous afcherons le message dans un cadre (voir Figure 7.6).
Figure 7.6
Un cadre qui afche des informations.

Il est possible de dessiner directement le message dans le cadre, mais ce nest pas considr comme une bonne technique de programmation. En Java, les cadres sont conus pour tre les conteneurs dautres composants (barre de menus et autres lments dinterface). On dessine normalement sur un autre composant pralablement ajout au cadre. La structure de JFrame est tonnamment complexe, comme vous pouvez le constater en observant la Figure 7.7. Vous voyez que JFrame possde quatre couches superposes. Nous navons pas nous proccuper ici de la racine (JRoot), de la couche superpose (JLayeredPane) et de la vitre ; elles sont ncessaires pour lorganisation de la barre de menus et du contenu, ainsi que pour implmenter laspect (look and feel) du cadre. La partie qui intresse les programmeurs Swing est la couche contenu. Lorsque nous concevons un cadre, nous ajoutons les composants au contenu laide dinstructions comme celles-ci :
Container contentPane = frame.getContentPane(); Component c = . . .; contentPane.add(c);

Chapitre 7

Programmation graphique

301

Jusqu Java SE 1.4, la mthode add de la classe JFrame tait dnie de manire dclencher une exception avec le message "Do not use JFrame.add(). Use JFrame.getContentPane().add() instead". Depuis Java SE 5.0, la mthode JFrame.add nessaie plus de rduquer les programmeurs et appelle simplement add sur le volet de contenu. Ainsi, depuis Java SE 5.0, vous pouvez simplement utiliser lappel :
frame.add(c);

Figure 7.7
La structure interne dun cadre JFrame.

Titre Cadre Volet racine Volet en couche Barre de menu (en option) Volet de contenu Vitrine

Dans notre cas, nous dsirons uniquement ajouter au contenu un composant sur lequel nous crirons le message. Pour dessiner sur un composant, vous dnissez une classe qui tend JComponent et remplace la mthode paintComponent dans cette classe. La mthode paintComponent reoit un paramtre, de type Graphics. Un objet Graphics mmorise une srie de paramtres pour le dessin dimages et de texte, tels que la police dnie ou la couleur. Un dessin en Java doit passer par un objet Graphics. Il possde des mthodes pour dessiner des motifs, des images et du texte.
INFO
Le paramtre Graphics est comparable un contexte dafchage de Windows ou un contexte graphique en programmation X11.

302

Au cur de Java 2 - Notions fondamentales

Voici comment crer un panneau, sur lequel vous pourrez dessiner :


class MyComponent extends JComponent { public void paintComponent(Graphics g) { code de dessin } }

Chaque fois quune fentre doit tre redessine, quelle quen soit la raison, le gestionnaire dvnement envoie une notication au composant. Les mthodes paintComponent de tous les composants sont alors excutes. Nappelez jamais directement la mthode paintComponent. Elle est appele automatiquement lorsquune portion de votre application doit tre redessine ; vous ne devez pas crer dinterfrence. Quelles sortes dactions sont dclenches par cette rponse automatique ? Par exemple, la fentre est redessine parce que lutilisateur a modi sa taille ou parce quil louvre nouveau aprs lavoir rduite dans la barre des tches. De mme, si lutilisateur a ouvert, puis referm une autre fentre au-dessus dune fentre existante, cette dernire doit tre redessine, car son afchage a t perturb (le systme graphique neffectue pas de sauvegarde des pixels cachs). Bien entendu, lors de sa premire ouverture, une fentre doit excuter le code qui indique comment, et o, les lments quelle contient sont afchs.
ASTUCE
Si vous devez forcer une fentre se redessiner, appelez la mthode repaint plutt que paintComponent. La mthode repaint appelle paintComponent pour tous les composants de la fentre, en fournissant chaque fois un objet Graphics appropri.

Comme vous avez pu le constater dans le fragment de code prcdent, la mthode paintComponent prend un seul paramtre de type Graphics. Les coordonnes et les dimensions appliques cet objet sont exprimes en pixels. Les coordonnes (0, 0) reprsentent le coin suprieur gauche du composant sur lequel vous dessinez. Lafchage de texte est considr comme une forme particulire de dessin. La classe Graphics dispose dune mthode drawString dont voici la syntaxe :
g.drawString(text, x, y)

Nous souhaitons dessiner la chane "Not a Hello, World program" peu prs au centre de la fentre originale. Bien que nous ne sachions pas encore comment mesurer la taille de la chane, nous la ferons commencer la position (75, 100). Cela signie que le premier caractre sera situ 75 pixels du bord gauche et 100 pixels du bord suprieur de la fentre (en fait, cest la ligne de base du texte qui se situe 100 pixels nous verrons bientt comment mesurer le texte). Notre mthode paintComponent ressemble donc ceci :
class NotHelloWorldComponent extends JComponent { public void paintComponent(Graphics g) {

Chapitre 7

Programmation graphique

303

g.drawString("Not a Hello, World program", MESSAGE_X, MESSAGE_Y); } public static final int MESSAGE_X = 75; public static final int MESSAGE_Y = 100; }

Le Listing 7.3 montre le code complet.


INFO
Au lieu dtendre JComponent, certains programmeurs prfrent tendre la classe JPanel. Un JPanel a pour but dtre un conteneur dautres composants, mais il est aussi possible de dessiner dessus. Il y a une seule diffrence. Un panneau est opaque, ce qui signie quil est charg de dessiner tous les pixels dans les limites. La manire la plus simple dy parvenir consiste remplir le panneau avec la couleur darrire-plan, en appelant super.paintComponent dans la mthode paintComponent de chaque sous-classe de panneau :
class NotHelloWorldPanel extends JPanel { public void paintComponent(Graphics g) { super.paintComponent(g); . . . // code de dessin } }

Listing 7.3 : NotHelloWorld.java


import javax.swing.*; import java.awt.*; /** * @version 1.32 2007-06-12 * @author Cay Horstmann */ public class NotHelloWorld { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { NotHelloWorldFrame frame = new NotHelloWorldFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); } } /** * Un cadre qui contient un panneau de message */

304

Au cur de Java 2 - Notions fondamentales

class NotHelloWorldFrame extends JFrame { public NotHelloWorldFrame() { setTitle("NotHelloWorld"); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); // ajouter le panneau au cadre NotHelloWorldPanel panel = new NotHelloWorldPanel(); add(panel); } public static final int DEFAULT_WIDTH = 300; public static final int DEFAULT_HEIGHT = 200; } /** * Un panneau qui affiche un message. */ class NotHelloWorldPanel extends JPanel { public void paintComponent(Graphics g) { g.drawString("Not a Hello, World program", MESSAGE_X, MESSAGE_Y); } public static final int MESSAGE_X = 75; public static final int MESSAGE_Y = 100; } javax.swing.JFrame 1.2

Container getContentPane()

Renvoie lobjet du panneau de contenu pour JFrame.


Component add(Component c)

Ajoute et renvoie le composant donn au volet de contenu de ce cadre (avant Java SE 5.0, cette mthode dclenchait une exception).
java.awt.Component 1.0

void repaint()

Provoque un nouveau dessin du composant "ds que possible".


public void repaint(int x, int y, int width, int height)

Provoque un nouveau dessin dune partie du composant "ds que possible".


javax.swing.JComponent 1.2

void paintComponent(Graphics g)

Surcharge cette mthode pour dcrire la manire dont votre composant doit tre dessin.

Chapitre 7

Programmation graphique

305

Formes 2D
Depuis la version 1.0 de Java, la classe Graphics disposait de mthodes pour dessiner des lignes, des rectangles, des ellipses, etc. Mais ces oprations de dessin sont trs limites. Par exemple, vous ne pouvez pas tracer des traits dpaisseurs diffrentes ni faire pivoter les formes. Java SE 1.2 a introduit la bibliothque Java 2D qui implmente un jeu doprations graphiques trs puissantes. Dans ce chapitre, nous allons seulement examiner les fonctions de base de la bibliothque Java 2D. Consultez le chapitre du Volume II traitant des fonctions AWT avances pour plus dinformations sur les techniques sophistiques. Pour dessiner des formes dans la bibliothque Java 2D, vous devez obtenir un objet de la classe Graphics2D. Il sagit dune sous-classe de la classe Graphics. Depuis Java SE 2, les mthodes telles que paintComponent reoivent automatiquement un objet de la classe Graphics2D. Il suft davoir recours un transtypage, de la faon suivante :
public void paintComponent(Graphics g) { Graphics2D g2 = (Graphics2D)g; . . . }

La bibliothque Java 2D organise les formes gomtriques dune faon oriente objet. En particulier, il existe des classes pour reprsenter des lignes, des rectangles et des ellipses :
Line2D Rectangle2D Ellipse2D

Ces classes implmentent toutes linterface Shape.


INFO
La bibliothque Java 2D gre des formes plus complexes en particulier, les arcs, les courbes quadratiques et cubiques et les objets "general path". Voir le Chapitre 7 du Volume II pour plus dinformations.

Pour dessiner une forme, vous devez dabord crer un objet dune classe qui implmente linterface Shape puis appeler la mthode draw de la classe Graphics2D. Par exemple :
Rectangle2D rect = . . .; g2.draw(rect);

INFO
Avant lapparition de la bibliothque Java 2D, les programmeurs utilisaient les mthodes de la classe Graphics telles que drawRectangle pour dessiner des formes. En apparence, les appels de mthode de lancien style paraissent plus simples. Cependant, avec la bibliothque Java 2D, vos options restent ouvertes vous pouvez ultrieurement amliorer vos dessins laide des nombreux outils que fournit la bibliothque.

Lutilisation des classes de formes Java 2D amne une certaine complexit. Contrairement aux mthodes de la version 1.0, qui utilisaient des entiers pour les coordonnes de pixels, Java 2D emploie des valeurs de coordonnes en virgule ottante. Cela est souvent pratique, car vous pouvez spcier

306

Au cur de Java 2 - Notions fondamentales

pour vos formes des coordonnes qui sont signicatives pour vous (comme des millimtres par exemple), puis les traduire en pixels. La bibliothque Java 2D utilise des valeurs float simple prcision pour nombre de ses calculs internes en virgule ottante. La simple prcision est sufsante aprs tout, le but ultime des calculs gomtriques est de dnir des pixels lcran ou sur limprimante. Tant quune erreur darrondi reste cantonne un pixel, laspect visuel nest pas affect. De plus, les calculs en virgule ottante sont plus rapides sur certaines plates-formes et les valeurs float requirent un volume de stockage rduit de moiti par rapport aux valeurs double. Cependant, la manipulation de valeurs float est parfois peu pratique pour le programmeur, car le langage Java est inexible en ce qui concerne les transtypages ncessaires pour la conversion de valeurs double en valeurs float. Par exemple, examinez linstruction suivante :
float f = 1.2; // Erreur

Cette instruction choue la compilation, car la constante 1.2 est du type double, et le compilateur est trs susceptible en ce qui concerne la perte de prcision. Le remde consiste ajouter un sufxe F la constante virgule ottante :
float f = 1.2F; // Ok

Considrez maintenant linstruction


Rectangle2D r = . . . float f = r.getWidth(); // Erreur

Cette instruction chouera galement la compilation, pour la mme raison. La mthode getWidth renvoie un double. Cette fois, le remde consiste prvoir un transtypage :
float f = (float)r.getWidth(); // Ok

Les sufxes et les transtypages tant un inconvnient vident, les concepteurs de la bibliothque 2D ont dcid de fournir deux versions de chaque classe de forme : une avec des coordonnes float pour les programmeurs conomes, et une avec des coordonnes double pour les paresseux (dans cet ouvrage, nous nous rangerons du ct des seconds, et nous utiliserons des coordonnes double dans la mesure du possible). Les concepteurs de la bibliothque ont choisi une mthode curieuse, et assez droutante au premier abord, pour le packaging de ces choix. Examinez la classe Rectangle2D. Cest une classe abstraite, avec deux sous-classes concrtes, qui sont aussi des classes internes statiques :
Rectangle2D.Float Rectangle2D.Double

La Figure 7.8 montre le schma dhritage.


Figure 7.8
Les classes Rectangle2D.
Rectangle2D

Rectangle2D .Float

Rectangle2D .Double

Chapitre 7

Programmation graphique

307

Il est prfrable de tenter dignorer le fait que les deux classes concrtes sont internes statiques cest juste une astuce pour viter davoir fournir des noms tels que FloatRectangle2D et DoubleRectangle2D (pour plus dinformations au sujet des classes internes statiques, reportez-vous au Chapitre 6). Lorsque vous construisez un objet Rectangle2D.Float, vous fournissez les coordonnes en tant que nombres float. Pour un objet Rectangle2D.Double, vous fournissez des nombres de type double :
Rectangle2D.Float floatRect = new Rectangle2D.Float(10.0F, 25.0F, 22.5F, 20.0F); Rectangle2D.Double doubleRect = new Rectangle2D.Double(10.0, 25.0, 22.5, 20.0);

En ralit, puisque la fois Rectangle2D.Float et Rectangle2D.Double tendent la classe commune Rectangle2D, et que les mthodes dans les sous-classes surchargent simplement les mthodes dans la superclasse Rectangle2D, il ny a aucun intrt mmoriser le type exact de forme. Vous pouvez simplement employer des variables Rectangle2D pour stocker les rfrences du rectangle :
Rectangle2D floatRect = new Rectangle2D.Float(10.0F, 25.0F, 22.5F, 20.0F); Rectangle2D doubleRect = new Rectangle2D.Double(10.0, 25.0, 22.5, 20.0);

Cest--dire que vous navez besoin dutiliser les empoisonnantes classes internes que lorsque vous construisez les objets forme. Les paramtres de construction indiquent le coin suprieur gauche, la largeur et la hauteur du rectangle.
INFO
En ralit, la classe Rectangle2D.Float possde une mthode supplmentaire qui nest pas hrite de Rectangle2D, il sagit de setRect(float x, float y, float h, float w). Vous perdez cette mthode si vous stockez la rfrence Rectangle2D.Float dans une variable Rectangle2D. Mais ce nest pas une grosse perte la classe Rectangle2D dispose dune mthode setRect avec des paramtres double.

Les mthodes de Rectangle2D utilisent des paramtres et des valeurs renvoyes de type double. Par exemple, la mthode getWidth renvoie une valeur double, mme si la largeur est stocke sous la forme de float dans un objet Rectangle2D.Float.
ASTUCE
Utilisez simplement les classes de forme Double pour viter davoir manipuler des valeurs float. Cependant, si vous construisez des milliers dobjets forme, envisagez demployer les classes Float pour conomiser la mmoire.

Ce que nous venons de voir pour les classes Rectangle2D est valable aussi pour les autres classes. Il existe de plus une classe Point2D avec les sous-classes Point2D.Float et Point2D.Double. Voici comment lutiliser :
Point2D p = new Point2D.Double(10, 20);

308

Au cur de Java 2 - Notions fondamentales

ASTUCE
La classe Point2D est trs utile elle est plus oriente objet pour fonctionner avec les objets Point2D quavec des valeurs x et y spares. De nombreux constructeurs et mthodes acceptent des paramtres Point2D. Nous vous suggrons dutiliser des objets Point2D autant que possible ils facilitent gnralement la comprhension des calculs gomtriques.

Les classes Rectangle2D et Ellipse2D hritent toutes deux dune superclasse commune RectangularShape. Bien sr, les ellipses ne sont pas rectangulaires, mais elles sont incluses dans un rectangle englobant (voir Figure 7.9).
Figure 7.9
Le rectangle englobant dune ellipse.

La classe RectangularShape dnit plus de vingt mthodes qui sont communes ces formes, parmi lesquelles les incontournables getWidth, getHeight, getCenterX et getCenterY (mais malheureusement, au moment o nous crivons ces lignes, il nexiste pas de mthode getCenter qui renverrait le centre sous la forme dun objet Point2D). On trouve enn deux classes hrites de Java 1.0 qui ont t insres dans la hirarchie de la classe Shape. Les classes Rectangle et Point, qui stockent un rectangle et un point avec des coordonnes entires, tendent les classes Rectangle2D et Point2D. La Figure 7.10 montre les relations entre les classes Shape. Les sous-classes Double et Float sont omises. Les classes hrites sont grises. Les objets Rectangle2D et Ellipse2D sont simples construire. Vous devez spcier :
m m

les coordonnes x et y du coin suprieur gauche ; la largeur et la hauteur.


Ellipse2D e = new Ellipse2D.Double(150, 200, 100, 50);

Pour les ellipses, ces valeurs font rfrence au rectangle englobant. Par exemple, construit une ellipse englobe dans un rectangle dont le coin suprieur gauche a les coordonnes (150, 200), avec une largeur de 100 et une hauteur de 50. Il arrive que vous ne disposiez pas directement des valeurs du coin suprieur gauche. Il est assez frquent davoir les deux coins opposs de la diagonale dun rectangle, mais peut-tre quil ne sagit pas des coins suprieur gauche et infrieur droit. Vous ne pouvez pas construire simplement un rectangle ainsi :
Rectangle2D rect = new Rectangle2D.Double(px, py, qx - px, qy - py); // Erreur

Chapitre 7

Programmation graphique

309

Figure 7.10
Relations entre les classes Shape.
Point2D Forme

Point

Line2D

Forme rectangulaire

Ellipse2D

Rectangle2D

Rectangle

Si p nest pas le coin suprieur gauche, lune des diffrences de coordonnes, ou les deux, sera ngative et le rectangle sera vide. Dans ce cas, crez dabord un rectangle vide et utilisez la mthode setFrameFromDiagonal :
Rectangle2D rect = new Rectangle2D.Double(); rect.setFrameFromDiagonal(px, py, qx, qy);

Ou, mieux encore, si vous connaissez les points dangle en tant quobjets Point2D, p et q :
rect.setFrameFromDiagonal(p, q);

Lors de la construction dune ellipse, vous connaissez gnralement le centre, la largeur et la hauteur, mais pas les points des angles du rectangle englobant (qui ne reposent pas sur lellipse). Il existe une mthode setFrameFromCenter qui utilise le point central, mais qui requiert toujours lun des quatre points dangle. Vous construisez donc gnralement une ellipse de la faon suivante :
Ellipse2D ellipse = new Ellipse2D.Double(centerX - width / 2, centerY - height / 2, width, height);

Pour construire une ligne, vous fournissez les points de dpart et darrive, sous la forme dobjets Point2D ou de paires de nombres :
Line2D line = new Line2D.Double(start, end);

ou
Line2D line = new Line2D.Double(startX, startY, endX, endY);

Le programme du Listing 7.4 trace un rectangle, lellipse incluse dans le rectangle, une diagonale du rectangle et un cercle ayant le mme centre que le rectangle. La Figure 7.11 montre le rsultat.

310

Au cur de Java 2 - Notions fondamentales

Figure 7.11
Rectangles et ellipses.

Listing 7.4 : DrawTest.java


import java.awt.*; import java.awt.geom.*; import javax.swing.*; /** * @version 1.32 2007-04-14 * @author Cay Horstmann */ public class DrawTest { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { DrawFrame frame = new DrawFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); } } /** * Un cadre contenant un panneau avec des dessins */ class DrawFrame extends JFrame { public DrawFrame() { setTitle("DrawTest"); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);

Chapitre 7

Programmation graphique

311

// ajouter un panneau au cadre DrawComponent component = new DrawComponent(); add(component); } public static final int DEFAULT_WIDTH = 400; public static final int DEFAULT_HEIGHT = 400; } /** Un composant qui affiche des rectangles et des ellipses. */ class DrawComponent extends JComponent { public void paintComponent(Graphics g) { Graphics2D g2 = (Graphics2D)g; // dessiner un rectangle double double double double leftX = 100; topY = 100; width = 200; height = 150;

Rectangle2D rect = new Rectangle2D.Double(leftX, topY, width, height); g2.draw(rect); // dessiner lellipse lintrieur Ellipse2D ellipse = new Ellipse2D.Double(); ellipse.setFrame(rect); g2.draw(ellipse); // tracer une ligne diagonale g2.draw(new Line2D.Double(leftX, topY, leftX+width, topY+height)); // dessiner un cercle ayant le mme centre double centerX = rect.getCenterX(); double centerY = rect.getCenterY(); double radius = 150; Ellipse2D circle = new Ellipse2D.Double(); circle.setFrameFromCenter(centerX, centerY, centerX+radius, centerY+radius); g2.draw(circle); } } java.awt.geom.RectangularShape 1.2 double getCenterX() double getCenterY() double getMinX() double getMinY()

312

Au cur de Java 2 - Notions fondamentales

double getMaxX() double getMaxY()

Renvoient le centre, les valeurs x ou y minimum ou maximum du rectangle englobant.


double getWidth() double getHeight()

Renvoient la largeur ou la hauteur du rectangle englobant.


double getX() double getY()

Renvoient les coordonnes x ou y du coin suprieur gauche du rectangle englobant.


java.awt.geom.Rectangle2D.Double 1.2 Rectangle2D.Double(double x, double y, double w, double h)

Construit un rectangle avec les valeurs donnes pour le coin suprieur gauche, la largeur et la hauteur.
java.awt.geom.Rectangle2D.Float 1.2 Rectangle2D.Float(float x, float y, float w, float h)

Construit un rectangle avec les valeurs donnes pour le coin suprieur gauche, la largeur et la hauteur.
java.awt.geom.Ellipse2D.Double 1.2 Ellipse2D.Double(double x, double y, double w, double h)

Construit une ellipse dont le rectangle englobant a les valeurs donnes pour le coin suprieur gauche, la largeur et la hauteur.
java.awt.geom.Point2D.Double 1.2 Point2D.Double(double x, double y)

Construit un point avec les coordonnes indiques.


java.awt.geom.Line2D.Double 1.2 Line2D.Double(Point2D start, Point2D end) Line2D.Double(double startX, double startY, double endX, double endY)

Construisent une ligne avec les points de dpart et darrive indiqus.

Couleurs
La mthode setPaint de la classe Graphics2D permet de slectionner une couleur qui sera employe par toutes les oprations de dessin ultrieures pour le contexte graphique. Par exemple :
g2.setPaint(Color.red); g2.drawString("Warning!", 100, 100);

Vous pouvez remplir de couleur lintrieur de formes fermes (rectangles ou ellipses, par exemple). Appelez simplement fill au lieu de draw :
Rectangle2D rect = . . .; g2.setPaint(Color.RED); g2.fill(rect); // remplir rect de rouge

Chapitre 7

Programmation graphique

313

Pour dessiner avec plusieurs couleurs, utilisez draw ou fill, puis choisissez une autre couleur et rptez lopration. Les couleurs sont dnies laide de la classe Color. La classe java.awt.Color propose des constantes prdnies pour les treize couleurs standard :
BLACK, BLUE, CYAN, DARK_GRAY, GRAY, GREEN, LIGHT_GRAY, MAGENTA, ORANGE, PINK, RED, WHITE, YELLOW

INFO
Avant Java SE 1.4, les noms constants des couleurs taient en minuscules, comme Color.red. Ceci est trange car la convention de codage standard consiste crire les noms constants en majuscules. Vous pouvez maintenant crire les noms des couleurs standard en majuscules ou, pour une compatibilit avec les versions antrieures, en minuscules.

Vous pouvez spcier une couleur personnalise en crant un objet Color laide de ses composantes rouge, verte et bleue. En utilisant une chelle de 0 255 (cest--dire un octet) pour les proportions de rouge, de vert et de bleu, appelez le constructeur de Color de la faon suivante :
Color(int redness, int greenness, int blueness)

Voici un exemple de dnition de couleur personnalise :


g2.setPaint(new Color(0, 128, 128)); // un bleu-vert fonc g2.drawString("Welcome!", 75, 125);

INFO
En plus des couleurs franches, vous pouvez slectionner des dnitions de "peinture" plus complexes, comme des images ou des teintes nuances. Consultez le chapitre du Volume II traitant de AWT pour plus de dtails. Si vous utilisez un objet Graphics au lieu de Graphics2D, vous devrez avoir recours la mthode setColor pour dnir les couleurs.

Pour spcier la couleur darrire-plan (ou de fond), utilisez la mthode setBackground de la classe Component, qui est un anctre de JComponent :
MyComponent p = new MyComponent(); p.setBackground(Color.PINK);

Il existe aussi une mthode setForeground. Elle spcie la couleur par dfaut utilise pour le dessin.
ASTUCE
Les mthodes brighter() et darker() de la classe Color produisent des versions plus vives ou plus fonces de la couleur actuelle. La mthode brighter permet de mettre un lment en surbrillance, mais avive en ralit peine la couleur. Pour obtenir une couleur nettement plus visible, appelez trois fois la mthode :
c.brighter().brighter().brighter().

314

Au cur de Java 2 - Notions fondamentales

Java fournit des noms prdnis pour de nombreuses couleurs dans sa classe SystemColor. Les constantes de cette classe encapsulent les couleurs employes pour divers lments du systme de lutilisateur. Par exemple,
frame.setBackground(SystemColor.window)

affecte larrire-plan du panneau la couleur utilise par dfaut par toutes les fentres du bureau (larrire-plan est rempli avec cette couleur chaque fois que la fentre est repeinte). Lemploi des couleurs de la classe SystemColor est particulirement utile si vous voulez dessiner des lments dinterface dont les couleurs doivent saccorder avec celles de lenvironnement. Le Tableau 7.1 dcrit les couleurs systme.
Tableau 7.1 : Couleurs du systme

Nom desktop activeCaption activeCaptionText activeCaptionBorder inactiveCaption inactiveCaptionText inactiveCaptionBorder window windowBorder windowText menu menuText text textText textInactiveText textHighlight textHighlightText control controlText controlLtHighlight controlHighlight controlShadow controlDkShadow scrollbar info infoText

Objet Arrire-plan du bureau Arrire-plan des titres Texte des titres Bordure des titres Arrire-plan des titres inactifs Texte des titres inactifs Bordure des titres inactifs Arrire-plan des fentres Bordure des fentres Texte lintrieur dune fentre Arrire-plan des menus Texte des menus Arrire-plan du texte Couleur du texte Texte des contrles inactifs Arrire-plan du texte en surbrillance Couleur du texte en surbrillance Arrire-plan des contrles Texte des contrles Couleur de surbrillance claire des contrles Couleur de surbrillance des contrles Ombre des contrles Ombre fonce des contrles Arrire-plan des barres de dlement Arrire-plan du texte des bulles daide Couleur du texte des bulles daide

Chapitre 7

Programmation graphique

315

java.awt.Color 1.0

Color(int r, int g, int b)

Cre un objet couleur. Paramtres : r g b


java.awt.Graphics 1.0

Valeur de la composante rouge (0-255). Valeur de la composante verte (0-255). Valeur de la composante bleue (0-255).

Color getColor() void setColor(Color c)

Rcuprent ou dnissent la couleur courante. Toutes les oprations graphiques ultrieures utiliseront la nouvelle couleur. Paramtres :

Nouvelle couleur.

java.awt.Graphics2D 1.2 Paint getPaint() void setPaint(Paint p)

Rcuprent ou dnissent les paramtres de peinture de ce contexte graphique. La classe Color implmente linterface Paint. Vous pouvez donc utiliser cette mthode pour affecter les attributs une couleur.

void fill(Shape s)

Remplit la forme de la peinture actuelle.


java.awt.Component 1.0

Color getBackground() void setBackground (Color c)

Rcuprent ou dnissent la couleur darrire-plan. Paramtres :


Nouvelle couleur darrire-plan.

Color getForeground() void setForeground(Color c)

Rcuprent ou dnissent la couleur davant-plan. Paramtres : c Nouvelle couleur davant-plan.

Texte et polices
Le programme NotHelloWorld au dbut de ce chapitre afchait une chane avec la fonte par dfaut. Il est possible dcrire un texte avec une fonte diffrente. Une fonte est spcie grce son nom de police. Un nom de police se compose dun nom de famille de polices, comme "Helvetica" et dun sufxe facultatif, comme "Bold" (gras) ou "Italic" (italique). Autrement dit, les polices "Helvetica", "Helvetica Bold" et "Helvetica Italic" font toutes partie de la famille "Helvetica". Pour connatre les fontes disponibles sur un ordinateur, appelez la mthode getAvailableFontFamilyNames de la classe GraphicsEnvironment. Cette mthode renvoie un tableau de chanes

316

Au cur de Java 2 - Notions fondamentales

contenant les noms de toutes les fontes disponibles. La mthode statique getLocalGraphicsEnvironment permet dobtenir une instance de la classe GraphicsEnvironment qui dcrit lenvironnement graphique du systme utilisateur. Le programme suivant donne en sortie une liste de tous les noms de fonte de votre systme :
import java.awt.*; public class ListFonts { public static void main(String[] args) { String[] fontNames = GraphicsEnvironment .getLocalGraphicsEnvironment() .getAvailableFontFamilyNames(); for (String fontName: fontNames) System.out.println(fontName); } }

Pour un systme donn, la liste commencera ainsi :


Abadi MT Condensed Light Arial Arial Black Arial Narrow Arioso Baskerville Binner Gothic . . .

et se poursuivra avec quelque soixante-dix fontes supplmentaires. Les noms de fontes peuvent tre dposs, et faire lobjet de copyright sous certaines juridictions. La distribution des fontes implique donc souvent le versement de droits dauteur. Bien entendu, comme dans le cas des parfums clbres, on trouve des imitations peu coteuses de certaines fontes rputes. A titre dexemple, limitation dHelvetica distribue avec Windows se nomme Arial. Pour tablir une ligne de base commune, AWT dnit cinq noms de fontes logiques :
SansSerif

Serif Espacement fixe


Dialog DialogInput

Ces noms sont toujours transcrits en fontes existant dans lordinateur client. Par exemple, dans un systme Windows, SansSerif est transcrit en Arial. En outre, le JDK de Sun comprend toujours trois familles de polices nommes "Lucida Sans", "Lucida Bright" et "Lucida Sans Typewriter". Pour crire des caractres dans une fonte, vous devez dabord crer un objet de la classe Font. Vous spciez le nom de fonte, son style et la taille en points. Voici un exemple de construction dun objet Font :
Font sansbold14 = new Font("SansSerif", Font.BOLD, 14);

Chapitre 7

Programmation graphique

317

Le troisime paramtre est la taille en points. Le point est souvent utilis en typographie pour indiquer la taille dune police. Un pouce comprend 72 points. Le nom de fonte logique peut tre employ dans le constructeur de Font. Il faut ensuite spcier le style (plain, bold, italic ou bold italic) dans le second paramtre du constructeur, en lui donnant une des valeurs suivantes :
Font.PLAIN Font.BOLD Font.ITALIC Font.BOLD+Font.ITALIC

INFO
La correspondance entre les noms de fontes logiques et physiques est dnie dans le chier fontcong.properties du sous-rpertoire jre/lib de linstallation Java. Pour plus dinformations sur ce chier, voir http://java.sun.com/javase/ 6/docs/technotes/guides/intl/fontcong.html.

Vous pouvez lire les fontes TrueType ou PostScript Type 1. Un ux dentre est ncessaire pour la fonte gnralement partir dun chier disque ou dune adresse URL (voir le Chapitre 1 du Volume II pour plus dinformations sur les ux). Appelez ensuite la mthode statique Font.createFont :
URL url = new URL("http://www.fonts.com/Wingbats.ttf"); InputStream in = url.openStream(); Font f = Font.createFont(Font.TRUETYPE_FONT, in);

La fonte est normale (plain) avec une taille de 1 point. Employez la mthode deriveFont pour obtenir une fonte de la taille dsire :
Font df = f1.deriveFont(14.0F);

ATTENTION
Il existe deux versions surcharges de la mthode deriveFont. Lune delles (ayant un paramtre float) dnit le corps de la police, lautre (avec un paramtre int), son style. Ds lors, f1.deriveFont(14) dnit le style et non le corps (le rsultat est une police italique car la reprsentation binaire de 14 dnit le type ITALIC mais non le type BOLD).

Les fontes Java contiennent les habituels caractres ASCII et des symboles. Par exemple, si vous afchez le caractre \u2297 de la fonte Dialog, vous obtenez un caractre reprsentant une croix dans un cercle. Seuls sont disponibles les symboles dnis dans le jeu de caractres Unicode. Voici maintenant le code afchant la chane "Hello, World!" avec la fonte sans srif standard de votre systme, en utilisant un style gras en 14 points :
Font sansbold14 = new Font("SansSerif", Font.BOLD, 14); g2.setFont(sansbold14); String message = "Hello, World!"; g2.drawString(message, 75, 100);

318

Au cur de Java 2 - Notions fondamentales

Nous allons maintenant centrer la chane dans son composant au lieu de lcrire une position arbitraire. Nous devons connatre la largeur et la hauteur de la chane en pixels. Ces dimensions dpendent de trois facteurs :
m m m

La fonte utilise (ici, sans srif, bold, 14 points). La chane (ici, "Hello, World!"). Lunit sur laquelle la chane est crite (ici, lcran de lutilisateur).

Pour obtenir un objet qui reprsente les caractristiques de fonte de lcran, appelez la mthode getFontRenderContext de la classe Graphics2D. Elle renvoie un objet de la classe FontRenderContext. Vous passez simplement cet objet la mthode getStringBounds de la classe Font :
FontRenderContext context = g2.getFontRenderContext(); Rectangle2D bounds = f.getStringBounds(message, context);

La mthode getStringBounds renvoie un rectangle qui englobe la chane. Pour interprter les dimensions de ce rectangle, il est utile de connatre certains des termes de typographie (voir Figure 7.12). La ligne de base (baseline) est la ligne imaginaire sur laquelle saligne la base des caractres comme "e". Le jambage ascendant (ascent) reprsente la distance entre la ligne de base et la partie suprieure dune lettre "longue du haut", comme "b" ou "k" ou un caractre majuscule. Le jambage descendant (descent) reprsente la distance entre la ligne de base et la partie infrieure dune lettre "longue du bas", comme "p" ou "g".
Figure 7.12
Illustration des termes de typographie.
ligne de base hauteur ligne de base

e b k p g

jambage ascendant jambage descendant interligne

Linterligne (leading) est lintervalle entre la partie infrieure dune ligne et la partie suprieure de la ligne suivante. La hauteur (height) est la distance verticale entre deux lignes de base successives et quivaut (jambage descendant + interligne + jambage ascendant). La largeur (width) du rectangle que renvoie la mthode getStringBounds est ltendue horizontale de la chane. La hauteur du rectangle est la somme des jambages ascendant, descendant et de linterligne. Lorigine du rectangle se trouve la ligne de base de la chane. La coordonne y suprieure du rectangle est ngative. Vous pouvez obtenir les valeurs de largeur, de hauteur et du jambage ascendant dune chane, de la faon suivante :
double stringWidth = bounds.getWidth(); double stringHeight = bounds.getHeight(); double ascent = -bounds.getY();

Si vous devez connatre le jambage descendant ou linterligne, appelez la mthode getLineMetrics de la classe Font. Elle renvoie un objet de la classe LineMetrics, possdant des mthodes permettant dobtenir le jambage descendant et linterligne :
LineMetrics metrics = f.getLineMetrics(message, context); float descent = metrics.getDescent(); float leading = metrics.getLeading();

Chapitre 7

Programmation graphique

319

Le code suivant utilise toutes ces informations pour centrer une chane lintrieur de son composant contenant :
FontRenderContext context = g2.getFontRenderContext(); Rectangle2D bounds = f.getStringBounds(message, context); // (x,y) = coin suprieur gauche du texte double x = (getWidth() - bounds.getWidth()) / 2; double y = (getHeight() - bounds.getHeight()) / 2; // ajouter le jambage ascendant y pour atteindre la ligne de base double ascent = -bounds.getY(); double baseY = y+ascent; g2.drawString(message, (int)x, (int)(baseY);

Pour comprendre le centrage, considrez que getWidth() renvoie la largeur du composant. Une portion de cette largeur, bounds.getWidth(), est occupe par la chane de message. Le reste doit tre quitablement rparti des deux cts. Par consquent, lespace vide de chaque ct reprsente la moiti de la diffrence. Le mme raisonnement sapplique la hauteur.
INFO
Pour calculer des dimensions de mise en page en dehors de la mthode paintComponent, sachez que le contexte de rendu de la fonte ne peut pas tre obtenu de lobjet Graphics2D. Appelez plutt la mthode getFontMetrics de la classe JComponent, puis appelez getFontRenderContext.
FontRenderContext context = getFontMetrics(f).getFontRenderContext();

Pour montrer que le positionnement est prcis, lexemple dessine la ligne de base et le rectangle englobant. La Figure 7.13 montre lafchage lcran, le Listing 7.5 le listing du programme.
Figure 7.13
Dessin de la ligne de base et des limites de la chane.

Listing 7.5 : FontTest.java


import import import import java.awt.*; java.awt.font.*; java.awt.geom.*; javax.swing.*;

/** * @version 1.33 2007-04-14 * @author Cay Horstmann */ public class FontTest

320

Au cur de Java 2 - Notions fondamentales

{ public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { FontFrame frame = new FontFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); } } /** * Un cadre avec un composant pour un message texte */ class FontFrame extends JFrame { public FontFrame() { setTitle("FontTest"); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); // ajouter un composant au cadre FontComponent component = new FontComponent(); add(component); } public static final int DEFAULT_WIDTH = 300; public static final int DEFAULT_HEIGHT = 200; } /** * Un composant affichant un message centr dans une bote. */ class FontComponent extends JComponent { public void paintComponent(Graphics g) { Graphics2D g2 = (Graphics2D)g; String message = "Hello, World!"; Font f = new Font("Serif", Font.BOLD, 36); g2.setFont(f); // mesurer la taille du message FontRenderContext context = g2.getFontRenderContext(); Rectangle2D bounds = f.getStringBounds(message, context); // dfinir (x,y) = coin suprieur gauche du texte double x = (getWidth() - bounds.getWidth()) / 2; double y = (getHeight() - bounds.getHeight()) / 2;

Chapitre 7

Programmation graphique

321

// ajouter jambage ascendant y pour atteindre la ligne de base double ascent = -bounds.getY(); double baseY = y+ascent; // crire le message g2.drawString(message, (int)x, (int)(baseY); g2.setPaint(Color.LIGHT_GRAY); // dessiner la ligne de base g2.draw(new Line2D.Double(x, baseY, x+bounds.getWidth(), baseY)); // dessiner le rectangle englobant Rectangle2D rect = new Rectangle2D.Double(x, y, bounds.getWidth(), bounds.getHeight()); g2.draw(rect); } } java.awt.Font 1.0

Font(String name, int style, int size)

Cre un nouvel objet fonte. Paramtres : name Le nom de la fonte. Il sagit soit dun nom de police (comme "Helvetica Bold"), soit dun nom de fonte logique (comme "Serif" ou "SansSerif"). Le style (Font.PLAIN, Font.BOLD, Font.ITALIC ou Font.BOLD +Font.ITALIC). La taille en points (par exemple, 12).

style size

String getFontName()

Renvoie le nom de police (par exemple, "Helvetica Bold").


String getFamily()

Renvoie le nom de la famille de polices (comme "Helvetica").


String getName()

Renvoie le nom logique (par exemple, "SansSerif") si la fonte a t cre avec un nom de police logique ; sinon la mthode renvoie le "nom de police" de la fonte.

Rectangle2D getStringBounds(String s, FontRenderContext context) 1.2

Renvoie un rectangle qui englobe la chane. Lorigine du rectangle se trouve sur la ligne de base. La coordonne y suprieure du rectangle est gale la valeur ngative du jambage ascendant. La hauteur du rectangle gale la somme des jambages ascendant et descendant et de linterligne. La largeur est celle de la chane.

LineMetrics getLineMetrics(String s, FontRenderContext context) 1.2

Renvoie un objet Line pour dterminer ltendue de la chane.

322

Au cur de Java 2 - Notions fondamentales

Font deriveFont(int style) 1.2 Font deriveFont(float size) 1.2 Font deriveFont(int style, float size) 1.2

Renvoient une nouvelle fonte quivalant cette fonte, avec la taille et le style demands.
java.awt.font.LineMetrics 1.2

float getAscent()

Renvoie la taille du jambage ascendant distance entre la ligne de base et le sommet des caractres majuscules.

float getDescent()

Renvoie la taille du jambage descendant distance entre la ligne de base et le bas des lettres "longues du bas", comme "p" ou "g".

float getLeading()

Renvoie linterligne lespace entre la partie infrieure dune ligne de texte et la partie suprieure de la ligne suivante.

float getHeight()

Renvoie la hauteur totale de la fonte distance entre deux lignes de base (jambage descendant + interligne + jambage ascendant).
java.awt.Graphics 1.0

Font getFont() void setFont(Font font)

Renvoient ou dnissent la fonte actuelle. Cette fonte sera employe dans les oprations ultrieures dafchage de texte. Paramtres :

font

Une fonte.

void drawString(String str, int x, int y)

Dessine une chane avec la fonte et la couleur courantes. Paramtres : str x y


java.awt.Graphics2D 1.2

La chane dessiner. Coordonne x du dbut de la chane. Coordonne y de la ligne de base de la chane.

FontRenderContext getFontRenderContext ()

Extrait un contexte de rendu de fonte qui spcie les caractristiques de la fonte dans ce contexte graphique.

void drawString(String str, float x, float y)

Dessine une chane avec la fonte et la couleur courantes. Paramtres : str x y La chane dessiner. Coordonne x du dbut de la chane. Coordonne y de la ligne de base de la chane.

Chapitre 7

Programmation graphique

323

javax.swing.JComponent 1.2

FontMetrics getFontMetrics(Font f) 5.0

Renvoie la mesure de la fonte donne. La classe FontMetrics est un prcurseur de la classe LineMetrics.
java.awt.FontMetrics 1.0

FontRenderContext getFontRenderContext() 1.2

Renvoie un contexte de rendu de fonte pour la fonte donne.

Afchage dimages
Vous avez vu comment crer des images simples en traant des lignes et des formes. Des images complexes, telles que des photographies, sont habituellement gnres en externe, par exemple avec un scanner ou un logiciel ddi la manipulation dimages (comme vous le verrez au Volume II, il est galement possible de produire une image pixel par pixel, et de stocker le rsultat dans un tableau. Cette procdure est courante pour les images fractales, par exemple). Lorsque des images sont stockes dans des chiers locaux ou sur Internet, vous pouvez les lire dans une application Java et les afcher sur des objets de type Graphics. Depuis Java SE 1.4, la lecture dune image est trs simple. Si limage est stocke dans un chier local, appelez
String filename = "..."; Image image = ImageIO.read(new File(filename));

Sinon, vous pouvez fournir une URL :


String urlname = "..."; Image image = ImageIO.read(new URL(urlname));

La mthode de lecture dclenche une exception IOException si limage nest pas disponible. Nous traiterons du sujet gnral de la gestion des exceptions au Chapitre 11. Pour linstant, notre exemple de programme se contente dintercepter cette exception et afche un stack trace le cas chant. La variable image contient alors une rfrence un objet qui encapsule les donnes image. Vous pouvez afcher limage grce la mthode drawImage de la classe Graphics :
public void paintComponent(Graphics g) { . . . g.drawImage(image, x, y, null); }

Le Listing 7.6 va un peu plus loin et afche limage en mosaque dans la fentre. Le rsultat ressemble celui de la Figure 7.14. Lafchage en mosaque est effectu dans la mthode paintComponent. Nous dessinons dabord une copie de limage dans le coin suprieur gauche, puis nous appelons copyArea pour la copier dans toute la fentre :
for (int i = 0; i * imageWidth <= getWidth(); i++) for (int j = 0; j * imageHeight <= getHeight(); j++) if (i+j > 0) g.copyArea(0, 0, imageWidth, imageHeight, i * imageWidth, j * imageHeight);

324

Au cur de Java 2 - Notions fondamentales

Figure 7.14
Afchage dune image en mosaque dans une fentre.

Le Listing 7.6 prsente le code source du programme de dmonstration permettant dafcher une image.
Listing 7.6 : ImageTest.java
import import import import java.awt.*; java.io.*; javax.imageio.*; javax.swing.*;

/** * @version 1.33 2007-04-14 * @author Cay Horstmann */ public class ImageTest { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { ImageFrame frame = new ImageFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); } } /** * Un cadre avec un composant pour une image */ class ImageFrame extends JFrame { public ImageFrame() { setTitle("ImageTest"); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); // ajouter un composant au cadre ImageComponent component = new ImageComponent(); add(component); }

Chapitre 7

Programmation graphique

325

public static final int DEFAULT_WIDTH = 300; public static final int DEFAULT_HEIGHT = 200; } /** * Un composant qui affiche une image en mosaque */ class ImageComponent extends JComponent { public ImageComponent() { // acqurir limage try { image = ImageIO.read(new File("blue-ball.gif")); } catch (IOException e) { e.printStackTrace(); } } public void paintComponent(Graphics g) { if (image == null) return; int imageWidth = image.getWidth(this); int imageHeight = image.getHeight(this); // dessiner limage dans le coin suprieur gauche g.drawImage(image, 0, 0, null); // afficher limage en mosaque dans le composant for (int i = 0; i * imageWidth <= getWidth(); i++) for (int j = 0; j * imageHeight <= getHeight(); j++) if (i+j > 0) g.copyArea(0, 0, imageWidth, imageHeight, i * imageWidth, j * imageHeight); } private Image image; } javax.imageio.ImageIO 1.4

static BufferedImage read(File f) static BufferedImage read(URL u)

Renvoient une image partir du chier donn ou de lURL.


java.awt.Graphics 1.0

boolean drawImage(Image img, int x, int y, ImageObserver observer)

Dessine une image non mise lchelle. Remarque : cette mthode peut renvoyer un rsultat avant que limage ne soit compltement dessine.

326

Au cur de Java 2 - Notions fondamentales

Paramtres :

img x y observer

Limage dessiner. Coordonne x du coin suprieur gauche. Coordonne y du coin suprieur gauche. Lobjet qui doit tre noti en ce qui concerne la progression de lafchage (cette valeur peut tre null).

boolean drawImage(Image img, int x, int y, int width, int height, ImageObserver observer)

Dessine une image mise lchelle. Le systme adapte la taille de limage an quelle occupe une zone ayant la largeur et la hauteur spcies. Remarque : cette mthode peut renvoyer un rsultat avant que limage ne soit compltement dessine. Paramtres : img x y width height observer

Limage dessiner. Coordonne x du coin suprieur gauche. Coordonne y du coin suprieur gauche. Largeur souhaite pour limage. Hauteur souhaite pour limage. Lobjet qui doit tre noti en ce qui concerne la progression de lafchage (cette valeur peut tre null).

void copyArea(int x, int y, int width, int height, int dx, int dy)

Copie une zone de lcran. Paramtres : x y width height dx dy Coordonne x du coin suprieur gauche de la zone source. Coordonne y du coin suprieur gauche de la zone source. Largeur de la zone source. Hauteur de la zone source. Distance horizontale entre la zone source et la zone cible. Distance verticale entre la zone source et la zone cible.

Cela conclut notre introduction sur la programmation du graphisme dans Java. Pour passer des techniques plus avances, dcouvrez le graphisme et les images 2D dans le Volume II. Au prochain chapitre, nous verrons la raction dun programme une saisie utilisateur.

8
Gestion des vnements
Au sommaire de ce chapitre

Introduction la gestion des vnements Actions Evnements de la souris Hirarchie des vnements AWT
La gestion des vnements est dune importance capitale pour les programmes ayant une interface. Pour implmenter des interfaces utilisateur graphiques, vous devez matriser la gestion des vnements. Ce chapitre explique le fonctionnement du modle dvnement AWT. Vous apprendrez capturer les vnements des composants de linterface utilisateur et des dispositifs dentre. Nous verrons galement comment utiliser les actions, une technique plus structure pour traiter les vnements daction.

Introduction la gestion des vnements


Tout environnement dexploitation qui gre les interfaces utilisateur graphiques doit constamment dtecter les vnements tels que la pression sur une touche du clavier ou sur un bouton de la souris. Lenvironnement dexploitation en informe alors les programmes en cours dexcution. Chaque programme dtermine ensuite sil doit rpondre ces vnements. Dans des langages comme Visual Basic, la correspondance entre les vnements et le code est vidente. Le programmeur crit une routine pour chaque vnement digne dintrt et place ce code dans ce que lon appelle une procdure dvnement. Par exemple, un bouton nomm "HelpButton" serait associ une procdure dvnement HelpButton_Click. Le code de cette procdure sexcute en rponse un clic de la souris sur ce bouton. En Visual Basic, chaque composant dinterface rpond un ensemble xe dvnements, et il est impossible de modier les vnements auxquels il peut rpondre. En revanche, si vous employez un langage comme le C pur pour faire de la programmation vnementielle, vous devez crire le code qui vrie constamment la le dvnements (en gnral, ce code est imbriqu dans une boucle gante contenant une norme instruction switch !). Cette technique

328

Au cur de Java 2 - Notions fondamentales

est videmment peu lgante et difcile programmer. Elle a cependant un avantage : les vnements auxquels lapplication peut rpondre ne sont pas limits comme dans les langages qui sefforcent de dissimuler la le dvnements au programmeur (tel Visual Basic). Lenvironnement de programmation Java a choisi une approche intermdiaire entre celle du C et celle de Visual Basic, ce qui augmente sa complexit. Dans les limites des vnements connus dAWT, vous contrlez compltement la manire dont les vnements sont transmis de la source dvnement (par exemple, un bouton ou une barre de dlement) lcouteur dvnement (event listener). Vous pouvez dsigner nimporte quel objet comme couteur dvnement dans la pratique, vous choisissez un objet qui est capable de fournir une rponse approprie lvnement. Ce modle de dlgation dvnement offre une exibilit bien suprieure celle de Visual Basic, o lcouteur est prdtermin. Les sources dvnement ont des mthodes qui leur permettent denregistrer (ou de recenser) les couteurs dvnement. Lorsquun vnement arrive la source, celle-ci envoie une notication tous les objets couteurs recenss pour cet vnement. Bien entendu, dans un langage orient objet comme Java, linformation relative lvnement est encapsule dans un objet vnement. Tous les objets vnement drivent, directement ou indirectement, de la classe java.util.EventObject. Il existe videmment des sous-classes pour chaque type dvnement, comme ActionEvent et WindowEvent. Diffrentes sources dvnement peuvent produire diffrentes sortes dvnements. Par exemple, un bouton peut envoyer des objets ActionEvent alors quune fentre enverra des objets WindowEvent. Pour rsumer, voici en gros comment les vnements sont grs avec AWT :
m

Un objet couteur est une instance dune classe qui implmente une interface spciale appele interface couteur (listener interface). Une source dvnement est un objet qui peut recenser des objets couteurs et leur envoyer des objets vnements. Lorsquun vnement se produit, la source dvnement envoie lobjet vnement tous les couteurs recenss. Les objets couteurs utilisent alors linformation contenue dans lobjet vnement pour dterminer leur rponse.

La Figure 8.1 montre les relations entre les classes de gestion dvnement et les interfaces.
Figure 8.1
Relation entre des sources dvnements et des couteurs.
Source dvnement 1 * <<jeu de un ou plus>> Ecouteur dvnement

implmente

Interface dcouteur

Chapitre 8

Gestion des vnements

329

Voici un exemple permettant de spcier un couteur :


ActionListener listener = . . .; JButton button = new JButton("Ok"); button.addActionListener(listener);

Lobjet listener recevra dsormais une notication chaque fois quun "vnement Action" concernera le bouton button. Dans le cas dun bouton, un vnement Action est un clic de la souris sur ce bouton. Pour implmenter linterface ActionListener, la classe de lcouteur doit possder une mthode (nomme actionPerformed) qui recevra lobjet ActionEvent comme paramtre :
class MyListener implements ActionListener { . . . public void actionPerformed(ActionEvent event) { // la raction un clic sur le bouton, ici . . . } }

Chaque fois que lutilisateur clique sur le bouton, lobjet JButton cre un objet ActionEvent et appelle listener.actionPerformed(event) en lui passant cet objet vnement. Une source dvnement, par exemple un bouton, peut avoir plusieurs couteurs. Dans ce cas, le bouton appelle les mthodes actionPerformed de tous les couteurs, chaque fois que lutilisateur clique sur le bouton. La Figure 8.2 montre linteraction entre la source de lvnement, lcouteur dvnement et lobjet vnement.
Figure 8.2
Notication dvnement.
MyFrame

new

JButton

new addActionListener

MyListener

actionPerformed

330

Au cur de Java 2 - Notions fondamentales

Exemple : gestion dun clic de bouton


Pour mieux comprendre le modle de dlgation dvnement, examinons ce quil nous faut pour construire un simple exemple de rponse un clic sur un bouton. Pour cet exemple, nous prsenterons un panneau rempli de trois boutons. Trois objets couteurs sont ajouts comme couteurs daction des boutons. Dans cet exemple, chaque fois que lutilisateur clique sur un des boutons du panneau, lobjet couteur associ reoit un objet ActionEvent indiquant un clic sur un bouton. Dans notre exemple, lobjet couteur change alors la couleur darrire-plan du panneau. Avant de vous montrer le programme lcoute des clics sur les boutons, nous devons expliquer comment crer des boutons et les ajouter au panneau (pour dautres informations sur les lments dune interface graphique, reportez-vous au Chapitre 9). Pour crer un bouton, nous spcions une chane de libell ou une icne (ou les deux) dans le constructeur du bouton. Voici deux exemples :
JButton yellowButton = new JButton("Yellow"); JButton blueButton = new JButton(new ImageIcon("blue-ball.gif"));

Les boutons sont ajouts au panneau par des appels une mthode nomme add :
JButton yellowButton = new JButton("Yellow"); JButton blueButton = new JButton("Blue"); JButton redButton = new JButton("Red"); buttonPanel.add(yellowButton); buttonPanel.add(blueButton); buttonPanel.add(redButton);

Le rsultat est montr la Figure 8.3.

Figure 8.3
Un panneau contenant des boutons.

Il faut maintenant crire le code qui permet dcouter ces boutons. Cela ncessite des classes implmentant linterface ActionListener qui ne possde quune seule mthode, nomme actionPerformed, dont voici la signature :
public void actionPerformed(ActionEvent event)

Chapitre 8

Gestion des vnements

331

INFO
Linterface ActionListener utilise dans notre exemple nest pas limite aux clics sur des boutons. Elle peut tre utilise dans bien dautres situations, par exemple : lors de la slection dun lment de menu ; lors de la slection dun lment dans une zone de liste laide dun double-clic ; lorsque la touche "Entre" est active dans un champ de texte ; lorsquun composant Timer dclenche une impulsion aprs lcoulement dun temps donn. Nous reviendrons sur cette interface dans la suite de ce chapitre et dans le suivant.

ActionListener sutilise de la mme manire dans toutes les situations : sa mthode (unique) actionPerformed reoit en paramtre un objet de type ActionEvent. Cet objet vnement fournit des informations sur lvnement
qui a t dclench.

Lorsquun bouton est cliqu, nous voulons modier la couleur darrire-plan du panneau contenant les boutons et nous stockons la couleur choisie dans notre classe couteur :
class ColorAction implements ActionListener { public ColorAction(Color c) { backgroundColor = c; } public void actionPerformed(ActionEvent event) { // dfinir la couleur darrire-plan du panneau . . . } private Color backgroundColor; }

Nous construisons ensuite un objet pour chaque couleur et les dnissons comme couteurs des boutons :
ColorAction yellowAction = new ColorAction(Color.YELLOW); ColorAction blueAction = new ColorAction(Color.BLUE); ColorAction redAction = new ColorAction(Color.RED); yellowButton.addActionListener(yellowAction); blueButton.addActionListener(blueAction); redButton.addActionListener(redAction);

Par exemple, si un utilisateur clique sur un bouton marqu "Yellow", la mthode actionPerformed de lobjet yellowAction est appele. Son champ dinstance backgroundColor est dni Color.YELLOW et la mthode peut alors poursuivre la dnition de la couleur darrire-plan du panneau. Il reste un problme. Lobjet ColorAction na pas accs la variable buttonPanel. Vous pouvez rsoudre ce problme de deux faons. Vous pouvez stocker le panneau dans lobjet ColorAction et le dnir dans le constructeur de ColorAction. Ou, mieux encore, vous pouvez construire lobjet ColorAction dans une classe interne de la classe ButtonPanel. Ses mthodes peuvent alors

332

Au cur de Java 2 - Notions fondamentales

accder au panneau externe automatiquement (pour plus dinformations sur les classes internes, voir le Chapitre 6). Nous allons adopter la seconde approche. Voici comment placer la classe ColorAction lintrieur de la classe ButtonFrame :
class ButtonPanel extends JPanel { . . . private class ColorAction implements ActionListener { . . . public void actionPerformed(ActionEvent event) { buttonPanel.setBackground(backgroundColor); } private Color backgroundColor; } private JPanel buttonPanel; }

Examinez attentivement la mthode actionPerformed. La classe ColorAction na pas de champ buttonPanel. Mais la classe externe ButtonFrame en possde. Cette situation est trs courante. Les objets couteurs dvnement ont habituellement besoin de raliser une action qui affecte dautres objets. Vous pouvez souvent placer stratgiquement la classe couteur lintrieur de la classe dont ltat doit tre modi par lcouteur. Le Listing 8.1 contient la totalit du programme. Chaque fois que vous cliquez sur lun des boutons, lcouteur daction appropri modie la couleur darrire-plan du panneau.
Listing 8.1 : ButtonTest.java
import java.awt.*; import java.awt.event.*; import javax.swing.*; /** * @version 1.33 2007-06-12 * @author Cay Horstmann */ public class ButtonTest { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { ButtonFrame frame = new ButtonFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); } }

Chapitre 8

Gestion des vnements

333

/** * Un cadre avec un panneau contenant des boutons */ class ButtonFrame extends JFrame { public ButtonFrame() { setTitle("ButtonTest"); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); // crer les boutons JButton yellowButton = new JButton("Yellow"); JButton blueButton = new JButton("Blue"); JButton redButton = new JButton("Red"); buttonPanel = new JPanel(); // ajouter les boutons au panneau buttonPanel.add(yellowButton); buttonPanel.add(blueButton); buttonPanel.add(redButton); // ajouter le panneau au cadre add(buttonPanel); // crer les actions des boutons ColorAction yellowAction = new ColorAction(Color.YELLOW); ColorAction blueAction = new ColorAction(Color.BLUE); ColorAction redAction = new ColorAction(Color.RED); // associer les actions aux boutons yellowButton.addActionListener(yellowAction); blueButton.addActionListener(blueAction); redButton.addActionListener(redAction); } /** * Un couteur daction qui dfinit la couleur darrire-plan du panneau. */ private class ColorAction implements ActionListener { public ColorAction(Color c) { backgroundColor = c; } public void actionPerformed(ActionEvent event) { buttonPanel.setBackground(backgroundColor); } private Color backgroundColor; } private JPanel buttonPanel; public static final int DEFAULT_WIDTH = 300; public static final int DEFAULT_HEIGHT = 200; }

334

Au cur de Java 2 - Notions fondamentales

javax.swing.JButton 1.2

JButton(String label) JButton(Icon icon) JButton(String label, Icon icon)

Construisent un bouton. La chane du label peut tre du texte pur ou HTML partir de Java SE 1.3 ; par exemple, "<html><b>Ok</b></html>".
java.awt.Container 1.0

Component add(Component c)

Ajoute le composant c ce conteneur.


javax.swing.ImageIcon 1.2

ImageIcon(String filename)

Construit une icne dont limage est stocke dans un chier.

Etre confortable avec les classes internes


Certains dtestent les classes internes, car ils ont limpression quune prolifration de classes et dobjets ralentit les programmes. Voyons ce quil en est. Vous navez pas besoin dune nouvelle classe pour chaque composant de linterface utilisateur. Dans notre exemple, les trois boutons partagent la mme classe couteur. Bien sr, chacun deux a un objet couteur spar, mais ces objets ne sont pas gros. Ils contiennent chacun une valeur de couleur et une rfrence au panneau. Et la solution traditionnelle, avec des instructions if . . . else, rfrence les mmes objets couleur que ceux stocks par les couteurs daction, comme des variables locales et non comme des champs dinstance. Voici un bon exemple de la faon dont les classes internes anonymes peuvent rellement simplier votre code. Si vous examinez le code du Listing 8.1, vous verrez que chaque bouton requiert le mme traitement : 1. construire le bouton avec un libell ; 2. ajouter le bouton au panneau ; 3. construire un couteur daction avec la couleur approprie ; 4. ajouter cet couteur daction. Implmentons une mthode "assistante" (helper) pour simplier ces tches :
public void makeButton(String name, Color backgroundColor) { JButton button = new JButton(name); buttonPanel.add(button); ColorAction action = new ColorAction(backgroundColor); button.addActionListener(action); }

Nous appelons alors simplement :


makeButton("yellow", Color.YELLOW); makeButton("blue", Color.BLUE); makeButton("red", Color.RED);

Chapitre 8

Gestion des vnements

335

Vous pouvez encore simplier. Notez que la classe ColorAction nest ncessaire quune fois, dans la mthode makeButton. Vous pouvez donc en faire une classe anonyme :
public void makeButton(String name, final Color backgroundColor) { JButton button = new JButton(name); buttonPanel.add(button); button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { buttonPanel.setBackground(backgroundColor); } }); }

Le code de lcouteur daction a t simpli. La mthode actionPerformed fait simplement rfrence la variable paramtre backgroundColor (comme pour toutes les variables locales auxquelles on accde dans la classe interne, le paramtre doit tre dclar comme final). Aucun constructeur explicite nest ncessaire. Comme vous avez vu au Chapitre 6, le mcanisme de classe interne gnre automatiquement un constructeur qui stocke toutes les variables final locales qui sont utilises dans lune des mthodes de la classe interne.
ASTUCE
Les classes internes anonymes peuvent paratre droutantes. Mais vous pouvez vous habituer les dchiffrer en vous entranant analyser le code de la faon suivante :
button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { buttonPanel.setBackground(backgroundColor); } });

Cest--dire que laction du bouton consiste dnir une couleur darrire-plan. Tant que le gestionnaire dvnement est constitu simplement de quelques instructions, cela reste trs lisible, en particulier si vous ne vous proccupez pas des mcanismes de classe interne.

INFO
Vous tes tout fait libre de dsigner nimporte quel objet dune classe qui implmente linterface ActionListener comme couteur du bouton. Nous prfrons utiliser les objets dune nouvelle classe qui a t expressment cre pour raliser les actions souhaites. De nombreux programmeurs ne sont pas trs laise avec les classes internes et choisissent une stratgie diffrente. Ils amnent le composant des sources dvnement implmenter linterface ActionListener. Puis le conteneur se dnit lui-mme comme couteur, comme ceci :
yellowButton.addActionListener(this); blueButton.addActionListener(this); redButton.addActionListener(this);

336

Au cur de Java 2 - Notions fondamentales

Notez que, maintenant, les trois boutons nont plus dcouteurs individuels. Ils partagent un unique objet couteur, le cadre avec les boutons. Par consquent, la mthode actionPerformed doit dterminer sur quel bouton il a t cliqu.
class ButtonFrame extends JFrame implements ActionListener { . . . public void actionPerformed(ActionEvent event) { Object source = event.getSource(); if (source == yellowButton) . . . else if (source == blueButton) . . . else if (source == redButton ) . . . else . . . } }

Vous le voyez, cela peut tre un peu confus et nous le dconseillons.

java.util.EventObject 1.1

Object getSource()

Renvoie une rfrence sur lobjet o lvnement sest produit.


java.awt.event.ActionEvent 1.1

String getActionCommand()

Renvoie la chane de commande associe cet vnement action. Si lvnement a t dclench par un bouton, la chane de commande contient le libell du bouton, moins dun changement intervenu par le biais de la mthode setActionCommand.
java.beans.EventHandler 1.4

static Object create(Class listenerInterface, Object target, String action) static Object create(Class listenerInterface, Object target, String action, String eventProperty) static Object create(Class listenerInterface, Object target, String action, String eventProperty, String listenerMethod)

Ces mthodes construisent un objet dune classe proxy qui implmente linterface donne. La mthode nomme ou toutes les mthodes de linterface ralisent laction donne sur lobjet cible. Laction peut tre un nom de mthode ou une proprit de la cible. Sil sagit dune proprit, sa mthode setter est excute. Par exemple, une action "text" est transforme en appel de la mthode setText. La proprit dvnement est constitue dun ou de plusieurs noms de proprit spars par un point. La premire proprit est lue depuis le paramtre de la mthode couteur. La deuxime est lue partir de lobjet de rsultat, etc. Le rsultat dnitif devient le paramtre de laction. Par exemple, la proprit "source.text" est transforme en appels aux mthodes getSource et getText.

Chapitre 8

Gestion des vnements

337

Crer des couteurs contenant un seul appel de mthode


Java SE 1.4 introduit un mcanisme qui vous permet de spcier des couteurs dvnement simples sans programmer de classes internes. Supposons par exemple que vous ayez un bouton intitul Load dont le gestionnaire dvnement contienne un seul appel de mthode :
frame.loadData();

Vous pouvez bien entendu utiliser une classe interne anonyme :


loadButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { frame.loadData(); } });

Or, la classe EventHandler peut crer automatiquement cet couteur, par lappel
EventHandler.create(ActionListener.class, frame, "loadData")

Vous devrez bien sr installer malgr tout le gestionnaire :


loadButton.addActionListener( EventHandler.create(ActionListener.class, frame, "loadData"));

Si lcouteur dvnement appelle une mthode avec un seul paramtre, driv du gestionnaire dvnement, vous pouvez utiliser une autre forme de la mthode create. Par exemple, lappel
EventHandler.create(ActionListener.class, frame, "loadData", source.text")

quivaut
new ActionListener() { public void actionPerformed(ActionEvent event) { frame.loadData(((JTextField) event.getSource()).getText() ); } }

Les noms des proprits source et text se transforment en appels de mthode getSource et getText.

Exemple : modication du "look and feel"


Par dfaut, les programmes Swing utilisent laspect et le comportement (look and feel) Metal. Il y a deux moyens de choisir un look and feel diffrent. Le premier consiste placer, dans les rpertoires jre/lib, un chier swing.properties qui donne la proprit swing.defaultlaf de la classe le look and feel que vous dsirez employer. Par exemple :
swing.defaultlaf=com.sun.java.swing.plaf.motif.MotifLookAndFeel

Notez que le look and feel Metal est situ dans le package javax.swing. Les autres se trouvent dans le package com.sun.java et ne doivent pas ncessairement tre prsents dans chaque implmentation de Java. Actuellement, pour des raisons de copyright, les look and feel Windows et Macintosh sont uniquement fournis avec les versions Windows ou Macintosh de lenvironnement dexcution de Java.

338

Au cur de Java 2 - Notions fondamentales

ASTUCE
Comme les lignes dbutant par le caractre # sont ignores dans les chiers de proprits, vous pouvez spcier plusieurs look and feel dans le chier swing.properties et slectionner celui que vous dsirez en supprimant le caractre #, de la faon suivante :
#swing.defaultlaf=javax.swing.plaf.metal.MetalLookAndFeel swing.defaultlaf=com.sun.java.swing.plaf.motif.MotifLookAndFeel #swing.defaultlaf=com.sun.java.swing.plaf.windows.WindowsLookAndFeel

Vous devez relancer votre programme pour modier le look and feel de cette manire. Un programme Swing ne lit quune seule fois le chier swing.properties, au dmarrage.

La deuxime mthode consiste modier dynamiquement le look and feel. Appelez la mthode statique UIManager.setLookAndFeel et passez-lui le nom du look and feel souhait. Appelez ensuite la mthode statique SwingUtilities.updateComponentTreeUI pour actualiser lensemble des composants. Vous navez besoin de fournir quun seul composant cette mthode ; elle trouvera tous les autres. La mthode UIManager.setLookAndFeel peut lancer un certain nombre dexceptions si le look and feel recherch nest pas trouv ou si une erreur survient durant son chargement. Comme dhabitude, nous vous conseillons de ne pas vous proccuper des exceptions pour linstant ; elles seront dtailles au Chapitre 11. Voici un exemple permettant de passer au look and feel Motif :
String plaf = "com.sun.java.swing.plaf.motif.MotifLookAndFeel"; try { UIManager.setLookAndFeel(plaf); SwingUtilities.updateComponentTreeUI(panel); } catch(Exception e) { e.printStackTrace(); }

Pour numrer toutes les implmentations de look and feel installes, appelez
UIManager.LookAndFeelInfo[] infos = UIManager.getInstalledLookAndFeels();

Vous obtenez ensuite le nom et le nom de classe pour chaque look and feel sous la forme
String name = infos[i].getName(); String className = infos[i].getClassName();

Le Listing 8.2 est un programme de dmonstration qui montre comment changer de look and feel (voir Figure 8.4). Ce programme ressemble beaucoup celui du Listing 8.1. Conformment aux explications de la prcdente section, nous employons une mthode assistante makeButton et une classe interne anonyme pour spcier laction du bouton, cest--dire le changement de look and feel. Il y a une subtilit dans ce programme. La mthode actionPerformed de la classe couteur daction interne doit pouvoir passer la rfrence this de la classe externe PlafFrame la mthode updateComponentTreeUI. Souvenez-vous, nous avons vu au Chapitre 6 que le pointeur this de lobjet extrieur doit avoir pour prxe le nom de la classe externe :
SwingUtilities.updateComponentTreeUI( PlafPanel.this);

Chapitre 8

Gestion des vnements

339

Figure 8.4
Modication du look and feel.

Listing 8.2 : PlafTest.java


import java.awt.EventQueue; import java.awt.event.*; import javax.swing.*; /** * @version 1.32 2007-06-12 * @author Cay Horstmann */ public class PlafTest { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { PlafFrame frame = new PlafFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); } } /** * Un cadre avec un panneau et des boutons pour * changer le "look and feel" */ class PlafFrame extends JFrame { public PlafFrame() { setTitle("PlafTest"); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); buttonPanel = new JPanel();

340

Au cur de Java 2 - Notions fondamentales

UIManager.LookAndFeelInfo[] infos = UIManager.getInstalledLookAndFeels(); for (UIManager.LookAndFeelInfo info: infos) makeButton(info.getName(), info.getClassName()); add(buttonPanel); } /** * Construit un bouton pour changer le look and feel. * @param name Le nom du bouton * @param plafName Le nom de la classe look and feel */ void makeButton(String name, final String plafName) { // ajouter un bouton au panneau JButton button = new JButton(name); ButtonPanel.add(button); // dfinir laction du bouton button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { // action du bouton: passer au nouveau look and feel try { UIManager.setLookAndFeel(plafName); SwingUtilities.updateComponentTreeUI(PlafFrame.this); } catch(Exception e) { e.printStackTrace(); } } }); } private JPanel buttonPanel; public static final int DEFAULT_WIDTH = 300; public static final int DEFAULT_HEIGHT = 200; } javax.swing.UIManager 1.2

static UIManager.LookAndFeelInfo[] getInstalledLookAndFeels()

Rcupre un tableau dobjets qui dcrit les implmentations du look and feel install.
static setLookAndFeel(String className)

Dnit le look and feel actuel, laide du nom de classe donn (comme "javax.swing.plaf. metal.MetalLookAndFeel").

Chapitre 8

Gestion des vnements

341

javax.swing.UIManager.LookAndFeelInfo 1.2

String getName()

Renvoie le nom dafchage pour le look and feel.


String getClassName()

Renvoie le nom de la classe dimplmentation pour le look and feel.

Classes adaptateurs
Tous les vnements ne sont pas aussi simples grer que les clics de boutons. Dans un programme professionnel, vous devez surveiller le moment o lutilisateur tente de fermer le cadre principal car il ne doit pas perdre de travail. Vous pouvez souhaiter afcher une bote de dialogue lorsque lutilisateur ferme le cadre et ne sortir quaprs conrmation de lutilisateur. Quand lutilisateur tente de fermer un cadre, lobjet JFrame est la source dun vnement WindowEvent. Nous devons avoir un objet couteur appropri et lajouter la liste des couteurs de fentre du cadre :
WindowListener listener = . . .; frame.addWindowListener(listener);

Lcouteur de fentre doit tre un objet dune classe implmentant linterface WindowListener, qui possde sept mthodes. Le cadre les appelle en rponse aux sept vnements distincts qui peuvent se produire dans une fentre. Voici linterface complte de WindowListener :
public interface WindowListener { void windowOpened(WindowEvent e); void windowClosing(WindowEvent e); void windowClosed(WindowEvent e); void windowIconified(WindowEvent e); void windowDeiconified(WindowEvent e); void windowActivated(WindowEvent e); void windowDeactivated(WindowEvent e); }

INFO
Pour savoir si une fentre a t maximise, vous devez installer un WindowStateListener. Voyez les notes API pour en savoir plus.

Comme toujours en Java, toute classe qui implmente une interface doit implmenter toutes les mthodes de cette interface ; dans ce cas, cela signie que les sept mthodes doivent tre implmentes. Une seule nous intresse en loccurrence : la mthode windowClosing. Nous pouvons bien sr dnir une classe qui implmente linterface, ajouter un appel System.exit(0) dans la mthode windowClosing et fournir des blocs vides pour les six autres mthodes :
class Terminator implements WindowListener { public void windowClosing(WindowEvent e)

342

Au cur de Java 2 - Notions fondamentales

{ if (lutilisateur accepte) System.exit(0); } public public public public public public } void void void void void void windowOpened(WindowEvent e) {} windowClosed(WindowEvent e) {} windowIconified(WindowEvent e) {} windowDeiconified(WindowEvent e) {} windowActivated(WindowEvent e) {} windowDeactivated(WindowEvent e) {}

Il est fastidieux dcrire les signatures de six mthodes qui ne font rien. Pour simplier la tche du programmeur, chacune des interfaces AWT possdant plusieurs mthodes est accompagne dune classe adaptateur qui implmente toutes les mthodes de linterface en leur attribuant des instructions vides. Par exemple, la classe WindowAdapter possde sept mthodes qui ne font rien. Cela signie que la classe adaptateur satisfait automatiquement aux exigences techniques imposes par Java pour limplmentation de linterface couteur qui lui est associe. Nous pouvons tendre la classe adaptateur an de spcier les ractions souhaites pour certains vnements, mais sans avoir besoin de rpondre explicitement tous les vnements de linterface (une interface comme ActionListener, qui ne possde quune seule mthode, na pas besoin de classe adaptateur). Protons de cette caractristique et utilisons ladaptateur de Window. Nous pouvons tendre la classe WindowAdapter, hritant ainsi de six mthodes qui ne font rien et nous contenter de surcharger la mthode WindowClosing :
class Terminator extends WindowAdapter { public void windowClosing(WindowEvent e) { if (lutilisateur accepte) System.exit(0); } }

Nous pouvons maintenant recenser un objet de type Terminator en tant qucouteur dvnement :
WindowListener listener = new Terminator(); frame.addWindowListener(listener);

Chaque fois que le cadre dclenchera un vnement de fentre, il passera cet vnement lobjet listener en appelant une de ses sept mthodes (voir Figure 8.5). Six dentre elles ne feront rien ; la mthode windowClosing appelle System.exit(0) pour terminer lexcution de lapplication.
ATTENTION
Si vous avez mal orthographi le nom dune mthode lors de lextension dune classe dadaptateur, le compilateur ninterceptera pas votre erreur. Si vous dnissez par exemple une mthode windowIsClosing dans une classe WindowAdapter, vous obtenez une classe avec huit mthodes et la mthode windowClosing ne fait rien.

Chapitre 8

Gestion des vnements

343

Figure 8.5
Un couteur de fentre.
MyFrame

new

Terminator

addWindowListener

windowClosing

windowClosed

La cration dune classe couteur drive de WindowAdapter reprsente une nette amlioration, mais nous pouvons aller encore plus loin, car il nest pas ncessaire de donner un nom lobjet listener. Ecrivons simplement :
frame.addWindowListener(new Terminator());

Et pourquoi sarrter en si bon chemin ? Nous pouvons faire de la classe couteur une classe interne anonyme du cadre :
frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { if (lutilisateur accepte) System.exit(0); } });

Voici les oprations effectues par cet extrait de code :


m m m m m

Il dnit une classe anonyme qui tend la classe WindowAdapter. Il ajoute une mthode windowClosing cette classe anonyme (cette mthode termine lapplication). Il hrite des six autres mthodes "vides" de WindowAdapter. Il cre un objet de cette classe. Lobjet est lui-mme anonyme. Il passe cet objet la mthode addWindowListener.

344

Au cur de Java 2 - Notions fondamentales

Comme indiqu, la syntaxe pour lutilisation de classes internes anonymes demande une certaine habitude. Lintrt est que le code rsultant est aussi court que possible.
java.awt.event.WindowListener 1.1

void windowOpened(WindowEvent e)

Cette mthode est appele lorsque la fentre a t ouverte.


void windowClosing(WindowEvent e)

Cette mthode est appele lorsque lutilisateur a mis une commande de gestionnaire de fentre pour fermer la fentre. Sachez que la fentre ne se fermera quavec un appel sa mthode hide ou dispose.

void windowClosed(WindowEvent e)

Cette mthode est appele lorsque la fentre sest ferme.


void windowIconified(WindowEvent e)

Cette mthode est appele une fois que la fentre a t transforme en icne.
void windowDeiconified(WindowEvent e)

Cette mthode est appele lorsque la fentre nest plus ltat dicne.
void windowActivated(WindowEvent e)

Cette mthode est appele lorsque la fentre est devenue active. Seul un cadre ou une bote de dialogue peut tre actif. Gnralement, le gestionnaire de fentres dcore la fentre active, par exemple en surlignant sa barre de titre.

void windowDeactivated(WindowEvent e)

Cette mthode est appele lorsque la fentre a t dsactive.


java.awt.event.WindowStateListener 1.4

void windowStateChanged(WindowEvent event)

Cette mthode est appele lorsque la fentre a t maximise, transforme en icne ou restaure sa taille normale.
java.awt.event.WindowEvent 1.1

int getNewState() 1.4 int getOldState() 1.4

Ces mthodes renvoient lancien tat et le nouvel tat dune fentre dans un vnement de modication de ltat de la fentre. Lentier renvoy correspond lune des valeurs suivantes :
Frame.NORMAL Frame.ICONIFIED Frame.MAXIMIZED_HORIZ Frame.MAXIMIZED_VERT Frame.MAXIMIZED_BOTH

Actions
Il existe souvent plusieurs moyens dactiver la mme commande. Lutilisateur peut slectionner une fonction par lintermdiaire dun menu, dun raccourci clavier ou dun bouton dans une barre

Chapitre 8

Gestion des vnements

345

doutils. Lopration est simple raliser dans le modle dvnement AWT : il suft de lier tous les vnements au mme couteur. Supposons par exemple que blueAction soit un couteur daction dont la mthode actionPerformed colorie larrire-plan en bleu. Vous pouvez attacher le mme objet comme couteur de plusieurs sources dvnement :
m m m

un bouton de la barre doutils libell "Blue" ; une option de menu baptise "Blue" ; une combinaison de touches Ctrl+B.

Puis chaque commande de changement de couleur est gre dune seule manire, quelle que soit laction qui la dclenche : un clic sur un bouton, un choix de menu ou une frappe au clavier. Le package Swing fournit un mcanisme trs pratique pour encapsuler des commandes et les attacher plusieurs sources dvnement : il sagit de linterface Action. Une action est un objet qui encapsule :
m m

une description de la commande (comme une chane de texte et une icne facultative) ; les paramtres ncessaires lexcution de la commande (dans notre cas, la couleur dsire).
void actionPerformed(ActionEvent event) void setEnabled(boolean b) boolean isEnabled() void putValue(String key, Object value) Object getValue(String key) void addPropertyChangeListener(PropertyChangeListener listener) void removePropertyChangeListener(PropertyChangeListener listener)

Une interface Action possde les mthodes suivantes :

La premire mthode est la mthode habituelle de linterface ActionListener : en fait, linterface Action est drive dActionListener. Par consquent, il est possible dutiliser un objet Action partout o un objet ActionListener est attendu. Les deux mthodes suivantes permettent dactiver ou de dsactiver laction et de vrier si elle est active. Lorsquune action attache un menu ou une barre doutils est dsactive, loption correspondante apparat en gris. Les mthodes putValue et getValue sont employes pour stocker et rcuprer un couple arbitraire nom/valeur dans lobjet Action. Il existe deux chanes prdnies Action.NAME et Action.SMALL_ICON pour faciliter le stockage des noms et des icnes dans un objet Action :
action.putValue(Action.NAME, "Blue"); action.putValue(Action.SMALL_ICON, new ImageIcon("blue-ball.gif"));

Le Tableau 8.1 dcrit les noms des actions prdnies. Si lobjet Action est ajout un menu ou une barre doutils, le nom et licne sont automatiquement rcuprs et afchs dans loption du menu ou sur le bouton de la barre doutils. La valeur de SHORT_DESCRIPTION safche dans une bulle daide. Les deux dernires mthodes de linterface Action permettent aux autres objets en particulier les menus ou les barres doutils qui ont dclench laction de recevoir une notication lorsque les proprits de lobjet Action sont modies. Par exemple, si un menu est recens en tant qucouteur de changement de proprit dun objet Action et que lobjet Action soit ensuite dsactiv, le menu est prvenu et peut alors afcher en gris la rubrique correspondant laction. Les couteurs de

346

Au cur de Java 2 - Notions fondamentales

changement de proprit sont une construction gnrique intgre au modle de composant des Java beans. Vous en apprendrez plus sur les beans et leurs proprits dans le Volume II.
Tableau 8.1 : Noms des actions prdnies

Nom NAME SMALL_ICON SHORT_DESCRIPTION LONG_DESCRIPTION MNEMONIC_KEY ACCELERATOR_KEY ACTION_COMMAND_KEY DEFAULT

Valeur Nom de laction ; afch sur les boutons et les options de menu. Emplacement de stockage dune petite icne ; pour afchage sur un bouton, une option de menu ou dans la barre doutils. Courte description de licne ; pour afchage dans une bulle daide. Description dtaille de licne ; pour utilisation potentielle dans laide en ligne. Aucun composant Swing nutilise cette valeur. Abrviation mnmonique ; pour afchage dans une option de menu (voir Chapitre 9). Emplacement pour le stockage dune pression au clavier. Aucun composant Swing nutilise cette valeur. Utilise dans la mthode registerKeyboardAction, maintenant obsolte. Proprit fourre-tout. Aucun composant Swing nutilise cette valeur.

Notez quAction est une interface et non une classe. Toute classe implmentant cette interface doit donc implmenter les sept mthodes cites. Heureusement, un bon samaritain a implment toutes ces mthodes sauf actionPerformed dans une classe nomme AbstractAction, qui se charge de stocker les couples nom/valeur et de grer les couteurs de changement de proprit. Il ne vous reste plus qu tendre AbstractAction et crire une mthode actionPerformed. Nous allons construire un objet Action capable dexcuter la commande de changement de couleur. Nous lui fournirons le nom de la commande, une icne et la couleur souhaite. Nous stockerons la couleur dans la table des couples nom/valeur propose par la classe AbstractAction. Voici le code de la classe ColorAction. Le constructeur spcie le couple nom/valeur et la mthode actionPerformed se charge de modier la couleur :
public class ColorAction extends AbstractAction { public ColorAction(String name, Icon icon, Color c) { putValue(Action.NAME, name); putValue(Action.SMALL_ICON, icon); putValue("color", c); putValue(Action.SHORT_DESCRIPTION, "Set panel color to " + name.toLowerCase()); } public void actionPerformed(ActionEvent event) { Color c = (Color) getValue("color");

Chapitre 8

Gestion des vnements

347

buttonPanel.setBackground(c); } }

Notre programme de test cre trois objets de cette classe, comme celui-ci :
Action blueAction = new ColorAction("Blue", new ImageIcon("blue-ball.gif"),Color.BLUE);

Associons maintenant cette action un bouton. Cest une opration aise, car il existe un constructeur de JButton qui accepte un objet Action :
JButton blueButton = new JButton(blueAction);

Ce constructeur lit le nom et licne dans laction, affecte la courte description la bulle daide et dnit laction en tant qucouteur. Vous pouvez voir licne et la bulle daide la Figure 8.6. Vous verrez au prochain chapitre quil est aussi trs simple dajouter la mme action un menu.
Figure 8.6
Les boutons afchent les icnes provenant des objets Action.

Il nous reste associer les objets Action au clavier pour que les actions soient ralises lorsque lutilisateur tape des commandes. Pour associer les actions des frappes de touches, nous devons dabord crer des objets de la classe KeyStroke. Il sagit dune classe trs pratique qui encapsule la description dune touche. Pour gnrer un objet KeyStroke, nous nappelons pas un constructeur, mais nous employons la mthode statique getKeyStroke de la classe KeyStroke.
KeyStroke ctrlBKey = KeyStroke.getKeyStroke("ctrl B");

Pour comprendre la prochaine tape, vous devez connatre la notion de focus du clavier. Une interface utilisateur peut avoir plusieurs boutons, menus, barres de dlement et autres composants. Lorsque vous appuyez sur une touche, le composant qui a le focus est sollicit. Ce composant est gnralement visuellement diffrenciable. Ainsi, dans le look and feel Java, un bouton ayant le focus prsente une ne bordure rectangulaire autour de son libell. Vous pouvez utiliser la touche Tab pour dplacer le focus entre les composants. Ds que vous appuyez sur la barre despacement, cela revient cliquer sur le bouton ayant le focus. Dautres touches ralisent dautres actions, notamment les touches ches qui permettent de dplacer une barre de dlement. Toutefois, ici, nous ne voulons pas envoyer la pression de touche au composant qui a le focus. Sinon, chacun des boutons devrait savoir comment grer les combinaisons de touches Ctrl+Y, Ctrl+B et Ctrl+R. Ce problme est courant et les concepteurs de Swing ont imagin une solution pratique pour le rsoudre. Chaque JComponent a trois affectations dentre qui associent les objets KeyStroke aux actions. Les trois affectations correspondent trois conditions diffrentes (voir Tableau 8.2).

348

Au cur de Java 2 - Notions fondamentales

Le traitement des frappes au clavier vrie ces affectations dans lordre suivant : 1. Vrie lindicateur WHEN_FOCUSED du composant ayant le focus de saisie. Si la combinaison de touches existe, excute laction correspondante. Si laction est active, stoppe le traitement. 2. En commenant par le composant ayant le focus de saisie, vrie les indicateurs WHEN_ ANCESTOR_OF_FOCUSED_COMPONENT de ses composants parent. Ds quune correspondance avec la combinaison de touches est trouve, excute laction correspondante. Si laction est active, stoppe le traitement. 3. Recherche tous les composants visibles et activs dans la fentre avec le focus dentre, ayant cette combinaison de touches enregistre dans un indicateur WHEN_IN_FOCUSED_WINDOW. Donne ces composants (dans lordre de leur enregistrement) une chance dexcuter laction correspondante. Ds que la premire action active est excute, stoppe le traitement. Cette partie du processus est toutefois peu able, si une combinaison de touches apparat dans plusieurs indicateurs WHEN_IN_FOCUSED_WINDOW.
Tableau 8.2 : Conditions daffectations dentre

Indicateur WHEN_FOCUSED WHEN_ANCESTOR_OF_FOCUSED_COMPONENT WHEN_IN_FOCUSED_WINDOW

Invoquer laction Quand ce composant a le focus clavier Quand ce composant contient le composant qui a le focus clavier Quand ce composant se trouve dans la mme fentre que le composant qui a le focus clavier

Vous obtenez une affectation dentre du composant laide de la mthode getInputMap, par exemple :
InputMap imap = panel.getInputMap(JComponent.WHEN_FOCUSED);

La condition WHEN_FOCUSED signie que cette affectation est consulte lorsque le composant courant a le focus clavier. Dans notre cas, ce nest pas laffectation que nous voulons. Lun des boutons, et non le panneau, a le focus de saisie. Lun des deux autres choix daffectation fonctionne trs bien pour linsertion de la combinaison de touches relative au changement de couleur. Dans notre exemple de programme, nous utilisons WHEN_ANCESTOR_OF_FOCUSED_COMPONENT. InputMap naffecte pas directement les objets KeyStroke aux objets Action. Lassignation est faite vers des objets arbitraires, et une seconde affectation, implmente par la classe ActionMap, attribue les objets aux actions. Cela facilite le partage des mmes actions parmi les combinaisons de touches provenant de diffrentes affectations dentre. Chaque composant a donc trois affectations dentre et une affectation daction. Pour les associer, vous devez proposer des noms pour les actions. Voici comment attacher une touche une action :
imap.put(KeyStroke.getKeyStroke("ctrl Y"), "panel.yellow"); ActionMap amap = panel.getActionMap(); amap.put("panel.yellow", yellowAction);

Chapitre 8

Gestion des vnements

349

Il est courant demployer la chane "none" pour une action qui ne fait rien. Il est ainsi facile de dsactiver une touche :
imap.put(KeyStroke.getKeyStroke("ctrl C"), "none");

ATTENTION
La documentation JDK suggre dutiliser le nom de laction comme cl de recherche daction. Ce nest pas forcment une bonne ide. Le nom de laction est afch sur les boutons et dans les options de menu ; il peut par consquent changer selon lhumeur du concepteur de linterface utilisateur ou tre traduit en cas de localisation du programme. De telles chanes, instables, ne sont pas de bons choix pour une recherche. Nous vous recommandons donc de choisir, pour les actions, des noms indpendants de ceux afchs.

Pour rsumer, voici comment procder pour raliser la mme action en rponse un clic de bouton, un choix de menu ou une frappe de touches : 1. Crez une classe qui tend la classe AbstractAction. Vous devez pouvoir utiliser la mme classe pour plusieurs actions connexes. 2. Crez un objet de la classe action. 3. Crez un bouton ou une option de menu partir de lobjet action. Le constructeur lira le texte de libell et licne dans lobjet action. 4. Pour les actions pouvant tre dclenches par des frappes de touches, vous devez raliser des tapes supplmentaires. Localisez dabord le composant de plus haut niveau dans la fentre, tel quun panneau qui contient tous les autres composants. 5. Extrayez laffectation dentre WHEN_ANCESTOR_OF_FOCUSED_COMPONENT de ce composant de haut niveau. Crez un objet KeyStroke pour la combinaison de touches dsire. Crez un objet touche daction, tel quune chane qui dcrit votre action. Ajoutez la paire (frappe de touches, touche daction) dans laffectation dentre. 6. Enn, extrayez laffectation dentre du composant de plus haut niveau. Ajoutez la paire (touche daction, objet action) laffectation. Le Listing 8.3 montre le code complet du programme qui affecte la fois les boutons et les frappes de touches aux objets action. Testez-le le fait de cliquer sur les boutons ou de taper Ctrl+Y, Ctrl+B ou Ctrl+R modie la couleur du panneau.
Listing 8.3 : ActionTest.java
import java.awt.*; import java.awt.event.*; import javax.swing.*; /** * @version 1.33 2007-06-12 * @author Cay Horstmann */ public class ActionTest { public static void main(String[] args) {

350

Au cur de Java 2 - Notions fondamentales

EventQueue.invokeLater(new Runnable() { public void run() { ActionFrame frame = new ActionFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); } } /** * Un cadre avec un panneau pour faire la dmonstration * des actions de changement de couleur. */ class ActionFrame extends JFrame { public ActionFrame() { setTitle("ActionTest"); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); buttonPanel = new JPanel(); // dfinir les actions Action yellowAction = new ColorAction("Yellow", new ImageIcon("yellow-ball.gif"), Color.YELLOW); Action blueAction = new ColorAction("Blue", new ImageIcon("blue- ball.gif"), Color.BLUE); Action redAction = new ColorAction("Red", new ImageIcon("red-ball.gif"), Color.RED); // ajoute des boutons pour ces actions buttonPanel.add(new JButton(yellowAction)); buttonPanel.add(new JButton(blueAction)); buttonPanel.add(new JButton(redAction)); // ajoute un panneau au cadre add(buttonPanel); // associe les touches Y, B et R aux noms InputMap imap = buttonPanel.getInputMap( JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); imap.put(KeyStroke.getKeyStroke("ctrl Y"), "panel.yellow"); imap.put(KeyStroke.getKeyStroke("ctrl B"), "panel.blue"); imap.put(KeyStroke.getKeyStroke("ctrl R"), "panel.red"); // associe les noms aux actions ActionMap amap = buttonPanel.getActionMap(); amap.put("panel.yellow", yellowAction); amap.put("panel.blue", blueAction); amap.put("panel.red", redAction); } public class ColorAction extends AbstractAction {

Chapitre 8

Gestion des vnements

351

/** * Construit une action de couleur. * @param name Le nom afficher sur le bouton * @param icon Licne afficher sur le bouton * @param c La couleur de fond */ public ColorAction(String name, Icon icon, Color c) { putValue(Action.NAME, name); putValue(Action.SMALL_ICON, icon); putValue(Action.SHORT_DESCRIPTION, "Set panel color to " + name.toLowerCase()); putValue("color", c); } public void actionPerformed(ActionEvent event) { Color c = (Color) getValue("color"); buttonPanel.setBackground(c); } } private JPanel buttonPanel; public static final int DEFAULT_WIDTH = 300; public static final int DEFAULT_HEIGHT = 200; } javax.swing.Action 1.2

void setEnabled(boolean b) boolean isEnabled()

Rcuprent ou dnissent la proprit enabled de cette action.


void putValue(String key, Object value)

Place une paire nom/valeur dans lobjet Action. Paramtres : key Le nom de la fonctionnalit stocker avec lobjet action. Cela peut tre nimporte quelle chane, mais il en existe quatre prdnies (voir Tableau 8.1). Lobjet associ au nom.

value

Object getValue(String key)

Renvoie la valeur extraite dun couple nom/valeur.


javax.swing.KeyStroke 1.2

static KeyStroke getKeyStroke(String description)

Cre un objet KeyStroke partir dune description en clair. La description est une squence de mots cls spars par des espaces. La description dmarre par zro, un ou plusieurs modicateurs shift control ctrl meta alt altGraph et se termine par la chane typed, suivie dune chane dun seul caractre, par exemple, "typed a" ou dun spcicateur dvnement optionnel (pressed, par dfaut, ou released, suivi dun code de touche). Le code de touche, lorsquil est prx par VK_, doit correspondre une constante KeyEvent; ainsi, "INSERT" correspond KeyEvent.VK_INSERT.

352

Au cur de Java 2 - Notions fondamentales

javax.swing.JComponent 1.2

ActionMap getActionMap() 1.3

Renvoie laffectation daction qui attribue les frappes de touches (qui peuvent tre des objets arbitraires) aux touches daction.

InputMap getInputMap(int flag) 1.3

Extrait laffectation dentre qui attribue les pressions de touches aux cls dobjets action. Paramtres : flag Une condition du focus de saisie pour dclencher laction, dont les valeurs gurent au Tableau 8.2.

Evnements de la souris
Il nest pas ncessaire de grer explicitement les vnements de la souris si vous dsirez seulement que lutilisateur puisse cliquer sur un bouton ou sur un menu. Ces oprations sont rgies de faon interne par les divers composants de linterface utilisateur. Cependant, si vous voulez permettre lutilisateur de dessiner avec la souris, il vous faudra intercepter les mouvements, les clics et les oprations de glisser-dplacer. Nous allons vous proposer un diteur graphique simpli qui permettra lutilisateur de placer, de dplacer et deffacer des carrs sur une grille (voir Figure 8.7).
Figure 8.7
Un programme de test de la souris.

Lorsque lutilisateur clique avec un bouton de la souris, trois mthodes de lcouteur sont appeles : mousePressed quand le bouton est enfonc, mouseReleased quand il est relch et, enn, mouseClicked. Vous pouvez ignorer les deux premires mthodes si vous ntes intress que par des clics "complets". En utilisant getX et getY sur largument MouseEvent, vous pouvez obtenir les coordonnes x et y du pointeur de la souris au moment du clic. Si vous souhaitez faire une distinction entre clic simple et double-clic (voire triple-clic), employez la mthode getClickCount. Certains concepteurs dinterfaces inigent leurs utilisateurs des combinaisons de clic de souris et de touches, comme Ctrl+Maj+clic. Cette technique est, selon nous, assez critiquable mais, si cela vous plat, vous dcouvrirez que linterception des modications de boutons de souris et de clavier est trs complique. Les masques de bits permettent de tester les modicateurs qui ont t dnis. Dans lAPI dorigine, deux des masques de bouton galent deux masques de modication de clavier, savoir :
BUTTON2_MASK == ALT_MASK BUTTON3_MASK == META_MASK

Chapitre 8

Gestion des vnements

353

Cela permet aux utilisateurs qui disposent dune souris un seul bouton de simuler la prsence dautres boutons en maintenant enfonces des touches de modication. Toutefois, depuis Java SE 1.4, une nouvelle mthode est recommande. Il existe maintenant des masques :
BUTTON1_DOWN_MASK BUTTON2_DOWN_MASK BUTTON3_DOWN_MASK SHIFT_DOWN_MASK CTRL_DOWN_MASK ALT_DOWN_MASK ALT_GRAPH_DOWN_MASK META_DOWN_MASK

La mthode getModifiersEx signale prcisment les modicateurs de souris et de clavier dun vnement de souris. Notez que, sous Windows, BUTTON3_DOWN_MASK est employ pour dterminer ltat du bouton droit (non primaire). Voici un exemple permettant de savoir si le bouton droit est enfonc :
if ((event.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK)!=0) ... // code du clic droit

Dans notre programme de dmonstration, nous fournissons la fois une mthode mousePressed et une mthode mouseClicked. Lorsque vous cliquez sur un pixel qui ne se trouve pas lintrieur dun des carrs dj dessins, un nouveau carr est ajout dans la fentre. Ce mcanisme est implment dans la mthode mousePressed an que lutilisateur obtienne une rponse immdiate et nait pas besoin dattendre le relchement du bouton. Lors dun double clic lintrieur dun carr existant, celui-ci est effac. Ce traitement du double clic est implment dans la mthode mouseClicked, car nous devons connatre le nombre de clics :
public void mousePressed(MouseEvent event) { current = find(event.getPoint()); if (current == null) // pas lintrieur dun carr add(event.getPoint()); } public void mouseClicked(MouseEvent event) { current = find(event.getPoint()); if (current!= null && event.getClickCount() >= 2) remove(current); }

Quand le curseur de la souris passe au-dessus dune fentre, celle-ci reoit un ot dvnements de mouvement de souris. Vous voyez quil y a des interfaces MouseListener et MouseMotionListener. Cela se fait des ns defcacit : on trouve de nombreux vnements de souris lorsque lutilisateur dplace celle-ci et un couteur qui ne se proccupe que des clics ne sera pas intress par des dplacements de souris indsirables. Notre programme de dmonstration intercepte malgr tout ces vnements an de donner au pointeur une forme diffrente (une croix) lorsquil se trouve au-dessus dun carr. Nous employons pour cela la mthode getPredefinedCursor de la classe Cursor. Le Tableau 8.3 prsente les constantes utilisables avec cette mthode et les pointeurs tels quils apparaissent sous Windows.

354

Au cur de Java 2 - Notions fondamentales

Tableau 8.3 : Exemples de pointeurs

Icne

Constante DEFAULT_CURSOR

Icne

Constante NE_RESIZE_CURSOR

CROSSHAIR_CURSOR

E_RESIZE_CURSOR

HAND_CURSOR

SE_RESIZE_CURSOR

MOVE_CURSOR TEXT_CURSOR WAIT_CURSOR N_RESIZE_CURSOR

S_RESIZE_CURSOR SW_RESIZE_CURSOR W_RESIZE_CURSOR NW_RESIZE_CURSOR

Voici la mthode mouseMoved de MouseMotionListener dans notre exemple de programme :


public void mouseMoved(MouseEvent event) { if (find(event.getPoint()) == null) setCursor(Cursor.getDefaultCursor()); else setCursor(Cursor.getPredefinedCursor (Cursor.CROSSHAIR_CURSOR)); }

INFO
Vous pouvez dnir vos propres types de pointeurs grce la mthode createCustomCursor de la classe Toolkit :
Toolkit tk = Toolkit.getDefaultToolkit(); Image img = tk.getImage("dynamite.gif"); Cursor dynamiteCursor = tk.createCustomCursor(img, new Point(10, 10), "dynamite stick");

Le premier paramtre de createCustomCursor dtermine limage du pointeur. Le deuxime spcie le dcalage de son "point chaud". Le troisime est une chane qui le dcrit. Elle peut tre employe pour le support daccessibilit, par exemple, an dindiquer la forme du pointeur un utilisateur ayant des problmes de vue ou ne se trouvant pas directement en face de lcran.

Si lutilisateur appuie sur un bouton de la souris pendant que celle-ci se dplace, des appels mouseDragged sont gnrs la place des appels mouseMoved. Notre programme de dmonstration

Chapitre 8

Gestion des vnements

355

permet ainsi de faire glisser un carr se trouvant sous le pointeur. Le carr en cours de dplacement est simplement mis jour pour quil se trouve centr sous la position de la souris. Puis la grille est redessine pour montrer la nouvelle position de la souris :
public void mouseDragged(MouseEvent event) { if (current >= null) { int x = event.getX(); int y = event.getY(); current.setFrame( x - SIDELENGTH / 2, y - SIDELENGTH / 2, SIDELENGTH, SIDELENGTH); repaint(); } }

INFO
La mthode mouseMoved nest appele que tant que la souris reste lintrieur du composant. En revanche, la mthode mouseDragged continue tre appele mme pendant que la souris est tire en dehors du composant.

Deux autres mthodes sont utilises pour la gestion des vnements de souris : mouseEntered et mouseExited. Elles sont appeles respectivement quand la souris entre dans un composant et quand elle le quitte. Nous devons encore expliquer comment couter les vnements de souris. Les clics sont signals par la mthode mouseClicked, qui fait partie de linterface MouseListener. Comme de nombreuses applications ne sintressent quaux clics de la souris et pas ses mouvements et comme les vnements de dplacement se produisent trs frquemment, les vnements de glisser-dplacer de la souris sont dnis dans une interface spare appele MouseMotionListener. Dans notre programme, les deux types dvnements de la souris nous intressent. Deux classes internes sont dnies : MouseHandler et MouseMotionHandler. La classe MouseHandler tend la classe MouseAdapter, car elle ne dnit que deux des cinq mthodes de MouseListener. La classe MouseMotionHandler implmente MouseMotionListener et dnit les deux mthodes de cette interface. Le programme est dcrit dans le Listing 8.4.
Listing 8.4 : MouseTest.java
import import import import import java.awt.*; java.awt.event.*; java.util.*; java.awt.geom.*; javax.swing.*;

/** * @version 1.32 2007-06-12 * @author Cay Horstmann */

356

Au cur de Java 2 - Notions fondamentales

public class MouseTest { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { MouseFrame frame = new MouseFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); } } /** * Un cadre avec un panneau pour tester les oprations de la souris */ class MouseFrame extends JFrame { public MouseFrame() { setTitle("MouseTest"); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); // ajouter un composant au cadre MouseComponent component = new MouseComponent(); add(component); } public static final int DEFAULT_WIDTH = 300; public static final int DEFAULT_HEIGHT = 200; } /** * Un composant avec des oprations de la souris * pour lajout et la suppression de carrs. */ class MouseComponent extends JComponent { public MouseComponent() { squares = new ArrayList<Rectangle2D>(); current = null; addMouseListener(new MouseHandler()); addMouseMotionListener(new MouseMotionHandler()); } public void paintComponent(Graphics g) { Graphics2D g2 = (Graphics2D)g; // dessiner tous les carrs for (Rectangle2D r: squares) g2.draw(r); }

Chapitre 8

Gestion des vnements

357

/** * Trouve le premier carr contenant un point. * @param p Un point * @return Le premier carr contenant p */ public Rectangle2D find(Point2D p) { for (Rectangle2D r: squares) { if (r.contains(p)) return r; } return null; } /** * Ajoute un carr la collection. * @param p Le centre du carr */ public void add(Point2D p) { double x = p.getX(); double y = p.getY(); current = new Rectangle2D.Double( x - SIDELENGTH / 2, y - SIDELENGTH / 2, SIDELENGTH, SIDELENGTH); squares.add(current); repaint(); } /** * Supprime un carr de la collection. * @param s Le carr supprimer */ public void remove(Rectangle2D s) { if (s == null) return; if (s == current) current = null; squares.remove(s); repaint(); } private static final int SIDELENGTH = 10; private ArrayList<Rectangle2D> squares; private Rectangle2D current; // le carr contenant le pointeur de la souris private class MouseHandler extends MouseAdapter { public void mousePressed(MouseEvent event) { // ajouter un nouveau carr si le pointeur // nest pas lintrieur dun carr current = find(event.getPoint()); if (current == null) add(event.getPoint()); }

358

Au cur de Java 2 - Notions fondamentales

public void mouseClicked(MouseEvent event) { // supprimer le carr courant si double-clic dessus current = find(event.getPoint()); if (current!= null && event.getClickCount() >= 2) remove(current); } } private class MouseMotionHandler implements MouseMotionListener { public void mouseMoved(MouseEvent event) { // dfinit le pointeur sous forme de croix // sil est lintrieur dun carr if (find(event.getPoint()) == null) setCursor(Cursor.getDefaultCursor()); else setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); } public void mouseDragged(MouseEvent event) { if (current!= null) { int x = event.getX(); int y = event.getY(); // tirer le carr courant pour le centrer la position (x, y) current.setFrame( x - SIDELENGTH / 2, y - SIDELENGTH / 2, SIDELENGTH, SIDELENGTH); repaint(); } } } } java.awt.event.MouseEvent 1.1

int getX() int getY() Point getPoint()

Renvoient les coordonnes x (horizontale) et y (verticale) ou le point auquel sest produit lvnement dans le systme de coordonnes de la source, partir du coin suprieur gauche du composant.

int getClickCount()

Renvoie le nombre de clics conscutifs associs lvnement (lintervalle qui dtermine deux clics "conscutifs" dpend du systme).

Chapitre 8

Gestion des vnements

359

java.awt.event.InputEvent 1.1

int getModifiersEx() 1.4

Renvoie les modicateurs tendus ou "bas" de cet vnement. Utilisez les valeurs de masque suivantes pour tester la valeur renvoye :
BUTTON1_DOWN_MASK BUTTON2_DOWN_MASK BUTTON3_DOWN_MASK SHIFT_DOWN_MASK CTRL_DOWN_MASK ALT_DOWN_MASK ALT_GRAPH_DOWN_MASK META_DOWN_MASK

static String getModifiersExText(int modifiers) 1.4

Renvoie une chane comme "Maj+Button1" dcrivant les modicateurs tendus ou "bas" dans le jeu de balises donn.
java.awt.Toolkit 1.0

public Cursor createCustomCursor (Image image, Point hotSpot, String name) 1.2

Cre un nouvel objet pointeur personnalis. Paramtres : image hotSpot name Limage afcher lorsque le pointeur est actif. Le point chaud du pointeur (par exemple lextrmit dune che ou le centre dune croix). Une description du pointeur, pour prendre en charge des options daccessibilit particulires.

java.awt.Component 1.0

public void setCursor(Cursor cursor) 1.1

Attribue au pointeur limage de lun des pointeurs prdnis spcis par le paramtre cursor.

Hirarchie des vnements AWT


Aprs vous avoir donn un avant-got de la gestion des vnements, nous allons tudier la gestion des vnements Java dune manire plus gnrale. Comme nous lavons dj crit, la gestion des vnements est oriente objet et tous les vnements descendent de la classe EventObject du package java.util (la superclasse commune ne sappelle pas Event, car ce nom est employ pour dsigner la classe dvnement dans lancien modle. Bien que celui-ci soit dprci, ses classes font toujours partie de la bibliothque Java). La classe EventObject possde une sous-classe AWTEvent qui est le parent de toutes les classes dvnements AWT. La Figure 8.8 prsente le schma de lhritage des vnements AWT. Certains composants Swing gnrent des objets vnements qui appartiennent dautres types qui sont directement drivs dEventObject (et pas dAWTEvent).

360

Au cur de Java 2 - Notions fondamentales

Figure 8.8
Schma de lhritage des classes dvnements AWT.
Event Object

AWT Event

Action Event

Adjustment Event

Component Event

Item Event

Focus Event

Input Event

Paint Event

Window Event

Key Event

Mouse Event

MouseWheel Event

Un objet vnement encapsule des informations sur lvnement que la source dvnement communique aux couteurs. En cas de besoin, nous pouvons alors analyser lobjet vnement qui a t pass lobjet couteur, comme nous lavons fait laide des mthodes getSource et getActionCommand dans lexemple consacr aux boutons. Certaines classes dvnements AWT ne sont daucune utilit pratique pour le programmeur Java. Par exemple, AWT insre des objets PaintEvent dans la le dvnements, mais ces objets ne sont pas envoys aux couteurs. Les programmeurs Java ncoutent pas les vnements de dessin ; ils doivent surcharger la mthode paintComponent pour contrler le dessin dun composant. AWT gnre galement plusieurs vnements qui ne sont ncessaires quaux programmeurs systme, an de fournir les systmes de saisie des langues idographiques, les robots de test automatis, etc. Nous ne traiterons pas de ces types dvnements spcialiss.

Chapitre 8

Gestion des vnements

361

Evnements smantiques et de bas niveau


AWT fait une distinction utile entre vnements de bas niveau et vnements smantiques. Un vnement smantique exprime ce que fait lutilisateur, par exemple "cliquer sur un bouton" ; en consquence, un vnement ActionEvent est smantique. Les vnements de bas niveau sont ceux qui rendent laction possible. Dans le cas dun clic sur un bouton de linterface utilisateur, cela reprsente une pression sur le bouton de la souris, des dplacements du pointeur, puis un relchement du bouton de la souris (mais seulement si le pointeur se trouve encore dans la zone du bouton afch sur lcran). Ce peut tre galement une pression sur une touche, au cas o lutilisateur slectionne le bouton avec la touche de tabulation puis lactive avec la barre despacement. De la mme manire, un ajustement de la position dune barre de dlement est un vnement smantique, mais le dplacement de la souris est un vnement de bas niveau. Voici les classes dvnements smantiques les plus utilises dans le package java.awt.event :
m

ActionEvent (pour un clic sur un bouton, une slection dun lment de menu ou de liste, ou une pression de la touche Entre dans un champ de texte). AdjustmentEvent (lutilisateur a dplac le curseur dune barre de dlement). ItemEvent (lutilisateur a fait une slection dans un groupe de cases cocher ou dans une liste). KeyEvent (une touche du clavier a t presse ou relche). MouseEvent (le bouton de la souris a t enfonc ou relch ; le pointeur de la souris a t dplac ou gliss). MouseWheelEvent (la molette de la souris a t tourne). FocusEvent (un composant a obtenu ou perdu le focus). WindowEvent (ltat de la fentre a chang).
ActionListener AdjustmentListener FocusListener ItemListener KeyListener MouseListener MouseMotionListener MouseWheelListener WindowListener WindowFocusListener WindowStateListener

m m

Il y a cinq classes dvnements de bas niveau :


m m

m m m

Les interfaces suivantes permettent dcouter ces vnements. :

Certaines interfaces couteur AWT celles qui possdent plusieurs mthodes sont accompagnes dune classe adaptateur qui implmente toutes les mthodes de linterface an quelles naccomplissent aucune action (les autres interfaces nont quune seule mthode et il est donc inutile demployer des classes adaptateur dans ce cas). Voici les classes adaptateur les plus souvent utilises :
FocusAdapter KeyAdapter MouseAdapter MouseMotionAdapter WindowAdapter

Le Tableau 8.4 montre les interfaces couteur, les vnements et les sources dvnements les plus importants de AWT.

362

Au cur de Java 2 - Notions fondamentales

Tableau 8.4 : Rsum de la gestion des vnements

Interface
ActionListener

Mthodes
actionPerformed

Paramtres/ accesseurs
ActionEvent getActionCommand getModifiers AdjustmentEvent getAdjustable getAdjustmentType getValue ItemEvent getItem getItemSelectable getStateChange FocusEvent
isTemporary

Evnements gnrs par


AbstractButton JComboBox JTextField Timer JScrollbar

AdjustmentListener

adjustmentValueChanged

ItemListener

itemStateChanged

AbstractButton JComboBox

FocusListener KeyListener

focusGained focusLost keyPressed keyReleased keyTyped

Component Component

KeyEvent
getKeyChar getKeyCode getKeyModifiersText getKeyText isActionKey

MouseListener

mousePressed mouseReleased mouseEntered mouseExited mouseClicked mouseDragged mouseMoved mouseWheelMoved

MouseEvent
getClickCount getX getY getPoint translatePoint

Component

MouseMotionListener MouseWheelListener

MouseEvent MouseWheelEvent getWheelRotation getScrollAmount

Component Component

Chapitre 8

Gestion des vnements

363

Tableau 8.4 : Rsum de la gestion des vnements

Interface
WindowListener

Mthodes
windowClosing windowOpened windowIconified windowDeiconified windowClosed windowActivated windowDeactivated windowGainedFocus windowLostFocus windowStateChanged

Paramtres/ accesseurs
WindowEvent
getWindow

Evnements gnrs par


Window

WindowFocusListener WindowStateListener

WindowEvent
getOppositeWindow

Window Window

WindowEvent getOldState getNewState

Cela clt notre examen de la gestion des vnements AWT. Le chapitre suivant montre comment rassembler les composants les plus communs proposs par Swing, avec une description dtaille des vnements quils gnrent.

9
Swing et les composants dinterface utilisateur
Au sommaire de ce chapitre

Swing et larchitecture Modle-Vue-Contrleur Introduction la gestion de mise en forme Entre de texte Composants du choix Menus Mise en forme sophistique Botes de dialogue
Le chapitre prcdent a illustr essentiellement lemploi du modle dvnements de Java. Vous avez accompli les premires tapes de llaboration dune interface utilisateur. Ce chapitre vous prsentera les principaux outils ncessaires la cration dinterfaces dotes dun plus grand nombre de fonctionnalits. Nous commencerons par les caractristiques de larchitecture sous-jacente de la bibliothque Swing ; ainsi, vous pourrez en utiliser efcacement les composants les plus avancs. Nous poursuivrons avec les lments graphiques de cette bibliothque les plus couramment employs, tels que les champs de saisie de texte, les boutons radio et les menus. Vous tudierez aussi lexploitation des fonctionnalits de gestion de mise en forme utilises avec Java pour pouvoir disposer ces composants dans une fentre, indpendamment du style de look and feel adopt pour linterface utilisateur. Pour nir, vous aborderez limplmentation des botes de dialogue avec Swing. Ce chapitre couvre tous les composants Swing de base, tels que les outils de texte, les boutons et les curseurs. Il sagit de ceux auxquels vous recourrez le plus souvent. Les composants Swing plus sophistiqus sont tudis dans le Volume II.

366

Au cur de Java 2 - Notions fondamentales

Swing et larchitecture Modle-Vue-Contrleur


Comme nous lindiquions, nous dmarrons ce chapitre par une section dcrivant larchitecture des composants Swing. Nous verrons dabord la notion de modles de conception, puis le motif "Modle-Vue-Contrleur", qui a considrablement inuenc la conception du cadre Swing.

Modles de conception
Gnralement, lors de la rsolution dun problme, vous nbauchez pas une solution partir des principes de base. Vous tes plus vraisemblablement guid par lexprience ou vous demandez conseil dautres experts. Les modles de conception constituent un mode de prsentation structur de cette expertise. Au cours des dernires annes, les concepteurs de logiciels ont commenc rassembler de tels modles. Les pionniers dans ce domaine ont t inspirs par les modles de conception architecturale de Christopher Alexander. Dans son livre, The Timeless Way of Building (Oxford University Press, 1979), il fournit une collection de modles pour la conception des espaces de vie publics et privs. En voici un exemple typique :
Espace fentre Tout le monde aime les places prs des fentres, les baies vitres ou encore avec de larges rebords de faible hauteur, avec des fauteuils confortables proximit. Une pice qui ne dispose pas dun tel espace ne vous permettra pas de vous sentir bien install ou parfaitement laise. La pice dpourvue dune fentre qui permettrait damnager un tel "espace de vie" soumettra son occupant un dilemme : (1) Sasseoir et tre confortablement install et (2) Etre proche de la lumire. Si les espaces confortables ceux o vous avez envie de vous asseoir sont loigns des fentres, il nexiste alors aucune possibilit de rsoudre ce conit. Par consquent, dans chaque pice o vous sjournez souvent, transformez au moins une fentre en un "espace fentre".

Chaque modle, dans le catalogue dAlexander ainsi que dans les catalogues de modles logiciels, suit un format particulier. Il dcrit tout dabord un contexte, une situation qui donne lieu un problme de conception. Celui-ci est ensuite expliqu, gnralement sous la forme dun ensemble de forces en conit. Finalement, la solution indique une conguration qui quilibre ces forces. Dans le modle "espace fentre" (voir Figure 9.1), le contexte est une pice dans laquelle vous passez un certain temps dans la journe. Les forces en conit sont le fait que vous souhaitez vous asseoir confortablement, et le fait dtre attir par la lumire. La solution est de fabriquer un "espace fentre".
Figure 9.1
Un espace fentre.
rebord bas place

Chapitre 9

Swing et les composants dinterface utilisateur

367

Dans larchitecture Modle-Vue-Contrleur, que nous verrons la section suivante, le contexte est un systme dinterface utilisateur qui prsente des informations et reoit les entres de lutilisateur. Il y a plusieurs forces. Il peut y avoir plusieurs reprsentations visuelles des mmes donnes qui doivent tre mises jour ensemble. La reprsentation visuelle peut changer, par exemple pour sadapter divers look and feel standard. Les mcanismes dinteraction peuvent aussi changer, par exemple pour grer des commandes vocales. La solution consiste rpartir les responsabilits en trois composants spars qui interagissent : le modle, la vue et le contrleur. Larchitecture Modle-Vue-Contrleur nest pas le seul modle utilis dans la conception dAWT et de Swing. Voici dautres exemples :
m m m

Les conteneurs et les composants sont des exemples de motif "composite". Le volet de dlement est un "dcorateur". Les gestionnaires de mise en page suivent le motif "stratgie".

Un aspect important des modles de conception est quils font maintenant partie de la culture. Les programmeurs travers le monde comprennent si vous parlez de larchitecture Modle-VueContrleur ou du modle "dcorateur". Les modles darchitecture sont un moyen efcace de discuter des problmes de conception. Vous trouverez une description formelle de plusieurs motifs logiciels utiles dans louvrage sur le mouvement des motifs, Design PatternsElements of Reusable Object-Oriented Software, par Erich Gamma et al. (Addison-Wesley, 1995). Nous recommandons galement trs fortement lexcellent livre A system of Patterns, de Frank Buschmann et al. (John Wiley & Sons, 1996), moins riche et plus abordable.

Larchitecture Modle-Vue-Contrleur
Rchissons un peu tous les lments qui entrent dans la constitution dun composant dinterface utilisateur comme un bouton, une case cocher, un champ de texte ou un contrle darborescence sophistiqu. Chaque composant possde trois caractristiques :
m m m

son contenu, tel que ltat dun bouton (activ ou non) ou la valeur dun champ de texte ; son apparence (couleur, taille, etc.) ; son comportement (raction aux vnements).

Mme un composant apparemment simple, comme un bouton, cache une interaction relativement complexe entre ces caractristiques. Lapparence dun bouton dpend videmment du style que lon souhaite donner. Le style Metal est visuellement diffrent du style Windows ou Motif. Cette apparence dpend aussi de ltat du bouton : lorsquil est press, il doit tre redessin pour afcher un aspect diffrent. Cet tat dcoule des vnements quil reoit. Lorsque lutilisateur clique sur le bouton, celui-ci senfonce. Bien sr, lorsque vous vous servez dun bouton dans vos programmes, vous le considrez simplement comme un bouton, sans rchir davantage ses rouages internes. Cest au programmeur qui la implment que revient le travail de rexion. Il se doit de mettre en uvre tous les composants dune interface pour quils puissent fonctionner quel que soit le look and feel de linterface install. Pour cela, les concepteurs de Swing se sont tourns vers une architecture bien connue : Modle-VueContrleur. A linstar dautres architectures, elle repose sur lun des principes de la conception

368

Au cur de Java 2 - Notions fondamentales

oriente objet (voir Chapitre 5), qui prconisait de ne pas trop assigner de responsabilits un seul objet. Evitez quune seule classe de boutons ne soit charge de tout faire. Au contraire, associez le style dun composant un seul objet et stockez-en le contenu dans un autre objet. Larchitecture Modle-Vue-Contrleur (MVC) nous enseigne comment raliser cette opration en implmentant trois classes spares :
m m m

le modle qui stocke le contenu ; la vue qui afche le contenu ; le contrleur qui gre lentre utilisateur.

Larchitecture prvoit prcisment de quelle faon ces trois objets interagissent. Le modle conserve la valeur du composant et ne possde pas dinterface utilisateur. Pour un bouton, le contenu, ou sa valeur, est assez ordinaire : juste un petit ensemble dindicateurs qui signalent sil est press ou non, actif ou inactif, etc. Pour un champ de texte, le contenu est un peu plus intressant. Cest un objet de type chane qui contient le texte courant. Il ne sagit pas de la vue du contenu ; si celui-ci est plus grand que le champ, lutilisateur nen voit quune partie (voir Figure 9.2).
Figure 9.2
Modle et vue dun champ de texte.

modle

"The quick brown fox jumps over the lazy dog"

vue

Le modle doit implmenter des mthodes pour modier le contenu et dcouvrir ce quil reprsente. Par exemple, un modle texte possde des mthodes pour ajouter ou supprimer des caractres du contenu actuel et renvoyer la valeur courante sous forme dune chane. Encore une fois, gardez lesprit que le modle est compltement non visuel. Il incombe la vue de dessiner les donnes qui sont stockes dans le modle.
INFO
Le choix du terme "modle" est peut-tre malheureux, car un modle est souvent interprt comme tant une reprsentation dun concept abstrait. Les concepteurs de voitures ou davions construisent des modles pour simuler les vraies machines. Cette analogie peut tre confondante lorsque lon pense larchitecture Modle-Vue-Contrleur. Ici, le modle conserve lintgralit du contenu et la vue en donne une reprsentation visuelle partielle ou complte. Une meilleure analogie serait de prendre un modle qui pose pour un artiste. Il revient ce dernier dobserver le premier et den crer une vue. Selon lartiste, la vue pourrait tre un portrait guratif, une peinture impressionniste ou encore un dessin cubiste reprsentant lesprit en dtranges contorsions.

Un des avantages de larchitecture Modle-Vue-Contrleur est de pouvoir possder plusieurs vues, chacune montrant une partie ou un aspect diffrents du contenu. Par exemple, un diteur HTML peut offrir deux vues simultanes dun mme contenu, lune WYSIWYG et lautre brute avec les balises (voir Figure 9.3). Lorsque le modle est mis jour par lintermdiaire du contrleur de lune des vues, il en avise les deux vues associes. Lorsque celles-ci reoivent la notication, elles sactualisent automatiquement. Bien sr, pour un simple composant dinterface, tel quun bouton, vous nobtiendrez pas plusieurs vues du mme modle.

Chapitre 9

Swing et les composants dinterface utilisateur

369

Figure 9.3
Deux vues spares du mme modle.
modle
P

OL

LI LI LI

vue WYSIWG

1. 2. 3.

vue avec balises

<P> <OL> <LI> <LI> <LI> </OL>

</P> </LI> </LI> </LI>

Le contrleur gre les vnements dentre utilisateur tels que les clics de souris ou les frappes au clavier. Lorsquils se produisent, le contrleur dcide de les traduire en changements dans le modle ou la vue. Par exemple, si lutilisateur tape un caractre dans un champ de texte, le contrleur appelle la commande dinsertion de caractre du modle. Le modle commande ensuite la vue de se mettre jour. Celle-ci ignore toujours pourquoi le texte a chang. Mais si lutilisateur appuie sur une touche, le contrleur peut indiquer la vue de dler. Ce dlement na aucun effet sur le texte sousjacent, le modle ne sait donc jamais que cet vnement sest produit. La Figure 9.4 illustre les interactions entre les objets modle, vue et contrleur. En tant que programmeur utilisant les composants Swing, vous navez pas besoin de penser larchitecture Modle-Vue-Contrleur. Chaque interface dispose dune classe enveloppe, ou wrapper, comme JButton ou JTextField qui stocke le modle et la vue. Lorsque vous souhaitez connatre le contenu dun composant, par exemple dun champ de texte, la classe enveloppe transmet cette requte la vue. Toutefois, il existe des occasions o la classe ne fonctionne pas de manire idale pour la transmission des commandes. Vous devez alors lui demander de rcuprer le modle pour agir directement avec ce dernier. Vous navez pas besoin de travailler directement avec la vue, car cette tche incombe au code qui est li au style dinterface implment (LookAndFeel). Outre la possibilit dexcuter laction approprie, cette architecture permet aux concepteurs dimplmenter un style dinterface dynamique, le plaf ou pluggable look and feel. Le modle dun bouton ou dun champ de texte est indpendant du style dinterface, mais sa reprsentation visuelle dpend totalement de la conception dune interface de style particulier. Le contrleur peut galement varier. Par exemple, sur une machine contrle par la voix, le contrleur doit faire face un ensemble dvnements totalement diffrent de celui dun ordinateur standard muni dun clavier et dune souris. En sparant le modle sous-jacent de linterface utilisateur, les concepteurs de Swing peuvent rutiliser le code des modles et mme passer dun look and feel un autre au cours dun programme en cours dexcution.

370

Au cur de Java 2 - Notions fondamentales

Figure 9.4
Interactions entre les objets modles, vue et contrleur.
Contrleur Vue Modle

dessiner la vue lire le contenu

actualiser le contenu contenu modifi

actualiser la vue

Bien sr, les modles sont prvus pour servir de guides ; vous ntes pas forc de les suivre la lettre. Ils ne sont pas applicables dans toutes les situations. Par exemple, vous pouvez prouver des difcults suivre le modle "espace de fentre" pour rorganiser votre studette. De la mme manire, les concepteurs Swing se sont aperus que dans la ralit, limplmentation dun style dynamique dinterface ne permet pas toujours une ralisation propre de larchitecture Modle-Vue-Contrleur. Les modles sont faciles distinguer ; chaque composant dinterface possde une classe de modle. Mais les responsabilits de la vue et du contrleur ne sont pas toujours clairement spares et sont rparties travers un certain nombre de classes diffrentes. Bien sr, en tant quutilisateur de ces classes, vous ne serez pas concern par tout cela. En fait, comme nous lavons signal, vous naurez pas vous soucier des modles ; vous pourrez simplement exploiter les classes enveloppes du composant.

Une analyse Modle-Vue-Contrleur des boutons Swing


Vous avez dj appris utiliser des boutons au chapitre prcdent sans avoir vous proccuper des objets contrleur, modle ou vue. Les boutons comptent parmi les lments dinterface les plus simples ; nous les utiliserons donc pour nous familiariser avec cette architecture. Vous rencontrerez des types de classes et dinterfaces similaires pour les composants Swing plus sophistiqus. Pour la plupart des composants, la classe modle implmente une interface dont le nom se termine par Model, do linterface appele ButtonModel. Les classes limplmentant peuvent dnir ltat

Chapitre 9

Swing et les composants dinterface utilisateur

371

des divers types de boutons. En fait, les boutons ne sont pas aussi complexes et la bibliothque Swing contient une unique classe appele DefaultButtonModel qui implmente cette interface. Pour connatre le type des donnes qui sont gres par un modle de boutons, examinez les proprits de linterface ButtonModel (voir Tableau 9.1).
Tableau 9.1 : Les proprits de linterface ButtonModel

Nom de proprit actionCommand mnemonic armed enabled pressed rollover selected

Valeur La chane de commande daction associe ce bouton Le raccourci clavier pour ce bouton true si le bouton a t press et que la souris est toujours au-dessus du bouton true si le bouton peut tre slectionn true si le bouton de commande a t press, mais que le bouton de la souris na pas encore t relch true si la souris se trouve au-dessus du bouton true si ltat du bouton a t bascul (pour les cases cocher et les boutons radio)

Chaque objet JButton stocke un objet modle de bouton que vous pouvez rcuprer :
JButton button = new JButton("Blue"); ButtonModel model = button.getModel();

Dans la pratique, vous navez pas vous en proccuper les dtails de ltat du bouton nintressent que la vue qui le dessine. Les informations importantes, telles que savoir si un bouton est activ, sont disponibles au moyen de la classe JButton (bien sr, cest lobjet JButton qui demande son modle dobtenir ces informations). Examinez linterface ButtonModel pour noter ce qui manque. Le modle ne conserve pas le libell ou licne du bouton. Il est impossible de savoir ce qui se trouve en surface dun bouton par un simple examen de son modle. En fait, comme vous le constaterez dans la section relative aux groupes de boutons radio, ce ct pur de la conception est une source dennuis pour le programmeur. Il importe galement de signaler que le mme modle, en loccurrence DefaultButtonModel, est utilis pour les boutons de commande, les boutons radio, les cases cocher et mme les options de menu. Bien sr, chacun de ces types de boutons possde des vues et des contrleurs diffrents. Lorsque le style dinterface Metal est employ, lobjet JButton utilise une classe appele BasicButtonUI pour la vue, et une classe appele ButtonUIListener comme contrleur. En gnral, chaque composant Swing possde un objet vue associ qui se termine par les lettres UI. Mais tous les composants Swing ne possdent pas dobjets contrleur ddis. Aprs cette brve introduction sur les particularits sous-jacentes de JButton, vous vous interrogez sans doute sur la nature relle de cet objet : en fait, il sagit dune classe enveloppe drive de la classe JComponent qui contient lobjet DefaultButtonModel, certaines donnes de vue comme le libell et les icnes et lobjet BasicButtonUI, responsable de la vue du bouton.

372

Au cur de Java 2 - Notions fondamentales

Introduction la gestion de mise en forme


Avant de poursuivre avec dautres composants Swing, comme les champs de texte et les boutons radio, nous devons brivement traiter de la faon dont les composants peuvent tre disposs dans un cadre. A la diffrence de VisualBasic, le JDK ne possde pas de concepteur de formulaire. Vous devez crire du code pour positionner les composants dinterface. Bien sr, si vous disposez dun environnement de dveloppement qui accepte Java, il possdera probablement un outil de mise en forme pour automatiser certaines de ces tches, ou la totalit. Nanmoins, il importe de matriser le processus interne de telles oprations, car mme le meilleur de ces outils ncessitera une intervention manuelle. Commenons par rexaminer le programme du Chapitre 8 qui utilise des boutons pour modier la couleur de fond dune fentre (voir Figure 9.5).
Figure 9.5
Un panneau avec trois boutons.

Les boutons gurent dans un objet JPanel et sont grs par le gestionnaire de mise en forme du ux, le gestionnaire par dfaut pour un panneau. La Figure 9.6 montre ce qui se produit avec six boutons dans la fentre. Comme vous pouvez le constater, une nouvelle ligne est commence lorsquil ny a plus de place.
Figure 9.6
Une fentre avec six boutons grs par le gestionnaire FlowLayout.

De plus, les boutons restent centrs mme lorsque lutilisateur redimensionne la fentre (voir Figure 9.7). En gnral, tous les composants sont placs dans des conteneurs et un gestionnaire de mise en forme dtermine leurs positions et leurs tailles des composants dans ceux-ci. Les boutons, les champs de texte et dautres lments dinterface utilisateur tendent la classe Component. Les composants peuvent tre placs dans des conteneurs tels des panneaux. Les conteneurs pouvant eux-mmes tre imbriqus dans dautres, la classe Container tend Component. La Figure 9.8 montre la hirarchie dhritage pour Component.

Chapitre 9

Swing et les composants dinterface utilisateur

373

Figure 9.7
Le changement de taille de la fentre provoque la rorganisation automatique des boutons.

Figure 9.8
Hirarchie dhritage pour la classe Component.
Object

Component

Container

Window

JComponent

Frame

Dialog

JPanel

JText Component

JLabel

JScrollPane

JComboBox

Abstract Button

JMenuBar

JFrame

JDialog

JTextField

JTextArea

JButton

JToggle Button

JMenuItem

INFO
Malheureusement, la hirarchie dhritage est peu agrable deux gards. Tout dabord, les fentres de haut niveau comme JFrame sont des sous-classes de Container et donc de Component, mais elles ne peuvent pas tre places dans dautres conteneurs. De plus, JComponent est une sous-classe de Container, et non de Component, et ne peut donc pas ajouter dautres composants dans un JButton (ces composants ne seraient pas afchs).

Chaque conteneur possde un gestionnaire de mise en forme par dfaut, mais vous pouvez toujours spcier le vtre. Par exemple, linstruction
panel.setLayout(new GridLayout(4, 4));

utilisera la classe GridLayout pour placer les composants dans le panneau. Ajoutez des composants au conteneur. La mthode add de ce dernier passe le composant et toutes les directives de placement au gestionnaire.

374

Au cur de Java 2 - Notions fondamentales

java.awt.Container 1.0 void setLayout(LayoutManager m)

Congure le gestionnaire de mise en forme pour ce conteneur.



Component add(Component c) Component add(Component c, Object constraints) 1.1

Ajoutent un composant ce conteneur et renvoient la rfrence du composant. Paramtres : c constraints Le composant ajouter. Un identiant compris par le gestionnaire de mise en forme.

java.awt.FlowLayout 1.0 FlowLayout() FlowLayout(int align) FlowLayout(int align, int hgap, int vgap)

Construisent un nouveau gestionnaire FlowLayout. Paramtres : align hgap vgap Lune des constantes dalignement LEFT, CENTER ou RIGHT. Lintervalle horizontal en pixels utiliser (les valeurs ngatives provoquent un chevauchement). Lintervalle vertical en pixels utiliser (les valeurs ngatives provoquent un chevauchement).

Gestionnaire BorderLayout
Le gestionnaire de mise en forme de bordure est le gestionnaire par dfaut du panneau de contenu contentPane de chaque JFrame. A la diffrence de FlowLayout qui contrle compltement la position de chaque composant, BorderLayout vous permet de choisir lemplacement de chaque composant par lintermdiaire dune constante de positionnement sinspirant des points cardinaux : au centre, au nord, au sud, lest ou louest du panneau conteneur (voir Figure 9.9).
Figure 9.9
Le gestionnaire BorderLayout.
West North

Center

East

South

Par exemple :
frame.add(component, BorderLayout.SOUTH);

Les composants en bordure sont placs en premier et lespace disponible restant est occup par le centre. Lorsque le conteneur est redimensionn, la densit des composants en lisire ne change pas, mais le composant central voit ses dimensions modies. Vous ajoutez un composant en spciant une constante CENTER, NORTH, SOUTH, EAST ou WEST de la classe BorderLayout. Toutes ces positions

Chapitre 9

Swing et les composants dinterface utilisateur

375

ne doivent pas ncessairement tre occupes. Si vous ne fournissez aucune chane, la constante "Center" est utilise.
INFO
Les constantes BorderLayout sont dnies comme des chanes. Par exemple, BorderLayout.SOUTH est dni comme la chane "South". De nombreux programmeurs prfrent utiliser directement les chanes, plus courtes ; par exemple frame.add(component, "South"). Cependant, si vous faites une erreur en crivant la chane, le compilateur ne pourra pas la dtecter.

A la diffrence du gestionnaire FlowLayout, BorderLayout augmente la taille des composants pour remplir lespace disponible. FlowLayout conserve chaque composant sa taille prfre. Or, cela pose problme lorsque vous ajoutez un bouton :
frame.add(yellowButton, BorderLayout.SOUTH); // ne pas faire

La Figure 9.10 illustre ce qui se produit lorsque vous utilisez le fragment de code prcdent. Le bouton a t agrandi pour emplir toute la rgion sud de la fentre. Si vous en ajoutiez un autre dans la mme rgion, il dplacerait le premier.
Figure 9.10
Un seul bouton gr par un gestionnaire BorderLayout.

Une mthode couramment employe pour contourner ce problme est lusage des panneaux supplmentaires. A la Figure 9.11, par exemple, les trois boutons au bas de lcran sont tous contenus dans un panneau. Celui-ci est ensuite plac au sud du volet de contenu.
Figure 9.11
Un panneau plac au sud du cadre.

Pour parvenir cette conguration, crez dabord un nouvel objet JPanel avant lajout de chaque bouton. Le gestionnaire de mise en forme par dfaut pour un panneau est FlowLayout, ce qui convient dans notre cas. Vous ajouterez ensuite les boutons individuels au moyen de la mthode add dj tudie. La position et la taille des boutons sont soumises au contrle du gestionnaire

376

Au cur de Java 2 - Notions fondamentales

FlowLayout. Ainsi, ils resteront centrs par rapport au panneau et ne sagrandiront pas pour occuper toute sa surface. Enn, ajoutez un panneau au volet de contenu du cadre :
JPanel panel = new JPanel(); panel.add(yellowButton); panel.add(blueButton); panel.add(redButton); frame.add(panel, BorderLayout.SOUTH);

La mise en forme de la bordure agrandit la taille du panneau pour remplir toute la rgion sud.
java.awt.BorderLayout 1.0 BorderLayout() BorderLayout(int hgap, int vgap)

Construisent un nouveau gestionnaire BorderLayout. Paramtres : hgap vgap Lintervalle horizontal en pixels utiliser (les valeurs ngatives provoquent un chevauchement). Lintervalle vertical en pixels utiliser (les valeurs ngatives provoquent un chevauchement).

Disposition des grilles


La disposition des grilles organise les composants, un peu la manire des lignes et des colonnes dun tableur. Toutefois, tous les composants ont une taille identique. Le programme de calculatrice (voir Figure 9.12) emploie la disposition de grille pour en organiser les boutons. Lorsque vous redimensionnez la fentre, les boutons sagrandissent et diminuent tout en conservant des tailles identiques.
Figure 9.12
Une calculette.

Dans le constructeur de lobjet GridLayout, vous spciez le nombre de lignes et de colonnes requis :
panel.setLayout(new GridLayout(5, 4));

Vous ajoutez les composants en commenant par la premire entre sur la premire ligne, puis la deuxime sur la mme ligne, etc. :
panel.add(new JButton("1")); panel.add(new JButton("2"));

Le Listing 9.1 prsente le code source du programme de la calculette. Il sagit dune calculatrice ordinaire, et non dune version qui utilise la notation "polonaise inverse", si populaire dans les didacticiels Java. Dans ce programme, nous appelons la mthode pack aprs avoir ajout le composant au cadre. Cette mthode emploie les tailles prfres de tous les composants pour calculer la largeur et la hauteur du cadre.

Chapitre 9

Swing et les composants dinterface utilisateur

377

Bien sr, peu dapplications possdent une mise en forme aussi rigide que la faade dune calculatrice. Dans la pratique, de petites grilles (contenant gnralement une seule ligne ou colonne) permettent dorganiser des zones partielles dune fentre. Si vous voulez disposer dune ligne de boutons de taille identique, vous pouvez les placer lintrieur dun panneau gr par un gestionnaire GridLayout avec une seule ligne.
Listing 9.1 : Calculator.java
import java.awt.*; import java.awt.event.*; import javax.swing.*; /** * @version 1.33 2007-06-12 * @author Cay Horstmann */ public class Calculator { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { CalculatorFrame frame = new CalculatorFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); } } /** * Un cadre avec un panneau pour une calculatrice. */ class CalculatorFrame extends JFrame { public CalculatorFrame() { setTitle("Calculator"); CalculatorPanel panel = new CalculatorPanel(); add(panel); pack(); } } /** * Un panneau avec les boutons de la calculatrice * et laffichage des rsultats. */ class CalculatorPanel extends JPanel { public CalculatorPanel() { setLayout(new BorderLayout()); result = 0; lastCommand = "="; start = true;

378

Au cur de Java 2 - Notions fondamentales

// ajouter la zone daffichage display = new JButton("0"); display.setEnabled(false); add(display, BorderLayout.NORTH); ActionListener insert = new InsertAction(); ActionListener command = new CommandAction(); // ajouter les boutons dans une grille 4x4 panel = new JPanel(); panel.setLayout(new GridLayout(4, 4)); addButton("7", addButton("8", addButton("9", addButton("/", addButton("4", addButton("5", addButton("6", addButton("*", addButton("1", addButton("2", addButton("3", addButton("-", addButton("0", addButton(".", addButton("=", addButton("+", insert); insert); insert); command); insert); insert); insert); command); insert); insert); insert); command); insert); insert); command); command);

add(panel, BorderLayout.CENTER); } /** * Ajoute un bouton au panneau central. * @param label Le libell du bouton * @param listener Lcouteur du bouton */ private void addButton(String label, ActionListener listener) { JButton button = new JButton(label); button.addActionListener(listener); panel.add(button); } /** * Cette action insre la chane daction du bouton * la fin du texte daffichage. */ private class InsertAction implements ActionListener { public void actionPerformed(ActionEvent event) { String input = event.getActionCommand();

Chapitre 9

Swing et les composants dinterface utilisateur

379

if (start) { display.setText(""); start = false; } display.setText(display.getText() + input); } } /** * Cette action excute la commande indique par la chane * daction du bouton. */ private class CommandAction implements ActionListener { public void actionPerformed(ActionEvent event) { String command = event.getActionCommand(); if (start) { if (command.equals("-")) { display.setText(command); start = false; } else lastCommand = command; } else { calculate(Double.parseDouble(display.getText())); lastCommand = command; start = true; } } } /** * Excute le calcul en attente. * @param x La valeur cumuler avec le rsultat prcdent. */ public void calculate(double x) { if (lastCommand.equals("+")) result += x; else if (lastCommand.equals("-")) result -= x; else if (lastCommand.equals("*")) result *= x; else if (lastCommand.equals("/")) result /= x; else if (lastCommand.equals("=")) result = x; display.setText("" + result); } private private private private private } JButton display; JPanel panel; double result; String lastCommand; boolean start;

380

Au cur de Java 2 - Notions fondamentales

java.awt.GridLayout 1.0

GridLayout(int rows, int columns) GridLayout(int rows, int columns, int hgap, int vgap)

Construisent une nouvelle grille GridLayout. rows ou columns (mais non les deux) peut tre gal zro, indiquant un nombre arbitraire de composants par ligne ou par colonne. Paramtres : rows columns hgap vgap Le nombre de lignes dans la grille. Le nombre de colonnes dans la grille. Lintervalle horizontal en pixels (les valeurs ngatives provoquent un chevauchement). Lintervalle vertical en pixels (les valeurs ngatives provoquent un chevauchement).

java.awt.Window 1.0

void pack()

Redimensionne la fentre, en prenant en compte les tailles prfres de ses composants.

Entre de texte
Nous sommes prts pour lintroduction des composants Swing dinterface utilisateur. Vous pouvez utiliser les composants JTextField et JTextArea pour collecter les entres de texte. Un champ de texte naccepte quune ligne de texte et les zones de texte peuvent en accueillir plusieurs. Un JPasswordField accepte une ligne de texte sans afcher le contenu. Ces trois classes drivent dune classe appele JTextComponent. Vous ne pourrez pas construire vous-mme un objet JTextComponent, car il sagit dune classe abstract. En revanche, cas frquent avec Java lors de la lecture de la documentation sur les API, vous constaterez que les mthodes recherches se trouveront plus souvent dans la classe parent JTextComponent que dans les classes drives. Par exemple, les mthodes qui permettent de rcuprer ou de dnir le texte dans un champ ou une zone de texte sont en ralit des mthodes de la classe JTextComponent.
javax.swing.text.JTextComponent 1.2

String getText() void setText(String text)

Rcuprent ou dnissent le texte dun composant texte.


Boolean isEditable() void setEditable(boolean b)

Rcuprent ou dnissent la proprit editable qui dtermine si lutilisateur peut modier le contenu de ce composant de texte.

Champs de texte
La mthode pour ajouter un champ de texte dans une fentre consiste lajouter un panneau ou un conteneur, comme pour un bouton :

Chapitre 9

Swing et les composants dinterface utilisateur

381

JPanel panel = new JPanel(); JTextField textField = new JTextField("Default input", 20); panel.add(textField);

Ce code ajoute un champ de texte et linitialise avec la chane "Default input". Le second paramtre de ce constructeur en dnit la largeur. Dans notre exemple, la largeur est de 20 "colonnes". Malheureusement, une colonne est une unit de mesure assez imprcise. Elle reprsente la largeur attendue dun caractre dans la police employe pour le texte. Si vous prvoyez des entres utilisateur dau plus n caractres, vous tes suppos indiquer n en tant que largeur de colonne. Dans la pratique, cette mesure ne donne pas de trs bons rsultats et vous devrez ajouter 1 ou 2 la longueur dentre maximale prvue. Gardez aussi lesprit que le nombre de colonnes nest quune suggestion pour AWT pour indiquer une taille de prfrence. Si le gestionnaire de mise en forme a besoin dagrandir ou de rduire le champ de texte, il peut en ajuster la taille. La largeur de colonne que vous dnissez dans le constructeur de la classe JTextField ne limite pas pour autant le nombre de caractres que lutilisateur peut taper. Il peut introduire des chanes plus longues ; toutefois, la vue de lentre dle lorsque le texte dpasse la longueur du champ, ce qui est assez irritant pour lutilisateur ; prvoyez donc gnreusement lespace ncessaire. Si vous devez rednir le nombre de colonnes durant lexcution, vous pouvez le faire avec la mthode setColumns.
ASTUCE
Aprs avoir chang la taille dun champ de texte avec la mthode setColumns, vous devez appeler la mthode revalidate du conteneur :
textField.setColumns(10); panel.revalidate();

Cette mthode recalcule la taille et la disposition de tous les composants dans un conteneur. Aprs son utilisation, le gestionnaire de mise en forme redimensionne le conteneur avec la taille du champ modie. La mthode revalidate appartient la classe JComponent. Elle ne redimensionne pas immdiatement le composant, elle le signale simplement pour un redimensionnement ultrieur. Cette approche vite des calculs rptitifs lorsquil faut redimensionner plusieurs composants. Si vous souhaitez toutefois recalculer tous les composants dans un JFrame, vous devez appeler la mthode validate (JFrame ntend pas JComponent).

En gnral, vous autorisez lutilisateur ajouter du texte (ou modier le texte existant) dans les champs de texte ; ceux-ci apparaissent donc vides le plus souvent lors du premier afchage. Pour quun champ soit vide, il suft de ne pas passer de chane pour le paramtre concern du constructeur de JTextField :
JTextField textField = new JTextField(20);

Vous pouvez modier le contenu du champ de texte tout moment avec la mthode setText de la classe parent TextComponent mentionne la section prcdente. Par exemple :
textField.setText("Hello!");

De plus, vous pouvez dterminer ce que lutilisateur a tap en appelant la mthode getText. Elle renvoie tout ce que lutilisateur a entr, y compris les espaces en tte et en n de chane. Vous pouvez les supprimer grce la mthode trim lors de la rcupration de lentre :
String Text = textField.getText().trim();

Pour modier la police du texte saisi par lutilisateur, appelez la mthode setFont.

382

Au cur de Java 2 - Notions fondamentales

javax.swing.JTextField 1.2

JTextField(int cols)

Construit un JTextField vide avec un nombre spci de colonnes.


JTextField(String text, int cols)

Construit un nouveau JTextField avec une chane initiale et le nombre spci de colonnes.
int getColumns() void setColumns(int cols)

Rcuprent ou dnissent le nombre de colonnes utiliser.


javax.swing.JComponent 1.2

void revalidate()

Entrane un nouveau calcul de la position et de la taille dun composant.


void setFont(Font f)

Dnit la police de ce composant.


java.awt.Component 1.0

void validate()

Recalcule la position et la taille dun composant. Si le composant est un conteneur, les positions et tailles de ses composants sont recalcules.

Font getFont()

Rcupre la police de ce composant.

Etiquettes et composants dtiquetage


Les tiquettes (ou libells) sont des composants qui contiennent du texte. Ils ne possdent pas dornements, telle une bordure, et ne ragissent pas aux entres de lutilisateur. Vous pouvez employer une tiquette pour identier des composants comme les composants de texte, qui, contrairement aux boutons, nont pas de libell. Pour associer une tiquette un composant, procdez ainsi : 1. Construisez un composant JLabel avec le texte voulu. 2. Placez-le sufsamment prs du composant identier pour viter toute ambigut. Le constructeur dun objet JLabel permet de spcier le texte initial ou licne et, en option, lalignement du contenu. Vous pouvez utiliser linterface SwingConstants pour spcier lalignement grce aux constantes suivantes quelle dnit : LEFT, RIGHT, CENTER, NORTH, EAST, etc. La classe JLabel est lune des classes Swing qui implmentent cette interface. Par consquent, vous pouvez spcier que le texte dune tiquette soit align sur la droite au moyen de lune des deux mthodes suivantes :
JLabel label = new JLabel("User name: ", SwingConstants.RIGHT);

ou :
JLabel label = new JLabel("User name: ", JLabel.RIGHT);

Les mthodes setText et setIcon vous permettent de dnir le texte et licne de ltiquette au moment de lexcution.

Chapitre 9

Swing et les composants dinterface utilisateur

383

ASTUCE
A partir de la version 1.3 de Java SE, vous pouvez avoir recours au texte normal ou HTML pour les boutons, les tiquettes et les options de menu. Lusage du format HTML nest pas recommand pour les boutons il interfre avec le look and feel. Il peut en revanche tre trs efcace pour les tiquettes. Encadrez simplement la chane dtiquettes avec les balises <html>. . .</html>, de la faon suivante :
label = new JLabel("<html><b>Required</b> entry:</html>");

Sachez cependant que le premier composant avec une tiquette HTML prend un certain temps avant dtre afch, du fait du chargement du code de rendu HTML, plutt complexe.

Les tiquettes peuvent tre positionnes lintrieur dun conteneur, linstar de tout autre composant. Ainsi, vous pouvez exploiter les techniques dj tudies pour les disposer.
javax.swing.JLabel 1.2

JLabel(String text) JLabel(Icon icon) JLabel(String text, int align) JLabel(String text, Icon icon, int align)

Construisent une tiquette. Paramtres : text icon align



String getText() void setText(String text)

Le texte de ltiquette. Licne de ltiquette. Lune des constantes SwingConstants LEFT (par dfaut), CENTER ou RIGHT.

Rcuprent ou dnissent le texte de cette tiquette.


Icon getIcon() void setIcon(Icon icon)

Rcuprent ou dnissent licne de cette tiquette.

Champs de mot de passe


Le champ de mot de passe est un type spcial de champ de texte. Pour viter que des voisins curieux ne puissent apercevoir le mot de passe entr par un utilisateur, les caractres taps ne sont pas afchs. Un caractre dcho est utilis la place, gnralement un astrisque (*). La bibliothque Swing fournit une classe JPasswordField qui implmente ce genre de champ. Le champ de mot de passe est un autre exemple de la puissance de larchitecture Modle-VueContrleur. Il utilise le mme modle quun champ de texte standard pour conserver les donnes, mais sa vue a t modie pour nafcher que des caractres dcho.
javax.swing.JPasswordField 1.2

JPasswordField(String text, int columns)

Construit un nouveau champ de mot de passe.

384

Au cur de Java 2 - Notions fondamentales

void setEchoChar(char echo)

Dnit le caractre dcho pour le champ de mot de passe. Un certain style dinterface peut proposer son propre choix de caractres dcho. La valeur 0 rtablit le caractre dcho utilis par dfaut.

char[] getPassword()

Renvoie le texte contenu dans le champ de mot de passe. Pour renforcer la scurit, vous devez craser le contenu du tableau renvoy aprs utilisation. Le mot de passe nest pas retourn en tant que String, car une chane resterait dans la machine virtuelle jusqu ce quelle soit limine par le processus de nettoyage de mmoire (le ramasse-miettes ou garbage collector).

Zones de texte
Parfois, vous avez besoin de recueillir une entre dutilisateur dune longueur suprieure une ligne. Pour mmoire, vous pouvez employer le composant JTextArea pour le faire. Lorsque vous placez un composant de ce type dans votre programme, un utilisateur peut taper nimporte quel nombre de lignes de texte en utilisant la touche Entre pour les sparer. Chaque ligne se termine par un caractre retour de ligne \n. La Figure 9.13 montre comment se prsente une zone de texte.
Figure 9.13
Des composants de texte.

Dans le constructeur du composant JTextArea, vous spciez le nombre de lignes et de colonnes pour la zone. Par exemple :
textArea = new JTextArea(8, 40); // 8 lignes de 40 colonnes chacune

o le paramtre qui indique le nombre de colonnes fonctionne comme auparavant ; vous devez toujours ajouter quelques colonnes (caractres) supplmentaires par prcaution. Lutilisateur nest pas limit au nombre de lignes et de colonnes ; le texte dlera si lentre est suprieure aux valeurs spcies. Vous pouvez galement modier le nombre de colonnes et de lignes en utilisant, respectivement, les mthodes setColumns et setRows. Les valeurs donnes nindiquent quune prfrence, le gestionnaire de mise en forme peut toujours agrandir ou rduire la zone de texte. Si le texte entr dpasse la capacit dafchage de la zone de texte, le reste du texte est coup. Pour viter que des longues lignes ne soient tronques, vous pouvez activer le retour automatique la ligne avec la mthode :
textArea.setLineWrap(true); // sauts de ligne automatiques

Chapitre 9

Swing et les composants dinterface utilisateur

385

Ce renvoi la ligne nest quun effet visuel. Le texte dans le document nest pas modi, aucun caractre \n nest insr.

Volets de dlement
Dans Swing, une zone de texte ne dispose pas de barres de dlement. Si vous souhaitez en ajouter, prvoyez la zone de texte dans un panneau avec barres de dlement JScrollPane.
textArea = new JTextArea(8, 40); JScrollPane scrollPane = new JScrollPane(textArea);

Le panneau de dlement gre ensuite la vue de la zone de texte. Des barres de dlement apparaissent automatiquement si le texte entr dpasse la zone dafchage ; elles disparaissent si, lors dune suppression, le texte restant tient dans la zone de texte. Le dlement est gr en interne par le panneau de dlement votre programme na pas besoin de traiter les vnements de dlement. Cest un mcanisme gnral qui fonctionne avec nimporte quel composant, et pas uniquement les zones de texte. Pour ajouter des barres de dlement un composant, placez-le lintrieur dun panneau de dlement. Le Listing 9.2 prsente les divers composants de texte. Le programme afche simplement un champ de texte, un champ de mot de passe et une zone de texte avec des barres de dlement. Les champs de texte et de mot de passe sont tiquets. Cliquez sur le bouton "Insert" pour insrer le contenu du champ dans la zone de texte.
INFO
Le composant JTextArea nafche que du texte brut, sans polices spciales ni mise en forme. Pour afcher du texte format tel que HTML, servez-vous de la classe JEditorPane, qui est vue dans le Volume II.

Listing 9.2 : TextComponentTest.java


import java.awt.*; import java.awt.event.*; import javax.swing.*; /** * @version 1.40 2007-04-27 * @author Cay Horstmann */ public class TextComponentTest { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { TextComponentFrame frame = new TextComponentFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); }

386

Au cur de Java 2 - Notions fondamentales

} /** * Un cadre avec des exemples de composants de texte */ class TextComponentFrame extends JFrame { public TextComponentFrame() { setTitle("TextComponentTest"); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); final JTextField textField = new JTextField(); final JPasswordField passwordField = new JPasswordField(); JPanel northPanel = new JPanel(); northPanel.setLayout(newGridLayout(2, 2)); northPanel.add(new JLabel("User name: ", SwingConstants.RIGHT)); northPanel.add(textField); northPanel.add(new JLabel("Password: ", SwingConstants.RIGHT)); northPanel.add(passwordField); add(northPanel, BorderLayout.NORTH); final JTextArea textArea = new JTextArea(8, 40); JScrollPane scrollPane = new JscrollPane(textArea); add(scrollPane, BorderLayout.CENTER); // un bouton pour ajouter du texte dans la zone JPanel southPanel = new JPanel(); JButton insertButton = new JButton("Insert"); southPanel.add(insertButton); insertButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { textArea.append("User name: " + textField.getText() + " Password: " + new String(passwordField.getPassword()) + "\n"); } }); add(southPanel, BorderLayout.SOUTH); // ajouter une zone de texte avec dfilement } public static final int DEFAULT_WIDTH = 300; public static final int DEFAULT_HEIGHT = 300; }

Chapitre 9

Swing et les composants dinterface utilisateur

387

javax.swing.JTextArea 1.2

JTextArea() JTextArea(int rows, int cols) JTextArea(String text, int rows, int cols)

Construisent une nouvelle zone de texte.


void setColumns(int cols)

Indique la zone de texte le nombre de colonnes prfr utiliser.


void setRows(int rows)

Indique la zone de texte le nombre de lignes prfr utiliser.


void append(String newText)

Ajoute le texte spci la n du texte dj prsent dans la zone de texte.


void setLineWrap(boolean wrap)

Active ou dsactive le retour automatique la ligne.


void setWrapStyleWord(boolean word)

Si le paramtre word vaut true, les lignes longues sont scindes au niveau des ns de mot. Si la valeur est false, elles sont divises sans tenir compte des ns de mot.

void setTabSize(int c)

Dnit les marques de tabulation toutes les c colonnes. Notez que les tabulations ne sont pas converties en espaces, mais provoquent lalignement avec la marque suivante.
javax.swing.JScrollPane 1.2

JScrollPane(Component c)

Cre un panneau avec dlement qui afche le contenu du composant spci. Les barres de dlement apparaissent automatiquement lorsque le contenu du composant est plus grand que la vue.

Composants du choix
Vous savez maintenant comment recueillir du texte tap par les utilisateurs. Toutefois, il est souvent prfrable de leur offrir un ensemble ni de choix plutt que de leur demander dintroduire des donnes dans un composant de texte. Au moyen dun ensemble de boutons ou dune liste doptions, vous pouvez indiquer aux utilisateurs les choix mis leur disposition. De plus, vous naurez pas besoin de crer de procdures de contrle derreurs pour ces types de slections. Dans cette section, vous apprendrez programmer des cases cocher, des boutons radio, des listes doptions et des curseurs.

Cases cocher
Si vous souhaitez recueillir une rponse qui se limite une entre "oui" ou "non", utilisez une case cocher. Ce type de composant saccompagne dun intitul qui permet de les identier. Lutilisateur clique lintrieur de la case pour la cocher et fait de mme pour la dsactiver. Pour ajouter/supprimer la coche, lutilisateur peut galement appuyer sur la barre despacement si le focus dentre se trouve dans la case cocher.

388

Au cur de Java 2 - Notions fondamentales

La Figure 9.14 illustre un programme simple avec deux cases cocher : lune pour activer ou dsactiver lattribut de mise en italique dune police et lautre pour lattribut de mise en gras. Notez que le focus dentre se trouve sur la deuxime case doption, comme lindique le rectangle autour de son intitul. Chaque fois que lutilisateur clique sur lune des options, lcran est mis jour avec les nouveaux attributs de la police.
Figure 9.14
Exemples de cases cocher.

Les cases cocher saccompagnent dun libell qui indique leur rle. Le texte prvu pour le libell est pass au constructeur.
bold = new JCheckBox("Bold");

La mthode setSelected permet dactiver ou de dsactiver une case cocher, comme dans la ligne suivante :
bold.setSelected(true);

La mthode isSelected extrait le statut actuel de chaque case cocher. Il est false si la case nest pas coche, true le reste du temps. Lorsque lutilisateur clique sur une case cocher, un vnement daction est dclench. Comme toujours, vous associez un couteur daction la case cocher. Dans notre programme, les deux cases partagent le mme couteur daction :
ActionListener listener = . . . bold.addActionListener(listener); italic.addActionListener(listener);

La mthode actionPerformed interroge le statut des cases cocher bold et italic et dnit la police du panneau Normal, Gras ou Italique, ou les deux :
public void actionPerformed(ActionEvent event) { int mode = 0; if (bold.isSelected()) mode += Font.BOLD; if (italic.isSelected()) mode += Font.ITALIC; label.setFont(new Font("Serif", mode, FONTSIZE)); }

Le Listing 9.3 prsente le programme complet dimplmentation des cases cocher.


Listing 9.3 : CheckBoxtTest.java
import java.awt.*; import java.awt.event.*; import javax.swing.*;

Chapitre 9

Swing et les composants dinterface utilisateur

389

/** * @version 1.33 2007-06-12 * @author Cay Horstmann */ public class CheckBoxTest { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { CheckBoxFrame frame = new CheckBoxFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); } } /** * Un cadre avec un label pour lexemple de texte et des * cases cocher pour slectionner des attributs de police. */ class CheckBoxFrame extends JFrame { public CheckBoxFrame() { setTitle("CheckBoxTest"); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); // ajouter le label dexemple de texte label = new JLabel("The quick brown fox jumps over the lazy dog."); label.setFont(new Font("Serif", Font.PLAIN, FONTSIZE)); add(label, BorderLayout.CENTER); // Cet couteur affecte lattribut de police du libell // le statut de la case cocher ActionListener listener = new ActionListener() { public void actionPerformed(ActionEvent event) { int mode = 0; if (bold.isSelected()) mode += Font.BOLD; if (italic.isSelected()) mode += Font.ITALIC; label.setFont(new Font("Serif", mode, FONTSIZE)); } }; // ajouter les cases cocher JPanel buttonPanel = new JPanel(); bold = new JCheckBox("Bold"); bold.addActionListener(listener); buttonPanel.add(bold);

390

Au cur de Java 2 - Notions fondamentales

italic = new JCheckBox("Italic"); italic.addActionListener(listener); buttonPanel.add(italic); add(buttonPanel, BorderLayout.SOUTH); } public static final int DEFAULT_WIDTH = 300; public static final int DEFAULT_HEIGHT = 200; private JLabel label; private JCheckBox bold; private JCheckBox italic; private static final int FONTSIZE = 12; } javax.swing.JCheckBox 1.2

JCheckBox(String label) JCheckBox(String label, Icon icon)

Construisent une case cocher qui est, lorigine, non slectionne.


JCheckBox(String label, boolean state)

Construit une case cocher avec ltiquette donne et ltat initial.


boolean isSelected() void setSelected(boolean state)

Dnissent ou rcuprent ltat de slection de la case cocher.

Boutons radio
Dans lexemple prcdent, lutilisateur pouvait activer ou dsactiver une ou plusieurs options, ou aucune. De nombreuses situations exigent que lutilisateur ne puisse choisir quune seule option parmi un ensemble de choix. Lorsquune seconde option est slectionne, la premire est dsactive. Cet ensemble doptions est souvent mis en uvre au moyen dun groupe de boutons radio. Ils sont ainsi appels, car ils fonctionnent de la mme manire que les boutons dune radio : lorsque vous appuyez sur un bouton, celui qui tait enfonc ressort. La Figure 9.15 illustre un exemple typique. Lutilisateur est autoris slectionner une taille de police parmi plusieurs possibilits : Petit, Moyen, Grand et Trs grand bien sr, il ne peut en choisir quune la fois.
Figure 9.15
Un groupe de boutons radio.

Chapitre 9

Swing et les composants dinterface utilisateur

391

Limplmentation des groupes de boutons radio est facile avec Swing. Construisez un objet de type ButtonGroup pour chaque groupe de boutons. Ajoutez ensuite des objets du type JRadioButton au groupe. Lobjet groupe est responsable de la dsactivation de la premire option choisie lors dune seconde slection.
ButtonGroup group = new ButtonGroup(); JRadioButton smallButton = new JRadioButton("Small", false); group.add(smallButton); JRadioButton mediumButton = new JRadioButton("Medium", true); group.add(mediumButton); . . .

Passez la valeur true comme second argument au constructeur pour loption qui doit tre initialement slectionne et false pour les autres. Notez quun groupe de boutons ne contrle que le comportement des boutons. Si vous voulez grouper les options pour des raisons de prsentation, vous devez les placer dans un conteneur tel que JPanel. Si vous observez de nouveau les Figures 9.14 et 9.15, vous noterez que les boutons radio nont pas la mme apparence que les cases cocher. Ces dernires se prsentent sous la forme dun carr qui contient une coche une fois quelles sont slectionnes. Les boutons radio sont ronds et contiennent un point lorsquils sont activs. Le mcanisme de notication dvnement est le mme pour les boutons radio que pour les autres boutons. Lorsque lutilisateur active lun des boutons, il gnre un vnement daction. Dans notre exemple de programme, nous dnissons un couteur daction qui dnit la taille de police une valeur donne :
ActionListener listener = new ActionListener() { public void actionPerformed(ActionEvent event) { // size fait rfrence au paramtre final de la mthode addRadioButton label.setFont(new Font("Serif", Font.PLAIN, size)); } };

Comparez cette conguration dcouteur celle de lexemple de case cocher. Chaque bouton radio obtient un objet couteur diffrent. Chaque objet couteur sait exactement ce quil doit faire dnir la taille de police une valeur particulire. Dans le cas des cases cocher, nous avons utilis une approche diffrente. Les deux cases cocher avaient le mme couteur daction ; il appelait une mthode qui examinait ltat des deux cases cocher. Pourrions-nous adopter une approche similaire ici ? Nous pourrions avoir un seul couteur qui calculerait la taille de la faon suivante :
if (smallButton.isSelected()) size = 8; else if (mediumButton.isSelected()) size = 12; . . .

Nous prfrons toutefois avoir recours des objets couteur daction diffrents, car ils associent les valeurs de taille aux boutons avec une plus grande prcision.

392

Au cur de Java 2 - Notions fondamentales

INFO
Si vous utilisez un groupe de boutons radio, vous savez que seul lun dentre eux peut tre choisi. Il serait intressant de pouvoir le localiser rapidement sans avoir interroger tous les boutons du groupe. Puisque lobjet ButtonGroup contrle tous les boutons, il devrait pouvoir nous donner une rfrence celui qui a t slectionn. La classe ButtonGroup possde bien une mthode getSelection, mais elle ne renvoie pas le bouton choisi. Elle retourne une rfrence ButtonModel au modle associ au bouton. Malheureusement, aucune des mthodes de ButtonModel nest vraiment dun grand secours. Linterface ButtonModel hrite dune mthode getSelectedObjects de linterface ItemSelectable qui, sans grande utilit, renvoie la valeur null. La mthode getActionCommand semble prometteuse, car la "commande daction" dun bouton radio est son texte de libell. Mais la commande daction de son modle vaut null. Ce nest que si vous dnissez explicitement les commandes daction de tous les boutons avec la mthode setActionCommand que les valeurs de commande daction des modles sont aussi dnies. Vous pouvez alors extraire la commande daction du bouton actuellement slectionn laide de buttonGroup.getSelection().getActionCommand().

Le Listing 9.4 illustre le programme complet de slection de taille de police qui met en uvre un ensemble de boutons radio.
Listing 9.4 : RadioButtonTest.java
import java.awt.*; import java.awt.event.*; import javax.swing.*; /** * @version 1.33 2007-06-12 * @author Cay Horstmann */ public class RadioButtonTest { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { RadioButtonFrame frame = new RadioButtonFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); } } /** * Un cadre avec un label pour lexemple de texte et des * boutons radio pour la slection de taille de police. */ class RadioButtonFrame extends JFrame { public RadioButtonFrame() { setTitle("RadioButtonTest"); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); // ajouter le libell dexemple de texte

Chapitre 9

Swing et les composants dinterface utilisateur

393

label = new JLabel("The quick brown fox jumps over the lazy dog."); label.setFont(new Font("Serif", Font.PLAIN, DEFAULT_SIZE)); add(label, BorderLayout.CENTER); // ajouter les boutons radio buttonPanel = new JPanel(); group = new ButtonGroup(); addRadioButton("Small", 8); addRadioButton("Medium", 12); addRadioButton("Large", 18); addRadioButton("Extra large", 36); add(buttonPanel, BorderLayout.SOUTH); } /** * Ajoute un bouton radio qui dfinit la taille de police * de lexemple de texte. * @param name La chane de libell du bouton * @param size La taille de police que dfinit ce bouton */ public void addRadioButton(String name, final int size) { boolean selected = size == DEFAULT_SIZE; JRadioButton button = new JRadioButton(name, selected); group.add(button); buttonPanel.add(button); // Cet couteur dfinit la taille de police de lintitul ActionListener listener = new ActionListener() { public void actionPerformed(ActionEvent event) { // size fait rfrence au paramtre final de // la mthode addRadioButton label.setFont(new Font("Serif", Font.PLAIN, size)); } }; button.addActionListener(listener); } public static final int DEFAULT_WIDTH = 400; public static final int DEFAULT_HEIGHT = 200; private JPanel buttonPanel; private ButtonGroup group; private JLabel label; private static final int DEFAULT_SIZE = 12; }

394

Au cur de Java 2 - Notions fondamentales

javax.swing.JRadioButton 1.2

JRadioButton(String label, Icon icon)

Construit un bouton radio, non slectionn au dpart.


JRadioButton(String label, boolean state)

Construit un bouton radio avec le libell donn et ltat initial.


javax.swing.ButtonGroup 1.2

void add(AbstractButton b)

Ajoute le bouton au groupe.


ButtonModel getSelection()

Renvoie le modle du bouton slectionn.


javax.swing.ButtonModel 1.2

String getActionCommand()

Renvoie la commande daction du modle du bouton.


javax.swing.AbstractButton 1.2

void setActionCommand(String s)

Dnit la commande daction pour le bouton et son modle.

Bordures
Si vous prsentez plusieurs groupes de boutons radio dans une fentre, vous devrez indiquer visuellement quels sont ceux qui appartiennent au mme groupe. Swing fournit un ensemble de bordures (ou cadres) qui sont utiles pour raliser cet effet. Vous pouvez appliquer une bordure nimporte quel composant qui tend la classe JComponent. Lusage le plus courant est de placer une bordure autour dun panneau et de le remplir avec dautres lments dinterface tels que des boutons radio. Il y a plusieurs choix de bordures, mais leur emploi respecte la mme procdure : 1. Appelez une mthode statique de BorderFactory pour crer une bordure. Vous pouvez choisir parmi les styles suivants (voir Figure 9.16) : LoweredBevel (relief incrust) ; RaisedBevel (relief en saillie) ; Etched (grave) ; Line (trait uniforme) ; Matte (avec trait variable ou texture) ; Empty (vide ; pour crer un espace autour du composant). 2. Vous pouvez ajouter un titre votre bordure en la passant avec le titre la mthode BorderFactory.createTitledBorder. 3. Il est aussi possible de combiner plusieurs bordures avec la mthode BorderFactory.createCompoundBorder.

Chapitre 9

Swing et les composants dinterface utilisateur

395

4. Ajoutez la bordure rsultante votre composant au moyen de la mthode setBorder de la classe JComponent. Voici, par exemple, de quelle faon ajouter une bordure grave avec un titre dans un panneau :
Border etched = BorderFactory.createEtchedBorder() Border titled = BorderFactory.createTitledBorder(etched, "A Title"); panel.setBorder(titled);

Excutez le programme du Listing 9.5 pour avoir un aperu de lapparence des diverses bordures proposes. Les bordures saccompagnent doptions qui permettent den dnir lpaisseur et la couleur. Reportezvous la documentation API pour plus de renseignements ce sujet. Les fans de bordures apprcieront la classe SoftBevelBorder qui permet de crer une bordure avec des angles arrondis ; une bordure LineBorder peut aussi avoir des coins arrondis. Toutefois, leur cration nest possible quau moyen dun des constructeurs de la classe ; il nexiste pas de mthode BorderFactory pour ces types.
Figure 9.16
Test des types de bordures.

Listing 9.5 : BorderTest.java


import import import import java.awt.*; java.awt.event.*; javax.swing.*; javax.swing.border.*;

/** * @version 1.33 2007-06-12 * @author Cay Horstmann */ public class BorderTest { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { BorderFrame frame = new BorderFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); } }

396

Au cur de Java 2 - Notions fondamentales

/** * Un cadre avec des boutons radio pour choisir un style de bordure. */ class BorderFrame extends JFrame { public BorderFrame() { setTitle("BorderTest"); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); demoPanel = new JPanel(); buttonPanel = new JPanel(); group = new ButtonGroup(); addRadioButton("Lowered bevel", BorderFactory.createLoweredBevelBorder()); addRadioButton("Raised bevel", BorderFactory.createRaisedBevelBorder()); addRadioButton("Etched", BorderFactory.createEtchedBorder()); addRadioButton("Line", BorderFactory.createLineBorder(Color.BLUE)); addRadioButton("Matte", BorderFactory.createMatteBorder( 10, 10, 10, 10, Color.BLUE)); addRadioButton("Empty", BorderFactory.createEmptyBorder()); Border etched = BorderFactory.createEtchedBorder(); Border titled = BorderFactory.createTitledBorder (etched, "Border types"); buttonPanel.setBorder(titled); setLayout(new GridLayout(2, 1)); add(buttonPanel); add(demoPanel); } public void addRadioButton(String buttonName, final Border b) { JRadioButton button = new JRadioButton(buttonName); button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { demoPanel.setBorder(b); } }); group.add(button); buttonPanel.add(button); } public static final int DEFAULT_WIDTH = 600; public static final int DEFAULT_HEIGHT = 200; private JPanel demoPanel; private JPanel buttonPanel; private ButtonGroup group; }

Chapitre 9

Swing et les composants dinterface utilisateur

397

javax.swing.BorderFactory 1.2

static Border createLineBorder(Color color) static Border createLineBorder(Color color, int thickness)

Crent une bordure avec un trait simple rgulier.


static MatteBorder createMatteBorder(int top, int left, int bottom, int right, Color color) static MatteBorder createMatteBorder(int top, int left, int bottom, int right, Icon tileIcon)

Crent une bordure paisse remplie avec une couleur ou une rptition dicnes (mosaque).

static Border createEmptyBorder() static Border createEmptyBorder(int top, int left, int bottom, int right)

Crent une bordure vide.


static Border createEtchedBorder() static Border createEtchedBorder(Color highlight, Color shadow) static Border createEtchedBorder(int type) static Border createEtchedBorder(int type, Color highlight, Color shadow)

Crent une bordure avec un effet 3D. Paramtres : highlight, shadow type

Couleurs pour un effet 3D. Lun des types EtchedBorder.RAISED ou EtchedBorder.LOWERED.

static Border createBevelBorder(int type) static Border createBevelBorder(int type, Color highlight, Color shadow) static Border createLoweredBevelBorder() static Border createRaisedBevelBorder()

Crent une bordure qui donne leffet dune surface avec un relief incrust ou en saillie. Paramtres : type highlight, shadow

Lun des types EtchedBorder.LOWERED ou EtchedBorder.RAISED Couleurs pour un effet 3D.

static TitledBorder createTitledBorder(String title) static TitledBorder createTitledBorder(Border border) static TitledBorder createTitledBorder(Border border, String title) static TitledBorder createTitledBorder(Border border, String title, int justification, int position) static TitledBorder createTitledBorder(Border border, String title, int justification, int position, Font font) static TitledBorder createTitledBorder(Border border, String title, int justification, int position, Font font, Color color)

Crent une bordure de titre avec les proprits spcies. Paramtres : title border justification La chane pour le titre. La bordure laquelle associer le titre. Lune des constantes TitleBorder LEFT, CENTER, RIGHT, LEADING, TRAILING ou DEFAULT_JUSTIFICATION ( gauche).

398

Au cur de Java 2 - Notions fondamentales

position

Lune des constantes TitledBorder ABOVE_TOP, TOP, BELOW_TOP, ABOVE_BOTTOM, BOTTOM, BELOW_BOTTOM ou DEFAULT_POSITION (en haut). La police du titre. La couleur du titre.

font color

static CompoundBorder createCompoundBorder(Border outsideBorder, Border insideBorder)

Combine deux bordures pour en crer une nouvelle.


javax.swing.border.SoftBevelBorder 1.2

SoftBevelBorder(int type) SoftBevelBorder(int type, Color highlight, Color shadow)

Crent une bordure en relief avec des angles arrondis. Paramtres : type highlight, shadow
javax.swing.border.LineBorder 1.2

Lun des types EtchedBorder.LOWERED ou EtchedBorder.RAISED. Couleurs pour un effet 3D.

public LineBorder(Colorcolor, intthickness, booleanroundedCorners)

Cre une ligne de bordure avec lpaisseur et la couleur indiques. Si roundedCorners vaut true, les coins sont arrondis.
javax.swing.JComponent 1.2

void setBorder(Border border)

Dnit la bordure pour le composant.

Listes droulantes
Ds que le nombre doptions augmente, les boutons radio ne conviennent plus, car ils occupent trop despace lcran. Vous pouvez, dans ce cas, utiliser une liste droulante. Lorsque lutilisateur clique sur le champ, une liste de choix se droule ; il peut ainsi faire une slection (voir Figure 9.17).
Figure 9.17
Une liste droulante.

Chapitre 9

Swing et les composants dinterface utilisateur

399

Si la liste droulante est congure pour accepter une saisie (editable), il est alors possible de changer le choix actuel en tapant dans le champ, comme pour un champ de texte. La liste droulante est aussi appele liste combine ; elle allie la souplesse dun champ de texte et une liste de choix prdnis. La classe JComboBox implmente ce composant. Vous appelez la mthode setEditable pour que la liste puisse tre modiable. Notez que la saisie ne modie que llment courant et ne change pas le contenu de la liste. Vous pouvez obtenir la slection courante ou le texte tap en appelant la mthode getSelectedItem. Dans notre exemple de programme, lutilisateur peut choisir un style de police partir dune liste (Serif, SansSerif, Monospaced, etc.). Il peut aussi taper une autre police dans le champ de slection. Vous ajoutez les options avec la mthode addItem. Dans notre programme, elle nest appele que dans le constructeur, mais vous pouvez lappeler tout moment :
faceCombo = new JComboBox(); faceCombo.setEditable(true); faceCombo.addItem("Serif"); faceCombo.addItem("SansSerif"); . . .

Cette mthode ajoute la chane la n de la liste. Vous pouvez ajouter de nouvelles options nimporte quel endroit dans la liste avec la mthode insertItemAt :
faceCombo.insertItemAt("Monospaced", 0); // ajoute loption au dbut

Vous pouvez ajouter des lments de tout type la liste droulante invoque la mthode toString de chaque lment pour lafcher. Si vous devez supprimer des options au cours de lexcution, utilisez la mthode removeItem ou removeItemAt, selon que vous spciez llment ou sa position :
faceCombo.removeItem("Monospaced"); faceCombo.removeItemAt(0); // supprimer le premier lment

Il existe aussi une mthode removeAllItems qui supprime toutes les options de la liste.
ASTUCE
Si vous devez ajouter un grand nombre dlments une liste droulante, la mthode addItem fonctionnera mal. Construisez plutt un DefaultComboBoxModel, remplissez-le en appelant addElement, puis appelez la mthode setModel de la classe JComboBox.

Lorsque lutilisateur slectionne une option de la liste droulante, un vnement daction est gnr. Pour trouver llment slectionn, appelez getSource sur le paramtre event (vnement) pour obtenir une rfrence la liste qui a envoy lvnement. Appelez ensuite la mthode getSelectedItem pour rcuprer loption slectionne. Vous devez convertir la valeur renvoye vers le type appropri, gnralement une chane (String).
public void actionPerformed(ActionEvent event) { label.setFont(new Font( (String) faceCombo.getSelectedItem() , Font.PLAIN, DEFAULT_SIZE)); }

400

Au cur de Java 2 - Notions fondamentales

Le Listing 9.6 dcrit le programme complet.


INFO
Pour afcher une liste en permanence au lieu dutiliser une liste droulante, faites appel au composant JList. Nous traiterons de JList au Chapitre 6 du Volume II.

Listing 9.6 : ComboBoxTest.java


import java.awt.*; import java.awt.event.*; import javax.swing.*; public class ComboBoxTest { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { ComboBoxFrame frame = new ComboBoxFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); } } /** * Un cadre avec un label pour lexemple de texte et une zone de liste * droulante pour slectionner les types de police. */ class ComboBoxFrame extends JFrame { public ComboBoxFrame() { setTitle("ComboBoxTest"); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); // ajouter le libell dexemple de texte label = new JLabel("The quick brown fox jumps over the lazy dog."); label.setFont(new Font("Serif", Font.PLAIN, DEFAULT_SIZE)); add(label, BorderLayout.CENTER); // crer une liste droulante et ajouter les types de polices faceCombo = new JComboBox(); faceCombo.setEditable(true); faceCombo.addItem("Serif"); faceCombo.addItem("SansSerif"); faceCombo.addItem("Monospaced"); faceCombo.addItem("Dialog"); faceCombo.addItem("DialogInput");

Chapitre 9

Swing et les composants dinterface utilisateur

401

// Lcouteur de la liste droulante remplace la police du // libell contenant lexemple par celle slectionne faceCombo.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { label.setFont(new Font( (String) faceCombo.getSelectedItem(), Font.PLAIN, DEFAULT_SIZE)); } }); // ajouter une liste droulante un panneau dans la // partie sud du cadre JPanel comboPanel = new JPanel(); comboPanel.add(faceCombo); add(comboPanel, BorderLayout.SOUTH); } public static final int DEFAULT_WIDTH = 300; public static final int DEFAULT_HEIGHT = 200; private JComboBox faceCombo; private JLabel label; private static final int DEFAULT_SIZE = 12; }

javax.swing.JComboBox 1.2

boolean isEditable() void setEditable(boolean b)

Rcuprent ou dnissent la proprit editable de cette liste droulante.


void addItem(Object item)

Ajoute un lment dans la liste.


void insertItemAt(Object item, int index)

Insre un lment dans la liste la position donne par lindice.


void removeItem(Object item)

Supprime un lment de la liste.


void removeItemAt(int index)

Supprime llment dans la liste la position donne par lindice.


void removeAllItems()

Supprime tous les lments de la liste.


Object getSelectedItem()

Renvoie llment actuellement slectionn.

402

Au cur de Java 2 - Notions fondamentales

Curseurs
Les zones de liste droulantes permettent lutilisateur de choisir parmi un ensemble de valeurs. Les curseurs proposent un choix exhaustif de valeurs, par exemple, nimporte quel nombre entre 1 et 100. La mthode la plus courante pour construire un curseur est la suivante :
JSlider slider = new JSlider(min, max, initialValue);

Si vous omettez les valeurs minimale, maximale et initiale, elles sont respectivement initialises 0, 100 et 50. Vous pouvez positionner le curseur verticalement, laide de lappel suivant au constructeur :
JSlider slider = new JSlider(SwingConstants.VERTICAL, min, max, initialValue);

Ces constructeurs crent un curseur normal, comme celui se trouvant en haut de la Figure 9.18. Nous allons vous indiquer comment y ajouter des oritures.
Figure 9.18
Diffrents types de curseurs.

Au fur et mesure que lutilisateur fait glisser le curseur, la valeur volue entre les valeurs minimum et maximum. Un vnement ChangeEvent est alors envoy tous les couteurs de changement. Pour obtenir une notication du changement, vous devez appeler la mthode addChangeListener et installer un objet qui implmente linterface ChangeListener. Cette interface possde une seule mthode, stateChanged, de laquelle vous devez extraire la valeur du curseur :
public void stateChanged(ChangeEvent event) { JSlider slider = (JSlider) event.getSource(); int value = slider.getValue(); . . . }

Chapitre 9

Swing et les composants dinterface utilisateur

403

Vous pouvez amliorer le curseur en afchant des repres. Par exemple, dans notre programme exemple, le deuxime curseur utilise la conguration suivante :
slider.setMajorTickSpacing(20); slider.setMinorTickSpacing(5);

Le curseur afche des grands repres toutes les 20 units et des petits repres toutes les 5 units. Les repres font rfrence aux valeurs du curseur, et non des pixels. Les instructions ci-dessus ne font que dnir les units pour les repres. Pour les afcher, vous devez appeler :
slider.setPaintTicks(true);

Les petites et grandes marques de repres sont indpendantes. Vous pouvez parfaitement dnir les grandes marques toutes les 20 units et les petites toutes les 7 units, mais vous risquez dobtenir une chelle assez droutante. Vous pouvez forcer le curseur saligner sur les repres. Chaque fois que lutilisateur relche le curseur dans ce mode de dplacement, il est positionn automatiquement sur le repre le plus proche. Vous activez ce mode laide de lappel :
slider.setSnapToTicks(true);

ATTENTION
Le mode dalignement sur les repres ne fonctionne pas aussi bien que prvu. Tant que le curseur nest pas rellement align, lcouteur de changement fait toujours tat de valeurs qui ne correspondent pas des repres. Et si vous cliquez ensuite prs du curseur une action qui est cense faire bouger le curseur de la valeur dun repre le curseur ne se dplace pas vers le repre suivant (ou prcdent).

Vous pouvez demander lafchage de valeurs de repres pour les grandes marques, en appelant :
slider.setPaintLabels(true);

Par exemple, pour un curseur allant de 0 100 ayant des grandes marques espaces de 20, les repres seront libells 0, 20, 40, 60, 80 et 100. Vous pouvez aussi fournir dautres libells, comme des chanes ou des icnes (voir Figure 9.18). Le processus est un peu compliqu. Vous devez remplir une table de hachage avec des cls de type Integer et des valeurs de type Component (lautoboxing simplie ceci partir de la version 5.0 de Java SE). Vous appelez ensuite la mthode setLabelTable. Les composants sont placs sous les marques de repres. Vous avez gnralement recours des objets JLabel. Voici comment libeller les marques avec A, B, C, D, E et F :
Hashtable<Integer, Component> labelTable = new Hashtable<Integer, Component>(); labelTable.put(0, new JLabel("A")); labelTable.put(20, new JLabel("B")); . . . labelTable.put(100, new JLabel("F")); slider.setLabelTable(labelTable);

Reportez-vous au Chapitre 2 du Volume II pour plus dinformations au sujet des tables de hachage.

404

Au cur de Java 2 - Notions fondamentales

Le Listing 9.7 montre aussi comment programmer un curseur avec des icnes comme libells de repres.
ASTUCE
Si vos marques ou libells de repres ne safchent pas, vriez que vous avez bien appel setPaintTicks(true) et setPaintLabels(true).

Le quatrime curseur de la Figure 9.18 ne possde pas de couloir. Pour supprimer le "couloir" dans lequel se dplace le curseur, appelez
slider.setPaintTrack(false);

Le remplissage du cinquime curseur est invers par lappel suivant :


slider.setInverted(true);

Le programme dexemple montre comment obtenir tous ces effets visuels sur une srie de curseurs. Un couteur dvnement de changement est install pour chaque curseur ; il place la valeur actuelle du curseur dans le champ de texte en bas du cadre.
Listing 9.7 : SliderTest.java
import import import import java.awt.*; java.util.*; javax.swing.*; javax.swing.event.*;

/** * @version 1.13 2007-06-12 * @author Cay Horstmann */ public class SliderTest { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { SliderTestFrame frame = new SliderTestFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); } } /** * Un cadre avec une srie de curseurs et une zone de texte pour * afficher les valeurs du curseur. */ class SliderTestFrame extends JFrame { public SliderTestFrame() { setTitle("SliderTest");

Chapitre 9

Swing et les composants dinterface utilisateur

405

setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); sliderPanel = new JPanel(); sliderPanel.setLayout(new FlowLayout(FlowLayout.LEFT)); // couteur commun tous les curseurs listener = new ChangeListener() { public void stateChanged(ChangeEvent event) { // mettre jour le champ de texte lorsque la // valeur du curseur change JSlider source = (JSlider) event.getSource(); textField.setText(""+source.getValue()); } }; // ajouter un curseur normal JSlider slider = new JSlider(); addSlider(slider, "Plain"); // ajouter un curseur avec des repres grands et petits slider = new JSlider(); slider.setPaintTicks(true); slider.setMajorTickSpacing(20); slider.setMinorTickSpacing(5); addSlider(slider, "Ticks"); // ajouter un curseur qui saligne sur les repres slider = new JSlider(); slider.setPaintTicks(true); slider.setSnapToTicks(true); slider.setMajorTickSpacing(20); slider.setMinorTickSpacing(5); addSlider(slider, "Snap to ticks"); // ajouter un curseur sans couloir slider = new JSlider(); slider.setPaintTicks(true); slider.setMajorTickSpacing(20); slider.setMinorTickSpacing(5); slider.setPaintTrack(false); addSlider(slider, "No track"); // ajouter un curseur invers slider = new JSlider(); slider.setPaintTicks(true); slider.setMajorTickSpacing(20); slider.setMinorTickSpacing(5); slider.setInverted(true); addSlider(slider, "Inverted");

406

Au cur de Java 2 - Notions fondamentales

// ajouter un curseur avec des libells numriques slider = new JSlider(); slider.setPaintTicks(true); slider.setPaintLabels(true); slider.setMajorTickSpacing(20); slider.setMinorTickSpacing(5); addSlider(slider, "Labels"); // ajouter un curseur avec des libells alphabtiques slider = new JSlider(); slider.setPaintLabels(true); slider.setPaintTicks(true); slider.setMajorTickSpacing(20); slider.setMinorTickSpacing(5); Dictionary<Integer, Component> labelTable = new Hashtable<Integer, Component>(); labelTable.put(0, new JLabel("A")); labelTable.put(20, new JLabel("B")); labelTable.put(40, new JLabel("C")); labelTable.put(60, new JLabel("D")); labelTable.put(80, new JLabel("E")); labelTable.put(100, new JLabel("F")); slider.setLabelTable(labelTable); addSlider(slider, "Custom labels"); // ajouter un curseur avec des icnes comme libells slider = new JSlider(); slider.setPaintTicks(true); slider.setPaintLabels(true); slider.setSnapToTicks(true); slider.setMajorTickSpacing(20); slider.setMinorTickSpacing(20); labelTable = new Hashtable<Integer, Component>(); // ajouter les images de cartes labelTable.put(0, new JLabel(new ImageIcon("nine.gif"))); labelTable.put(20, new JLabel(new ImageIcon("ten.gif"))); labelTable.put(40, new JLabel(new ImageIcon("jack.gif"))); labelTable.put(60, new JLabel(new ImageIcon("queen.gif"))); labelTable.put(80, new JLabel(new ImageIcon("king.gif"))); labelTable.put(100, new JLabel(new ImageIcon("ace.gif"))); slider.setLabelTable(labelTable); addSlider(slider, "Icon labels");

Chapitre 9

Swing et les composants dinterface utilisateur

407

// ajouter le champ de texte affichant la valeur du curseur textField = new JTextField(); add(sliderPanel, BorderLayout.CENTER); add(textField, BorderLayout.SOUTH); } /** * Ajoute un curseur au panneau curseur et attache lcouteur * @param s Le curseur * @param description La description du curseur */ public void addSlider(JSlider s, String description) { s.addChangeListener(listener); JPanel panel = new JPanel(); panel.add(s); panel.add(new JLabel(description)); sliderPanel.add(panel); } public static final int DEFAULT_WIDTH = 350; public static final int DEFAULT_HEIGHT = 450; private JPanel sliderPanel; private JTextField textField; private ChangeListener listener; } javax.swing.JSlider 1.2

JSlider() JSlider(int direction) JSlider(int min, int max) JSlider(int min, int max, int initialValue) JSlider(int direction, int min, int max, int initialValue)

Construisent un curseur horizontal avec la direction et les valeurs minimum, maximum et initiale donnes. Paramtres : direction min, max InitialValue

void setPaintTicks(boolean b)

Lune des valeurs SwingConstants.VERTICAL ou SwingConstants.HORIZONTAL. Par dfaut horizontal. Mini et maxi pour les valeurs de curseurs. Par dfaut 0 et 100. Valeur initiale du curseur. Par dfaut 50.

Afche des repres si b vaut true.


void setMajorTickSpacing(int units) void setMinorTickSpacing(int units)

Dnissent les repres grands et petits comme multiples des units de curseur donnes.
void setPaintLabels(boolean b)

Si b vaut true, les libells des repres sont afchs.

408

Au cur de Java 2 - Notions fondamentales

void.setTable(Dictionary table)

Dnit les composants utiliser comme libells des repres. Chaque paire cl/valeur dans la table a la forme new Integer(valeur)/composant.

void setSnapToTicks(boolean b)

Si b vaut true, le curseur saligne sur le repre le plus proche aprs chaque dplacement.
void setPaintTrack(boolean b)

Si b vaut true, le curseur se dplace dans un couloir.

Menus
Ce chapitre a dbut par une introduction aux composants les plus connus pouvant tre placs dans une fentre : les diffrents types de boutons, les champs de texte et les listes droulantes. Swing gre aussi un autre type dlment dinterface, les menus droulants, utiliss largement dans les interfaces GUI. Une barre de menus situe au sommet de la fentre contient les noms des menus droulants. Il suft de cliquer dessus pour les ouvrir, ce qui fait apparatre des options de menu et de sous-menus. Lorsque lutilisateur clique sur une option de menu, tous les menus sont ferms et un message est envoy au programme. La Figure 9.19 prsente un menu classique comprenant un sous-menu.
Figure 9.19
Un menu contenant un sous-menu.

Cration dun menu


La cration de menus est une opration assez simple. On commence par crer une barre de menus :
JMenuBar menuBar = new JMenuBar();

Une barre de menus est un simple composant que vous pouvez ajouter nimporte o. Le plus souvent, elle est place au sommet dun cadre. Pour cela, la mthode setJMenuBar est appele :
frame.setJMenuBar(menuBar);

Pour chaque menu, vous devez crer un objet menu :


JMenu editMenu = new JMenu("Edit");

Vous ajoutez les menus principaux la barre de menus :


menuBar.add(editMenu);

Vous ajoutez ensuite lobjet menu les options du menu, les sparateurs et les sous-menus :
JMenuItem pasteItem = new JMenuItem("Paste"); editMenu.add(pasteItem);

Chapitre 9

Swing et les composants dinterface utilisateur

409

editMenu.addSeparator(); JMenu optionsMenu = . . .; // un sous-menu editMenu.add(optionsMenu);

Vous pouvez voir les sparateurs la Figure 9.19 sous les lments "Paste" et "Read-only". Lorsque lutilisateur slectionne un menu, un vnement daction est dclench. Vous devez installer un couteur daction pour chaque option de menu :
ActionListener listener = . . .; pasteItem.addActionListener(listener);

Il existe une mthode, JMenu.add(String s), qui ajoute une option la n dun menu, par exemple :
editMenu.add("Paste");

La mthode add renvoie loption de menu cre, an que vous puissiez la capturer et lui associer un couteur, de la faon suivante :
JMenuItem pasteItem = editMenu.add("Paste"); pasteItem.addActionListener(listener);

Il arrive frquemment que des options de menu lancent des commandes qui peuvent aussi tre actives par le biais dautres lments de linterface utilisateur, comme les boutons de la barre doutils. Au Chapitre 8, vous avez vu comment spcier des commandes par lintermdiaire des objets Action. Vous dnissez une classe qui implmente linterface Action, gnralement par drivation de la classe AbstractAction. Vous spciez le libell de loption de menu dans le constructeur de lobjet AbstractAction, puis vous surchargez la mthode actionPerformed pour quelle contienne le gestionnaire daction du menu. Par exemple :
Action exitAction = new AbstractAction("Exit") // texte de loption de menu ici { public void actionPerformed(ActionEvent event) { // code de laction ici System.exit(0); } };

Vous pouvez alors ajouter laction au menu :


JMenuItem exitItem = fileMenu.add(exitAction);

Cette commande ajoute une option au menu, laide du nom de laction. Lobjet action devient son couteur. Cest un raccourci pratique pour :
JMenuItem exitItem = new JMenuItem(exitAction); fileMenu.add(exitItem);

INFO
Sous Windows et Macintosh, les menus sont gnralement dnis dans un chier de ressources externes, qui est li aux applications par lintermdiaire des identiants de ressources. Dans Java, les menus sont habituellement conus au sein des programmes, car le mcanisme de gestion des ressources externes est beaucoup plus limit que ceux de Windows ou de Mac OS.

410

Au cur de Java 2 - Notions fondamentales

javax.swing.JMenu 1.2

JMenu(String label)

Construit un menu avec le label donn.


JMenuItem add(JMenuItem option)

Ajoute une option de menu (ou un menu).


JMenuItem add(String label)

Ajoute une option au menu avec le label donn et renvoie llment.


JMenuItem add(Action a)

Ajoute une option de menu avec laction donne et renvoie llment.


void addSeparator()

Ajoute une ligne de sparation dans le menu.


JMenuItem insert(JMenuItem menu, int index)

Ajoute une nouvelle option (ou sous-menu) dans le menu la position donne par lindice.
JMenuItem insert(Action a, int index)

Ajoute une nouvelle option au menu avec laction donne, lindice spcique.
void insertSeparator(int index)

Ajoute un sparateur au menu. Paramtres :


index

Emplacement o ajouter le sparateur.

void remove(int index) void remove(JMenuItem item)

Suppriment une option spcique du menu.


javax.swing.JMenuItem 1.2

JMenuItem(String label)

Construit un lment de menu avec un intitul donn.


JMenuItem(Action a) 1.3

Construit un lment de menu pour laction donne.


javax.swing.AbstractButton 1.2

void setAction(Action a) 1.3

Dnit laction pour ce bouton ou lment de menu.


javax.swing.JFrame 1.2

void setJMenuBar(JMenuBar menubar)

Dnit la barre de menus de la fentre.

Icnes et options de menu


Les options de menu sont trs semblables aux boutons. En ralit, la classe JMenuItem tend la classe AbstractButton. Comme les boutons, les menus peuvent contenir un libell, une icne ou les deux. Vous pouvez spcier une icne laide des constructeurs JMenuItem (String, Icon)

Chapitre 9

Swing et les composants dinterface utilisateur

411

ou JMenuItem (Icon) ou la dnir laide de la mthode setIcon dont la classe JMenuItem hrite de la classe AbstractButton. Par exemple :
JMenuItem cutItem = new JMenuItem("Cut", new ImageIcon("cut.gif"));

La Figure 9.19 prsente un menu dont certaines options comprennent des icnes. Le texte des options est plac par dfaut droite de licne. Si vous prfrez que le texte soit plac gauche, appelez la mthode setHorizontalTextPosition dont la classe JMenuItem hrite de la classe AbstractButton. Par exemple, lappel
cutItem.setHorizontalTextPosition(SwingConstants.LEFT);

dplace le texte de loption de menu gauche de licne. Vous pouvez aussi ajouter une icne une action :
cutAction.putValue(Action.SMALL_ICON, new ImageIcon("cut.gif"));

Lorsque vous construisez une option de menu partir dune action, la valeur Action.NAME devient le texte de loption de menu et la valeur Action.SMALL_ICON devient licne. Vous pouvez aussi dnir licne dans le constructeur de AbstractAction :
cutAction = new AbstractAction("Cut", new ImageIcon("cut.gif")) { public void actionPerformed(ActionEvent event) { // le code de laction ici } }; javax.swing.JMenuItem 1.2

JMenuItem(String label, Icon icon)

Construit un lment de menu avec lintitul donn et une icne.


javax.swing.AbstractButton 1.2

void setHorizontalTextPosition(int pos)

Dnit la position horizontale du texte par rapport licne. Paramtres : pos SwingConstants.RIGHT (texte plac droite de licne) ou SwingConstants.LEFT (texte plac gauche).
javax.swing.AbstractAction 1.2

AbstractAction(String name, Icon smallIcon)

Construit une action abstraite avec le nom donn et une icne.

Options de menu avec cases cocher et boutons radio


Il est possible dajouter des cases cocher ou des boutons radio aux options de menu (voir Figure 9.19). Lorsque lutilisateur slectionne loption, elle passe ltat activ ou dsactiv, selon son tat initial.

412

Au cur de Java 2 - Notions fondamentales

Except leur aspect, vous traitez ces options de la mme manire que toutes les autres. Voici, par exemple, comment crer une option avec une case cocher :
JCheckBoxMenuItem readonlyItem = new JCheckBoxMenuItem("Read-only"); optionsMenu.add(readonlyItem);

Les boutons radio des options de menu fonctionnent de faon standard. Vous devez les inclure dans un groupe de boutons. Quand lun de ces boutons est choisi, tous les autres sont automatiquement dsactivs :
ButtonGroup group = new ButtonGroup(); JRadioButtonMenuItem insertItem = new JRadioButtonMenuItem("Insert"); insertItem.setSelected(true); JRadioButtonMenuItem overtypeItem = new JRadioButtonMenuItem("Overtype"); group.add(insertItem); group.add(overtypeItem); optionsMenu.add(insertItem); optionsMenu.add(overtypeItem);

Lorsque vous utilisez ce type doption, vous navez gnralement pas besoin dtre inform du moment exact o lutilisateur effectue sa slection. Vous pouvez simplement employer la mthode isSelected pour tester ltat actuel dune option (cela implique bien sr que vous conserviez une rfrence sur loption de menu stocke dans un champ dinstance). Utilisez la mthode setSelected pour dnir ltat dune option.
javax.swing.JCheckBoxMenuItem 1.2

JCheckBoxMenuItem(String label)

Cre une option de menu avec case cocher et lintitul spci.


JCheckBoxMenuItem(String label, boolean state)

Cre une option de menu avec case cocher avec lintitul et ltat initial spcis (true signie coche).
javax.swing.JRadioButtonMenuItem 1.2

JRadioButtonMenuItem(String label)

Cre une option de menu avec bouton radio et lintitul spci.


JRadioButtonMenuItem(String label, boolean state)

Cre une option de menu avec bouton radio avec lintitul et ltat initial spcis (true signie active).
javax.swing.AbstractButton 1.2

boolean isSelected() void setSelected(boolean state)

Dnissent ou rcuprent ltat dune option (true signie coche).

Menus contextuels
Un menu contextuel est un menu qui nest pas attach une barre de menus, mais qui otte lintrieur dune fentre (voir Figure 9.20).

Chapitre 9

Swing et les composants dinterface utilisateur

413

Figure 9.20
Un menu contextuel.

Vous crez un menu contextuel comme nimporte quel autre menu, sachant quil ne possde pas de titre :
JPopupMenu popup = new JPopupMenu();

Vous ajoutez ensuite les options comme laccoutume :


JMenuItem item = new JMenuItem("Cut"); item.addActionListener(listener); popup.add(item);

Contrairement la barre de menus standard qui apparat toujours au sommet dun cadre, un menu contextuel doit tre explicitement afch laide de la mthode show. Vous devez spcier le composant parent et la position du menu contextuel en fonction du systme de coordonnes du parent. Par exemple :
popup.show(panel, x, y);

Le code est habituellement crit pour que le menu surgisse au moment o lutilisateur clique sur un bouton particulier de la souris. Sous Windows et Linux, il sagit souvent du bouton droit. Pour faire apparatre un menu lorsque lutilisateur clique sur un composant, appelez simplement la mthode :
component.setComponentPopupMenu(popup);

Trs occasionnellement, vous pouvez placer un composant dans un autre qui dispose dun menu contextuel. Le composant enfant peut hriter du menu contextuel du composant parent en appelant
child.setInheritsPopupMenu(true);

Ces mthodes ont t ajoutes Java SE 5.0 pour isoler les programmeurs des dpendances du systme avec les menus droulants. Avant Java SE 5.0, vous deviez installer un couteur de souris et ajouter le code suivant sur les mthodes dcouteur mousePressed et mouseReleased :
if (popup.isPopupTrigger(event)) popup.show(event.getComponent(), event.getX(), event.getY());

Certains systmes dclenchent des menus droulants lorsque la souris descend, dautres, lorsque la souris remonte.
javax.swing.JPopupMenu 1.2

void show(Component c, int x, int y)

Afche le menu contextuel. Paramtres : c x, y Le composant sur lequel le menu contextuel doit apparatre. Les coordonnes (dans lespace de coordonnes de c) du coin suprieur gauche du menu contextuel.

414

Au cur de Java 2 - Notions fondamentales

boolean isPopupTrigger(MouseEvent event) 1.3

Renvoie true si lvnement de la souris est le dclencheur du menu contextuel.


java.awt.event.MouseEvent 1.1 boolean isPopupTrigger()

Renvoie true si lvnement de la souris est le dclencheur du menu contextuel.


javax.swing.JComponent 1.2 JPopupMenu getComponentPopupMenu() 5.0 void setComponentPopupMenu(JPopupMenu popup) 5.0

Dnissent ou rcuprent le menu contextuel pour ce composant.



void setInheritsPopupMenu(boolean b) 5.0 boolean getInheritsPopupMenu() 5.0

Dnissent ou rcuprent la proprit inheritsPopupMenu. Si la proprit est dnie et que le menu contextuel de ce composant soit null, elle utilise le menu contextuel de son parent.

Caractres mnmoniques et raccourcis clavier


Les utilisateurs expriments slectionnent souvent une option de menu par le biais dun caractre mnmonique. Vous pouvez dnir un caractre mnmonique en le spciant dans le constructeur de loption de menu :
JMenuItem aboutItem = new JMenuItem("About", A);

Il apparat automatiquement sous forme dun soulignement de la lettre concerne dans le nom de loption (voir Figure 9.21). Dans lexemple prcdent, il sagit de la lettre A de loption About. Quand le menu est droul, il suft lutilisateur dappuyer sur la touche A pour slectionner loption (lorsque la lettre utilise ne fait pas partie du libell, elle napparat pas dans le menu, mais son activation permet nanmoins de slectionner loption. On peut bien entendu douter de lutilit de caractres mnmoniques invisibles). Parfois, vous ne souhaiterez pas souligner la premire lettre de llment de menu correspondant au caractre mnmonique. Si vous avez, par exemple, un "E" mnmonique pour le menu "Enregistrer sous", vous pourriez souligner le deuxime "E". Depuis Java SE 1.4, vous pouvez spcier le caractre qui sera soulign en appelant la mthode setDisplayedMnemonicIndex. Si vous avez un objet Action, vous pouvez ajouter le caractre mnmonique comme valeur de la touche Action.MNEMONIC_KEY, de la faon suivante :
cutAction.putValue(Action.MNEMONIC_KEY, new Integer(A));

Figure 9.21
Caractres mnmoniques.

Chapitre 9

Swing et les composants dinterface utilisateur

415

Vous ne pouvez spcier de caractre mnmonique que dans le constructeur dune option de menu, et non dans le constructeur dun menu. Pour associer un caractre mnmonique un menu, vous devez appeler la mthode setMnemonic :
JMenu helpMenu = new JMenu("Help"); helpMenu.setMnemonic(H);

Pour slectionner le nom dun menu principal dans la barre de menus, il faut maintenir enfonce la touche Alt puis appuyer sur la touche mnmonique. Par exemple, si vous appuyez sur Alt+H, le menu Help (Aide) sera slectionn. Les touches mnmoniques permettent de slectionner une option ou un sous-menu partir dun menu dj ouvert. Les combinaisons de touches ou acclrateurs sont des raccourcis clavier qui permettent de slectionner des options sans avoir ouvrir le menu. Par exemple, de nombreux programmes associent les combinaisons Ctrl+O et Ctrl+S aux options Ouvrir et Enregistrer du menu Fichier. Pour lier une combinaison de touches une option, utilisez la mthode setAccelerator, qui reoit un objet de type Keystroke. Lappel suivant associe la combinaison Ctrl+O loption openItem (Ouvrir) :
openItem.setAccelerator(KeyStroke.getKeyStroke("ctrl O"));

Si lutilisateur excute cette combinaison de touches, loption de menu correspondante est slectionne et un vnement daction est dclench, comme dans le cas dune slection manuelle. Vous ne pouvez associer de raccourcis clavier quaux options de menu, et non aux menus. Les raccourcis clavier nouvrent pas le menu. Ils dclenchent directement lvnement daction associ loption du menu. Dun point de vue conceptuel, lajout dun raccourci clavier une option de menu est semblable la technique dajout dun raccourci un composant Swing (nous avons tudi cette technique au Chapitre 8). Cependant, lorsque le raccourci clavier est ajout une option de menu, la combinaison de touches est automatiquement afche en regard de loption (voir Figure 9.22).
INFO
Sous Windows, la combinaison Alt+F4 ferme une fentre. Mais cet acclrateur na pas t programm sous Java. Il sagit dun raccourci dni par le systme dexploitation et qui dclenche toujours lvnement WindowClosing de la fentre active, indpendamment du fait quil existe ou non une option de menu Fermer.

Figure 9.22
Raccourcis clavier.

416

Au cur de Java 2 - Notions fondamentales

javax.swing.JMenuItem 1.2

JMenuItem(String label, int mnemonic)

Construit un lment de menu avec un intitul donn et un caractre mnmonique. Paramtres : label mnemonic

Lintitul de loption de menu. Le caractre mnmonique de loption ; il apparat soulign dans lintitul.

void setAccelerator(KeyStroke k)

Dnit la combinaison de touche k comme raccourci de loption de menu. La combinaison safche en face de lintitul.
javax.swing.AbstractButton 1.2

void setMnemonic(int mnemonic)

Dnit le caractre mnmonique du bouton. Il apparat soulign dans lintitul.


void setDisplayedMnemonicIndex(int index) 1.4

Dnit lindice du caractre souligner dans le texte du bouton. Utilisez cette mthode si la premire occurrence du caractre mnmonique ne doit pas tre souligne.

Activation et dsactivation des options de menu


Il arrive quune option de menu spcique ne doive pouvoir tre slectionne que dans certains contextes. Par exemple, lorsquun document est ouvert en lecture seule, les options Enregistrer et Enregistrer sous nont aucune utilit. Bien sr, on pourrait les supprimer du menu laide de la mthode JMenu.remove, mais les utilisateurs seraient dsorients de voir son contenu changer ainsi. Par consquent, il vaut mieux dsactiver les options pouvant donner lieu des commandes inappropries. Une option de menu dsactive apparat grise et ne peut pas tre slectionne (voir Figure 9.23).
Figure 9.23
Options de menu dsactives.

Pour activer ou dsactiver une option de menu, utilisez la mthode setEnabled de la faon suivante :
saveItem.setEnabled(false);

Il existe en fait deux faons de procder. Chaque fois quune situation change, vous pouvez appeler la mthode setEnabled sur les options de menu ou les actions concernes. Par exemple, ds quun document a t dni en lecture seule, vous pouvez localiser les options Enregistrer et Enregistrer sous et les dsactiver. Vous pouvez aussi dsactiver les lments juste avant que les options ne soient afches. Pour cela, vous devez enregistrer un couteur pour lvnement "menu slectionn". Le package javax.swing.event dnit une interface MenuListener comprenant trois mthodes :

Chapitre 9

Swing et les composants dinterface utilisateur

417

void menuSelected(MenuEvent event) void menuDeselected(MenuEvent event) void menuCanceled(MenuEvent event)

La mthode menuSelected est appele avant que le menu ne soit afch. Par consquent, elle peut tre utilise pour activer ou dsactiver une option. Le code suivant montre comment dsactiver les actions Enregistrer et Enregistrer sous lorsque le mode Lecture seule est slectionn :
public void menuSelected(MenuEvent event) { saveAction.setEnabled(!readonlyItem.isSelected()); saveAsAction.setEnabled(!readonlyItem.isSelected()); }

ATTENTION
Dsactiver des lments de menu juste avant lafchage du menu est une ide brillante, mais cela ne fonctionne pas pour ceux qui disposent aussi de touches acclrateur. Le menu ntant jamais ouvert la pression de la touche acclrateur, laction nest donc jamais dsactive ; elle est toujours dclenche par la touche acclrateur.

javax.swing.JMenuItem 1.2

void setEnabled(boolean b)

Active ou dsactive une option de menu.


javax.swing.event.MenuListener 1.2

void menuSelected(MenuEvent e)

Appele lorsquun menu a t slectionn, avant quil ne soit ouvert.


void menuDeselected(MenuEvent e)

Appele lorsquun menu a t dslectionn, aprs quil a t ferm.


void menuCanceled(MenuEvent e)

Appele lorsque le processus de slection du menu est abandonn, par exemple en cliquant en dehors du menu. Le Listing 9.8 est un programme qui gnre un ensemble de menus. Il prsente toutes les fonctionnalits tudies dans cette section : menus imbriqus, options de menu dsactives, options avec cases cocher et boutons radio, menus contextuels, caractres mnmoniques et raccourcis clavier.
Listing 9.8 : MenuTest.java
import java.awt.EventQueue; import java.awt.event.*; import javax.swing.*; /** * @version 1.23 2007-05-30 * @author Cay Horstmann */ public class MenuTest { public static void main(String[] args) { EventQueue.invokeLater(new Runnable()

418

Au cur de Java 2 - Notions fondamentales

{ public void run() { MenuFrame frame = new MenuFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); } } /** * Un cadre avec une barre de menus. */ class MenuFrame extends JFrame { public MenuFrame() { setTitle("MenuTest"); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); JMenu fileMenu = new JMenu("File"); fileMenu.add(new TestAction("New")); // dmonstration des raccourcis clavier (acclrateurs) JMenuItem openItem = fileMenu.add(new TestAction("Open")); openItem.setAccelerator(KeyStroke.getKeyStroke("ctrl 0")); fileMenu.addSeparator(); saveAction = new TestAction("Save"); JMenuItem saveItem = fileMenu.add(saveAction); saveItem.setAccelerator(KeyStroke.getKeyStroke("ctrl S")); saveAsAction = new TestAction("Save As"); fileMenu.add(saveAsAction); fileMenu.addSeparator(); fileMenu.add(new AbstractAction("Exit") { public void actionPerformed(ActionEvent event) { System.exit(0); } }); // dmonstration de menus avec cases cocher et boutons radio readonlyItem = new JCheckBoxMenuItem("Read-only"); readonlyItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { boolean saveOk =!readonlyItem.isSelected(); saveAction.setEnabled(saveOk); saveAsAction.setEnabled(saveOk); } });

Chapitre 9

Swing et les composants dinterface utilisateur

419

ButtonGroup group = new ButtonGroup(); JRadioButtonMenuItem insertItem = new JRadioButtonMenuItem("Insert"); insertItem.setSelected(true); JRadioButtonMenuItem overtypeItem = new JRadioButtonMenuItem("Overtype"); group.add(insertItem); group.add(overtypeItem); // dmonstration des icnes Action cutAction = new TestAction("Cut"); cutAction.putValue(Action.SMALL_ICON, new ImageIcon("cut.gif")); Action copyAction = new TestAction("Copy"); copyAction.putValue(Action.SMALL_ICON, new ImageIcon("copy.gif")); Action pasteAction = new TestAction("Paste"); pasteAction.putValue(Action.SMALL_ICON, new ImageIcon("paste.gif")); JMenu editMenu = new JMenu("Edit"); editMenu.add(cutAction); editMenu.add(copyAction); editMenu.add(pasteAction); // dmonstration de menus imbriqus JMenu optionMenu = new JMenu("Options"); optionMenu.add(readonlyItem); optionMenu.addSeparator(); optionMenu.add(insertItem); optionMenu.add(overtypeItem); editMenu.addSeparator(); editMenu.add(optionMenu); // dmonstration de caractres mnmoniques JMenu helpMenu = new JMenu("Help"); helpMenu.setMnemonic(H); JMenuItem indexItem = new JMenuItem("Index"); indexItem.setMnemonic(I); helpMenu.add(indexItem); // ajout possible du caractre mnmonique une action Action aboutAction = new TestAction("About"); aboutAction.putValue(Action.MNEMONIC_KEY, new Integer(A)); helpMenu.add(aboutAction); // ajouter tous les menus principaux la barre de menus JMenuBar menuBar = new JMenuBar(); setJMenuBar(menuBar); menuBar.add(fileMenu); menuBar.add(editMenu); menuBar.add(helpMenu);

420

Au cur de Java 2 - Notions fondamentales

// dmonstration de menu contextuel popup = new JPopupMenu(); popup.add(cutAction); popup.add(copyAction); popup.add(pasteAction); JPanel panel = new JPanel(); panel.setComponentPopupMenu(popup); add(panel); // la ligne suivante contourne le bogue 4966109 panel.addMouseListener(new MouseAdapter() { }); } public static final int DEFAULT_WIDTH = 300; public static final int DEFAULT_HEIGHT = 200; private private private private } /** * Un exemple daction qui affiche le nom de laction dans System.out */ class TestAction extends AbstractAction { public TestAction(String name) { super(name); } public void actionPerformed(ActionEvent event) { System.out.println(getValue(Action.NAME) +" selected."); } } Action saveAction; Action saveAsAction; JCheckBoxMenuItem readonlyItem; JPopupMenu popup;

Barres doutils
Une barre doutils est une barre contenant des boutons qui permettent daccder rapidement aux commandes le plus frquemment utilises dans un programme (voir Figure 9.24). La particularit des barres doutils est de pouvoir tre dplaces nimporte o. Vous pouvez faire glisser la barre doutils vers lun des quatre bords du cadre (voir Figure 9.25). Lorsque vous relchez le bouton de la souris, la barre doutils est place au nouvel emplacement (voir Figure 9.26).
INFO
Le glissement dune barre doutils fonctionne si elle se trouve lintrieur dun conteneur avec une bordure, ou avec tout autre gestionnaire de mise en forme grant les contraintes North, East, South et West.

Chapitre 9

Swing et les composants dinterface utilisateur

421

Figure 9.24
Une barre doutils.

Figure 9.25
Faire glisser la barre doutils.

Figure 9.26
Faire glisser la barre doutils vers une autre bordure.

La barre doutils peut mme tre compltement dtache du cadre. Elle est alors contenue dans son propre cadre (voir Figure 9.27). Lorsque vous fermez le cadre contenant une barre doutils dtache, celle-ci revient dans le cadre dorigine.
Figure 9.27
Dtacher la barre doutils.

Les barres doutils sont assez simples programmer. Vous ajoutez les composants la barre doutils :
JToolBar bar = new JToolBar(); bar.add(blueButton);

422

Au cur de Java 2 - Notions fondamentales

La classe JToolBar possde aussi une mthode pour ajouter un objet Action. Remplissez simplement la barre doutils avec les objets Action, de la faon suivante :
bar.add(blueAction);

La petite icne de laction safche dans la barre doutils. Vous pouvez sparer des groupes de boutons laide dun sparateur :
bar.addSeparator();

Par exemple, la barre doutils de la Figure 9.24 a un sparateur entre le troisime et le quatrime bouton. Vous ajoutez ensuite la barre doutils au cadre.
add(bar, BorderLayout.NORTH);

Vous pouvez aussi spcier un titre pour la barre doutils, il apparatra lorsque la barre doutils sera dtache :
bar = new JToolBar(titleString);

Par dfaut, les barres doutils sont positionnes horizontalement. Pour quune barre doutils soit verticale, utilisez :
bar = new JToolBar(SwingConstants.VERTICAL)

ou
bar = new JToolBar(titleString, SwingConstants.VERTICAL)

Les boutons sont les composants les plus courants des barres doutils. Il ny a toutefois aucune restriction en ce qui concerne les composants pouvant tre ajouts. Vous pouvez, par exemple, ajouter une zone de liste droulante une barre doutils.

Bulles daide
Un inconvnient des barres doutils est que les utilisateurs sont souvent dsorients par la signication des minuscules icnes qui y gurent. Des concepteurs dinterface utilisateur ont alors conu les bulles daide. Une bulle daide est active lorsque le curseur stationne au-dessus dun bouton. Le texte de la bulle daide apparat lintrieur dun rectangle color. Lorsque lutilisateur dplace la souris, la bulle daide disparat (voir Figure 9.28).
Figure 9.28
Une bulle daide.

Dans Swing, vous pouvez ajouter des bulles daide nimporte quel objet JComponent simplement en appelant la mthode setToolTipText :
exitButton.setToolTipText("Exit");

Chapitre 9

Swing et les composants dinterface utilisateur

423

Vous pouvez aussi, si vous utilisez des objets Action, associer la bulle daide la touche laide de SHORT_DESCRIPTION :
exitAction.putValue(Action.SHORT_DESCRIPTION, "Exit");

Le Listing 9.9 est un programme montrant comment le mme objet Action peut tre ajout un menu et une barre doutils. Notez que les noms dactions apparaissent comme noms des options dans le menu et les courtes descriptions, comme bulles daide dans la barre doutils.
Listing 9.9 : ToolBarTest.java
import java.awt.*; import java.awt.event.*; import javax.swing.*; /** * @version 1.13 2007-06-12 * @author Cay Horstmann */ public class ToolBarTest { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { ToolBarFrame frame = new ToolBarFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); } } /** * Un cadre avec une barre doutils et un menu pour * choisir une nouvelle couleur. */ class ToolBarFrame extends JFrame { public ToolBarFrame() { setTitle("ToolBarTest"); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); // ajouter un panneau pour le changement de couleur panel = new JPanel(); add(panel, BorderLayout.CENTER); // configurer les actions Action blueAction = new ColorAction("Blue", new ImageIcon("blue-ball.gif"), Color.BLUE); Action yellowAction = new ColorAction("Yellow", new ImageIcon("yellow-ball.gif"), Color.YELLOW); Action redAction = new ColorAction("Red", new ImageIcon("red-ball.gif"), Color.RED);

424

Au cur de Java 2 - Notions fondamentales

Action exitAction = new AbstractAction("Exit", new ImageIcon("exit.gif")) { public void actionPerformed(ActionEvent event) { System.exit(0); } }; exitAction.putValue(Action.SHORT_DESCRIPTION, "Exit"); // complter la barre doutils JToolBar bar = new JToolBar(); bar.add(blueAction); bar.add(yellowAction); bar.add(redAction); bar.addSeparator(); bar.add(exitAction); add(bar, BorderLayout.NORTH); // complter le menu JMenu menu = new JMenu("Color"); menu.add(yellowAction); menu.add(blueAction); menu.add(redAction); menu.add(exitAction); JMenuBar menuBar = new JMenuBar(); menuBar.add(menu); setJMenuBar(menuBar); } public static final int DEFAULT_WIDTH = 300; public static final int DEFAULT_HEIGHT = 200; private JPanel panel; /** * Laction couleur dfinit larrire-plan du cadre * sur une couleur donne. */ class ColorAction extends AbstractAction { public ColorAction(String name, Icon icon, Color c) { putValue(Action.NAME, name); putValue(Action.SMALL_ICON, icon); putValue(Action.SHORT_DESCRIPTION, name + " background"); putValue("Color", c); } public void actionPerformed(ActionEvent event) { Color c = (Color) getValue("Color"); panel.setBackground(c); } } }

Chapitre 9

Swing et les composants dinterface utilisateur

425

javax.swing.JToolbar 1.2 JToolBar() JToolBar(String titleString) JToolBar(int orientation) JToolBar(String titleString, int orientation)

Construisent une barre doutils avec le titre et lorientation donns. orientation est lune des valeurs SwingConstants.HORIZONTAL (par dfaut) et SwingConstants.VERTICAL.

JButton add(Action a)

Construit un nouveau bouton dans la barre doutils, avec le nom, licne, une courte description et le rappel daction (callback) de laction donne, et ajoute le bouton la n de la barre doutils.

void addSeparator()

Ajoute un sparateur la n de la barre doutils.


javax.swing.JComponent 1.2 void setToolTipText(String text)

Dnit le texte qui doit tre afch comme bulle daide lorsque la souris stationne au-dessus du composant.

Mise en forme sophistique


Nous avons russi jusqu prsent disposer les composants dinterface utilisateur de nos exemples dapplication au moyen des gestionnaires BorderLayout, GridLayout et FlowLayout. Pour des tches plus complexes, cette faon de procder sera insufsante. Dans cette section, nous allons examiner des gestionnaires de mise en forme avancs de la bibliothque Java standard. Les programmeurs Windows peuvent se demander pourquoi Java fait autant cas de ses gestionnaires de mise en forme. Aprs tout, dans Windows, la gestion de mise en forme nest pas trs complique. Tout dabord, vous utilisez un diteur de botes de dialogue pour organiser vos composants au moyen de la fonction glisser-dposer, puis vous employez des outils ddition pour les aligner, les espacer, les centrer, etc. Si vous travaillez sur un projet de grande ampleur, vous naurez probablement pas vous en proccuper, la mise en forme des composants sera prise en charge par un concepteur dinterface. Le problme de cette approche est que la mise en forme qui en rsulte doit tre actualise manuellement si la taille des lments dinterface change. Pourquoi leur taille changerait-elle ? Il y a deux raisons cela. Tout dabord, un utilisateur peut choisir une police plus grande pour les libells de bouton et autres textes de bote de dialogue. Si vous optez pour cet agrandissement en le testant vous-mme dans Windows, vous vous apercevrez que de nombreuses applications le grent mdiocrement. Les boutons ne sagrandissent pas et les polices plus grandes sont connes dans le mme espace. Le mme problme peut se produire lors de la traduction des chanes dune application dans une autre langue. Si un bouton a t conu de telle faon que lespace corresponde strictement son libell dorigine et que le mot traduit soit plus long (comme Save As qui devient Enregistrer sous en franais), la version trangre apparatra tronque. Pourquoi les boutons dans Windows ne sadaptent-ils pas aux libells ? Le concepteur de linterface utilisateur na donn aucune instruction sur la direction dans laquelle ils devraient sagrandir. Aprs leur placement et leur organisation, lditeur de botes de dialogue se souvient simplement de la

426

Au cur de Java 2 - Notions fondamentales

position en pixels et de la taille de chaque composant. Il ne se rappelle pas pourquoi les lments ont t ainsi organiss. Les gestionnaires de mise en forme proposent une bien meilleure approche de lorganisation dune interface. La mise en forme dtient alors des instructions sur les relations entre les diffrents composants, ce qui tait particulirement important avec la bibliothque AWT dorigine, qui utilisait des lments dinterface natifs. La taille dun bouton ou dune zone de liste dans Motif, Windows et Macintosh pouvait varier considrablement, et une application ou un applet ignorait a priori sur quelle plate-forme il afcherait son interface utilisateur. Dans une certaine limite, ce degr de variabilit a t limin avec Swing. Si votre application implmente un certain style dinterface dynamique, comme Metal, elle aura la mme apparence sur toutes les plates-formes. Toutefois, si vous laissez les utilisateurs de votre application choisir leur look and feel favori, vous devrez nouveau faire appel la souplesse des gestionnaires pour disposer les composants. Depuis Java 1.0, AWT inclut le gestionnaire GridBagLayout qui dispose les composants en lignes et en colonnes, mais les dimensions de ces lignes et colonnes sont variables et les composants peuvent en occuper plusieurs. Ce gestionnaire est trs souple, mais aussi trs complexe. Lors dune tentative infructueuse de mise au point dun gestionnaire qui affranchirait les programmeurs de la tyrannie du GridBagLayout, les concepteurs de Swing ont produit le gestionnaire BoxLayout. Selon la documentation du JDK pour la classe BoxLayout : "Limbrication de plusieurs panneaux avec diffrentes combinaisons horizontales et verticales [sic] donne un effet analogue GridBagLayout, sans en avoir la complexit." Comme les BoxLayout sont placs indpendamment les uns des autres, vous ne pouvez pas les utiliser pour organiser des composants voisins la fois horizontalement et verticalement. Java SE 1.4 a connu une autre tentative pour concevoir le remplaant du GridBagLayout, la mise en forme spring. Vous utilisez des ressorts (springs) imaginaires pour relier les composants dans un conteneur. Lorsque le conteneur est redimensionn, les ressorts stirent ou rtrcissent, ce qui ajuste les positions des composants. Cette procdure tait, comme elle le parat, laborieuse et droutante. Le springLayout est rapidement retomb dans lobscurit. En 2005, lquipe NetBeans a invent la technologie Matisse, qui associe un outil et un gestionnaire de mise en page. Un concepteur dinterface utilisateur se sert de loutil pour dposer les composants dans un conteneur et indiquer ceux qui doivent saligner. Loutil traduit lintention des concepteurs en instructions pour le gestionnaire de mise en page du groupe. Cela est bien plus pratique que dcrire le code de gestion de la mise en page la main. Ce gestionnaire fait maintenant partie de Java SE 6. Et mme si vous nutilisez pas NetBeans comme IDE, envisagez dutiliser son outil de constructeur de GUI. Vous pouvez concevoir votre GUI dans NetBeans et coller le rsultat du code dans lIDE de votre choix. Nous verrons, au cours de prochaines sections, le gestionnaire GridBagLayout car il est frquemment utilis et demeure le mcanisme le plus simple pour produire un code de mise en page des anciennes versions de Java. Nous vous proposons une stratgie qui rend les gestionnaires GridBagLayout relativement indolores dans les situations communes. Nous verrons ensuite loutil Matisse et le gestionnaire de mise en page du groupe. Vous dcouvrirez le fonctionnement du gestionnaire de mise en page du groupe pour savoir si Matisse a enregistr les instructions correctes lorsque vous avez positionn visuellement vos composants.

Chapitre 9

Swing et les composants dinterface utilisateur

427

Nous terminerons ltude sur les gestionnaires de mise en page en vous indiquant comment contourner la gestion de la mise en page et placer manuellement les composants, mais aussi comment crire votre propre gestionnaire de mise en page.

Gestionnaire GridBagLayout
Vous pouvez considrer le gestionnaire GridBagLayout comme un GridLayout sans ses limitations. Dans un GridBagLayout, les lignes et les colonnes peuvent avoir des tailles variables. Vous pouvez joindre des cellules adjacentes an de prvoir la place pour des composants plus grands. De nombreux programmes de traitement de texte ainsi que des diteurs HTML possdent la mme fonctionnalit lorsquils grent des tableaux. Vous commencez avec une grille et fusionnez des cellules adjacentes si besoin est. Il nest pas ncessaire que les composants remplissent la zone entire dune cellule, et vous pouvez spcier leur alignement lintrieur des cellules. Examinez la bote de dialogue de slection de polices de la Figure 9.29. Elle se compose des lments suivants :
m m m m

deux zones de liste droulantes pour spcier la police et sa taille ; des libells pour ces deux listes combines ; deux cases cocher pour slectionner les attributs Gras et Italique ; un champ de texte pour lexemple de chane.

Figure 9.29
La bote de dialogue de slection de polices.

Maintenant, divisez le conteneur en une grille de cellules, comme le montre la Figure 9.30 (les lignes et les colonnes nont pas besoin dtre de taille gale). Comme vous pouvez le constater, chaque case cocher stend sur 2 colonnes et la zone de texte sur 4 lignes.
Figure 9.30
Une grille de bote de dialogue utilise pour la conception.

428

Au cur de Java 2 - Notions fondamentales

Pour fournir la mise en forme au gestionnaire GridBagLayout, vous devez suivre la procdure ciaprs : 1. Crez un objet de type GridBagLayout. Ne lui indiquez pas le nombre de lignes et de colonnes que la grille sous-jacente doit possder. Le gestionnaire essayera de le deviner laide des informations que vous fournirez plus tard. 2. Dnissez cet objet comme tant le gestionnaire de mise en forme pour le composant. 3. Pour chaque composant, crez un objet de type GridBagConstraints. Dnissez les valeurs de champ de lobjet GridBagConstraints pour spcier de quelle faon les composants sont disposs lintrieur du GridBag. 4. Ajoutez enn le composant avec les contraintes au moyen dun appel :
add(component, constraints);

Voici un exemple du code requis. Nous tudierons plus en dtail les contraintes par la suite. Ne vous inquitez pas si vous ignorez leffet de certaines contraintes.
GridBagLayout layout = new GridBagLayout(); panel.setLayout(layout); GridBagConstraints constraints = new GridBagConstraints(); constraints.weightx = 100; constraints.weighty = 100; constraints.gridx = 0; constraints.gridy = 2; constraints.gridwidth = 2; constraints.gridheight = 1; panel.add(component, constraints);

Lastuce est de savoir comment dnir ltat de lobjet GridBagConstraints. Nous passerons en revue les contraintes les plus importantes pour lemploi de cet objet dans les sections qui suivent.

Les paramtres gridx, gridy, gridwidth et gridheight


Ces contraintes dnissent lemplacement du composant dans la grille. Les valeurs gridx et gridy spcient les positions de colonne et de ligne du coin suprieur gauche du composant ajouter. Les valeurs gridwidth et gridheight dterminent le nombre de colonnes et de lignes que le composant occupe. Les coordonnes de la grille commencent 0. En particulier, gridx = 0 et gridy = 0 indiquent le coin suprieur gauche. Par exemple, la zone de texte dans notre exemple a les valeurs gridx = 2, gridy = 0, car elle dmarre la colonne 2 (cest--dire la troisime colonne) de la ligne 0. Elle a les valeurs gridwidth = 1 et gridheight = 4, car elle stend sur une colonne et quatre lignes.

Champs de poids
Vous devez toujours dnir les champs de poids (weightx et weighty) pour chaque zone dans un GridBagLayout. Si vous dnissez le poids 0, la zone ne sagrandit ou ne diminue jamais au-del de sa taille initiale dans cette direction. Dans la mise en forme illustre la Figure 9.29, nous avons dni le champ weightx des tiquettes 0. Cela leur permet de conserver une largeur constante lorsque vous redimensionnez la fentre. En revanche, si vous dnissez les poids pour toutes les

Chapitre 9

Swing et les composants dinterface utilisateur

429

zones 0, le conteneur se ramassera au centre de sa zone alloue au lieu de stendre pour loccuper pleinement. Du point de vue conceptuel, le problme avec les paramtres de poids est que les poids sont des proprits des lignes et des colonnes, et non des cellules individuelles. Mais vous devez les spcier en termes de cellules, puisque GridBagLayout nexpose pas les lignes et les colonnes. Les poids de lignes et de colonnes sont calculs comme les maxima des poids de cellule dans chaque ligne ou colonne. Par consquent, si vous voulez quune ligne ou quune colonne reste une taille xe, vous devez dnir les poids de tous ses composants zro. Notez que les poids ne donnent pas rellement les tailles relatives des colonnes. Ils indiquent quelle proportion de lespace non appliqu devrait tre alloue chaque zone, si le conteneur dpasse sa taille prfre. Ce nest pas particulirement intuitif. Nous vous recommandons de mettre tous les poids 100. Excutez ensuite le programme et observez lapparence de la mise en forme. Redimensionnez la bote de dialogue pour vrier la faon dont les lignes et les colonnes sadaptent. Si vous jugez quune ligne ou colonne particulire ne devrait pas sagrandir, dnissez les poids de tous les composants qui sy trouvent zro. Vous pouvez remanier les autres valeurs de poids, mais cette opration est sans intrt la plupart du temps.

Les paramtres ll et anchor


Si vous ne voulez pas quun composant stire et remplisse toute la zone, vous devez dnir la contrainte fill. Vous disposez de quatre options pour ce paramtre : les valeurs valides sont GridBagConstraints.NONE (aucun), GridBagConstraints.HORIZONTAL, GridBagConstraints.VERTICAL et GridBagConstraints.BOTH (les deux). Si le composant ne remplit pas la zone entire, vous pouvez spcier sa situation dans la zone en dnissant le champ anchor. Les valeurs valides sont : GridBagConstraints.CENTER (valeur par dfaut), GridBagConstraints.NORTH, GridBagConstraints.NORTHEAST, GridBagConstraints.EAST, etc.

Remplissage
Vous pouvez entourer un composant dun espace vierge supplmentaire en dnissant le champ insets de GridBagConstraints. Dnissez les valeurs left, top, right et bottom de lobjet Insets avec les valeurs despace que vous voulez avoir autour du composant. Cest le remplissage externe. Les valeurs ipadx et ipady dnissent le remplissage interne. Ces valeurs sont ajoutes aux largeur et hauteur minimales du composant. Cela garantit que le composant ne diminuera pas jusqu sa taille minimale.

Autre mthode de dnition des paramtres gridx, gridy, gridwidth et gridheight


La documentation AWT recommande de remplacer la dnition des paramtres gridx et gridy en valeurs absolues par la constante GridBagConstraints.RELATIVE. Ajoutez ensuite les composants GridBagLayout en suivant une squence standard, de la gauche vers la droite pour la premire ligne, puis en passant la ligne suivante, etc. Spciez toujours le nombre de colonnes et de lignes occupes en fournissant les champs gridwidth et gridheight. Sauf si le composant stend jusqu la dernire ligne ou colonne, vous ntes

430

Au cur de Java 2 - Notions fondamentales

pas cens spcier le nombre rel, mais la constante GridBagConstraints.REMAINDER. Cela signale au gestionnaire de mise en forme que le composant est le dernier sur sa ligne. Cette stratgie ne semble pas fonctionner. Mais il parat un peu insens de vouloir dissimuler les informations relles de placement au gestionnaire et esprer quil les redcouvre. Tout cela parat plutt complexe et fastidieux. Or, dans la pratique, la stratgie suivante simplie relativement les GridBagLayout : 1. Esquissez la mise en forme du composant sur une feuille. 2. Dterminez une grille an que les petits composants soient chacun contenus dans une cellule et que les grands puissent en occuper plusieurs. 3. Libellez les lignes et les colonnes de votre grille avec des chiffres : 0, 1, 2, 3 Vous pouvez maintenant relever les valeurs pour gridx, gridy, gridwidth et gridheight. 4. Pour chaque composant, demandez-vous sil doit remplir ses cellules horizontalement ou verticalement, ou comment il doit tre align. Cela vous donne les paramtres fill et anchor. 5. Dnissez tous les poids 100. Si vous souhaitez cependant quune ligne ou colonne particulire reste toujours sa taille par dfaut, dnissez les valeurs de weightx ou weighty 0 pour tous les composants qui appartiennent cette ligne ou colonne. 6. Ecrivez le code. Vriez attentivement vos paramtres pour GridBagConstraints. Une contrainte errone peut gcher toute votre mise en forme. 7. Compilez, excutez et apprciez le tout. Certains constructeurs de GUI possdent mme des outils pour spcier visuellement des contraintes. Consultez la Figure 9.31, la bote de dialogue de conguration dans NetBeans.
Figure 9.31
Spcier les contraintes de GridBagLayout dans NetBeans.

Chapitre 9

Swing et les composants dinterface utilisateur

431

Une classe daide pour matriser GridBagConstraints


Laspect le plus assommant du GridBagLayout est dcrire le code qui dnit les contraintes. La plupart des programmeurs crivent des fonctions daide ou une petite classe daide dans ce but. Nous prsentons cette classe aprs le code complet pour lexemple de la bote de dialogue des polices. Cette classe prsente les aspects suivants :
m m

Son nom est court, GBC au lieu de GridBagConstraints. Elle tend GridBagConstraints, vous pouvez donc utiliser des noms plus courts comme GBC.EAST pour les constantes. Utilisez un objet GBC lorsque vous ajoutez un composant, comme
add(component, new GBC(1, 2));

Il existe deux constructeurs pour dnir les paramtres les plus communs : gridx et gridy ou gridx, gridy, gridwidth et gridheight :
add(component, new GBC(1, 2, 1, 4));

Il existe des moyens de dnition commodes pour les champs qui viennent en paires x/y :
add(component, new GBC(1, 2).setWeight(100, 100));

Les mthodes de dnition renvoient this, vous pouvez donc les enchaner :
add(component, new GBC(1, 2).setAnchor(GBC.EAST).setWeight(100, 100));

Les mthodes setInsets construisent lobjet Insets pour vous. Pour obtenir des insets dun pixel, appelez simplement :
add(component, new GBC(1, 2).setAnchor(GBC.EAST).setInsets(1));

Le Listing 9.10 donne le code complet pour la bote de dialogue de polices. Voici le code qui ajoute les composants au GridBag :
add(faceLabel, new GBC(0, 0).setAnchor(GBC.EAST)); add(face, new GBC(1, 0).setFill(GBC.HORIZONTAL). setWeight(100, 0).setInsets(1)); add(sizeLabel, new GBC(0, 1).setAnchor(GBC.EAST)); add(size, new GBC(1, 1).setFill(GBC.HORIZONTAL). setWeight(100, 0).setInsets(1)); add(bold, new GBC(0, 2, 2, 1).setAnchor(GBC.CENTER).setWeight(100, 100)); add(italic, new GBC(0, 3, 2, 1).setAnchor(GBC.CENTER). setWeight(100, 100)); add(sample, new GBC(2, 0, 1, 4).setFill(GBC.BOTH).setWeight(100, 100));

Lorsque vous aurez compris GridBagConstraints, vous verrez que ce type de code est assez simple lire et dboguer.
INFO
Le didacticiel Sun, ladresse http://java.sun.com/docs/books/tutorial/uiswing/layout/gridbag.html, vous suggre de rutiliser le mme objet GridBagConstraints pour tous les composants. Nous considrons que le code de rsultat est difcile lire et sujet erreur. Etudiez par exemple la dmo de ladresse http://java.sun.com/docs/ books/ tutorial/uiswing/events/containerlistener.html. Lobjectif tait-il vraiment que les boutons stirent lhorizontale ou le programmeur a-t-il juste oubli de dsactiver la contrainte fill?

432

Au cur de Java 2 - Notions fondamentales

Listing 9.10 : GridBagLayoutTest.java


import java.awt.*; import java.awt.event.*; import javax.swing.*; /** * @version 1.33 2007-06-12 * @author Cay Horstmann */ public class GridBagLayoutTest { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { FontFrame frame = new FontFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); } } /** * Un cadre qui utilise GridBagLayout pour organiser * les composants de slection de police. */ class FontFrame extends JFrame { public FontFrame() { setTitle("GridBagLayoutTest"); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); GridBagLayout layout = new GridBagLayout(); setLayout(layout); ActionListener listener = new FontAction(); // construire les composants JLabel faceLabel = new JLabel("Face: "); face = new JComboBox(new String[] { "Serif", "SansSerif", "Monospaced", "Dialog", "DialogInput" }); face.addActionListener(listener); JLabel sizeLabel = new JLabel("Size: "); size = new JComboBox(new String[] { "8", "10", "12", "15", "18", "24", "36", "48" }); size.addActionListener(listener);

Chapitre 9

Swing et les composants dinterface utilisateur

433

bold = new JCheckBox("Bold"); bold.addActionListener(listener); italic = new JCheckBox("Italic"); italic.addActionListener(listener); sample = new JTextArea(); sample.setText("The quick brown fox jumps over the lazy dog"); sample.setEditable(false); sample.setLineWrap(true); sample.setBorder(BorderFactory.createEtchedBorder()); // ajouter les composants la grille, laide de la classe GBC add(faceLabel, new GBC(0, 0).setAnchor(GBC.EAST)); add(face, new GBC(1, 0).setFill(GBC.HORIZONTAL). setWeight(100, 0).setInsets(1)); add(sizeLabel, new GBC(0, 1).setAnchor(GBC.EAST)); add(size, new GBC(1, 1).setFill(GBC.HORIZONTAL). setWeight(100, 0).setInsets(1)); add(bold, new GBC(0, 2, 2, 1).setAnchor(GBC.CENTER). setWeight(100, 100)); add(italic, new GBC(0, 3, 2, 1).setAnchor(GBC.CENTER). setWeight(100, 100)); add(sample, new GBC(2, 0, 1, 4).setFill(GBC.BOTH). setWeight(100, 100)); } public static final int DEFAULT_WIDTH = 300; public static final int DEFAULT_HEIGHT = 200; private private private private private JComboBox JComboBox JCheckBox JCheckBox JTextArea face; size; bold; italic; sample;

/** * Un couteur daction qui change la police de * lexemple de texte. */ private class FontAction implements ActionListener { public void actionPerformed(ActionEvent event) { String fontFace = (String) face.getSelectedItem(); int fontStyle = (bold.isSelected()? Font.BOLD: 0) +(italic.isSelected()? Font.ITALIC: 0); int fontSize = Integer.parseInt( (String)size.getSelectedItem()); Font font = new Font(fontFace, fontStyle, fontSize); sample.setFont(font); sample.repaint(); } } }

434

Au cur de Java 2 - Notions fondamentales

Le Listing 9.11 prsente le code de la classe daide GBC.


Listing 9.11 : GBC.java
import java.awt.*; /** * Cette classe simplifie lutilisation de la classe GridBagConstraints. * @version 1.01 2004-05-06 * @author Cay Horstmann */ public class GBC extends GridBagConstraints { /** * Construit un GBC avec une position gridx et gridy donne * et toutes les autres valeurs de GridBagConstraints dfinies * sur le paramtre par dfaut. * @param gridx La position gridx * @param gridy La position gridy */ public GBC(int gridx, int gridy) { this.gridx = gridx; this.gridy = gridy; } /** * Construit un GBC avec gridx, gridy, gridwidth, gridheight * et toutes les autres valeurs de GridBagConstraints dfinies * sur la valeur par dfaut. * @param gridx La position gridx * @param gridy La position gridy * @param gridwidth Ltirement de cellule dans la direction x * @param gridheight Ltirement de cellule dans la direction y */ public GBC(int gridx, int gridy, int gridwidth, int gridheight) { this.gridx = gridx; this.gridy = gridy; this.gridwidth = gridwidth; this.gridheight = gridheight; } /** * Dfinit lancrage. * @param anchor La valeur de lancrage * @return this Objet pour une future modification */ public GBC setAnchor(int anchor) { this.anchor = anchor; return this; } /** * Dfinit la direction de fill. * @param fill La direction de fill * @return this Objet pour une future modification */

Chapitre 9

Swing et les composants dinterface utilisateur

435

public GBC setFill(int fill) { this.fill = fill; return this; } /** * Dfinit les poids de cellule. * @param weightx Le poids de cellule dans la direction x * @param weighty Le poids de cellule dans la direction y * @return this Objet pour une future modification */ public GBC setWeight(double weightx, double weighty) { this.weightx = weightx; this.weighty = weighty; return this; } /** * Dfinit les insets de cette cellule. * @param distance Lespacement utiliser dans toutes les directions * @return this Objet pour une future modification */ public GBC setInsets(int distance) { this.insets = new Insets(distance, distance, distance, distance); return this; } /** * Dfinit les insets de cette cellule. * @param top Lespacement utiliser en haut * @param left Lespacement utiliser gauche * @param bottom Lespacement utiliser en bas * @param right Lespacement utiliser droite * @return this Objet pour une future modification */ public GBC setInsets(int top, int left, int bottom, int right) { this.insets = new Insets(top, left, bottom, right); return this; } /** * Dfinit le remplissage interne * @param ipadx Le remplissage interne dans la direction x * @param ipady Le remplissage interne dans la direction y * @return this Objet pour une future modification */ public GBC setIpad(int ipadx, int ipady) { this.ipadx = ipadx; this.ipady = ipady; return this; } }

436

Au cur de Java 2 - Notions fondamentales

java.awt.GridBagConstraints 1.0

int gridx, gridy

Indique la colonne et la ligne de dpart de la cellule. Le paramtre par dfaut vaut 0.


int gridwidth, gridheight

Indique ltendue en colonnes et en lignes de la cellule. Le paramtre par dfaut vaut 1.


double weightx, weighty

Indique la capacit de la cellule sagrandir. Le paramtre par dfaut vaut 0.


int anchor

Indique lalignement du composant lintrieur de la cellule. Lune des positions absolues suivantes : CENTER, NORTH, NORTHEAST, EAST, SOUTHEAST, SOUTH, SOUTHWEST, WEST ou NORTHWEST ou leurs contreparties indpendantes de lorientation : FIRST_LINE_START, LINE_START, FIRST_ LINE_END, PAGE_START, CENTER, PAGE_END, LAST_LINE_START, LINE_END et LAST_LINE_END. Utilisez ces dernires si votre application risque dtre localise avec un texte de droite gauche ou de haut en bas. Le paramtre par dfaut vaut CENTER.

int fill

Indique le comportement de remplissage du composant lintrieur de la cellule. Lune des constantes suivantes : NONE, BOTH, HORIZONTAL ou VERTICAL. Le paramtre par dfaut vaut NONE.

int ipadx, ipady

Indique le remplissage interne du pourtour du composant. Le paramtre par dfaut vaut 0.


Insets insets

Indique le remplissage externe le long des limites de la cellule. Le paramtre par dfaut vaut aucun remplissage.

GridBagConstraints(int gridx, int gridy, int gridwidth, int gridheight, double weightx, double weighty, int anchor, int fill, Insets insets, int ipadx, int ipady) 1.2

Construit un composant GridBagConstraints avec tous ses champs passs en paramtres. Sun recommande de ne faire utiliser ce constructeur que par des gnrateurs de code automatique, car il diminue la lisibilit du code.

GroupLayout
Avant de traiter de lAPI de la classe GroupLayout, tudions rapidement le constructeur dinterface graphique Matisse dans NetBeans. Nous ne proposons pas ici un didacticiel complet sur le produit, reportez-vous plutt ladresse http://www.netbeans.org/kb/articles/matisse.html pour de plus amples informations. Voici le ux permettant de disposer le haut de la bote de dialogue de la Figure 9.13. Dmarrez un nouveau projet et ajoutez un nouveau formulaire JFrame. Faites glisser une tiquette jusqu ce que deux repres apparaissent pour le sparer des bords du conteneur :

Chapitre 9

Swing et les composants dinterface utilisateur

437

Placez une autre tiquette sous la premire :

Faites glisser un champ de texte de sorte que sa base saligne sur celle de la premire tiquette. Une fois de plus, notez les repres :

Enn, alignez un champ de mot de passe sur la gauche et le champ de texte au-dessus.

Matisse traduit ces actions en code Java comme suit :


layout.setHorizontalGroup( layout.createParallelGroup(GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addContainerGap() .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addComponent(jLabel1) .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) .addComponent(jTextField1)) .addGroup(layout.createSequentialGroup() .addComponent(jLabel2) .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) .addComponent(jPasswordField1))) .addContainerGap(222, Short.MAX_VALUE))); layout.setVerticalGroup( layout.createParallelGroup(GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addContainerGap() .addGroup(layout.createParallelGroup( GroupLayout.Alignment.BASELINE) .addComponent(jLabel1) .addComponent(jTextField1)) .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup( GroupLayout.Alignment.BASELINE) .addComponent(jLabel2) .addComponent(jPasswordField1)) .addContainerGap(244, Short.MAX_VALUE)));

Cela peut paratre effrayant, mais vous naurez heureusement pas besoin dcrire ce code. Il est toutefois utile de connatre les bases des actions de mise en page pour pouvoir dtecter les erreurs.

438

Au cur de Java 2 - Notions fondamentales

Nous analyserons la structure de base du code. Les notes API la n de la section expliquent chacune des classes et des mthodes en dtail. Les composants sont organiss en objets de type GroupLayout.SequentialGroup ou GroupLayout.ParallelGroup. Ces classes sont des sous-classes de GroupLayout.Group. Les groupes peuvent contenir des composants, des intervalles et des groupes imbriqus. Les diverses mthodes add des classes de groupe renvoient lobjet du groupe, de sorte que les appels de mthode puissent tre chans, comme ceci :
group.addComponent(...).addPreferredGap(...).addComponent(...);

Vous le voyez dans lexemple, la mise en page du groupe spare les calculs verticaux et horizontaux. Pour visualiser les calculs horizontaux, imaginez que les composants sont aplatis, de manire avoir zro hauteur, comme ceci :
Bordure du conteneur jLabel1 Intervalle Intervalle jLabel2 Intervalle jPasswordField1 jTextField1

Il existe deux squences parallles de composants, correspondant au code (lgrement simpli) :


.addContainerGap() .addGroup(layout.createParallelGroup() .addGroup(layout.createSequentialGroup() .addComponent(jLabel1) .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) .addComponent(jTextField1)) .addGroup(layout.createSequentialGroup() .addComponent(jLabel2) .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) .addComponent(jPasswordField1)))

Mais Cela ne peut pas tre juste. Si les tiquettes prsentent des longueurs diffrentes, le champ de texte et le champ de mot de passe ne salignent pas. Nous devons demander Matisse daligner les champs. Slectionnez-les, faites un clic droit, puis choisissez Align -> Left to Column dans le menu. Alignez galement les tiquettes (voir Figure 9.32). Cela modie considrablement le code de mise en page :
.addGroup(layout.createSequentialGroup() .addContainerGap() .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) .addComponent(jLabel1, GroupLayout.Alignment.TRAILING) .addComponent(jLabel2, GroupLayout.Alignment.TRAILING)) .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) .addComponent(jTextField1) .addComponent(jPasswordField1))

Chapitre 9

Swing et les composants dinterface utilisateur

439

Figure 9.32
Alignement des tiquettes et des champs de texte dans Matisse.

Dsormais, les tiquettes et les champs sont placs dans des groupes parallles. Le premier possde un alignement TRAILING (alignement droite lorsque le texte se lit de gauche droite) :
Bordure du conteneur jLabel1 Intervalle Intervalle jTextField1

jLabel2

jPasswordField1

Le fait que Matisse puisse traduire les instructions du concepteur en groupes imbriqus ressemble de la magie, mais comme la dit Arthur C. Clarke, toute technologie sufsamment avance est indissociable de la magie.

440

Au cur de Java 2 - Notions fondamentales

Pour tre exhaustif, tudions le calcul vertical. Vous devriez maintenant vous imaginer que les composants nont aucune largeur. Nous disposons dun groupe squentiel contenant deux groupes parallles, spars par des intervalles :
Bordure du conteneur jLabel1

Intervalle jTextField1

Intervalle jLabel2 jPasswordField1

Le code correspondant est le suivant :


layout.createSequentialGroup() .addContainerGap() .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) .addComponent(jLabel1) .addComponent(jTextField1)) .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) .addComponent(jLabel2) .addComponent(jPasswordField1))

On le voit dans le code, les composants sont aligns par leurs bases (la base correspond la ligne sur laquelle est align le texte du composant).
INFO
Il est impossible daligner correctement la base avec les prcdentes versions de Java. Java SE 6 a nalement ajout une mthode getBaseline la classe Component de manire dterminer la base exacte dun composant contenant du texte.

Vous pouvez forcer un jeu de composants avoir la mme taille. Ainsi, nous pouvons nous assurer que les champs Texte et Mot de passe aient une largeur identique. Sous Matisse, slectionnez les deux, faites un clic droit, puis slectionnez Same Size -> Same Width dans le menu (voir Figure 9.33). Matisse ajoute linstruction suivante au code de mise en page :
layout.linkSize(SwingConstants.HORIZONTAL, new Component[] {jPasswordField1, jTextField1});

Le code du Listing 9.12 indique comment mettre en page la bote de dialogue de slection de police de la section prcdente, laide de GroupLayout au lieu de GridBagLayout. Le code peut ne pas sembler plus simple que celui du Listing 9.10 mais nous navons pas eu lcrire. Matisse a ralis la mise en page, puis a quelque peu nettoy le code.

Chapitre 9

Swing et les composants dinterface utilisateur

441

Figure 9.33
Obliger deux composants avoir la mme largeur.

Listing 9.12 : GroupLayoutTest.java


import java.awt.*; import java.awt.event.*; import javax.swing.*; /** * @version 1.0 2007-04-27 * @author Cay Horstmann */ public class GroupLayoutTest { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { FontFrame frame = new FontFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); } } /** * Un cadre qui utilise une mise en forme de groupe pour agencer * les composants de slection de police. */ class FontFrame extends JFrame { public FontFrame() { setTitle("GroupLayoutTest"); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); ActionListener listener = new FontAction(); // construire les composants

442

Au cur de Java 2 - Notions fondamentales

JLabel faceLabel = new JLabel("Face: "); face = new JComboBox(new String[] { "Serif", "SansSerif", "Monospaced", "Dialog", "DialogInput" }); face.addActionListener(listener); JLabel sizeLabel = new JLabel("Size: "); size = new JComboBox(new String[] { "8", "10", "12", "15", "18", "24", "36", "48" }); size.addActionListener(listener); bold = new JCheckBox("Bold"); bold.addActionListener(listener); italic = new JCheckBox("Italic"); italic.addActionListener(listener); sample = new JTextArea(); sample.setText("The quick brown fox jumps over the lazy dog"); sample.setEditable(false); sample.setLineWrap(true); sample.setBorder(BorderFactory.createEtchedBorder()); pane = new JScrollPane(sample); GroupLayout layout = new GroupLayout(getContentPane()); setLayout(layout); layout.setHorizontalGroup(layout.createParallelGroup( GroupLayout.Alignment.LEADING) .addGroup( layout.createSequentialGroup() .addContainerGap().addGroup( layout.createParallelGroup( GroupLayout.Alignment.LEADING).addGroup( GroupLayout.Alignment.TRAILING, layout.createSequentialGroup().addGroup( layout.createParallelGroup( GroupLayout.Alignment.TRAILING) .addComponent(faceLabel) .addComponent(sizeLabel)) .addPreferredGap(LayoutStyle .ComponentPlacement.RELATED) .addGroup( layout.createParallelGroup( GroupLayout.Alignment.LEADING, false) .addComponent(size) .addComponent(face))) .addComponent(italic).addComponent(bold)) .addPreferredGap( LayoutStyle.ComponentPlacement.RELATED) .addComponent(pane) .addContainerGap())); layout.linkSize(SwingConstants.HORIZONTAL, new java.awt.Component[] { face, size });

Chapitre 9

Swing et les composants dinterface utilisateur

443

layout.setVerticalGroup(layout.createParallelGroup( GroupLayout.Alignment.LEADING) .addGroup( layout.createSequentialGroup() .addContainerGap().addGroup( layout.createParallelGroup(GroupLayout .Alignment.LEADING).addComponent( pane, GroupLayout.Alignment.TRAILING).addGroup( layout.createSequentialGroup().addGroup( layout.createParallelGroup( GroupLayout.Alignment.BASELINE) .addComponent(face).addComponent(faceLabel)) .addPreferredGap(LayoutStyle .ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup( GroupLayout.Alignment.BASELINE) .addComponent(size) .addComponent(sizeLabel)).addPreferredGap( LayoutStyle.ComponentPlacement.RELATED) .addComponent(italic, GroupLayout.DEFAULT_SIZE, GroupLayout .DEFAULT_SIZE, Short.MAX_VALUE) .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) .addComponent(bold, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) .addContainerGap())); } public static final int DEFAULT_WIDTH = 300; public static final int DEFAULT_HEIGHT = 200; private private private private private private JComboBox face; JComboBox size; JCheckBox bold; JCheckBox italic; JScrollPane pane; JTextArea sample;

/** * Un couteur daction qui modifie la police du texte. */ private class FontAction implements ActionListener { public void actionPerformed(ActionEvent event) { String fontFace = (String) face.getSelectedItem(); int fontStyle = (bold.isSelected()? Font.BOLD: 0) + (italic.isSelected()? Font.ITALIC: 0); int fontSize = Integer.parseInt((String) size.getSelectedItem()); Font font = new Font(fontFace, fontStyle, fontSize); sample.setFont(font); sample.repaint(); } } }

444

Au cur de Java 2 - Notions fondamentales

javax.swing.GroupLayout 6

GroupLayout(Container host)

Construit un GroupLayout pour disposer les composants dans le conteneur host (vous devrez toujours appeler setLayout sur lobjet hte).

void setHorizontalGroup(GroupLayout.Group g) void setVerticalGroup(GroupLayout.Group g)

Dnissent le groupe qui contrle la mise en page horizontale ou verticale.


void linkSize(Component... components) void linkSize(int axis, Component... component)

Obligent les composants donns avoir la mme taille ou afcher la mme taille le long de laxe donn (parmi SwingConstants.HORIZONTAL ou SwingConstants.VERTICAL).

GroupLayout.SequentialGroup createSequentialGroup()

Cre un groupe qui dispose ses enfants les uns la suite des autres.
GroupLayout.ParallelGroup createParallelGroup() GroupLayout.ParallelGroup createParallelGroup(GroupLayout.Alignment align) GroupLayout.ParallelGroup createParallelGroup(GroupLayout.Alignment align, boolean resizable)

Crent un groupe qui dispose ses enfants en parallle. Paramtres : align Une constante parmi BASELINE, LEADING (par dfaut), TRAILING ou CENTER.

resizable Vaut true (par dfaut) lorsque le groupe peut tre redimensionn, false si la taille prfre est aussi la taille minimale et maximale.

boolean getHonorsVisibility() void setHonorsVisibility(boolean b)

Rcuprent ou dnissent la proprit honorsVisibility. Lorsquils valent true (par dfaut), les composants invisibles ne sont pas disposs. Lorsquils valent false, ils sont disposs comme sils taient visibles. Cest utile lorsque vous masquez temporairement certains composants et que la mise en page ne doit pas changer.

boolean getAutoCreateGaps() void setAutoCreateGaps(boolean b) boolean getAutoCreateContainerGaps() void setAutoCreateContainerGaps(boolean b)

Rcuprent et dnissent les proprits autoCreateGaps et autoCreateContainerGaps. Lorsquils valent true, des intervalles sont automatiquement ajouts entre les composants ou aux limites du conteneur. Le paramtre par dfaut vaut false. Une valeur true est utile lorsque vous produisez manuellement un GroupLayout.
javax.swing.GroupLayout.Group

GroupLayout.Group addComponent(Component c) GroupLayout.Group addComponent(Component c, int minimumSize, int preferredSize, int maximumSize)

Chapitre 9

Swing et les composants dinterface utilisateur

445

Ajoutent un composant ce groupe. Les paramtres de taille peuvent tre des valeurs relles (non ngatives) ou les constantes spciales GroupLayout.DEFAULT_SIZE ou GroupLayout .PREFERRED_SIZE. Lorsquon utilise DEFAULT_SIZE, les mthodes getMinimumSize, getPreferredSize ou getMaximumSize du composant sont appeles. Lorsquon utilise PREFERRED_ SIZE, la mthode getPreferredSize du composant est appele.

GroupLayout.Group addGap(int size) GroupLayout.Group addGap(int minimumSize, int preferredSize, int maximumSize)

Ajoutent un intervalle de la taille rigide ou exible donne.


GroupLayout.Group addGroup(GroupLayout.Group g)

Ajoute le groupe donn ce groupe.


javax.swing.GroupLayout.ParallelGroup

GroupLayout.ParallelGroup addComponent(Component c, GroupLayout.Alignment align) GroupLayout.ParallelGroup addComponent(Component c, GroupLayout.Alignment align, int minimumSize, int preferredSize, int maximumSize) GroupLayout.ParallelGroup addGroup(GroupLayout.Group g, GroupLayout.Alignment align)

Ajoutent un composant ou un groupe ce groupe, laide de lalignement donn (parmi BASELINE, LEADING, TRAILING ou CENTER).
javax.swing.GroupLayout.SequentialGroup

GroupLayout.SequentialGroup addContainerGap() GroupLayout.SequentialGroup addContainerGap(int preferredSize, int maximumSize)

Ajoutent un intervalle pour sparer un composant du bord du conteneur.

GroupLayout.SequentialGroup addPreferredGap(LayoutStyle.ComponentPlacement type)

Ajoute un intervalle pour sparer les composants. Le type est LayoutStyle.ComponentPlacement.RELATED ou LayoutStyle.ComponentPlacement.UNRELATED.

Cration sans gestionnaire de mise en forme


Vous souhaiterez parfois ne pas utiliser de gestionnaire de mise en forme et pouvoir placer un composant un endroit xe (appel position absolue). Pour des applications indpendantes de la plate-forme, ce nest pas une trs bonne ide. En revanche, elle est adapte llaboration rapide dun prototype. Voici comment procder pour placer un composant un endroit xe : 1. Dnissez le gestionnaire de mise en forme avec la valeur null. 2. Ajoutez le composant de votre choix dans le conteneur. 3. Spciez ensuite la position et la taille voulues :
frame.setLayout(null); JButton ok = new JButton("OK"); frame.add(ok); ok.setBounds(10, 10, 30, 15);

446

Au cur de Java 2 - Notions fondamentales

java.awt.Component 1.0

void setBounds(int x, int y, int width, int height)

Dplace et redimensionne un composant. Paramtres : x, y width, height Le nouveau coin suprieur gauche du composant. La nouvelle taille du composant.

Gestionnaires de mise en forme personnaliss


Vous avez la possibilit de concevoir votre propre classe LayoutManager pour grer les composants de faon spciale. Par exemple, vous pourriez organiser tous les composants dun conteneur de faon former un cercle (voir Figure 9.34).
Figure 9.34
Mise en forme circulaire.

Pour disposer de votre propre gestionnaire de mise en forme, celui-ci doit implmenter linterface LayoutManager. Vous devez rednir les cinq mthodes suivantes :
void addLayoutComponent(String s, Component c); void removeLayoutComponent(Component c); Dimension preferredLayoutSize(Container parent); Dimension minimumLayoutSize(Container parent); void layoutContainer(Container parent);

Les deux premires mthodes sont appeles pour, respectivement, ajouter ou supprimer un composant. Si vous ne conservez aucune information supplmentaire sur les composants, vous pouvez faire en sorte quelles nexcutent aucune action. Les deux mthodes suivantes calculent lespace prfr et minimal pour lorganisation des composants. Ces deux valeurs sont gnralement quivalentes. La cinquime mthode effectue toute la mise en forme et invoque la mthode setBounds sur tous les composants.
INFO
AWT possde une seconde interface appele LayoutManager2 comprenant dix mthodes implmenter au lieu de cinq. Son objectif est de permettre lutilisateur demployer la mthode add avec des contraintes. Par exemple, les gestionnaires BorderLayout et GridBagLayout implmentent cette interface.

Le Listing 9.13 prsente le code du gestionnaire CircleLayout, qui aligne les composants le long dune ellipse au sein du conteneur parent.

Chapitre 9

Swing et les composants dinterface utilisateur

447

Listing 9.13 : CircleLayoutTest.java


import java.awt.*; import javax.swing.*; /** * @version 1.32 2007-06-12 * @author Cay Horstmann */ public class CircleLayoutTest { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { CircleLayoutFrame frame = new CircleLayoutFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); } } /** * Un cadre affichant des boutons disposs en cercle. */ class CircleLayoutFrame extends JFrame { public CircleLayoutFrame() { setTitle("CircleLayoutTest"); setLayout(new CircleLayout()); add(new JButton("Yellow")); add(new JButton("Blue")); add(new JButton("Red")); add(new JButton("Green")); add(new JButton("Orange")); add(new JButton("Fuschia")); add(new JButton("Indigo")); pack(); } } /** * Un gestionnaire de mise en forme disposant les composants en cercle. */ class CircleLayout implements LayoutManager { public void addLayoutComponent(String name, Component comp) { } public void removeLayoutComponent(Component comp) { }

448

Au cur de Java 2 - Notions fondamentales

public void setSizes(Container parent) { if (sizesSet) return; int n = parent.getComponentCount(); preferredWidth = 0; preferredHeight = 0; minWidth = 0; minHeight = 0; maxComponentWidth = 0; maxComponentHeight = 0; // calculer les largeur et hauteur maxi de composants et affecter // la somme des tailles des composants la taille prfre for (int i = 0; i < n; i++) { Component c = parent.getComponent(i); if (c.isVisible()) { Dimension d = c.getPreferredSize(); maxComponentWidth = Math.max(maxComponentWidth, d.width); maxComponentHeight = Math.max(maxComponentHeight, d.height); preferredWidth += d.width; preferredHeight += d.height; } } minWidth = preferredWidth / 2; minHeight = preferredHeight / 2; sizesSet = true; } public Dimension preferredLayoutSize(Container parent) { setSizes(parent); Insets insets = parent.getInsets(); int width = preferredWidth+insets.left +insets.right; int height = preferredHeight+insets.top +insets.bottom; return new Dimension(width, height); } public Dimension minimumLayoutSize(Container parent) { setSizes(parent); Insets insets = parent.getInsets(); int width = minWidth+insets.left+insets.right; int height = minHeight+insets.top+insets.bottom; return new Dimension(width, height); } public void layoutContainer(Container parent) { setSizes(parent); // calculer le centre du cercle Insets insets = parent.getInsets(); int containerWidth = parent.getSize().width - insets.left - insets.right; int containerHeight = parent.getSize().height - insets.top - insets.bottom;

Chapitre 9

Swing et les composants dinterface utilisateur

449

int xcenter = insets.left+containerWidth / 2; int ycenter = insets.top+containerHeight / 2; // calculer le rayon du cercle int xradius = (containerWidth - maxComponentWidth) / 2; int yradius = (containerHeight - maxComponentHeight) / 2; int radius = Math.min(xradius, yradius); // disposer les composants le long du cercle int n = parent.getComponentCount(); for (int i = 0; i < n; i++) { Component c = parent.getComponent(i); if (c.isVisible()) { double angle = 2 * Math.PI * i / n; // centre du composant int x = xcenter+ (int) (Math.cos(angle) * radius); int y = ycenter+ (int) (Math.sin(angle) * radius); // dplacer le composant pour que son centre soit (x, y) // et sa taille gale sa taille prfre Dimension d = c.getPreferredSize(); c.setBounds(x - d.width / 2, y - d.height / 2, d.width, d.height); } } } private private private private private private private } java.awt.LayoutManager 1.0 void addLayoutComponent(String name, Component comp) int minWidth = 0; int minHeight = 0; int preferredWidth = 0; int preferredHeight = 0; boolean sizesSet = false; int maxComponentWidth = 0; int maxComponentHeight = 0;

Ajoute un composant la mise en forme. Paramtres :


name comp

Un identiant pour le placement du composant. Le composant ajouter.

void removeLayoutComponent(Component comp)

Supprime un composant de la mise en forme.


Dimension preferredLayoutSize(Container cont)

Renvoie la taille prfre du conteneur pour cette mise en forme.


Dimension minimumLayoutSize(Container cont)

Renvoie la taille minimale du conteneur pour cette mise en forme.


void layoutContainer(Container cont)

Organise les composants dans un conteneur.

450

Au cur de Java 2 - Notions fondamentales

Squence de tabulation
Lorsque vous ajoutez de nombreux composants dans une fentre, vous devez penser les ordonner selon une squence de tabulation. Lorsquune fentre est afche pour la premire fois, le premier composant de la squence reoit le focus dentre. Chaque fois que lutilisateur appuie sur la touche de tabulation, le focus se dplace sur le composant suivant dans la squence de tabulation. Rappelezvous que les composants qui reoivent le focus peuvent tre manipuls laide du clavier. Par exemple, un bouton ayant reu le focus dentre peut tre "cliqu" au moyen de la barre despacement. Mme si vous nutilisez pas personnellement la touche de tabulation pour naviguer dans un ensemble de contrles, sachez quelle sert de nombreux utilisateurs. Parmi eux, on trouve ceux qui sont allergiques la souris, ou ceux qui ne peuvent tout simplement pas lutiliser en raison dun handicap ou bien parce quils commandent linterface utilisateur par la voix. Vous devez par consquent savoir de quelle manire Swing gre la squence de tabulation. La squence de tabulation se fait de faon classique, cest--dire dabord de gauche droite puis de haut en bas. Par exemple, dans la bote de dialogue des polices, la squence de tabulation est la suivante (voir Figure 9.35) : 1. Zone de liste droulante Face. 2. Zone de texte pour lchantillon de police (appuyez sur Ctrl+Tab pour passer au champ suivant ; la tabulation est considre comme une entre de texte). 3. Zone de liste droulante Size. 4. Case cocher Bold. 5. Case cocher Italic.
INFO
Dans lancienne version dAWT, lordre selon lequel les composants taient insrs dans le conteneur dterminait la squence de tabulation. Dans Swing, lordre dinsertion na aucune importance, seule la disposition des composants est prise en compte.

Figure 9.35
Squence de tabulation.

La situation est encore plus complexe si un conteneur en contient dautres. Lorsque le focus dentre est transmis un autre conteneur, il est reu par le composant situ en haut gauche dans ce conteneur. Il passe ensuite par tous les autres composants du conteneur. Enn, le focus est transmis au composant qui suit le conteneur dans la squence de tabulation.

Chapitre 9

Swing et les composants dinterface utilisateur

451

Vous pouvez exploiter cette fonctionnalit en regroupant des lments apparents dans un autre conteneur, comme un panneau.
INFO
Depuis Java SE 1.4, vous appelez
component.setFocusable(false);

pour supprimer un composant de la squence de tabulation. Prcdemment, vous deviez remplacer la mthode isFocusTraversable, mais cette mthode est aujourdhui dprcie.

En rsum, il existe deux politiques de tabulation standard dans Java SE 1.4 :


m

Les applications AWT pures utilisent DefaultFocusTraversalPolicy. Les composants sont inclus dans la squence de tabulation sils sont visibles, afchables, activs et peuvent avoir le focus et si leurs pairs natifs peuvent avoir le focus. Les composants sont tabuls dans lordre de leur insertion dans le conteneur. Les applications Swing utilisent LayoutFocusTraversalPolicy. Les composants sont inclus dans la squence de focus sils sont visibles, afchables, activs et peuvent avoir le focus. Les composants sont tabuls dans lordre gomtrique : de gauche droite, puis de haut en bas. Toutefois, un conteneur introduit un nouveau "cycle" : ses composants sont tabuls dabord avant que le successeur du conteneur ne reoive le focus.
INFO

La notion de "cycle" est un peu droutante. Aprs avoir atteint le dernier lment dun conteneur enfant, le focus ne revient pas au premier lment, il passe au successeur du conteneur. LAPI prend en charge de vrais cycles, comprenant des frappes au clavier qui montent et descendent dans une hirarchie de cycle. Toutefois, la politique standard de tabulation nutilise pas les cycles hirarchiques. Elle aplatit la hirarchie du cycle dans une tabulation linaire (depth-rst).

INFO
Dans Java SE 1.3, vous pouviez modier lordre de tabulation par dfaut en appelant la mthode setNextFocusableComponent de la classe JComponent. Cette mthode est maintenant dprcie. Pour modier lordre de tabulation, essayez de grouper des composants lis dans des panneaux, de sorte quils forment des cycles. Si cela ne fonctionne pas, vous devez installer un comparateur qui trie diffremment les composants ou remplace totalement la politique de tabulation. Aucune de ces oprations ne semble avoir pour objet la pusillanimit (voyez la documentation Sun API pour en savoir plus).

Botes de dialogue
Jusqu prsent, tous les composants dinterface que nous avons vus guraient lintrieur dun cadre cr dans un programme. Cest la situation la plus courante lorsque vous crivez des applets destins tre excuts dans un navigateur Web. Mais si vous crivez des applications, vous prfrerez utiliser des botes de dialogue spares qui safcheront pour proposer des informations lutilisateur ou en recevoir de sa part.

452

Au cur de Java 2 - Notions fondamentales

A limage de la plupart des systmes de fentrage, AWT fait la distinction entre les botes de dialogue modales et non modales. Une bote de dialogue modale ne permet pas lutilisateur dinteragir avec dautres fentres de lapplication tant quelle demeure ouverte. Vous lutilisez lorsque vous avez besoin de rcuprer des informations de la part de lutilisateur avant de poursuivre lexcution du programme. Par exemple, quand lutilisateur demande lire un chier, une bote de dialogue modale apparat, lui demandant de spcier le nom du chier pour que le programme puisse entreprendre une opration de lecture. Lapplication ne peut continuer sexcuter que lorsque lutilisateur a ferm la bote de dialogue. Une bote de dialogue non modale autorise lutilisateur y entrer des informations de mme que dans le reste de lapplication. Une barre doutils pourrait tre une bote de dialogue non modale. Elle peut rester apparente aussi longtemps que ncessaire pendant que lutilisateur interagit avec elle et avec lapplication. Nous allons commencer par ltude dune bote de dialogue modale simple contenant un seul message. Swing dispose dune classe trs utile, JOptionPane, qui vous permet dafcher une bote de dialogue standard sans avoir crire de code spcique. Vous verrez ensuite comment crer des botes de dialogue plus complexes en implmentant les vtres. Pour nir, vous tudierez lchange des donnes entre une application et une bote de dialogue. Nous conclurons cette section par la prsentation de deux botes de dialogue standard : Fichier et Couleurs. Elles sont complexes, et si vous souhaitez crire les vtres, il faudra absolument vous familiariser avec la classe JFileChooser de Swing. La bote de dialogue JColorChooser permet un utilisateur de choisir une couleur partir dune palette.

Botes de dialogue doptions


Swing propose un ensemble de botes de dialogue standard prtes lemploi, qui permettent de demander lutilisateur de fournir une information. La classe JOptionPane propose quatre mthodes static pour afcher ces botes de dialogue : showMessageDialog showConfirmDialog showOptionDialog showInputDialog Afche un message et attend que lutilisateur clique sur OK. Afche un message et reoit une conrmation de lutilisateur (comme OK/Annuler). Afche un message et rcupre loption choisie parmi plusieurs. Afche un message et rcupre une ligne entre par lutilisateur.

La Figure 9.36 prsente une bote de dialogue standard. Comme vous pouvez le voir, elle contient les composants suivants :
m m m

une icne ; un message ; un ou plusieurs boutons doption.

La bote de dialogue de saisie comprend un composant supplmentaire pour recueillir lentre de lutilisateur. Il peut sagir dun champ de texte dans lequel il tape une chane quelconque ou dune zone de liste droulante dans laquelle il slectionne une valeur. La mise en forme exacte de ces botes de dialogue et le choix des icnes pour les types de messages standard dpendent du style dinterface look and feel implment.

Chapitre 9

Swing et les composants dinterface utilisateur

453

Figure 9.36
Une bote de dialogue doptions.

Licne situe gauche dpend du type de message. Il en existe cinq :


ERROR_MESSAGE INFORMATION_MESSAGE WARNING_MESSAGE QUESTION_MESSAGE PLAIN_MESSAGE

Le type PLAIN_MESSAGE ne possde pas dicne. Pour chaque type de bote de dialogue, il existe une mthode vous permettant de fournir votre propre icne. Vous pouvez spcier un message pour chaque type de bote de dialogue. Ce message peut tre une chane, une icne, un composant dinterface utilisateur ou tout autre objet. Voici comment est afch lobjet message : String Icon Component Object[] tout autre objet La chane est afche. Licne est afche. Le composant est afch. Tous les objets du tableau sont afchs les uns au-dessus des autres. La mthode toString est applique et la chane rsultante est afche.

Pour obtenir un aperu de ces options, excutez le programme du Listing 9.14. Bien sr, le message le plus frquent est celui de type chane. Un objet Component offre une souplesse optimale puisque vous pouvez utiliser la mthode paintComponent pour dessiner tout ce que vous souhaitez. Les boutons situs en bas dune bote de dialogue dpendent de son type et du type doption. Les mthodes showMessageDialog et showInputDialog implmentent uniquement un jeu standard de boutons (respectivement OK et OK/Cancel). La mthode showConfirmMessage vous permet de choisir parmi les quatre options suivantes :
DEFAULT_OPTION YES_NO_OPTION YES_NO_CANCEL_OPTION OK_CANCEL_OPTION

Avec la mthode showOptionDialog, vous pouvez spcier un ensemble arbitraire doptions en fournissant un tableau dobjets. Chaque lment du tableau est trait de la faon suivante : String Icon Component tout autre objet Un bouton est cr avec la chane comme libell. Un bouton est cr avec licne comme libell. Le composant est afch. La mthode toString est applique et un bouton est cr avec pour libell la chane rsultante.

454

Au cur de Java 2 - Notions fondamentales

Voici les valeurs renvoyes par ces mthodes : showMessageDialog showConfirmDialog showOptionDialog showInputDialog Aucune. Un entier reprsentant loption choisie. Un entier reprsentant loption choisie. La chane entre ou slectionne par lutilisateur.

Les mthodes showConfirmDialog et showOptionDialog renvoient des entiers pour indiquer le bouton que lutilisateur a slectionn. Pour une bote de dialogue doptions, il sagit de lindice de llment choisi ou de la valeur CLOSED_OPTION si lutilisateur a ferm la bote de dialogue au lieu de faire une slection. Pour une bote de dialogue de conrmation, la valeur renvoye peut tre lune des suivantes :
OK_OPTION CANCEL_OPTION YES_OPTION NO_OPTION CLOSED_OPTION

La diversit des choix peut sembler dconcertante, mais en pratique cest trs simple. Procdez comme suit : 1. Choisissez le type de bote de dialogue (message, conrmation, options ou entre). 2. Choisissez licne (erreur, information, avertissement, question, aucune ou personnalise). 3. Choisissez le message (chane, icne, composant personnalis ou un tableau dobjets). 4. Pour une bote de dialogue de conrmation, choisissez un type doption pour le jeu de boutons (default, Yes/No, Yes/No/Cancel ou OK/Cancel). 5. Pour une bote de dialogue doptions, choisissez les options (chane, icne, composant personnalis) et loption par dfaut. 6. Pour une bote de dialogue de saisie, choisissez entre un champ de texte et une zone de liste droulante. 7. Dterminez la mthode approprie appeler dans lAPI JOptionPane. Supposons, par exemple, que vous vouliez afcher la bote de dialogue de la Figure 9.36. Elle contient un message et invite lutilisateur conrmer ou annuler. Par consquent, il sagit dune bote de dialogue de conrmation. Licne correspond un message de type question, le message est une chane et le jeu de boutons est OK_CANCEL_OPTION. Voici lappel que vous devez excuter :
int selection = JOptionPane.showConfirmDialog(parent, "Message", "Title", JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE); if (selection == JOptionPane.OK_OPTION) . . .

ASTUCE
Une chane de message peut contenir des caractres de n de ligne (\n). Lafchage est alors rparti sur plusieurs lignes.

Chapitre 9

Swing et les composants dinterface utilisateur

455

Figure 9.37
Le programme OptionDialogTest.

Le programme du Listing 9.14 vous permet deffectuer votre slection (voir Figure 9.37). Il afche ensuite la bote de dialogue qui en rsulte.
Listing 9.14 : OptionDialogTest.java
import import import import import java.awt.*; java.awt.event.*; java.awt.geom.*; java.util.*; javax.swing.*;

/** * @version 1.33 2007-04-28 * @author Cay Horstmann */ public class OptionDialogTest { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { OptionDialogFrame frame = new OptionDialogFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); } } /** * Un panneau contenant des boutons radio * dans une zone encadre avec un titre. */ class ButtonPanel extends JPanel

456

Au cur de Java 2 - Notions fondamentales

{ /** * Construit un panneau avec des boutons. * @param title Le titre du panneau * @param options Un tableau de libells de boutons radio */ public ButtonPanel(String title, String... options) { setBorder(BorderFactory.createTitledBorder (BorderFactory.createEtchedBorder(), title)); setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); group = new ButtonGroup(); // crer un bouton radio pour chaque option for (String option : options) { JRadioButton b = new JRadioButton(options); b.setActionCommand(option); add(b); group.add(b); b.setSelected(option == options[0]); } } /** * Extrait loption slectionne. * @return Le libell du bouton radio slectionn. */ public String getSelection() { return group.getSelection().getActionCommand(); } private ButtonGroup group; } /** * Un cadre contenant des paramtres pour la slection * de diffrentes botes de dialogue doptions. */ class OptionDialogFrame extends JFrame { public OptionDialogFrame() { setTitle("OptionDialogTest"); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); JPanel gridPanel = new JPanel(); gridPanel.setLayout(new GridLayout(2, 3)); typePanel = new ButtonPanel("Type", "Message", "Confirm", "Option", "Input"); messageTypePanel = new ButtonPanel("Message type", "ERROR_MESSAGE", "INFORMATION_MESSAGE", "WARNING_MESSAGE", "QUESTION_MESSAGE", "PLAIN_MESSAGE"); messagePanel = new ButtonPanel("Message", "String", "Icon", "Component", "Other", "Object[]"); optionTypePanel = new ButtonPanel("Confirm", "DEFAULT_OPTION", "YES_NO_OPTION", "YES_NO_CANCEL_OPTION", "OK_CANCEL_OPTION");

Chapitre 9

Swing et les composants dinterface utilisateur

457

optionsPanel = new ButtonPanel("Option", "String[]", "Icon[]", "Object[]"); inputPanel = new ButtonPanel("Input", "Text field", "Combo box"); gridPanel.add(typePanel); gridPanel.add(messageTypePanel); gridPanel.add(messagePanel); gridPanel.add(optionTypePanel); gridPanel.add(optionsPanel); gridPanel.add(inputPanel); // ajouter un panneau avec un bouton Afficher (Show) JPanel showPanel = new JPanel(); JButton showButton = new JButton("Show"); showButton.addActionListener(new ShowAction()); showPanel.add(showButton); add(gridPanel, BorderLayout.CENTER); add(showPanel, BorderLayout.SOUTH); } /** * Rcupre le message slectionn. * @return Une chane, une icne, un composant ou un tableau * dobjets, selon la slection dans le panneau Message */ public Object getMessage() { String s = messagePanel.getSelection(); if (s.equals("String")) return messageString; else if (s.equals("Icon")) return messageIcon; else if (s.equals("Component")) return messageComponent; else if (s.equals("Object[]")) return new Object[] { messageString, messageIcon, messageComponent, messageObject }; else if (s.equals("Other")) return messageObject; else return null; } /** * Rcupre les options actuellement slectionnes * @return Un tableau de chanes, dicnes ou dobjets, * selon la slection dans le panneau Option */ public Object[] getOptions() { String s = optionsPanel.getSelection(); if (s.equals("String[]")) return new String[] { "Yellow", "Blue", "Red" }; else if (s.equals("Icon[]")) return new Icon[] { new ImageIcon("yellow-ball.gif"), new ImageIcon("blue-ball.gif"), new ImageIcon("red-ball.gif") }; else if (s.equals("Object[]")) return new Object[] { messageString, messageIcon, messageComponent, messageObject };

458

Au cur de Java 2 - Notions fondamentales

else return null; } /** * Rcupre le message ou le type doption slectionn * @param panel Le panneau Message Type ou Confirm * @return La constante XXX_MESSAGE ou XXX_OPTION * de la classe JOptionPane */ public int getType(ButtonPanel panel) { String s = panel.getSelection(); try { return JOptionPane.class.getField(s).getInt(null); } catch(Exception e) { return -1; } } /** * Lcouteur daction pour le bouton Show affiche une * bote de dialogue Confirm, Input, Message ou Option * selon la slection dans le panneau Type. */ private class ShowAction implements ActionListener { public void actionPerformed(ActionEvent event) { if (typePanel.getSelection().equals("Confirm")) JOptionPane.showConfirmDialog( OptionDialogFrame.this, getMessage(), "Title", getType(optionTypePanel), getType(messageTypePanel)); else if (typePanel.getSelection().equals("Input")) { if (inputPanel.getSelection().equals("Text field")) JOptionPane.showInputDialog( OptionDialogFrame.this, getMessage(), "Title", getType(messageTypePanel)); else JOptionPane.showInputDialog( OptionDialogFrame.this, getMessage(), "Title", getType(messageTypePanel), null, new String[] { "Yellow", "Blue", "Red" }, "Blue"); }

Chapitre 9

Swing et les composants dinterface utilisateur

459

else if (typePanel.getSelection().equals("Message")) JOptionPane.showMessageDialog( OptionDialogFrame.this, getMessage(), "Title", getType(messageTypePanel)); else if (typePanel.getSelection().equals("Option")) JOptionPane.showOptionDialog( OptionDialogFrame.this, getMessage(), "Title", getType(optionTypePanel), getType(messageTypePanel), null, getOptions(), getOptions()[0]); } } public static final int DEFAULT_WIDTH = 600; public static final int DEFAULT_HEIGHT = 400; private private private private private private private private private private } /** * Un composant avec un fond color */ class SampleComponent extends JComponent { public void paintComponent(Graphics g) { Graphics2D g2 = (Graphics2D) g; Rectangle2D rect = new Rectangle2D.Double(0, 0, getWidth() - 1, getHeight() - 1); g2.setPaint(Color.YELLOW); g2.fill(rect); g2.setPaint(Color.BLUE); g2.draw(rect); } public Dimension getPreferredSize() { return new Dimension(10, 10); } } ButtonPanel ButtonPanel ButtonPanel ButtonPanel ButtonPanel ButtonPanel typePanel; messagePanel; messageTypePanel; optionTypePanel; optionsPanel; inputPanel;

String messageString = "Message"; Icon messageIcon = new ImageIcon("blue-ball.gif"); Object messageObject = new Date(); Component messageComponent = new SampleComponent();

460

Au cur de Java 2 - Notions fondamentales

javax.swing.JOptionPane 1.2

static void showMessageDialog(Component parent, Object message, String title, int messageType, Icon icon) static void showMessageDialog(Component parent, Object message, String title, int messageType) static void showMessageDialog(Component parent, Object message) static void showInternalMessageDialog(Component parent, Object message, String title, int messageType, Icon icon) static void showInternalMessageDialog(Component parent, Object message, String title, int messageType) static void showInternalMessageDialog(Component parent, Object message)

Afchent une bote de dialogue de message ou une bote de dialogue de message interne (cest-dire entirement dessine au sein de son cadre conteneur). Paramtres : parent message title messageType Le composant parent (peut tre null). Le message afcher dans la bote de dialogue (chane, icne, composant ou tableau dobjets). La chane dans la barre de titre de la bote de dialogue. Lune des constantes suivantes : ERROR_MESSAGE, INFORMATION_MESSAGE, WARNING_MESSAGE, QUESTION_MESSAGE, PLAIN_MESSAGE. Licne afcher la place de lune des icnes standard.

icon

static int showConfirmDialog(Component parent, Object message, String title, int typeOption, int messageType, Icon icon) static int showConfirmDialog(Component parent, Object message, String title, int optionType, int messageType) static int showConfirmDialog(Component parent, Object message, String title, int optionType) static int showConfirmDialog(Component parent, Object message) static int showInternalConfirmDialog(Component parent, Object message, String title, int optionType, int messageType, Icon icon) static int showInternalConfirmDialog(Component parent, Object message, String title, int optionType, int messageType) static int showInternalConfirmDialog(Component parent, Object message, String title, int optionType) static int showInternalConfirmDialog(Component parent, Object message)

Afchent une bote de dialogue de conrmation ou une bote de dialogue de conrmation interne (entirement dessine au sein de son cadre conteneur). Renvoient loption slectionne par lutilisateur (parmi les suivantes : OK_OPTION, CANCEL_OPTION, YES_OPTION, NO_OPTION) ou CLOSED_OPTION si lutilisateur a ferm la bote de dialogue. Paramtres : parent message title Le composant parent (peut tre null). Le message afcher dans la bote de dialogue (chane, icne, composant ou tableau dobjets). La chane dans la barre de titre de la bote de dialogue.

Chapitre 9

Swing et les composants dinterface utilisateur

461

messageType

Lune des constantes suivantes : ERROR_MESSAGE, INFORMATION_MESSAGE, WARNING_MESSAGE, QUESTION_MESSAGE, PLAIN_MESSAGE. Lune des constantes suivantes : DEFAULT_OPTION, YES_NO_ OPTION, YES_NO_CANCEL_OPTION, OK_CANCEL_OPTION. Licne afcher la place dune des icnes standard.

optionType icon

static int showOptionDialog(Component parent, Object message, String title, int optionType, int messageType, Icon icon, Object[] options, Object default) static int showInternalOptionDialog(Component parent, Object message, String title, int optionType, int messageType, Icon icon, Object[] options, Object default)

Afchent une bote de dialogue doptions ou une bote de dialogue doptions interne (entirement dessine au sein de son cadre conteneur). Renvoient lindice de loption slectionne par lutilisateur ou CLOSED_OPTION sil a ferm la bote de dialogue. Paramtres : parent message title messageType Le composant parent (peut tre null). Le message afcher dans la bote de dialogue (chane, icne, composant ou tableau dobjets). La chane dans la barre de titre de la bote de dialogue. Lune des constantes suivantes : ERROR_MESSAGE, INFORMATION_MESSAGE, WARNING_MESSAGE, QUESTION_MESSAGE, PLAIN_MESSAGE. Lune des constantes suivantes : DEFAULT_OPTION, YES_NO_ OPTION, YES_NO_CANCEL_OPTION, OK_CANCEL_OPTION. Licne afcher la place dune des icnes standard. Un tableau doptions (chanes, icnes ou composants) Loption par dfaut proposer lutilisateur.

optionType icon options default

Cration de botes de dialogue


A la section prcdente, vous avez tudi lutilisation de la classe JOptionPane pour afcher une bote de dialogue standard. Ici, vous apprendrez crer vous-mme une bote de dialogue. La Figure 9.38 prsente un modle classique de bote de dialogue modale, une fentre dinformation sur le programme en cours dexcution, qui est afche lorsque lutilisateur clique sur loption About. Pour implmenter une bote de dialogue, vous tendez une classe de JDialog. Ce processus est identique celui qui permet dtendre la fentre principale pour une application. Voici les tapes suivre : 1. Dans le constructeur de la bote de dialogue, appelez le constructeur de la superclasse JDialog. 2. Ajoutez les composants dinterface de la bote de dialogue. 3. Ajoutez les gestionnaires dvnement. 4. Dnissez la taille de la bote de dialogue.

462

Au cur de Java 2 - Notions fondamentales

Lorsque vous appelez le constructeur de la superclasse, vous devez fournir le nom du cadre propritaire, le titre de la bote de dialogue et la modalit. Le cadre propritaire commande lemplacement dafchage de la bote de dialogue. Vous pouvez fournir un propritaire null, la bote de dialogue appartient alors un cadre masqu. La modalit indique les autres fentres de lapplication qui sont bloques lorsque la bote de dialogue safche. Une bote de dialogue non modale ne bloque pas les autres fentres. A linverse, une bote de dialogue modale les bloque toutes (en dehors de ses enfants). Vous utiliserez une bote de dialogue non modale pour une bote outils laquelle lutilisateur peut toujours avoir accs et une bote de dialogue modale pour obliger lutilisateur y fournir des informations obligatoires.
INFO
Depuis Java SE 6, il existe deux autres types de modalits. Une bote de dialogue modale de document bloque toutes les fentres appartenant au mme "document" ou, plus prcisment, toutes les fentres ayant la mme fentre racine sans parent que la bote de dialogue. Cela rsout un problme des systmes daide. Dans les versions plus anciennes, il tait impossible dinteragir avec les fentres daide lorsquune bote de dialogue modale tait afche. Une bote de dialogue modale de bote outils bloque toutes les fentres de celle-ci. Une bote outils est un programme Java qui lance plusieurs applications, comme le moteur dapplet dans un navigateur. Pour plus dinformations sur ces problmes avancs, consultez http://java.sun.com/developer/technicalArticles/J2SE/Desktop/javase6/ modality.

Figure 9.38
Une bote de dialogue About.

Voici le code de la bote de dialogue :


public AboutDialog extends JDialog { public AboutDialog(JFrame owner) { super(owner, "About DialogTest ", true); add(new JLabel( "<html><h1><i>Core Java</i></h1><hr> By Cay Horstmann and Gary Cornell</html>"), BorderLayout.CENTER); JPanel panel = new JPanel(); JButton ok = new JButton("OK"); ok.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { setVisible(false); }

Chapitre 9

Swing et les composants dinterface utilisateur

463

}); panel.add(ok); add(panel, BorderLayout.SOUTH); setSize(250, 150); } }

Le constructeur ajoute les lments de linterface utilisateur, ici des tiquettes et un bouton. Il ajoute aussi un gestionnaire au bouton et dnit la taille de la bote de dialogue. Pour afcher la bote de dialogue, vous crez un nouvel objet bote de dialogue et vous lafchez :
JDialog dialog = new AboutDialog(this); dialog.setVisible(true);

En fait, dans le code ci-aprs, la bote de dialogue est cre une seule fois et peut ainsi tre rutilise chaque fois que lutilisateur clique sur loption About :
if (dialog == null) // Premire fois dialog = new AboutDialog(this); dialog.setVisible(true);

Lorsque lutilisateur clique sur OK, la bote de dialogue doit se fermer. Cette action est gre par le gestionnaire dvnement du bouton OK.
ok.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { setVisible(false); } });

Si lutilisateur clique sur la case de fermeture (X) de la fentre, la bote de dialogue est galement ferme. Comme pour la classe JFrame, vous pouvez rednir ce comportement au moyen de la mthode setDefaultCloseOperation. Le Listing 9.15 contient le code du programme de test de la bote de dialogue About.
Listing 9.15 : DialogTest.java
import java.awt.*; import java.awt.event.*; import javax.swing.*; /** * @version 1.33 2007-06-12 * @author Cay Horstmann */ public class DialogTest { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() {

464

Au cur de Java 2 - Notions fondamentales

DialogFrame frame = new DialogFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); } } /** * Un cadre avec un menu dont laction Fichier->A propos (File->About) * affiche une bote de dialogue. */ class DialogFrame extends JFrame { public DialogFrame() { setTitle("DialogTest"); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); // construire un menu File JMenuBar menuBar = new JMenuBar(); setJMenuBar(menuBar); JMenu fileMenu = new JMenu("File"); menuBar.add(fileMenu); // ajouter les options About et Exit au menu // Loption About affiche la bote de dialogue correspondante JMenuItem aboutItem = new JMenuItem("About"); aboutItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { if (dialog == null) // premire fois dialog = new AboutDialog(DialogFrame.this); dialog.setVisible(true); // bote de dialogue } }); fileMenu.add(aboutItem); // Loption Exit permet de quitter le programme JMenuItem exitItem = new JMenuItem("Exit"); exitItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { System.exit(0); } }); fileMenu.add(exitItem); }

Chapitre 9

Swing et les composants dinterface utilisateur

465

public static final int DEFAULT_WIDTH = 300; public static final int DEFAULT_HEIGHT = 200; private AboutDialog dialog; } /** * Une bote de dialogue modale qui affiche un message et * attend que lutilisateur clique sur le bouton OK. */ class AboutDialog extends JDialog { public AboutDialog(JFrame owner) { super(owner, "About DialogTest", true); // ajouter le libell HTML au centre add( new JLabel( "<html><h1><i>Core Java</i></h1><hr> By Cay Horstmann and Gary Cornell</html>"), BorderLayout.CENTER); // Le bouton OK ferme la bote de dialogue JButton ok = new JButton("OK"); ok.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { setVisible(false); } }); // ajouter le bouton OK dans la partie sud JPanel panel = new JPanel(); panel.add(ok); add(panel, BorderLayout.SOUTH); setSize(250, 150); } } javax.swing.JDialog 1.2

public JDialog(Frame parent, String title, boolean modal)

Cre une bote de dialogue qui nest pas visible tant quelle na pas t explicitement afche. Paramtres : parent title modal Le cadre propritaire de la bote de dialogue. Le titre de la bote de dialogue. true pour les botes de dialogue modales (une bote de dialogue modale empche linteraction avec les autres fentres).

466

Au cur de Java 2 - Notions fondamentales

Echange de donnes
On conoit le plus souvent une bote de dialogue dans le but de recueillir des informations de la part de lutilisateur. Vous avez pu constater que la cration dun objet bote de dialogue est une opration simple : il suft de fournir les donnes initiales, puis dappeler la mthode setVisible(true) pour afcher la bote lcran. Nous allons maintenant tudier comment des donnes peuvent tre transfres dans une bote de dialogue, ou extraites. Prenons lexemple de la bote de dialogue de la Figure 9.39, qui pourrait tre utilise pour obtenir un nom et un mot de passe utilisateur pour la connexion un service en ligne.
Figure 9.39
Une bote de dialogue de mot de passe.

Votre bote de dialogue doit fournir des mthodes pour dnir les donnes par dfaut. Par exemple, la classe PasswordChooser de notre exemple de programme possde une mthode setUser qui insre les valeurs par dfaut dans les champs suivants :
public void setUser(User u) { username.setText(u.getName()); }

Une fois que vous avez, le cas chant, dni les valeurs par dfaut, vous afchez la bote de dialogue en appelant setVisible(true). La bote de dialogue safche. Lutilisateur fournit les informations et clique sur le bouton OK ou Annuler. Les gestionnaires dvnement pour les deux boutons appellent setVisible(false), qui termine lappel setVisible(true). Lutilisateur peut aussi fermer la bote de dialogue. Si vous navez pas install dcouteur de fentre pour la bote de dialogue, le processus de fermeture de fentre par dfaut sapplique : la bote de dialogue devient invisible, ce qui a aussi pour effet de mettre n lappel setVisible(true). Ce dont il faut se souvenir, cest que lappel setVisible(true) se bloque jusqu ce que lutilisateur ait renvoy la bote de dialogue. Ceci facilite limplmentation des botes de dialogue modales. Il vous faudra savoir si lutilisateur a accept ou annul la bote de dialogue. Notre exemple de code dnit la balise OK sur false avant dafcher la bote de dialogue. Seul le gestionnaire dvnement du bouton OK dnit la balise OK sur true. Dans ce cas, vous pouvez rcuprer la saisie de lutilisateur dans la bote de dialogue.

Chapitre 9

Swing et les composants dinterface utilisateur

467

INFO
Le transfert de donnes partir dune bote de dialogue non modale nest pas une opration facile. Lors de lafchage dune telle bote de dialogue, lappel de la mthode setVisible(true) nest pas bloquant et le programme poursuit son excution. Si lutilisateur slectionne une option puis clique sur OK, la bote de dialogue doit envoyer un vnement un couteur dans le programme.

Notre programme contient une autre amlioration intressante. Lorsque vous construisez un objet JDialog, vous devez en spcier le cadre propritaire. Pourtant, assez souvent, vous voudrez afcher la mme bote de dialogue avec diffrents cadres propritaires. Il est plus facile de choisir le cadre propritaire lorsque vous tes prt afcher la bote de dialogue, plutt que lorsque vous construisez lobjet PasswordChooser. Lastuce consiste faire driver PasswordChooser de JPanel au lieu de JDialog. Crez un objet JDialog la vole dans la mthode showDialog :
public boolean showDialog(Frame owner, String title) { ok = false; if (dialog == null || dialog.getOwner()!= owner) { dialog = new JDialog(owner, true); dialog.add(this); dialog.pack(); } dialog.setTitle(title); dialog.setVisible(true); return ok; }

Notez que owner peut valoir null. Vous pouvez faire encore mieux. Il arrive que le cadre propritaire ne soit pas immdiatement disponible. Il est facile de le dterminer partir de nimporte quel composant parent, de la faon suivante :
Frame owner; if (parent instanceof Frame) owner = (Frame) parent; else owner = (Frame) SwingUtilities.getAncestorOfClass( Frame.class, parent);

Nous avons tir parti de cette amlioration dans notre programme. La classe JOptionPane utilise aussi ce mcanisme. De nombreuses botes de dialogue possdent un bouton par dfaut, automatiquement slectionn si lutilisateur appuie sur une touche de dclenchement (Entre dans la plupart des implmentations "look and feel"). Le bouton par dfaut est spcialement marqu, souvent par un soulign pais. Vous dnissez le bouton par dfaut dans le volet racine de la bote de dialogue :
dialog.getRootPane().setDefaultButton(okButton);

468

Au cur de Java 2 - Notions fondamentales

Si vous suivez notre suggestion de disposition de la bote de dialogue dans un panneau, vous devez prendre garde ne dnir le bouton par dfaut quaprs avoir envelopp le panneau dans une bote de dialogue. Le panneau lui-mme na pas de volet racine. Le Listing 9.16 contient la totalit du code illustrant le ot des donnes entrant et sortant dune bote de dialogue.
Listing 9.16 : DataExchangeTest.java
import java.awt.*; import java.awt.event.*; import javax.swing.*; /** * @version 1.33 2007-06-12 * @author Cay Horstmann */ public class DataExchangeTest { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { DataExchangeFrame frame = new DataExchangeFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); } } /** * Un cadre avec un menu dont loption File->Connect * affiche une bote de dialogue de mot de passe. */ class DataExchangeFrame extends JFrame { public DataExchangeFrame() { setTitle("DataExchangeTest"); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); // construire un menu File JMenuBar mbar = new JMenuBar(); setJMenuBar(mbar); JMenu fileMenu = new JMenu("File"); mbar.add(fileMenu); // ajouter les options Connect et Exit au menu JMenuItem connectItem = new JMenuItem("Connect"); connectItem.addActionListener(new ConnectAction()); fileMenu.add(connectItem); // Loption Exit permet de quitter le programme

Chapitre 9

Swing et les composants dinterface utilisateur

469

JMenuItem exitItem = new JMenuItem("Exit"); exitItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { System.exit(0); } }); fileMenu.add(exitItem); textArea = new JTextArea(); add(new JScrollPane(textArea), BorderLayout.CENTER); } public static final int DEFAULT_WIDTH = 300; public static final int DEFAULT_HEIGHT = 200; private PasswordChooser dialog = null; private JTextArea textArea; /** * Loption Connect affiche la bote de dialogue mot de passe. */ private class ConnectAction implements ActionListener { public void actionPerformed(ActionEvent event) { // Si premire fois, construire la bote de dialogue if (dialog == null) dialog = new PasswordChooser(); // dfinir les valeurs par dfaut dialog.setUser(new User("yourname", null)); // afficher la bote de dialogue if (dialog.showDialog(DataExchangeFrame.this, "Connect")) { // si accept, extraire lentre de lutilisateur User u = dialog.getUser(); textArea.append( "user name = "+u.getName() +", password = "+ (new String(u.getPassword())) +"\n"); } } } } /** * Un slecteur de mot de passe affich dans une bote de dialogue */ class PasswordChooser extends JPanel { public PasswordChooser() {

470

Au cur de Java 2 - Notions fondamentales

setLayout(new BorderLayout()); // construire un panneau avec les champs Nom dutilisateur // et Mot de passe JPanel panel = new JPanel(); panel.setLayout(new GridLayout(2, 2)); panel.add(new JLabel("User name:")); panel.add(username = new JTextField("")); panel.add(new JLabel("Password:")); panel.add(password = new JPasswordField("")); add(panel, BorderLayout.CENTER); // crer les boutons OK et Cancel pour // fermer la bote de dialogue okButton = new JButton("OK"); okButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { ok = true; dialog.setVisible(false); } }); JButton cancelButton = new JButton("Cancel"); cancelButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { dialog.setVisible(false); } }); // ajouter les boutons dans la zone sud JPanel buttonPanel = new JPanel(); buttonPanel.add(okButton); buttonPanel.add(cancelButton); add(buttonPanel, BorderLayout.SOUTH); } /** * Dfinit les valeurs par dfaut de la bote de dialogue. * @param u Les informations utilisateur par dfaut */ public void setUser(User u) { username.setText(u.getName()); } /** * Rcupre les entres dans la bote de dialogue. * @return Un objet User dont ltat reprsente * les entres dans la bote de dialogue */

Chapitre 9

Swing et les composants dinterface utilisateur

471

public User getUser() { return new User(username.getText(), password.getPassword()); } /** * Affiche le panneau slecteur dans une bote de dialogue * @param parent Un composant dans le cadre propritaire ou null * @param title Le titre de la bote de dialogue */ public boolean showDialog(Component parent, String title) { ok = false; // localiser le cadre propritaire Frame owner = null; if (parent instanceof Frame) owner = (Frame) parent; else owner = (Frame) SwingUtilities.getAncestorOfClass( Frame.class, parent); // si premire fois, ou si le propritaire a chang, // crer une nouvelle bote de dialogue if (dialog == null || dialog.getOwner()!= owner) { dialog = new JDialog(owner, true); dialog.add(this); dialog.getRootPane().setDefaultButton(okButton); dialog.pack(); } // dfinir le titre et afficher la bote de dialogue dialog.setTitle(title); dialog.setVisible(true); return ok; } private private private private private } /** * Un utilisateur a un nom et un mot de passe. Pour des raisons * de scurit, le mot de passe est stock sous forme de * tableau de caractres (char[]), au lieu dune chane. */ class User { public User(String aName, char[] aPassword) { name = aName; JTextField username; JPasswordField password; JButton okButton; boolean ok; JDialog dialog;

472

Au cur de Java 2 - Notions fondamentales

password = aPassword; } public String getName() { return name; } public char[] getPassword() { return password; } public void setName(String aName) { name = aName; } public void setPassword(char[] aPassword) { password = aPassword; } private String name; private char[] password; } javax.swing.SwingUtilities 1.2

Container getAncestorOfClass(Class c, Component comp)

Renvoie le conteneur parent le plus interne du composant donn qui appartient la classe donne ou lune de ses sous-classes.
javax.swing.JComponent 1.2

JRootPane getRootPane()

Rcupre le volet racine entourant ce composant ou null si ce composant ne possde pas danctre avec un volet racine.
javax.swing.JRootPane 1.2

void setDefaultButton(JButton button)

Dnit le bouton par dfaut pour ce volet racine. Pour dsactiver le bouton par dfaut, appelez cette mthode avec un paramtre null.
javax.swing.JButton 1.2

boolean isDefaultButton()

Renvoie true si ce bouton est le bouton par dfaut de son volet racine.

Botes de dialogue Fichier


Lorsque vous crivez une application, il est souvent ncessaire douvrir et denregistrer des chiers. Une bote de dialogue Fichier (File) bien conue, qui afche les chiers et les rpertoires et permet lutilisateur de naviguer dans le systme de chiers, est difcile dvelopper. Il est de plus totalement inutile de vouloir "rinventer la roue", puisque Swing propose une classe JFileChooser qui

Chapitre 9

Swing et les composants dinterface utilisateur

473

permet dafcher une bote de dialogue Fichier semblable celle quutilisent la plupart des applications natives. Les botes de dialogue JFileChooser sont toujours modales. Notez que cette classe nest pas une sous-classe de JDialog. Au lieu dappeler setVisible(true), vous appelez la mthode showOpenDialog pour afcher une bote de dialogue douverture de chier ou showSaveDialog pour afcher une bote de dialogue denregistrement de chier. Le bouton utilis pour accepter un chier est automatiquement libell Open (Ouvrir) ou Save (Enregistrer). Vous pouvez galement spcier votre propre intitul de bouton laide de la mthode showDialog. La Figure 9.40 montre un exemple de bote de dialogue de slection de chier.
Figure 9.40
Une bote de dialogue de slection de chier.

Voici les tapes suivre pour mettre en uvre une bote de dialogue Fichier et rcuprer la slection de lutilisateur : 1. Crez un objet JFileChooser. A la diffrence du constructeur de la classe JDialog, vous navez pas besoin dindiquer le composant parent, ce qui vous permet de rutiliser une mme bote de dialogue avec plusieurs cadres. Par exemple :
JFileChooser chooser = new JFileChooser();

ASTUCE
Rutiliser un objet slecteur de chier est une bonne ide, car le constructeur de JFileChooser peut se rvler assez lent, surtout sous Windows, si lutilisateur a de nombreux mappages dunits rseau.

2. Dnissez le rpertoire de travail en appelant la mthode setCurrentDirectory. Utilisez le rpertoire de travail courant, par exemple
chooser.setCurrentDirectory(new File("."));

Vous devez fournir un objet File (voir Chapitre 12). Tout ce quil vous faut savoir est quil existe un constructeur File(String filename), qui transforme un nom de chier ou de rpertoire en un objet File.

474

Au cur de Java 2 - Notions fondamentales

3. Si lutilisateur peut choisir un nom de chier par dfaut, appelez la mthode setSelectedFile de la faon suivante :
chooser.setSelectedFile(new File(filename));

4. Pour permettre lutilisateur de slectionner plusieurs chiers, appelez la mthode setMultiSelectionEnabled. Son utilisation est peu courante, et bien sr optionnelle :
chooser.setMultiSelectionEnabled(true);

5. Si vous voulez limiter lafchage un type de chier particulier (par exemple tous les chiers avec lextension .gif), vous devez alors dnir un ltre de chier, dont nous reparlerons plus loin dans cette section. 6. Par dfaut, un utilisateur ne peut slectionner que des chiers dans un slecteur de chier. Si vous voulez quil soit capable de slectionner des rpertoires, utilisez la mthode setFileSelectionMode, de la faon suivante : appelez-la avec JFileChooser.FILES_ONLY (chiers seulement, par dfaut), JFileChooser.DIRECTORIES_ONLY (rpertoires seulement) ou JFileChooser.FILES_AND_DIRECTORIES (les deux). 7. Afchez la bote de dialogue en appelant la mthode showOpenDialog ou showSaveDialog. Vous devez indiquer le composant parent pour ces appels :
int result = chooser.showOpenDialog(parent);

ou
int result = chooser.showSaveDialog(parent);

La seule diffrence entre ces appels est le libell du "bouton dapprobation", celui sur lequel lutilisateur clique pour mettre n la slection de chier. Vous pouvez aussi appeler la mthode showDialog et passer un texte explicite pour le bouton dapprobation :
int result = chooser.showDialog(parent, "Select");

A la suite de ces appels, le ux dexcution du programme ne revient pas tant que lutilisateur na pas accept ou refus un chier ou encore annul son action. La valeur renvoye est JFileChooser.APPROVE_OPTION, JFileChooser.CANCEL_OPTION ou JFileChooser.ERROR_OPTION. 8. Vous rcuprez le ou les chiers slectionns au moyen de la mthode getSelectedFile() ou getSelectedFiles(). Ces mthodes renvoient soit un seul objet File, soit un tableau dobjets File. Si vous avez uniquement besoin du nom de lobjet chier, appelez sa mthode getPath. Par exemple :
String filename = chooser.getSelectedFile().getPath();

Ces tapes sont dans lensemble assez simples. La principale difcult de limplmentation dune bote de dialogue Fichier rside dans la spcication dun sous-ensemble de chiers partir duquel lutilisateur pourra choisir. Supposons quil ait choisir un chier dimages GIF. Le slecteur de chier ne devrait alors afcher que les chiers dots de lextension .gif. Il faudrait aussi quil puisse signier lutilisateur que les chiers afchs sont dune certaine catgorie, comme "Images GIF". Mais la situation est parfois plus complexe. Si lutilisateur doit choisir un chier dimages JPEG, lextension peut tre soit .jpg, soit .jpeg. Plutt que dlaborer un mcanisme permettant de codier ces complexits, les concepteurs fournissent un mcanisme plus lgant : pour limiter les chiers afchs, vous fournissez un objet qui tend la classe abstraite javax.swing.filechooser.FileFilter. Le slecteur de chier passe chaque chier au ltre et afche seulement ceux quil a accepts.

Chapitre 9

Swing et les composants dinterface utilisateur

475

Au moment o ce livre est crit, deux sous-classes de ce type sont fournies : le ltre par dfaut, qui accepte tous les chiers, et un ltre qui accepte tous les chiers ayant une extension donne. De plus, il est parfois plus facile dcrire des ltres de chiers ad hoc. Vous pouvez toutefois facilement crire vos propres ltres. Il suft dimplmenter les deux mthodes abstraites de la superclasse FileFilter :
public boolean accept(File f); public String getDescription();

La premire mthode vrie si un chier doit tre accept. La seconde renvoie une description du type de chier qui peut tre afch dans la bote de dialogue.
INFO
Il existe dans le package java.io une interface FileFilter, sans aucun rapport, et qui possde une seule mthode, boolean accept(File f). Elle est utilise dans la mthode listFiles de la classe File pour numrer les chiers dun rpertoire. On peut se demander pourquoi les concepteurs de Swing nont pas tendu cette interface peut-tre que la bibliothque de classes Java est devenue maintenant si complexe que mme les programmeurs de Sun ne connaissent pas toutes les classes et interfaces standard. Vous devrez rsoudre le conit de nom entre ces deux types homonymes, si vous importez la fois les packages

java.io et javax.swing.filechooser. Le remde le plus simple consiste importer javax.swing.filechooser.FileFilter, et non javax.swing.filechooser.*.

Une fois que vous disposez dun objet ltre de chier, utilisez la mthode setFileFilter de la classe JFileChooser pour limplmenter dans lobjet slecteur de chier :
chooser.setFileFilter(new FileNameExtensionFilter ("Image files", "gif", "jpg");

Vous pouvez installer plusieurs ltres dans le slecteur de chier en appelant


chooser.addChoosableFileFilter(filter1); chooser.addChoosableFileFilter(filter2); . . .

Lutilisateur slectionne un ltre dans la zone de liste droulante situe en bas de la bote de dialogue. Par dfaut, le ltre "All les" (Tous chiers) est toujours prsent dans la liste. Il est recommand de procder ainsi pour toutes les botes de dialogue Fichier, pour le cas o un utilisateur de votre programme aurait besoin de slectionner un chier dont lextension nest pas standard. Si toutefois vous vouliez supprimer cette option "Tous chiers", appelez
chooser.setAcceptAllFileFilterUsed(false)

ATTENTION
Si vous rutilisez un slecteur de chier unique pour charger et enregistrer diffrents types de chiers, appelez
chooser.resetChoosableFilters()

pour effacer tout ltre de chier ancien avant den ajouter de nouveaux.

Enn, vous pouvez personnaliser la bote de dialogue de slection de chier en y ajoutant des icnes spciales et une description pour chaque chier afch. Pour cela, vous devez fournir un objet dune

476

Au cur de Java 2 - Notions fondamentales

classe qui tend la classe FileView du package javax.swing.filechooser. Il sagit l dune technique avance. Normalement, vous navez pas fournir une vue de chier, car le style dinterface dynamique implment (LookAndFeel) sen charge votre place. Mais si vous souhaitez afcher diffrentes icnes pour des types de chiers particuliers, vous pouvez implmenter votre propre vue de chier. Vous devez tendre la classe FileView et implmenter cinq mthodes :
Icon getIcon(File f); String getName(File f); String getDescription(File f); String getTypeDescription(File f); Boolean isTraversable(File f);

Vous appelez ensuite la mthode setFileView pour implmenter votre vue de chier dans le slecteur de chier. Le slecteur appelle vos mthodes pour chaque chier ou rpertoire quil souhaite afcher. Si une mthode relative licne, au nom ou la description renvoie null, le slecteur consulte alors la vue de chier par dfaut du style dinterface implment. Tout cela signie que vous ne devez grer que les types de chiers auxquels vous rservez un traitement diffrent. Le slecteur de chier appelle la mthode isTraversable pour dcider de louverture dun rpertoire lorsquun utilisateur clique dessus. Notez que cette mthode renvoie un objet Boolean et non pas une valeur boolean! Lopration parat trange, elle est pourtant approprie. Si vous voulez conserver la vue de chier par dfaut, contentez-vous de renvoyer null. Le slecteur de chier consultera alors la vue par dfaut. En dautres termes, la mthode renvoie un objet Boolean pour vous donner le choix entre trois options : vrai (Boolean.TRUE), faux (Boolean.FALSE) ou null si cela na pas dimportance. Lexemple de programme contient une simple classe vue de chier. Elle afche une icne particulire chaque fois quun chier correspond un ltre. Nous lutilisons pour afcher une icne palette pour tous les chiers images :
class FileIconView extends FileView { public FileIconView(FileFilter aFilter, Icon anIcon) { filter = aFilter; icon = anIcon; } public Icon getIcon(File f) { if (!f.isDirectory() && filter.accept(f)) return icon; else return null; } private FileFilter filter; private Icon icon; }

Vous installez cette vue de chier dans votre slecteur de chier laide de la mthode setFileView :
chooser.setFileView(new FileIconView(filter, new ImageIcon("palette.gif")));

Chapitre 9

Swing et les composants dinterface utilisateur

477

Le slecteur de chier afchera alors licne "palette" en regard des chiers Java qui passent le ltre et utilisera la vue de chier par dfaut pour tous les autres chiers. Nous utilisons bien entendu le mme ltre que celui dni dans le slecteur de chier.
ASTUCE
Vous trouverez dans le rpertoire demo/jfc/FileChooserDemo du JDK, la classe ExampleFileView qui permet dassocier des icnes et des descriptions des extensions arbitraires.

Enn, vous pouvez personnaliser une bote de dialogue Fichier en ajoutant un composant accessoire. Par exemple, la Figure 9.41 montre un accessoire daperu en regard de la liste de chiers. Cet accessoire afche une vue miniature du chier actuellement slectionn.
Figure 9.41
Une bote de dialogue Fichier avec un accessoire daperu.

Un accessoire peut tre nimporte quel composant Swing. Dans notre exemple, nous tendons la classe JLabel et affectons son icne une copie rduite de limage graphique :
class ImagePreviewer extends JLabel { public ImagePreviewer(JFileChooser chooser) { setPreferredSize(new Dimension(100, 100)); setBorder(BorderFactory.createEtchedBorder()); } public void loadImage(File f) { ImageIcon icon = new ImageIcon(f.getPath()); if(icon.getIconWidth() > getWidth()) icon = new ImageIcon(icon.getImage().getScaledInstance( getWidth(), -1, Image.SCALE_DEFAULT)); setIcon(icon); repaint(); } }

478

Au cur de Java 2 - Notions fondamentales

Il reste un d relever. Nous voulons mettre jour limage daperu chaque fois que lutilisateur slectionne un chier diffrent. Le slecteur de chier utilise le mcanisme "Java Beans" de notication aux couteurs intresss chaque fois quune de ses proprits change. Le chier slectionn est une proprit que vous pouvez surveiller en installant un PropertyChangeListener. Nous reviendrons sur ce mcanisme plus en dtail au Chapitre 8 du Volume II. Voici le code ncessaire pour intercepter les notications :
chooser.addPropertyChangeListener(new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent event) { if (event.getPropertyName() == JFileChooser.SELECTED_FILE_CHANGED_PROPERTY) { File newFile = (File) event.getNewValue() // mettre jour laccessoire ... } } });

Dans notre exemple de programme, nous ajoutons ce code au constructeur de ImagePreviewer. Le Listing 9.17 contient une adaptation du programme ImageViewer du Chapitre 2, dans laquelle le slecteur de chier a t amlior avec une vue personnalise du chier et un accessoire daperu.
Listing 9.17 : FileChooserTest.java
import import import import import import import import java.awt.*; java.awt.event.*; java.beans.*; java.util.*; java.io.*; javax.swing.*; javax.swing.filechooser.*; javax.swing.filechooser.FileView;

/** * @version 1.23 2007-06-12 * @author Cay Horstmann */ public class FileChooserTest { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { ImageViewerFrame frame = new ImageViewerFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); } }

Chapitre 9

Swing et les composants dinterface utilisateur

479

/** * Un cadre avec un menu pour le chargement dune image et * une zone daffichage pour limage charge. */ class ImageViewerFrame extends JFrame { public ImageViewerFrame() { setTitle("FileChooserTest"); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); // configurer la barre de menus JMenuBar menuBar = new JMenuBar(); setJMenuBar(menuBar); JMenu menu = new JMenu("File"); menuBar.add(menu); JMenuItem openItem = new JMenuItem("Open"); menu.add(openItem); openItem.addActionListener(new FileOpenListener()); JMenuItem exitItem = new JMenuItem("Exit"); menu.add(exitItem); exitItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { System.exit(0); } }); // utiliser un label pour afficher les images label = new JLabel(); add(label); // configurer le slecteur de fichier chooser = new JFileChooser(); // accepter tous les fichiers images avec // lextension .jpg, .jpeg, .gif /* final ExtensionFileFilter filter = new ExtensionFileFilter(); filter.addExtension("jpg"); filter.addExtension("jpeg"); filter.addExtension("gif"); filter.setDescription("Image files"); */ FileNameExtensionFilter filter = new FileNameExtensionFilter( "Image files", "jpg", "jpeg", "gif"); chooser.setFileFilter(filter); chooser.setAccessory(new ImagePreviewer(chooser)); chooser.setFileView(new FileIconView(filter, new ImageIcon("palette.gif"))); }

480

Au cur de Java 2 - Notions fondamentales

/** * Ecouteur du menu File->Open. */ private class FileOpenListener implements ActionListener { public void actionPerformed(ActionEvent event) { chooser.setCurrentDirectory(new File(".")); // afficher la bote de dialogue de slection de fichier int result = chooser.showOpenDialog(ImageViewerFrame.this); // si le fichier image est accept, le dfinir comme // icne pour le label if(result == JFileChooser.APPROVE_OPTION) { String name = chooser.getSelectedFile().getPath(); label.setIcon(new ImageIcon(name)); } } } public static final int DEFAULT_WIDTH = 300; public static final int DEFAULT_HEIGHT = 400; private JLabel label; private JFileChooser chooser; } /** * Une vue du fichier qui affiche une icne pour tous les * fichiers qui correspondent un filtre */ class FileIconView extends FileView { /** * Construit un FileIconView. * @param aFilter Un filtre de fichier - tous les fichiers que * ce filtre accepte safficheront avec licne * @param anIcon Licne affiche avec les fichiers reconnus. */ public FileIconView(FileFilter aFilter, Icon anIcon) { filter = aFilter; icon = anIcon; } public Icon getIcon(File f) { if (!f.isDirectory() && filter.accept(f)) return icon; else return null; } private FileFilter filter; private Icon icon; }

Chapitre 9

Swing et les composants dinterface utilisateur

481

/** * Un accessoire de slecteur de fichier donnant un aperu des images. */ class ImagePreviewer extends JLabel { /** * Construit un ImagePreviewer. * @param chooser Le slecteur de fichier dont la proprit change, * dclenche un changement de limage dans laperu */ public ImagePreviewer(JFileChooser chooser) { setPreferredSize(new Dimension(100, 100)); setBorder(BorderFactory.createEtchedBorder()); chooser.addPropertyChangeListener(new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent event) { if (event.getPropertyName() == JFileChooser.SELECTED_FILE_CHANGED_PROPERTY) { // lutilisateur a slectionn un nouveau fichier File f = (File) event.getNewValue(); if (f == null) { setIcon(null); return; } // rcuprer limage sous forme dicne ImageIcon icon = new ImageIcon(f.getPath()); // si licne est trop grande, la rduire if (icon.getIconWidth() > getWidth()) icon = new ImageIcon( icon.getImage().getScaledInstance( getWidth(), -1, Image.SCALE_DEFAULT)); setIcon(icon); } } }); } } javax.swing.JFileChooser 1.2 JFileChooser()

Cre une bote de dialogue de slection de chier qui peut tre employe pour plusieurs cadres.
void setCurrentDirectory(File dir)

Dnit le rpertoire initial de la bote de dialogue Fichier.


void setSelectedFile(File file) void setSelectedFiles(File[] file)

Dnissent le choix de chier par dfaut de la bote de dialogue Fichier.

482

Au cur de Java 2 - Notions fondamentales

void setMultiSelectionEnabled(boolean b)

Active ou dsactive le mode de slection multiple.


void setFileSelectionMode(intmode)

Permet lutilisateur de slectionner des chiers seulement (par dfaut), des rpertoires seulement ou les deux. Le paramtre mode est lune des constantes JFileChooser.FILES_ONLY, JFileChooser.DIRECTORIES_ONLY et JFileChooser.FILES_AND_DIRECTORIES.

int showOpenDialog(Component parent) int showSaveDialog(Component parent) int showDialog(Component parent, String approveButtonText)

Afchent une bote de dialogue dans laquelle le bouton dapprobation est libell "Open", "Save", ou avec la chane approveButtonText. Renvoient APPROVE_OPTION, CANCEL_OPTION (si lutilisateur a choisi le bouton Annuler ou renvoy la bote de dialogue) ou ERROR_OPTION (en cas derreur).

File getSelectedFile() File[] getSelectedFiles()

Rcuprent le ou les chiers slectionns par lutilisateur (ou renvoient null sil na slectionn aucun chier).

void setFileFilter(FileFilter filter)

Dnit le ltre de chier pour la bote de dialogue. Tous les chiers pour lesquels filter.accept renvoie true seront afchs. Ajoute galement le ltre la liste des ltres slectionnables.

void addChoosableFileFilter(FileFilter filter)

Ajoute un ltre de chier la liste des ltres slectionnables.


void setAcceptAllFileFilterUsed(boolean b)

Inclut ou supprime un ltre sur tous les chiers dans la liste droulante des ltres.
void resetChoosableFileFilters()

Efface la liste des ltres slectionnables. Seul le ltre de tous les chiers demeure, moins quil ne soit explicitement supprim.

void setFileView(FileView view)

Dnit une vue de chier pour fournir des informations sur les chiers afchs par le slecteur de chier.

void setAccessory(JComponent component)

Dnit un composant accessoire.


javax.swing.filechooser.FileFilter 1.2

boolean accept(File f)

Renvoie true si le slecteur de chier doit afcher le chier.


String getDescription()

Renvoie une description du ltre de chier, par exemple "Fichiers images (*.gif, *.jpeg)".

Chapitre 9

Swing et les composants dinterface utilisateur

483

javax.swing.filechooser.FileNameExtensionFilter 6

FileNameExtensionFilter(String description, String ... extensions)

Construit un ltre de chier avec les descriptions donnes, qui accepte tous les rpertoires et tous les chiers dont les noms se terminent par un point, suivi de lune des chanes dextension.
javax.swing.filechooser.FileNameExtensionFilter 6

FileNameExtensionFilter(String description, String ... extensions)

Construit un ltre de chier avec les descriptions donnes, qui accepte tous les rpertoires et tous les chiers dont les noms se terminent par un point, suivi de lune des chanes dextension.
javax.swing.filechooser.FileView 1.2

String getName(File f)

Renvoie le nom du chier f ou null. Normalement, cette mthode renvoie simplement f.getName().

String getDescription(File f)

Renvoie une description lisible du chier f ou null. Par exemple, si f est un document HTML, cette mthode peut renvoyer son titre.

String getTypeDescription(File f)

Renvoie une description lisible du chier f ou null. Par exemple, si f est un document HTML, cette mthode peut renvoyer une chane "Document hypertexte".

Icon getIcon(File f)

Renvoie une icne pour le chier f ou null. Par exemple, si f est un chier JPEG, cette mthode peut renvoyer une icne.

Boolean isTraversable(File f)

Renvoie Boolean.TRUE si f est un rpertoire que lutilisateur peut ouvrir. Cette mthode peut renvoyer false si le rpertoire est, dun point de vue conceptuel, un document compos. Comme toutes les mthodes FileView, celle-ci peut renvoyer null pour indiquer que le slecteur de chier doit adopter la vue de chier par dfaut.

Slecteurs de couleur
Comme vous lavez vu dans la section prcdente, un slecteur de chier performant est un composant dinterface utilisateur complexe et il est hors de question que vous limplmentiez vous-mme. De nombreuses botes outils dinterface utilisateur fournissent dautres botes de dialogue communes pour slectionner une date/heure, une valeur montaire, une police, une couleur, etc. Lintrt est double. Les programmeurs peuvent simplement utiliser une implmentation de qualit, plutt que de crer la leur propre. Et les utilisateurs ont une exprience intuitive de ces outils de slection. Actuellement, Swing ne fournit quun seul slecteur supplmentaire, JColorChooser (voir Figures 9.42 9.44). Il permet aux utilisateurs de choisir une valeur de couleur. Tout comme la classe JFileChooser, le slecteur de couleur est un composant, et non une bote de dialogue, mais il contient des mthodes pour crer des botes de dialogue contenant un composant pour la slection dune couleur.

484

Au cur de Java 2 - Notions fondamentales

Figure 9.42
Le panneau "chantillons" dun slecteur de couleur.

Figure 9.43
Le panneau HSB (teintesaturation-luminance) dun slecteur de couleur.

Voici comment afcher une bote de dialogue modale avec un slecteur de couleur :
Color selectedColor = JColorChooser.showDialog( parent, title, initialColor);

Vous pouvez aussi afcher une bote de dialogue slecteur de couleur non modale. Vous devez fournir :
m m m m m

un composant parent ; le titre de la bote de dialogue ; un indicateur pour slectionner une bote de dialogue, soit modale, soit non modale ; un slecteur de couleur ; des couteurs pour les boutons OK et Annuler (ou null si vous ne voulez pas dcouteur).

Chapitre 9

Swing et les composants dinterface utilisateur

485

Figure 9.44
Le panneau RGB (rouge-vert-bleu) dun slecteur de couleur.

Voici comment crer une bote de dialogue non modale qui dnit la couleur darrire-plan lorsque lutilisateur clique sur le bouton OK :
chooser = new JColorChooser(); dialog = JColorChooser.createDialog( parent, "Background color", false /* non modale */, chooser, new ActionListener() // couteur du bouton OK { public void actionPerformed(ActionEvent event) { setBackground(chooser.getColor()); } }, null /* pas dcouteur pour le bouton Annuler */);

Vous pouvez faire encore mieux et renvoyer immdiatement lutilisateur des informations sur la slection de couleur. Pour surveiller les slections de couleur, vous devez rcuprer le modle de slection du slecteur et ajouter un couteur de changement :
chooser.getSelectionModel().addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent event) { faire quelque chose avec chooser.getColor(); } });

Dans ce cas, les boutons OK et Cancel fournis par la bote de dialogue de slection de couleur nont aucun intrt. Vous pouvez simplement ajouter le composant slecteur de couleur dans une bote de dialogue non modale :

486

Au cur de Java 2 - Notions fondamentales

dialog = new JDialog(parent, false /* non modale */); dialog.add(chooser); dialog.pack();

Le programme du Listing 9.18 montre les trois types de botes de dialogue. Si vous cliquez sur le bouton Modal, vous devrez slectionner une couleur avant de faire quoi que ce soit dautre. Si vous cliquez sur le bouton Modeless, vous obtenez une bote de dialogue non modale, mais le changement de couleur ne se produira que lorsque vous cliquez sur le bouton OK de la bote de dialogue. Si vous choisissez le bouton Immediate, vous obtenez une bote de dialogue non modale sans boutons, et ds que vous slectionnerez une couleur diffrente, larrire-plan du panneau sera mis jour.
Listing 9.18 : ColorChooserTest.java
import import import import java.awt.*; java.awt.event.*; javax.swing.*; javax.swing.event.*;

/** * @version 1.03 2007-06-12 * @author Cay Horstmann */ public class ColorChooserTest { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { ColorChooserFrame frame = new ColorChooserFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); } } /** * Un cadre avec un panneau slecteur de couleur */ class ColorChooserFrame extends JFrame { public ColorChooserFrame() { setTitle("ColorChooserTest"); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); // ajouter le panneau slecteur de couleur au cadre ColorChooserPanel panel = new ColorChooserPanel(); add(panel); } public static final int DEFAULT_WIDTH = 300; public static final int DEFAULT_HEIGHT = 200; }

Chapitre 9

Swing et les composants dinterface utilisateur

487

/** * Un panneau avec des boutons pour afficher * trois types de slecteurs de couleur */ class ColorChooserPanel extends JPanel { public ColorChooserPanel() { JButton modalButton = new JButton("Modal"); modalButton.addActionListener(new ModalListener()); add(modalButton); JButton modelessButton = new JButton("Modeless"); modelessButton.addActionListener(new ModelessListener()); add(modelessButton); JButton immediateButton = new JButton("Immediate"); immediateButton.addActionListener(new ImmediateListener()); add(immediateButton); } /** * Cet couteur affiche un slecteur de couleur modal */ private class ModalListener implements ActionListener { public void actionPerformed(ActionEvent event) { Color defaultColor = getBackground(); Color selected = JColorChooser.showDialog( ColorChooserPanel.this, "Set background", defaultColor); if (selected!= null) setBackground(selected); } } /** * Cet couteur affiche un slecteur de couleur non modal. * La couleur du panneau est change lorsque lutilisateur * clique sur le bouton OK. */ private class ModelessListener implements ActionListener { public ModelessListener() { chooser = new JColorChooser(); dialog = JColorChooser.createDialog( ColorChooserPanel.this, "Background color", false /* non modal */, chooser, new ActionListener() // couteur bouton OK { public void actionPerformed(ActionEvent event) { setBackground(chooser.getColor()); } },

488

Au cur de Java 2 - Notions fondamentales

null /* pas dcouteur pour le bouton Annuler */); } public void actionPerformed(ActionEvent event) { chooser.setColor(getBackground()); dialog.setVisible(true); } private JDialog dialog; private JColorChooser chooser; } /** * Cet couteur affiche un slecteur de couleur non modal. * La couleur du panneau change ds que lutilisateur * slectionne une nouvelle couleur. */ private class ImmediateListener implements ActionListener { public ImmediateListener() { chooser = new JColorChooser(); chooser.getSelectionModel().addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent event) { setBackground(chooser.getColor()); } }); dialog = new JDialog( (Frame)null, false /* non modal */); dialog.add(chooser); dialog.pack(); } public void actionPerformed(ActionEvent event) { chooser.setColor(getBackground()); dialog.setVisible(true); } private JDialog dialog; private JColorChooser chooser; } } java.swing.JColorChooser 1.2

JColorChooser()

Construit un slecteur de couleur avec le blanc comme couleur initiale.


Color getColor() void setColor(Color c)

Permettent dobtenir et de dnir la couleur actuelle de ce slecteur de couleur.

Chapitre 9

Swing et les composants dinterface utilisateur

489

static Color showDialog(Component parent, String title, Color initialColor)

Afche une bote de dialogue modale qui contient un slecteur de couleur. Paramtres : parent title initialColor

Le composant sur lequel doit apparatre la bote de dialogue. Le titre de la bote de dialogue. La couleur initiale afcher dans le slecteur de couleur.

static JDialog createDialog(Component parent, String title, boolean modal, JColorChooser chooser, ActionListener okListener, ActionListener cancelListener)

Cre une bote de dialogue qui contient un slecteur de couleur. Paramtres : parent title modal chooser Le composant o doit apparatre la bote de dialogue. Le titre de la bote de dialogue. true si cet appel doit empcher toute autre action jusqu la fermeture de la bote de dialogue. Le slecteur de couleur ajouter la bote de dialogue.

okListener cancelListener Les couteurs des boutons OK et Cancel. Ici se termine notre tude des composants dinterface utilisateur. Les informations des Chapitres 7 9 vous ont appris comment implmenter des interfaces utilisateur graphiques simples avec Swing. Reportez-vous au Volume II pour tout ce qui concerne les composants Swing avancs et les techniques graphiques sophistiques.

10
Dployer des applets et des applications
Au sommaire de ce chapitre

Les chiers JAR Java Web Start Les applets Stockage des prfrences des applications
En principe, vous utilisez sans difcult la quasi-totalit des caractristiques du langage Java. De plus, vous avez bnci dune solide introduction aux bases de la programmation graphique en Java. Vous tes maintenant prt crer des applications pour vos utilisateurs, vous devez donc les emballer pour pouvoir les dployer sur leurs ordinateurs. Le choix traditionnel pour le dploiement, responsable de lincroyable succs de Java, durant les premires annes de son existence, provient des applets. Un applet est un type spcial de programme quun navigateur Web qui accepte Java peut tlcharger sur le Net et excuter. Les utilisateurs devaient ainsi tre librs des tracasseries de linstallation dun logiciel et allaient pouvoir avoir accs au logiciel depuis tout ordinateur ou priphrique Java possdant une connexion Internet. Pour plusieurs raisons, les applets nont jamais vraiment rpondu ces attentes. Cest pourquoi nous commenons ce chapitre par des instructions sur lemballage des applications. Nous passerons ensuite au mcanisme Java Web Start, une mthode alternative la livraison dapplications Internet, qui corrige certains problmes des applets. Enn, nous traiterons des applets et nous montrerons les cas dans lesquels vous continuerez les utiliser. Nous verrons galement comment vos applications peuvent stocker des informations de conguration et des prfrences utilisateur.

492

Au cur de Java 2 - Notions fondamentales

Les chiers JAR


Lorsque vous packagez votre application, vous devez proposer aux utilisateurs un chier unique, et non une structure de rpertoire comprenant des chiers de classe. Les chiers JAR (Java Archive) ont t conus dans ce but. Ils peuvent contenir la fois des chiers de classe et dautres types de chiers comme des chiers image et audio. Ils peuvent tre galement compresss au format de compression classique ZIP.
ASTUCE
Java SE 5.0 a introduit un nouveau programme de compression, appel "pack200", spciquement cr pour compresser les chiers de classe plus efcacement que lalgorithme de compression ZIP gnrique. Sun se targue dun taux de compression proche de 90 % pour les chiers de classe. Voir le site http://java.sun.com/javase/6/docs/ technotes/guide/deployment/deployment-guide/pack200.html pour plus dinformations.

Loutil jar permet de crer des chiers JAR (dans linstallation par dfaut, il se trouve dans le rpertoire jdk/bin). Voici la commande la plus courante pour crer un nouveau chier JAR :
jar cf JARFileName File1 File2 . . .

Exemple :
jar cvf CalculatorClasses.jar *.class icon.gif

La commande jar doit gnralement se conformer la syntaxe suivante :


jar options File1 File2 . . .

Le Tableau 10.1 donne toutes les options possibles pour le programme jar. Elles sont analogues aux options de la commande UNIX tar. Vous pouvez packager les programmes dapplication, les composants de programme (parfois appels "beans", voir le Chapitre 8 du Volume II) et des bibliothques de codes en chiers JAR. La bibliothque dexcution du JDK est parfois contenue dans un trs volumineux chier rt.jar.
Tableau 10.1 : Les options du programme jar

Option c C

Description Cre une nouvelle archive vide et y place des chiers. Si lun des noms de chiers indiqus est un rpertoire, le programme jar le traite de faon rcursive. Change temporairement le rpertoire. Par exemple : jar cvf JARFileName.jar -C classes *.class change en direction du sous-rpertoire classes pour ajouter les chiers classe. Cre un point dentre dans le manifeste (voir "Fichiers JAR excutables"). Spcie le nom du chier JAR comme second argument de la ligne de commande. Par dfaut, jar crit le rsultat sur la sortie standard (lors de la cration dun chier JAR) ou lit partir de lentre standard (lors de lextraction dun chier JAR). Cre un chier dindice (pour acclrer les recherches dans une grande archive).

e f

Chapitre 10

Dployer des applets et des applications

493

Tableau 10.1 : Les options du programme jar (suite)

Option m

Description Ajoute un chier manifeste au chier JAR. Un manifeste est la description du contenu et de lorigine de larchive. Toute archive possde un manifeste par dfaut, mais vous pouvez en fournir un spcial si vous souhaitez authentier le contenu de larchive. Ne cre pas de manifeste pour les entres. Afche la table des matires. Met jour un chier JAR existant. Sortie de messages dtaille. Extrait les chiers. Si vous fournissez un ou plusieurs noms de chiers, seuls ces derniers seront extraits. Sinon, extrait tous les chiers. Stockage sans compression ZIP.

M t u v x 0

Le manifeste
Un chier JAR contient des classes, des images et dautres ressources, ainsi quun chier manifeste qui dcrit les caractristiques particulires de larchive. Le chier manifeste est appel MANIFEST.MF et se trouve dans un sous-rpertoire META-INF du chier JAR. Le manifeste minimal lgal est un peu terne :
Manifest-version: 1.0

Des manifestes complexes peuvent possder davantage de donnes. Celles-ci sont regroupes en sections. La premire sappelle Main. Elle sapplique la totalit du chier JAR. Dautres donnes peuvent spcier les proprits des entits nommes telles que les chiers individuels, les packages ou les URL. Ces donnes doivent commencer par une entre Name. Les sections sont spares par des lignes blanches. Par exemple :
Manifest-Version: 1.0

lignes dcrivant cette archive


Name: Woozle.class

lignes dcrivant ce chier


Name: com/mycompany/mypkg

lignes dcrivant ce package Pour diter le manifeste, placez les lignes concernes dans un chier texte. Puis excutez :
jar cfm JARFileName ManifestFileName . . .

Par exemple, pour crer un nouveau chier JAR avec un manifeste, excutez :
jar cfm MyArchive.jar manifest.mf com/mycompany/mypkg/*.class

Pour actualiser un chier JAR existant, placez les ajouts dans un chier texte et utilisez une commande telle que
jar ufm MyArchive.jar manifest-additions.mf

494

Au cur de Java 2 - Notions fondamentales

INFO
Voyez ladresse http://java.sun.com/javase/6/docs/technotes/guides/jar pour en savoir plus sur le JAR et les formats de chier manifeste.

Fichiers JAR excutables


Depuis Java SE 6, vous pouvez utiliser loption e de la commande jar pour spcier le point dentre de votre programme la classe qui doit tre invoque en premier par le lanceur de programme Java.
jav cvfe MyProgram.jar com.mycompany.mypkg.MainAppClass

chiers ajouter

Les utilisateurs peuvent maintenant simplement lancer le programme sous la forme :


java -jar MyProgram.jar

Dans les anciennes versions du JDK, vous deviez spcier la classe principale de votre programme, en suivant cette procdure :
Main-Class: com.mycompany.mypkg.MainAppClass

Najoutez pas dextension .class au nom de la classe principale. Excutez ensuite la commande jar :
jar cvfm MyProgram.jar mainclass.mf

chiers ajouter

ATTENTION
La dernire ligne du manifeste doit se terminer par un caractre de nouvelle ligne, faute de quoi le manifeste ne pourra pas tre lu correctement. Produire un chier texte contenant simplement la ligne Main-Class sans terminaison est une erreur commune.

Dans certaines congurations du systme dexploitation, vous pourrez lancer lapplication en double-cliquant sur licne du chier JAR. Voici les comportements pour divers systmes dexploitation :
m

Sous Windows, linstallateur dexcution Java cre une association de chier pour lextension ".jar" qui lance le chier avec la commande javaw -jar ( la diffrence de la commande java, la commande javaw nouvre pas de fentre shell). Sous Solaris, le systme dexploitation reconnat le "nombre magique" dun chier JAR et le lance avec la commande java -jar. Sous Mac OS X, le systme dexploitation reconnat lextension de chier ".jar" et excute le programme Java lorsque vous double-cliquez sur un chier JAR.

Toutefois, un programme Java dun chier JAR ne possde pas le mme aspect quune application native. Sous Windows, vous pouvez employer des utilitaires denveloppe tiers qui transforment les chiers JAR en excutables Windows. Une enveloppe est un programme Windows possdant lextension .exe bien connue qui localise et lance la machine virtuelle Java (JVM) ou indique lutilisateur ce quil faut faire lorsquon ne trouve aucune JVM. Il existe plusieurs produits open source et payants, comme JSmooth (http://jsmooth.sourceforge.net) et Launch4J (http://launch4j.sourceforge.net).

Chapitre 10

Dployer des applets et des applications

495

Le gnrateur de linstallateur open source IzPack (http://izpack.org) contient galement un lanceur natif. Pour plus dinformations sur le sujet, voir le site http://www.javalobby.org/articles/java2exe. Sous Macintosh, la situation est un peu plus simple. Lutilitaire de package dapplication MRJAppBuilder permet de transformer un chier JAR en une application Mac de premier niveau. Pour plus dinformations, voir http://java.sun.com/developer/technicalArticles/JavaLP/JavaToMac3.

Les ressources
Les classes employes la fois dans les applets et les applications utilisent gnralement des chiers de donnes associs tels que :
m m m

des chiers dimages et de son ; des chiers de texte contenant les messages et les libells des boutons ; des chiers de donnes binaires, par exemple pour indiquer la disposition dune carte.

En Java, un chier associ de ce type est appel une ressource.


INFO
Sous Windows, le terme de "ressource" a une signication plus spcialise. Les ressources Windows sont galement constitues dimages, de libells de boutons, etc., mais elles sont attaches au chier excutable ; ce dernier y accde travers une interface de programmation standard. Les ressources Java sont stockes en tant que chiers spars, ne faisant pas partie des chiers de classe ; chaque classe a la charge dy accder et dinterprter les donnes de la ressource.

Supposons, par exemple, une classe AboutPanel afchant le message suivant (voir Figure 10.1) :
Figure 10.1
Afcher une ressource situe dans un chier JAR.

Le titre de ce livre ainsi que lanne du copyright changeront certainement avec la prochaine dition du livre. Pour pouvoir facilement suivre ce changement, nous allons mettre le texte dans un chier et non pas le coder en dur dans une chane de la classe dialogue. O devons-nous dposer un chier tel que about.txt ? Bien sr, il serait pratique de le placer au mme endroit que les autres programmes, par exemple dans un chier JAR.

496

Au cur de Java 2 - Notions fondamentales

Le chargeur de classes sait comment parcourir chacun des emplacements possibles jusqu retrouver le chier de classe. Toutefois, dans notre cas, nous devons rpter le processus de recherche manuellement pour localiser le chier de ressources associ. La fonctionnalit de chargement de ressource automatise cette tche. On suivra les tapes suivantes pour charger une ressource : 1. Charger lobjet Class pour la classe possdant une ressource, par exemple AboutPanel.class. 2. Si la ressource est une image ou un chier audio, appeler getResource(filename) pour obtenir le chier de ressources en tant quURL. Le lire ensuite avec la mthode getImage ou getAudioClip. 3. Pour les ressources autres que des images ou des chiers audio, utiliser la mthode getResourceAsStream pour lire les donnes du chier. Le chargeur de classes mmorise lemplacement o il a charg la classe ; il peut alors rechercher dans cet emplacement la ressource associe. Par exemple, vous pouvez utiliser les instructions suivantes pour crer une icne partir du chier image about.gif en suivant cette procdure :
URL url = ResourceTest.class.getResource("about.gif"); Image img = Toolkit.getDefaultToolkit().getImage(url);

Cela revient tablir le chier about.gif au mme endroit que celui o vous avez trouv ResourceTest.class. Par exemple, vous pouvez utiliser les instructions suivantes pour lire le chier about.txt :
InputStream stream = ResourceTest.class.getResourceAsStream("about.txt"); Scanner in = new Scanner(stream);

Il est possible de placer un chier de ressource dans un sous-rpertoire plutt que dans le rpertoire du chier de classe. Vous pouvez hirarchiser les noms des ressources :
data/text/about.txt

Ce nom de ressource relatif est interprt partir du package de la classe chargeant la ressource. Remarquez lutilisation obligatoire du sparateur /, quel que soit le sparateur de rpertoire du systme sur lequel se trouvent nalement les chiers de ressources. Ainsi, sous Windows, le chargeur de ressources convertit automatiquement / en sparateur \. Un nom de ressource commenant par un / est appel un nom de ressource absolu. Il est localis par Java de la mme faon quune classe situe dans un package. Par exemple, la ressource
/corejava/title.txt

est situe dans le rpertoire corejava (qui peut tre soit un sous-rpertoire dans le chemin des classes, soit dans un chier JAR soit, pour les applets, sur le serveur Web). Le dispositif de chargement de ressources se limite lautomatisation du chargement des chiers. Il nexiste pas de mthode standard pour interprter le contenu dun chier de ressources. Chaque programme doit interprter sa faon le contenu de ses chiers de ressources. Une autre application courante des ressources est linternationalisation des programmes. Les chanes dpendant dune langue, comme les messages et les libells de linterface utilisateur, sont stockes dans des chiers ressource, avec un chier par langue. LAPI Internationalization, prsente au Chapitre 5 du Volume II, supporte une mthode standard dorganisation et daccs ces chiers de localisation.

Chapitre 10

Dployer des applets et des applications

497

Le Listing 10.1 prsente le source du programme, qui montre le chargement des ressources. Compilez, construisez un chier JAR et excutez-le :
javac ResourceTest.java jar cvfm ResourceTest.jar ResourceTest.mf *.class *.gif *.txt java -jar ResourceTest.jar

Dplacez le chier JAR vers un autre rpertoire et lancez-le nouveau pour vrier que le programme lit les chiers de ressources du chier JAR, et non du rpertoire courant.
Listing 10.1 : ResourceTest.java
import import import import import java.awt.*; java.io.*; java.net.*; java.util.*; javax.swing.*;

/** * @version 1.4 2007-04-30 * @author Cay Horstmann */ public class ResourceText { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { ResourceTestFrame frame = new ResourceTestFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); } } /** * Un cadre qui charge des images et du texte. */ class ResourceTestFrame extends JFrame { public ResourceTestFrame() { setTitle("ResourceTest"); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); URL aboutURL = getClass().getResource("about.gif"); Image img = Toolkit.getDefaultToolkit().getImage(aboutURL); setIconImage(img); JTextArea textArea = new JTextArea(); InputStream stream = getClass().getResourceAsStream("about.txt"); Scanner in = new Scanner(stream); while (in.hasNext()) textArea.append(in.nextLine() + "\n"); add(textArea); }

498

Au cur de Java 2 - Notions fondamentales

public static final int DEFAULT_WIDTH = 300; public static final int DEFAULT_HEIGHT = 300; } java.lang.Class 1.0

URL getResource(String name) 1.1 InputStream getResourceAsStream(String name) 1.1

Recherchent la ressource dans le mme emplacement que la classe et renvoient une URL ou un ux dentre pour charger la ressource. La mthode renvoie null si la ressource nest pas trouve, et par consquent ne lance pas dexception derreur dE/S.

Verrouillage
Nous avons prcis au Chapitre 4 que vous pouvez verrouiller (seal) un package en langage Java pour empcher dautres classes Java de sinstaller. Un package doit tre verrouill si vous utilisez des classes, des mthodes et des champs visibles pour le package dans votre code. Sans cela, dautres classes peuvent se placer dans le mme package et ainsi obtenir un accs aux fonctionnalits qui lui sont visibles. Par exemple, si vous verrouillez le package com.mycompany.util, aucune classe extrieure ne peut tre dnie par linstruction
package com.mycompany.util;

Pour ce faire, dposez toutes les classes du package dans un chier JAR. Par dfaut, les packages dun chier JAR ne sont pas verrouills. Vous pouvez modier cela en crivant la ligne
Sealed: true

dans la section principale du manifeste. Pour chaque package, vous pouvez spcier si vous dsirez quil soit verrouill ou non, en ajoutant une section supplmentaire au manifeste du chier JAR :
Name: com/mycompany/util/ Sealed: true Name: com/mycompany/misc/ Sealed: false

Pour verrouiller un package, crez un chier texte avec les instructions du chier texte. Puis lancez la commande jar de la manire habituelle :
jar cvfm MyArchive.jar manifest.mf fichiers ajouter

Java Web Start


Java Web Start est une nouvelle technologie destine livrer, sur Internet, des applications qui prsentent les caractristiques suivantes :
m

Elles sont gnralement dlivres par un navigateur. Une fois quelles ont t tlcharges, elles peuvent tre dmarres sans navigateur. Elles ne demeurent pas dans un navigateur. Elles safchent en dehors de lui. Elles nutilisent pas limplmentation Java du navigateur. Le navigateur lance simplement une application externe lorsquil charge un descripteur dapplication Java Web Start. Le mcanisme

m m

Chapitre 10

Dployer des applets et des applications

499

ressemble celui utilis pour lancer dautres applications daide comme Adobe Acrobat ou RealAudio.
m

Les applications signes numriquement peuvent recevoir des droits daccs arbitraires sur la machine locale. Des applications non signes sexcutent dans un "bac sable" qui interdit les oprations potentiellement dangereuses.

Pour prparer une application en vue dune livraison par Java Web Start, vous devez la packager dans un ou plusieurs chiers JAR. Prparez ensuite un chier de description au format Java Network Launch Protocol (JNLP). Aprs cela, placez-les sur un serveur Web. Vous devez vous assurer que votre serveur Web rapporte un type MIME application/x-javajnlp-file pour les chiers ayant une extension .jnlp (les navigateurs utilisent le type MIME pour dterminer lapplication daide lancer). Consultez la documentation de votre serveur Web pour en savoir plus.
ASTUCE
Pour tester Java Web Start, installez Tomcat depuis ladresse http://jakarta.apache.org/tomcat. Tomcat est un conteneur destin aux servlets et aux pages de serveurs JSP Java, mais il sert aussi les pages Web. La version actuelle est prcongure pour servir le type MIME correct et les chiers JNLP.

Testons Java Web Start pour dlivrer lapplication de calculette du Chapitre 9. Procdez comme suit : 1. Compilez Calculator.java. 2. Prparez un chier de manifeste Calculator.mf avec la ligne
Main-Class: Calculator

3. Produisez un chier JAR avec la commande


jar cvfm Calculator.jar Calculator.mf *.class

4. Prparez le chier de lancement Calculator.jnlp avec le contenu suivant :


<?xml version="1.0" encoding="utf-8"?> <jnlp spec="1.0+" codebase="http://localhost:8080/calculator/" href="Calculator.jnlp"> <information> <title>Calculator Demo Application</title> <vendor>Cay S. Horstmann</vendor> <description>A Calculator</description> <offline-allowed/> </information> <resources> <j2se version="1.5.0+"/> <jar href="Calculator.jar"/> </resources> <application-desc/> </jnlp>

Vous remarquerez que le numro de version doit tre 1.5.0 et non 5.0. Depuis Java SE 6, vous pouvez utiliser le nom de balise java au lieu de j2se. Le format du chier de lancement est assez explicatif. Pour obtenir une spcication complte, voyez http://java.sun.com/products/javawebstart/docs/developersguide.html.

500

Au cur de Java 2 - Notions fondamentales

5. Si vous utilisez Tomcat, crez un rpertoire tomcat/webapps/calculator, o tomcat correspond au rpertoire de base de votre installation Tomcat. Crez un sous-rpertoire tomcat/webapps/ calculator/WEB-INF et placez le chier web.xml minimal suivant dans le sous-rpertoire WEBINF :
<?xml version="1.0" encoding="utf-8"?> <web-app> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_5.xsd"> </Web-app>

6. Placez le chier JAR et le chier de lancement sur votre serveur Web, de sorte que lURL concorde avec lentre de codebase du chier JNLP. Si vous utilisez Tomcat, placez-les dans le rpertoire tomcat/webapps/calculator. 7. Vriez que votre navigateur a t congur pour Java Web Start, en vous assurant que le type MIME application/x-java-jnlp-file est associ lapplication javaws. Si vous avez install le JDK, la conguration devrait se faire automatiquement. 8. Lancez Tomcat. 9. Pointez votre navigateur vers le chier JNLP. Si, par exemple, vous utilisez Tomcat, accdez http://localhost:8080/calculator/Calculator.jnlp. 10. La fentre de lancement de Java Web Start safche (voir Figure 10.2).
Figure 10.2
Lancement de Java Web Start.

11. Peu aprs, la calculatrice safche, avec une bordure indiquant une application Java (voir Figure 10.3).
Figure 10.3
La calculatrice fournie par Java Web Start.

Chapitre 10

Dployer des applets et des applications

501

11. Lorsque vous accdez nouveau au chier JNLP, lapplication est rcupre depuis la mmoire tampon. Vous pouvez en consulter le contenu laide du panneau de contrle du plug-in Java (voir Figure 10.4). Depuis Java SE 5.0, ce panneau de contrle sert la fois pour les applets et les applications Java Web Start. Sous Windows, recherchez ce panneau de contrle dans le Panneau de conguration. Sous Linux, excutez jdk/jre/bin/ControlPanel.

Figure 10.4
Le cache de lapplication.

ASTUCE
Si vous ne souhaitez pas excuter de serveur Web lorsque vous testez votre conguration JNLP, vous pouvez temporairement surcharger lURL du codebase dans le chier de lancement en excutant :
javaws codebase file://programDirectory JNLPfile

Sous UNIX, par exemple, vous pouvez simplement mettre la commande depuis le rpertoire contenant le chier JNLP :
javaws codebase file://pwd WebStartCalculator.jnlp

Bien entendu, vous nallez pas demander aux utilisateurs de lancer la visionneuse du cache ds quils veulent relancer lapplication. Vous pouvez, en revanche, amener linstallateur proposer la mise en place de raccourcis sur le bureau et dans le menu. Ajoutez ces lignes au chier JNLP :
<shortcut> <desktop/> <menu submenu="Accessories"/> </shortcut>

Lorsque le premier utilisateur tlcharge lapplication, un "avertissement dintgration sur le bureau" safche (voir Figure 10.5).

502

Au cur de Java 2 - Notions fondamentales

Figure 10.5
Lavertissement dintgration sur le bureau.

Vous devez toujours fournir une icne pour le raccourci du menu et lcran de lancement. Sun recommande de fournir une icne de 32 32 et une icne de 64 64. Placez les chiers dicnes sur le serveur Web, avec les chiers JNLP et JAR. Ajoutez ces lignes la section dinformation du chier JNLP :
<icon href="calc_icon32.png" width="32" height="32" /> <icon href="calc_icon64.png" width="64" height="64" />

Remarquez que ces icnes ne sont pas lies celle de lapplication. Si lapplication doit avoir une icne, vous devez ajouter une image spare dans le chier JAR et appeler la mthode setIconImage sur la classe du cadre (voir le Listing 10.1 pour obtenir un exemple).

Le bac sable
Ds lors quun code est charg depuis un site distant, puis excut en local, la scurit devient un enjeu essentiel. Le fait de cliquer sur un seul lien peut lancer une application Java Web Start, tlcharge sur Internet, qui prsente un risque intrinsque et sexcute dans un bac sable avec un accs minimal. Lafchage dune page Web lance automatiquement tous les applets de la page. Si vous cliquez sur un lien ou que vous visitiez une page Web, vous pourriez installer un code arbitraire sur lordinateur de lutilisateur. Les criminels nauraient alors aucune difcult voler des informations condentielles, accder des donnes nancires ou prendre le contrle des machines pour envoyer des spams. Pour que sa technologie ne soit pas employe des ns coupables, Java propose un modle de scurit labor, trait en dtail dans le Volume II. Un gestionnaire de scurit vrie laccs toutes les ressources du systme. Par dfaut, il nautorise que les oprations inoffensives. Pour autoriser des oprations supplmentaires, le code doit tre numriquement sign et lutilisateur doit approuver le certicat de signature. Mais que peut faire un code distant sur toutes les plates-formes ? Il est toujours bon dafcher des images et de diffuser du son, de rcuprer les frappes de touche et les clics de lutilisateur et de renvoyer la saisie utilisateur lhte partir duquel le code a t charg. Vous avez l sufsamment de fonctionnalits pour afcher des faits et des gures ou recevoir une saisie utilisateur visant passer commande. Lenvironnement dexcution restreint est souvent appel "bac sable". Le code qui sy excute ne peut pas modier le systme de lutilisateur ni lespionner.

Chapitre 10

Dployer des applets et des applications

503

Les programmes du bac sable prsentent notamment les restrictions suivantes :


m m

Ils ne peuvent jamais excuter de programme excutable local. Ils ne peuvent ni lire ni crire sur lordinateur local. Toutefois, avec des privilges de scurit minimale, lAPI JNLP fournit le systme de chiers. Ils ne peuvent retrouver aucune information sur lordinateur local, lexception de la version de Java utilise et de quelques dtails inoffensifs sur le systme dexploitation. Notamment, le code du bac sable ne retrouve pas le nom de lutilisateur, son adresse e-mail, etc. Les programmes chargs distance ne peuvent pas communiquer avec un hte autre que le serveur partir duquel ils ont t tlchargs ; ce serveur est appel hte dorigine. La rgle protge les utilisateurs du code qui pourrait essayer despionner les ressources Intranet (depuis Java SE 6, une application Java Web Start peut raliser dautres connexions rseau, mais lutilisateur du programme doit y consentir). Toutes les fentres contextuelles vhiculent un message davertissement. Ce message est une fonction de scurit pour sassurer que les utilisateurs ne confondent pas la fentre avec une application locale. La crainte est quun utilisateur peu suspicieux visite une page Web, soit amen par erreur excuter du code distant, puis tape un mot de passe ou un numro de carte de crdit, qui peut ensuite tre renvoy au serveur Web. Dans les premires versions du JDK, ce message tait trs confus : "Untrusted Java Applet Window" (fentre dapplet Java non able). Chaque version successive a quelque peu adouci lavertissement : "Unauthenticated Java Applet Window", puis "Warning: Java Applet Window". Le message indique dsormais "Java Applet Window" ou "Java Application Window".

Code sign
Les restrictions du bac sable sont trop importantes dans de nombreux cas. Ainsi, par exemple, sur un Intranet dentreprise, vous pouvez certainement imaginer une application Web Start ou un applet souhaitant accder aux chiers locaux. Il est possible de contrler trs prcisment les droits accorder une application particulire ; nous verrons cela au Chapitre 9 du Volume II. Bien entendu, une application peut simplement demander disposer de toutes les autorisations dune application de bureau, et quelques applications Java Web Start se contentent de faire cela. Cela est accompli en ajoutant les balises suivantes au chier JNLP :
<security> <all-permissions/> </security>

Pour sexcuter en dehors du bac sable, les chiers JAR dune application Java Web Start doivent tre numriquement signs. Un chier JAR sign transporte avec lui un certicat qui indique lidentit du signataire. Les techniques de cryptographie sassurent quun certicat ne puisse pas tre contrefait et que toute tentative de falsication du chier sign soit dtecte. Supposons par exemple que vous receviez une application produite et numriquement signe par yWorks GmbH, laide dun certicat dlivr par Thawte (voir Figure 10.6). Vous pouvez tre certain que : 1. Le code est exactement tel quil tait lorsquil a t sign ; aucun tiers na pu le falsier. 2. La signature provient rellement de yWorks.

504

Au cur de Java 2 - Notions fondamentales

3. Le certicat a rellement t dlivr par Thawte (Java Web Start sait vrier les certicats de Thawte et de certains autres fournisseurs). Cest malheureusement tout ce que vous saurez. Vous ne saurez pas si le code est sr en soi. En ralit, si vous cliquez sur le lien "More Information", vous saurez que lapplication sexcutera sans les restrictions de scurit gnralement prvues par Java. Faut-il alors installer et excuter lapplication ? Cela dpend rellement de la conance que vous faites yWorks GmbH.
Figure 10.6
Un certicat scuris.

Obtenir un certicat de lun des fournisseurs de conance cote des centaines de dollars par an. De nombreux dveloppeurs se contentent de gnrer le leur et lutilisent pour signer du code. Bien entendu, Java Web Start ne dispose daucun moyen pour vrier lexactitude de ces certicats. Lorsque vous recevez cette application, vous tes assur que : 1. Le code est exactement tel quil tait lorsquil a t sign ; aucun tiers ne la falsi. 2. Une personne a sign le code, mais Java Web Start ne peut pas vrier de qui il sagit. Cela est assez inutile ; nimporte qui pourrait avoir falsi le code, puis sign, en prtendant en tre lauteur. Nanmoins, Java Web Start naura aucune difcult prsenter le certicat pour approbation (voir Figure 10.7). En thorie, il est possible de vrier le certicat dune autre manire mais peu de personnes disposent des connaissances techniques ncessaires. Bien entendu, de nombreux utilisateurs tlchargent et excutent chaque jour des applications sur Internet. Sils font conance votre application et votre infrastructure Web, nhsitez pas, utilisez un certicat autosign (voir http://java.sun.com/javase/6/docs/technotes/guides/javaws/developersguide/development.html pour plus dinformations). Dans le cas contraire, offrez-leur la scurit et restez dans le bac sable. Avec lAPI JNLP (voir la section suivante), votre programme peut accder aux ressources de manire slective, aprs approbation de votre utilisateur.

Chapitre 10

Dployer des applets et des applications

505

Figure 10.7
Un certicat peu sr.

LAPI JNLP
LAPI JNLP permet aux applications non signes de sexcuter dans le bac sable et, dans le mme temps, daccder des ressources locales de manire scurise. Il existe par exemple des services pour charger et enregistrer des chiers. Lapplication ne peut pas tudier le systme de chiers ni spcier les noms des chiers. Une bote de dialogue safche, et cest lutilisateur quil revient de choisir le chier. Avant que la bote ne safche, lutilisateur reoit un avertissement quil doit accepter pour continuer (voir Figure 10.8). En outre, lAPI ne permet pas rellement au programme daccder un objet File. Plus prcisment, lapplication na aucun moyen de retrouver lemplacement du chier. Les programmeurs reoivent donc les outils pour implmenter les actions standard "Fichier/Ouvrir" et "Fichier/Enregistrer". Le plus dinformations systme possibles sont masques aux applications douteuses.
Figure 10.8
Un avertissement de scurit Java Web Start.

506

Au cur de Java 2 - Notions fondamentales

LAPI fournit les services suivants :


m m m m m m m

chargement et enregistrement de chiers ; accs au presse-papiers ; tlchargement dun chier ; impression ; afchage dun document dans le navigateur par dfaut ; stockage et rcupration dinformations de conguration persistantes ; vrication du fait quune seule instance dune application sexcute (nouveaut de Java SE 5.0).
FileSaveService service = (FileSaveService) ServiceManager.lookup("javax.jnlp.FileSaveService");

Pour accder un service, utilisez ServiceManager, comme ceci :

Cet appel dclenche une exception UnavailableServiceException si le service nest pas disponible.
INFO
Pour compiler les programmes qui utilisent lAPI JNLP, le chier javaws.jar doit tre inclus dans le chemin de classe. Il se trouve dans le sous-rpertoire jre/lib du JDK.

Nous allons voir maintenant les services JNLP les plus utiles. Pour enregistrer un chier, vous pouvez fournir des suggestions de noms, tant pour le chemin initial que pour les extensions de chiers dans la bote de dialogue Fichier, ainsi que pour les donnes enregistrer pour le nom de chier. Par exemple,
service.saveFileDialog(".", new String[] { "txt" }, data, "calc.txt");

Les donnes doivent tre dlivres dans un InputStream. Ceci peut tre dlicat agencer. Le programme du Listing 10.2 utilise la stratgie suivante : 1. Il cre un ByteArrayOutputStream pour contenir les octets enregistrer. 2. Il cre un PrintStream qui envoie ses donnes au ByteArrayOutputStream. 3. Il imprime des informations enregistrer sur le PrintStream. 4. Il cre un ByteArrayInputStream pour lire les octets enregistrs. 5. Il transfre le ux la mthode saveFileDialog. Vous en saurez plus sur les ux au Chapitre 1 du Volume II. Pour lheure, vous pouvez tudier les dtails du programme dexemple. Pour lire des donnes dun chier, utilisez plutt FileOpenService. Son openFileDialog reoit des suggestions pour le nom de chemin initial et les extensions de chier de la bote de dialogue Fichier ; il renvoie ensuite un objet FileContents. Vous pouvez alors appeler les mthodes getInputStream et getOutputStream pour lire et crire les donnes du chier. Si lutilisateur na pas choisi un chier, la mthode openFileDialog renvoie null :
FileOpenService service = (FileOpenService) ServiceManager.lookup("javax.jnlp.FileOpenService");

Chapitre 10

Dployer des applets et des applications

507

FileContents contents = service.openFileDialog(".", new String[] { "txt" }); if (contents!= null) { InputStream in = contents.getInputStream(); . . . }

Notez que lapplication ne connat ni le nom, ni lemplacement du chier. A linverse, si vous souhaitez ouvrir un chier spcique, utilisez ExtendedService :
ExtendedService service = (ExtendedService) ServiceManager.lookup("javax.jnlp.ExtendedService"); FileContents contents = service.openFile(new File("c:\\autoexec.bat")); if (contents != null) { OutputStream out = contents.getOutputStream(); . . . }

Lutilisateur du programme doit accepter daccder au chier (voir Figure 10.9).


Figure 10.9
Avertissement daccs un chier.

Pour afcher un document dans le navigateur par dfaut, utilisez linterface BasicService. Sachez que certains systmes peuvent ne pas avoir de navigateur par dfaut :
BasicService service = (BasicService) ServiceManager.lookup("javax.jnlp.BasicService"); if (service.isWebBrowserSupported()) service.showDocument(url); else . . .

Il existe un service rudimentaire, appel PersistenceService, qui permet une application de stocker de petites quantits dinformations de conguration et de les rcuprer lorsque lapplication sexcute nouveau. Le mcanisme est identique aux cookies HTTP. La banque persistante utilise les URL la manire de cls. Les URL nont pas besoin de pointer vers une vritable ressource Web. Le service sen sert simplement comme dun schma de dnomination hirarchique commode. Pour toute cl dURL donne, une application peut stocker des donnes binaires arbitraires (attention, toutefois, la banque peut restreindre la taille du bloc de donnes). Pour isoler les applications les unes des autres, une application donne peut nutiliser que les cls dURL commenant par son codebase (comme indiqu dans le chier JNLP). Si vous tlchargez par exemple une application ladresse http://myserver.com/apps, elle ne pourra utiliser que les cls

508

Au cur de Java 2 - Notions fondamentales

du formulaire http://myserver.com/apps/subkey1/subkey2/... Les tentatives daccs dautres cls choueront. Une application peut appeler la mthode getCodeBase du BasicService pour retrouver son codebase. Pour crer une nouvelle cl, faites appel la mthode create de PersistenceService :
URL url = new URL(codeBase, "mykey"); service.create(url, maxSize);

Pour accder aux informations associes une cl particulire, appelez la mthode get. Elle renvoie un objet FileContents par lequel vous pouvez lire et crire les donnes de cl. Par exemple,
FileContents contents = service.get(url); InputStream in = contents.getInputStream(); OutputStream out = contents.getOutputStream(true); // true = crasement

Malheureusement, il nexiste pas de mthode commode pour dcouvrir si une cl existe dj ou si vous devez la crer. Vous pouvez esprer que la cl existe et appeler get. Si lappel dclenche une exception FileNotFoundException, vous devez crer la cl.
INFO
Depuis Java SE 5.0, les applications Java Web Start et les applets peuvent imprimer grce lAPI dimpression normale. Un avertissement de scurit apparat, demandant la permission lutilisateur pour accder limprimante. Pour en savoir plus sur lAPI dimpression, passez au Chapitre 7 du Volume II.

Le programme du Listing 10.2 constitue une simple amlioration de lapplication de la calculette. Elle possde maintenant une bande papier virtuelle qui effectue le suivi de tous les calculs. Il est ainsi possible denregistrer et de charger lhistorique des calculs. Pour mettre la banque persistante en action, lapplication vous permet de dnir le titre du cadre. Si vous excutez nouveau lapplication, elle rcupre votre choix de titre dans la banque persistante (voir Figure 10.10).
Figure 10.10
Lapplication WebStartCalculator.

Chapitre 10

Dployer des applets et des applications

509

Listing 10.2 : WebStartCalculator.java


import import import import import import java.awt.EventQueue; java.awt.event.*; java.io.*; java.net.*; javax.swing.*; javax.jnlp.*;

/** * Une calculette avec lhistorique des calculs qui peut * tre dploy sous forme dapplication Java Web Start. * @version 1.02 2007-06-12 * @author Cay Horstmann */ public class WebStartCalculator { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { CalculatorFrame frame = new CalculatorFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); } } /** * Un cadre avec un panneau de calculatrice et un menu pour charger * et enregistrer lhistorique des calculs. */ class CalculatorFrame extends JFrame { public CalculatorFrame() { setTitle(); panel = new CalculatorPanel(); add(panel); JMenu fileMenu = new JMenu("File"); JMenuItem openItem = fileMenu.add("Open"); openItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { open(); } }); JMenuItem saveItem = fileMenu.add("Save"); saveItem.addActionListener(new ActionListener() {

510

Au cur de Java 2 - Notions fondamentales

public void actionPerformed(ActionEvent event) { save(); } }); JMenuBar menuBar = new JMenuBar(); menuBar.add(fileMenu); setJMenuBar(menuBar); pack(); } /** * Rcupre le titre de la banque persistante ou * demande le titre lutilisateur sil nexiste pas * dentre pralable. */ public void setTitle() { try { String title = null; BasicService basic = (BasicService) ServiceManager.lookup("javax.jnlp.BasicService"); URL codeBase = basic.getCodeBase(); PersistenceService service = (PersistenceService) ServiceManager.lookup("javax.jnlp.PersistenceService"); URL key = new URL(codeBase, "title"); try { FileContents contents = service.get(key); InputStream in = contents.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(in)); title = reader.readLine(); } catch (FileNotFoundException e) { title = JOptionPane.showInputDialog ("Please supply a frame title:"); if (title == null) return; service.create(key, 100); FileContents contents = service.get(key); OutputStream out = contents.getOutputStream(true); PrintStream printOut = new PrintStream(out); printOut.print(title); } setTitle(title); } catch (UnavailableServiceException e) { JOptionPane.showMessageDialog(this, e); } catch (MalformedURLException e) {

Chapitre 10

Dployer des applets et des applications

511

JOptionPane.showMessageDialog(this, e); } catch (IOException e) { JOptionPane.showMessageDialog(this, e); } } /** * Ouvre un fichier dhistorique et actualise laffichage. */ public void open() { try { FileOpenService service = (FileOpenService) ServiceManager.lookup("javax.jnlp.FileOpenService"); FileContents contents = service.openFileDialog (".", new String[] { "txt" }); JOptionPane.showMessageDialog(this, contents.getName()); if (contents!= null) { InputStream in = contents.getInputStream(); BufferedReader reader = new BufferedReader( new InputStreamReader(in)); String line; while ((line = reader.readLine())!= null) { panel.append(line); panel.append("\n"); } } } catch (UnavailableServiceException e) { JOptionPane.showMessageDialog(this, e); } catch (IOException e) { JOptionPane.showMessageDialog(this, e); } } /** * Enregistre lhistorique des calculs dans un fichier. */ public void save() { try { ByteArrayOutputStream out = new ByteArrayOutputStream(); PrintStream printOut = new PrintStream(out); printOut.print(panel.getText()); InputStream data = new ByteArrayInputStream(out.toByteArray()); FileSaveService service = (FileSaveService) ServiceManager.lookup("javax.jnlp.FileSaveService"); service.saveFileDialog(".", new String[] { "txt" }, data, "calc.txt");

512

Au cur de Java 2 - Notions fondamentales

} catch (UnavailableServiceException e) { JOptionPane.showMessageDialog(this, e); } catch (IOException e) { JOptionPane.showMessageDialog(this, e); } } private CalculatorPanel panel; } javax.jnlp.ServiceManager

static String[] getServiceNames()

Renvoie les noms de tous les services disponibles.


static Object lookup(String name)

Renvoie un service avec un nom donn.


javax.jnlp.BasicService

URL getCodeBase()

Renvoie le codebase de cette application.


boolean isWebBrowserSupported()

Renvoie true si lenvironnement Web Start peut lancer un navigateur Web.


boolean showDocument(URL url)

Tente dafcher lURL donne dans un navigateur. Renvoie true si la requte a russi.
javax.jnlp.FileContents

InputStream getInputStream()

Renvoie un ux dentre pour lire le contenu du chier.


OutputStream getOutputStream(boolean overwrite)

Renvoie un ux de sortie pour crire dans le chier. Si overwrite vaut true, le contenu du chier est remplac.

String getName()

Renvoie le nom du chier (mais pas le chemin complet du rpertoire).


boolean canRead() boolean canWrite()

Renvoient true si le chier sous-jacent est lisible ou que lon peut crire dedans.
javax.jnlp.FileOpenService

FileContents openFileDialog(String pathHint, String[] extensions) FileContents[] openMultiFileDialog(String pathHint, String[] extensions)

Afchent un avertissement utilisateur et un slecteur de chiers. Elles renvoient des descripteurs de contenu du ou des chiers choisis par lutilisateur ou null si lutilisateur na pas choisi de chier.

Chapitre 10

Dployer des applets et des applications

513

javax.jnlp.FileSaveService

FileContents saveFileDialog(String pathHint, String[] extensions, InputStream data, String nameHint) FileContents saveAsFileDialog(String pathHint, String[] extensions, FileContents data)

Afchent un avertissement utilisateur et un slecteur de chiers. Elles crivent les donnes et renvoient des descripteurs de contenu du ou des chiers choisis par lutilisateur ou null si lutilisateur na pas choisi de chier.
javax.jnlp.PersistenceService

long create(URL key, long maxsize)

Cre une entre dans une banque persistante pour la cl donne. Renvoie la taille maximale accorde par la banque persistante.

void delete(URL key)

Supprime lentre pour la cl donne.


String[] getNames(URL url)

Renvoie les noms relatifs de toutes les cls qui commencent par lURL donne.
FileContents get(URL key)

Rcupre un descripteur de contenu grce auquel vous pouvez modier les donnes associes la cl donne. Sil nexiste pas dentre pour la cl, une exception FileNotFoundException est dclenche.

Les applets
Les applets sont des programmes Java inclus dans une page HTML. Celle-ci doit indiquer au navigateur les applets charger, puis lendroit o placer chaque applet sur la page Web. La balise ncessaire pour utiliser un applet doit videmment indiquer au navigateur lendroit o rcuprer les chiers de classe et la manire dont lapplet se positionne sur la page Web (taille, emplacement, etc.). Le navigateur rcupre alors les chiers de classe sur Internet (ou dans le rpertoire sur la machine de lutilisateur) et excute automatiquement lapplet. Quand les applets sont apparus, il fallait un navigateur HotJava de Sun pour visualiser les pages Web qui les contenaient. Peu dutilisateurs se sont embarrasss dun second navigateur uniquement pour exploiter une nouvelle fonctionnalit du Web. Les applets de Java ne sont devenus populaires que lorsque Netscape incorpora une machine Java virtuelle dans son navigateur, suivi rapidement par Internet Explorer. Toutefois, deux problmes sont survenus. Netscape ne suivait pas les versions plus rcentes de Java et Microsoft hsitait entre supporter sans enthousiasme les versions obsoltes de Java et abandonner carrment la prise en charge. Pour surmonter ce problme, Sun a sorti un outil appel "Java plug-in". Grce aux diffrents mcanismes dextension dInternet Explorer ou de Navigator, il sintgre en toute homognit dans divers navigateurs et leur permet dexcuter des applets Java dans un environnement dexcution Java externe fourni par Sun. En maintenant le plug-in jour, vous avez la garantie de toujours utiliser les caractristiques les plus rcentes et les plus importantes de Java.

514

Au cur de Java 2 - Notions fondamentales

INFO
Pour excuter les applets de ce chapitre dans un navigateur, vous devrez installer la version actuelle du plug-in Java et vrier que votre navigateur y est connect. Accdez ladresse http://java.sun.com pour obtenir des informations sur le tlchargement et la conguration.

Un petit applet
Pour respecter la tradition, nous allons transformer en applet le programme NotHelloWorld. Un applet est simplement une classe Java qui tend la classe java.applet.Applet. Au l de ce livre, nous utilisons Swing pour implmenter les applets. Tous nos applets tendent la classe JApplet, qui est la superclasse des applets de Swing. On voit la Figure 10.11 que JApplet est une sous-classe immdiate de la classe standard Applet.
Figure 10.11
Le diagramme dhritage dapplet.
Objet

Composant

Conteneur

Fentre

Panneau

Cadre

Applet

JFrame

JApplet

Chapitre 10

Dployer des applets et des applications

515

INFO
Si lapplet contient des composants Swing, il faut tendre la classe JApplet. En effet, des composants Swing situs lintrieur dun applet ordinaire sont incapables de se redessiner correctement.

Le Listing 10.3 donne le code de la version applet de Not hello world. Remarquez les similitudes entre celui-ci et le programme correspondant du Chapitre 7. Ici, lapplet se trouvant dans une page Web, il est inutile de prciser une mthode pour sortir de lapplet.
Listing 10.3 : NotHelloWorldApplet.java
/* * Les balises HTML suivantes sont ncessaires pour afficher cet applet * dans un navigateur: * <applet code="NotHelloWorldApplet.class" width="300" height="100"> * </applet> */ import java.awt.*; import javax.swing.*; /** * @version 1.22 2007-06-12 * @author Cay Horstmann */ public class NotHelloWorldApplet extends JApplet { public void init() { EventQueue.invokeLater(new Runnable() { public void run() { JLabel label = new JLabel("Not a Hello, World applet", SwingConstants.CENTER); add(label); } }); } }

Pour excuter lapplet, il vous faudra : 1. compiler les chiers source Java en chiers de classe ; 2. crer un chier HTML pour indiquer au navigateur le chier charger ainsi que les dimensions de lapplet. Il est dusage (mais pas ncessaire) de donner aux chiers HTML le mme nom que celui de la classe applet quils contiennent, dans notre cas NotHelloWorldApplet.html. Voici son contenu :
<applet code = "NotHelloWorldApplet.class" width="300" height="300"> </applet>

Avant de visualiser lapplet dans un navigateur, testez-le dans le visualisateur dapplet, programme fourni dans le cadre du JDK. Pour lutiliser, dans notre exemple, saisissez :
appletviewer NotHelloWorldApplet.html

516

Au cur de Java 2 - Notions fondamentales

dans la ligne de commande. Notez que, dans largument de ligne de commande du programme visualisateur, cest le chier HTML et non le chier de classe qui est cit. La Figure 10.12 prsente le visualisateur afchant notre applet.
Figure 10.12
Afchage dun applet dans un visualisateur.

ASTUCE
Voici une astuce quelque peu tonnante pour viter le chier HTML supplmentaire. Ajoutez une balise dapplet comme commentaire lintrieur du chier source :
/* <applet code="MyApplet.class" width="300" height="300"> </applet> */ public class MyApplet extends JApplet . . .

Excutez ensuite le visualisateur dapplet avec le chier source en tant quargument de la ligne de commande :
appletviewer NotHelloWorldApplet.java

Il nest pas recommand den faire une pratique courante, mais cette procdure peut tre utile si vous dsirez minimiser le nombre de chiers traiter en phase de test.

ASTUCE
Vous pouvez galement excuter les applets depuis un environnement intgr. Dans Eclipse, slectionnez Run/Run as/Java Applet dans le menu.

Le visualisateur dapplet est adapt la premire phase de test. Toutefois, vous devrez ensuite excuter vos applets dans un navigateur de faon les voir fonctionner comme le ferait un utilisateur. Le visualisateur dapplet ne vous montre dailleurs que lapplet, priv de son environnement de texte HTML. Si un chier HTML contient plusieurs applets, le visualisateur afche autant de fentres. Pour afcher correctement lapplet, il vous suft de charger le chier HTML dans le navigateur (voir Figure 10.13). Si lapplet ne safche pas, cela signie que vous devez installer le plug-in Java.
ASTUCE
Si vous modiez votre applet et que vous le recompiliez, vous devez lancer le navigateur de sorte quil charge les nouveaux chiers de classe. Actualiser la page HTML ne chargera pas le nouveau code. Cest un problme lorsque vous dboguez un applet. Vous pouvez viter davoir redmarrer le navigateur en lanant la console Java et en mettant la commande x, qui efface le cache du chargeur de classes. Vous pouvez ensuite charger nouveau la page

Chapitre 10

Dployer des applets et des applications

517

HTML, pour utiliser le nouveau code de lapplet. Sous Windows, ouvrez la console du plug-in Java dans le Panneau de conguration. Sous Linux, excutez jcontrol et demandez lafchage de la console Java. La console safche ds quun applet est charg.

Figure 10.13
Visualiser un applet dans un navigateur.

Conversion dune application en applet


Une application graphique Java se convertit facilement en un applet qui pourra tre incorpor une page Web. Le code de linterface utilisateur reste presque inchang. Voici les tapes ncessaires cette conversion : 1. Crez une page HTML avec la balise approprie au chargement du code de lapplet. 2. Crez une sous-classe de JApplet. Rendez-la publique, faute de quoi lapplet ne serait pas charg. 3. Supprimez la mthode main de lapplication. Ne crez pas de cadre pour lapplication, elle safchera dans le navigateur. 4. Dplacez tout code dinitialisation depuis le constructeur de cadre vers la mthode init de lapplet. Inutile de construire explicitement lobjet applet le navigateur linstancie votre place et appelle la mthode init. 5. Eliminez lappel setSize. Pour les applets, le dimensionnement dpend des paramtres width et height du chier HTML. 6. Enlevez lappel to setDefaultCloseOperation. Un applet ne peut pas tre ferm. Il se termine lorsquon quitte le navigateur. 7. Si lapplication appelle setTitle, supprimez lappel la mthode. Les applets nont pas de barre de titre (mais rien ne vous empche de donner un titre la page Web en tant que telle via la balise HTML <title>). 8. Nappelez pas setVisible(true), lapplet safche automatiquement.
INFO
Plus loin dans ce chapitre, vous verrez comment implmenter un programme qui est la fois un applet et une application.

518

Au cur de Java 2 - Notions fondamentales

java.applet.Applet 1.0

void init()

Est appele au chargement initial de lapplet. Surcharger cette mthode et y placer le code dinitialisation.

void start()

Surcharge cette mthode pour le code qui doit tre excut chaque fois que lutilisateur visite la page du navigateur contenant cet applet. Une action ordinaire consiste ractiver un thread.

void stop()

Surcharge cette mthode pour le code qui doit tre excut chaque fois que lutilisateur quitte la page du navigateur contenant cet applet. Une action ordinaire consiste dsactiver un thread.

void destroy()

Surcharge cette mthode pour le code qui doit tre excut lorsque lutilisateur quitte le navigateur.

void reSize(int width, int height)

Modie la taille de lapplet. Cette mthode serait parfaite si elle fonctionnait correctement dans une page Web. Mais les navigateurs ne la laissent pas interfrer avec leurs mcanismes de mise en page.

Balises HTML et attributs pour applets


Dans sa forme la plus simple, un exemple dutilisation de la balise applet ressemble ceci :
<applet code="NotHelloWorldApplet.class" width="300" height="100">

Nous avons vu que la balise code donne le nom du chier de classe (lextension .class est obligatoire). Les balises width et height servent dimensionner la fentre qui va contenir lapplet (les dimensions sont exprimes en pixels). Il vous faut galement une balise </applet> pour marquer la n du balisage HTML qui lui est destin. Le texte situ entre les balises <applet> et </applet> ne sera afch que si le navigateur est incapable dafcher les applets. Les attributs code, width et height sont ncessaires. Si un seul manque, le navigateur ne pourra pas charger lapplet. Toutes ces informations sont normalement places dans une page HTML dont voici une esquisse :
<html> <head> <title>NotHelloWorldApplet</title> </head> <body> <p>La ligne de texte qui suit sera affiche par Java:</p> <applet code="NotHelloWorldApplet.class" width="100" height="100"> Aucun texte napparat ici dans le cas de navigateurs non compatibles Java. </applet> </body> </html>

Vous pouvez utiliser les attributs suivants dans la balise applet : width, height Ces attributs sont obligatoires et dnissent respectivement la largeur et la hauteur dun applet (en pixels). Pour le visualisateur dapplet, il sagit de la taille initiale de lapplet. Sil est possible

Chapitre 10

Dployer des applets et des applications

519

de modier les dimensions dans le visualisateur, cest impossible dans un navigateur. Il faudra deviner lespace ncessaire un applet pour quil soit correctement vu dans tous les cas.

align

Cet attribut xe lalignement dun applet. Les valeurs des attributs sont les mmes que pour lattribut align de la balise HTML img.

code

Cet attribut donne le nom du chier de classe de lapplet. Ce nom est relatif lemplacement du codebase (voir ci-aprs) ou de la page courante si le codebase nest pas spci. Le nom de chemin doit alors correspondre la classe de lapplet. Par exemple, si la classe de lapplet se trouve dans le groupe com.mycompany, lattribut est code="com/mycompany/MyApplet.class". Le code alternatif code="com.mycompany.MyApplet.class" est galement autoris. Mais vous ne pouvez pas utiliser ici de chemin en absolu. Utilisez lattribut codebase si votre chier class se trouve ailleurs. Lattribut code spcie seulement le nom de la classe qui contient la classe de lapplet. Evidemment, votre applet peut contenir dautres chiers de classe. Une fois que le chargeur de classe du navigateur charge la classe contenant la classe de lapplet, il "comprend" quil doit charger dautres chiers de classe, ce quil fera. Lun des attributs code ou object doit tre prsent.

codebase

Cet attribut facultatif est une URL permettant de localiser les chiers de classes. Vous pouvez utiliser une URL absolue, mme sur un serveur diffrent. Le plus souvent, cependant, il sagit dune URL relative qui pointe vers un sous-rpertoire. Par exemple si la disposition du chier ressemble ceci :
aDirectory/ MyPage.html myApplets/ MyApplet.class

Vous utilisez ensuite la balise suivante dans MyPage.html :


<applet code="CalculatorApplet.class" codebase="myApplets" width="100" height="150">

archive

Cet attribut facultatif produit la liste du ou des chiers darchives Java contenant les classes et les autres ressources ncessaires lapplet. Ces chiers sont tlchargs depuis le serveur Web avant le chargement de lapplet. Cette technique acclre le processus de chargement de faon sensible, car il suft dune seule requte HTTP pour charger un unique chier JAR contenant beaucoup de chiers plus petits. Les chiers JAR sont spars par des virgules. Par exemple :
<applet code="MyApplet.class" archive="MyClasses.jar,corejava/CoreJavaClasses.jar" width="100" height="150">

object

Cette balise permet de donner le nom dun chier contenant un objet applet srialis. On dit quun objet est srialis quand tous ses champs dinstance se trouvent dans un chier. La srialisation est aborde au Chapitre 1 du Volume II. Pour afcher lapplet, lobjet devra tre dsrialis

520

Au cur de Java 2 - Notions fondamentales

partir du chier an de retrouver son tat antrieur. Dans le cas de cette balise, la mthode init nest pas appele : cest la mthode start de lapplet qui est appele. Avant de srialiser un objet, il faut en principe appeler sa mthode stop si lon souhaite implmenter un navigateur persistant qui recharge automatiquement ses applets et les replace dans ltat o ils se trouvaient la fermeture du navigateur. Cette contrainte est rare et il est inutile de sen proccuper. Toute balise applet doit contenir lattribut code ou object. Par exemple,
<applet object="MyApplet.ser" width="100" height="150">

name

Les transcriptions essaieront de donner lapplet un attribut name quelles puissent utiliser pour se rfrer lapplet lors de la transcription. Netscape et Internet Explorer permettent dappeler des mthodes dapplet sur une page laide de JavaScript. Cet ouvrage ntant pas consacr JavaScript, nous ne prsentons que de brefs aspects du code ncessaire pour appeler du code Java depuis JavaScript.
INFO
JavaScript est un langage de script qui peut tre utilis dans une page Web (initialement appel LiveScript par Netscape qui la conu). La syntaxe de ce langage est un peu analogue celle de Java, mais la ressemblance sarrte l. Lappellation JavaScript est purement commerciale. Un sous-ensemble a t normalis (norme ECMA-262) sous le nom particulirement accrocheur dECMAScript. Comme il fallait sy attendre, les extensions du langage supportes par les deux grands navigateurs sont incompatibles. Pour plus dinformation sur JavaScript, se reporter aux autres ouvrages parus chez CampusPress.

Il faut commencer par donner un nom lapplet an de pouvoir y accder :


<applet code="MyApplet.class" width="100" height="150" name="mine"> </applet>

On peut alors faire rfrence lobjet en tant que document.applets.nom de lapplet. Par exemple,
var myApplet = document.applets. mine;

La magie de lintgration entre Java et JavaScript supporte par Netscape et Internet Explorer permet lappel dune mthode de lapplet :
myApplet.init();

Lattribut name est indispensable quand deux applets dune mme page doivent communiquer directement. Il faut donner un nom chaque instance dapplet et le passer la mthode getApplet de la classe AppletContext. Nous examinerons plus loin ce mcanisme de communication interapplets.
INFO
Sur le site http://www.javaworld.com/javatips/jw-javatip80.html, Francis Lu utilise une communication JavaScriptvers-Java pour rsoudre un problme vieux comme le monde : redimensionner un applet de telle faon quil ne soit pas li des valeurs ges des attributs width et height cods en dur. Cela constitue un bon exemple de lintgration entre Java et JavaScript.

Chapitre 10

Dployer des applets et des applications

521

alt

Java peut tre dsactiv dans le navigateur, par un administrateur systme paranoaque par exemple. Vous pouvez alors utiliser lattribut alt pour afcher un message destination de ces pauvres hres.
<applet code="MyApplet.class" width="100" height="150" alt="If you activated Java, you would see my applet here">

Si un navigateur ne parvient pas du tout afcher les applets, il ignore les balises applet et param inconnues. Tout le texte compris entre les balises <applet> et </applet> safche dans le navigateur. A linverse, les navigateurs compatibles Java nafchent pas de texte entre ces balises. Vous pouvez afcher les messages dans ces balises pour les infortuns utilisateurs dun navigateur prhistorique. Ainsi, par exemple :
<applet code="MyApplet.class" width="100" height="150"> If your browser could show Java, you would see my applet here. </applet>

La balise object
La balise object est dans la norme HTML 4.0 et devrait remplacer la balise applet selon le consortium W3. Elle possde 35 attributs, dont la plupart (comme onkeydown) ne sont utiles que pour crire en Dynamic HTML. Les diffrentes balises de positionnement telles que align ou height fonctionnent comme dans le cas de la balise applet. Lattribut essentiel de la balise object, dans le cas dun applet Java, sappelle classid. Il donne lemplacement de lobjet. La balise object permet le chargement de plusieurs autres types dobjets, par exemple de composants ActiveX, tel le plug-in. Lattribut codetype spcie la nature de lobjet, comme application/java pour un applet Java :
<object codetype="application/java" classid="java:MyApplet.class" width="100" height="150">

Signalons que lattribut classid peut tre suivi par lattribut codebase qui fonctionne exactement comme pour la balise applet.

Passer des informations un applet avec des paramtres


Les applets ont accs des paramtres localiss dans le chier HTML comme le font les applications pour prendre des informations dans la ligne de commande. On utilise cet effet la balise HTML param suivie dattributs dnis librement. Par exemple, pour laisser une page Web rednir la police de caractres utilise par un applet :
<applet code="FontParamApplet.class" width="200", height="200"> <param name="font" value="Helvetica"/> </applet>

La mthode getParameter de la classe Applet rcupre la valeur du paramtre :


public class FontParamApplet extends JApplet { public void init() {

522

Au cur de Java 2 - Notions fondamentales

String fontName = getParameter("font"); . . . } . . . }

INFO
On ne peut appeler la mthode getParameter qu partir de la mthode init de lapplet en vitant surtout de le faire partir du constructeur. A lexcution du constructeur, les paramtres ne sont pas encore disponibles. Comme laspect de la plupart des applets un peu labors dpend de paramtres, nous suggrons de ne pas crire de constructeur pour les applets, mais de placer tout simplement lensemble des initialisations dans la mthode init.

Les paramtres sont toujours renvoys en tant que chanes de caractres quil faudra, le cas chant, convertir en type numrique. Cette sparation seffectue via une mthode approprie telle que parseInt de la classe Integer. Par exemple, pour ajouter un paramtre de corps une police :
<applet code="FontParamApplet.class" width="200" height="200"> <param name="font" value="Helvetica"/> <param name="size" value="24"/> </applet>

Le source suivant montre la conversion du paramtre en entier :


public class FontParamApplet extends JApplet { public void init() { String fontName = getParameter("font"); int fontSize = Integer.parseInt(getParameter("size")); . . . } }

INFO
Les chanes donnant les noms des paramtres via la valeur dattribut name de la balise param et celles quutilise la mthode getParameter doivent tre identiques ; en particulier, il faut distinguer majuscules et minuscules.

Il est possible de dterminer si le paramtre size a t effectivement utilis, en comparant la longueur de la chane null :
int fontsize; String sizeString = getParameter("size"); if (sizeString == null) fontSize = 12; else fontSize = Integer.parseInt(sizeString);

Voici lexemple dun applet fort utile qui recourt abondamment des paramtres. Il trace cet histogramme (voir Figure 10.14).

Chapitre 10

Dployer des applets et des applications

523

Figure 10.14
Un applet construisant un histogramme.

Cet applet trouve les libells et la hauteur des barres dans les valeurs param du chier HTML. Voici le chier HTML correspondant la Figure 10.7 :
<applet code="Chart.class" width="400" height="300"> <param name="title" value="Diameters of the Planets"/> <param name="values" value="9"/> <param name="name.1" value="Mercury"/> <param name="name.2" value="Venus"/> <param name="name.3" value="Earth"/> <param name="name.4" value="Mars"/> <param name="name.5" value="Jupiter"/> <param name="name.6" value="Saturn"/> <param name="name.7" value="Uranus"/> <param name="name.8" value="Neptune"/> <param name="name.9" value="Pluto"/> <param name="value.1" value="3100"/> <param name="value.2" value="7500"/> <param name="value.3" value="8000"/> <param name="value.4" value="4200"/> <param name="value.5" value="88000"/> <param name="value.6" value="71000"/> <param name="value.7" value="32000"/> <param name="value.8" value="30600"/> <param name="value.9" value="1430"/> </applet>

Une autre faon de procder consiste placer dans lapplet un tableau de chanes de caractres et un tableau de valeurs, mais nous voyons deux avantages lemploi du premier mcanisme avec des param. Pour crer plusieurs copies du mme applet sur une page Web donnant des diagrammes diffrents, il suft de placer deux balises applet avec des jeux de paramtres diffrents dans la mme page. De plus, les donnes afcher peuvent tre modies. Il va de soi que le diamtre des plantes ne change pas rapidement, mais dans le cas dun diagramme de ventes annuelles, la mise jour de la page Web se fait sans difcult puisque ce nest quun texte. Il serait bien plus fastidieux de modier le source et de recompiler un chier Java chaque semaine.

524

Au cur de Java 2 - Notions fondamentales

Il existe des composants JavaBeans (beans Java) dans le commerce qui tracent des reprsentations graphiques beaucoup plus labores que notre petit applet. Mais si vous en achetez un, vous pourrez le placer dans une page Web et lui passer des paramtres sans avoir vous soucier de la faon dont lapplet procde pour construire la reprsentation graphique. Le Listing 10.4 donne le source de notre applet. On remarque que les paramtres sont lus par la mthode init et que cest la mthode paintComponent qui trace le diagramme.
Listing 10.4 : Chart.java
import import import import java.awt.*; java.awt.font.*; java.awt.geom.*; javax.swing.*;

/** * @version 1.33 2007-06-12 * @author Cay Horstmann */ public class Chart extends JApplet { public void init() { EventQueue.invokeLater(new Runnable() { public void run() { String v = getParameter("values"); if (v == null) return; int n = Integer.parseInt(v); double[] values = new double[n]; String[] names = new String[n]; for (int i = 0; i < n; i++) { values[i] = Double.parseDouble (getParameter("value."+(i+1))); names[i] = getParameter("name."+(i+1)); } add(new ChartPanel(values, names, getParameter("title"))); } }); } } /** * Un composant qui dessine un graphique en barres. */ class ChartComponent extends JComponent { /** * Construit un ChartComponent. * @param v Le tableau de valeurs pour le graphique * @param n Le tableau de noms pour les valeurs * @param t Le titre du graphique */

Chapitre 10

Dployer des applets et des applications

525

public ChartComponent(double[] v, String[] n, String t) { values = v; names = n; title = t; } public void paintComponent(Graphics g) { Graphics2D g2 = (Graphics2D)g; // Calculer les valeurs minimum et maximum if (values == null) return; double minValue = 0; double maxValue = 0; for (double v: values) { if (minValue > v) minValue = v; if (maxValue < v) maxValue = v; } if (maxValue == minValue) return; int panelWidth = getWidth(); int panelHeight = getHeight(); Font titleFont = new Font("SansSerif", Font.BOLD, 20); Font labelFont = new Font("SansSerif", Font.PLAIN, 10); // Calculer ltendue du titre FontRenderContext context = g2.getFontRenderContext(); Rectangle2D titleBounds = titleFont.getStringBounds(title, context); double titleWidth = titleBounds.getWidth(); double top = titleBounds.getHeight(); // Dessiner le titre double y = -titleBounds.getY(); // ascent double x = (panelWidth - titleWidth) / 2; g2.setFont(titleFont); g2.drawString(title, (float) x, (float) y); // Calculer ltendue des libells des barres LineMetrics labelMetrics = labelFont.getLineMetrics("", context); double bottom = labelMetrics.getHeight(); y = panelHeight - labelMetrics.getDescent(); g2.setFont(labelFont); // Obtenir le facteur chelle et la largeur des barres double scale = (panelHeight - top - bottom) / (maxValue - minValue); int barWidth = panelWidth / values.length; // Dessiner les barres for (int i = 0; i < values.length; i++) { // Rcuprer les coordonnes du rectangle de la barre double x1 = i * barWidth+1; double y1 = top; double height = values[i] * scale;

526

Au cur de Java 2 - Notions fondamentales

if (values[i] >= 0) y1 += maxValue - values[i]) * scale; else { y1 += maxValue * scale; height = -height; } // Remplir la barre et dessiner son contour Rectangle2D rect = new Rectangle2D.Double(x1, y1, barWidth - 2, height); g2.setPaint(Color.RED); g2.fill(rect); g2.setPaint(Color.BLACK); g2.draw(rect); // Dessiner le libell centr sous la barre Rectangle2D labelBounds = labelFont.getStringBounds(names[i], context); double labelWidth = labelBounds.getWidth(); x = x1 +(barWidth - labelWidth) / 2; g2.drawString(names[i], (float) x, (float) y); } } private double[] values; private String[] names; private String title; } java.applet.Applet 1.0

public String getParameter(String name)

Prend la valeur dun paramtre dni par la balise param dans la page Web ayant charg lapplet. Attention aux majuscules/minuscules !

public String getAppletInfo()

Mthode surcharge par beaucoup dauteurs dapplets, qui renvoient ainsi dans une chane leur nom, la version et le copyright de lapplet. Vous pouvez faire de mme en surchargeant cette mthode dans votre classe applet.

public String[][] getParameterInfo()

Mthode que vous pouvez surcharger pour passer un tableau donnant les options de la balise param supportes par lapplet. Chaque ligne contient trois entres : le nom du paramtre, son type et une description. Par exemple :
"fps", "1-10", "frames per second" "repeat", "boolean", "repeat image loop?" "images", "url", "directory containing images"

Accder aux images et chiers audio


Les applets peuvent supporter les images comme les sons. A lheure actuelle, les images sont aux formats GIF, PNG ou JPEG et les chiers audio aux formats AU, AIFF, WAV ou MIDI. Les images GIF animes sont acceptes et lanimation est effective.

Chapitre 10

Dployer des applets et des applications

527

Gnralement, les chiers contenant cette information sont spcis en tant quURL relatives. LURL de base sobtient en appelant getDocumentBase ou getCodeBase. Le premier rcupre lURL de la page HTML dans laquelle est contenu lapplet, le second lURL du rpertoire de codebase de lapplet.
INFO
Dans les versions prcdentes de Java SE, il existait une confusion importante sur ces mthodes : voyez le bogue no 4456393 dans la parade des bogues Java (http://bugs.sun.com/bugdatabase/index.jsp). La documentation a nalement t prcise pour Java SE 5.0.

Donnez lURL de base et lemplacement du chier aux mthodes getImage et getAudioClip. Par exemple :
Image cat = getImage(getCodeBase(), "images/cat.gif"); AudioClip meow = getAudioClip(getCodeBase(), "audio/meow.au");

Au Chapitre 7, nous avons tudi lafchage dune image. Pour lancer un clip audio, il suft den invoquer la mthode play qui peut aussi tre appele avec la mthode play de la classe Applet sans que le clip ait t charg au pralable :
play(getCodeBase(), "audio/meow.au"); java.applet.Applet 1.0

URL getDocumentBase()

Rcupre lURL de la page Web contenant lapplet.


URL getCodeBase()

Rcupre lURL du rpertoire de codebase partir duquel cet applet est charg. Il sagit de lURL absolue du rpertoire rfrenc par lattribut codebase ou du rpertoire du chier HTML lorsque aucun codebase nest spci.

void play(URL url) void play(URL url, String name)

La premire variante joue un chier audio spci par lURL. La seconde utilise la chane pour spcier en premier paramtre un chemin relatif lURL. Si le clip audio ne peut tre trouv, il ne se passe rien.

AudioClip getAudioClip(URL url) AudioClip getAudioClip(URL url, String name)

La premire variante rcupre un clip audio partir de lURL spcie. La seconde version utilise la chane name pour spcier en premier argument un chemin relatif lURL. Les mthodes renvoient null lorsque le clip audio est introuvable.

Image getImage(URL url) Image getImage(URL url, String name)

Rcuprent un objet image qui encapsule limage lURL spcie. Si limage nexiste pas, renvoient immdiatement null, sinon un thread spar est lanc pour charger limage.

528

Au cur de Java 2 - Notions fondamentales

Le contexte dapplet
Un applet sexcute lintrieur dun navigateur ou dun visualisateur dapplet. Il peut demander au navigateur deffectuer certaines actions pour lui, comme rcuprer un clip audio, afcher un court message dans la barre dtat ou montrer une autre page Web. Le navigateur actif peut traiter ces requtes ou les ignorer. Par exemple, si un applet sexcutant lintrieur du visualisateur dapplet demande au programme visualisateur dafcher une page Web, il ne se passe rien. Un applet communique avec le navigateur actif laide de la mthode getAppletContext. Cette mthode renvoie un objet qui implmente une interface de type AppletContext. Vous pouvez vous reprsenter limplmentation de cette interface comme une voie de communication entre lapplet et le navigateur actif. Cette interface contient en plus de getAudioClip et de getImage plusieurs mthodes fort utiles que nous dcrivons ci-aprs.

La communication interapplets
Une page Web peut contenir plus dun applet. Lorsque ces applets proviennent dun mme codebase, ils peuvent communiquer ensemble. Mais cest une fonctionnalit avance dont vous naurez sans doute pas souvent lusage. A condition de placer dans le chier HTML des attributs name pour chaque applet, on peut utiliser la mthode getApplet de linterface AppletContext pour obtenir une rfrence lapplet. Par exemple, si le chier HTML contient
<applet code="Chart.class" width="100" height="100" name="Chart1">

lappel suivant,
Applet chart1 = getAppletContext().getApplet("Chart1");

renvoie une rfrence lapplet. Que peut-on en faire ? En supposant que la classe Chart possde une mthode pour entrer de nouvelles donnes et dessiner nouveau lhistogramme, on peut alors lappeler condition deffectuer le transtypage voulu :
((Chart)chart1).setData(3, "Earth", 9000);

On peut galement lister lensemble des applets dans une page Web, sans quils aient ncessairement une balise name. La mthode getApplets renvoie un objet enumeration (vous en saurez plus sur les objets enumeration au Chapitre 13). Voici une boucle qui afche les noms des classes de tous les applets situs dans la page courante :
Enumeration<Applet> e = getAppletContext().getApplets(); while (e.hasMoreElements()) { Applet a = e.nextElement(); System.out.println(a.getClass().getName()); }

Les applets ne peuvent communiquer ensemble qu lintrieur de la mme page Web.

Faire afcher des informations par le navigateur


Vous pouvez vous servir de deux zones du navigateur actif : la barre dtat et la zone dafchage de la page Web. Dans les deux cas, vous utiliserez les mthodes de la classe AppletContext.

Chapitre 10

Dployer des applets et des applications

529

Un message showStatus permet dafcher une chane dans la barre dtat situe dans le bas du navigateur. Par exemple :
showStatus("Loading data... please wait");

ASTUCE
A notre avis, showStatus est peu utile, parce que le navigateur exploite galement la barre dtat pour afcher des messages sans aucun intrt comme "Applet en cours dexcution", lesquels craseront votre prcieux message. Pour cette raison, nutilisez la barre dtat que pour des indications non essentielles, du genre "Chargement Merci de patienter".

La mthode showDocument demande au navigateur de montrer une autre page Web, ce qui peut tre fait de plusieurs faons. La plus simple consiste appeler showDocument avec en argument lURL afcher :
URL u = new URL("http://java.sun.com/indx.html"); getAppletContext().showDocument(u);

Linconvnient avec cet appel est que la nouvelle page Web est ouverte dans la mme fentre que votre page courante, dplaant de ce fait votre applet et contraignant lutilisateur cliquer sur Prcdente pour y revenir. On peut demander au navigateur de montrer le document dans une autre fentre en transmettant un second paramtre dans lappel de showDocument (voir Tableau 10.2). Si vous fournissez la chane spciale "_blank" [vide], lindicateur ouvre une nouvelle fentre contenant le document au lieu de dplacer le document courant. Plus intressant encore, si vous vous servez des cadres HTML, vous pourrez scinder une fentre de navigateur en plusieurs cadres, chacun dot dun nom. Lapplet peut tre plac dans un cadre, et il afchera les documents dans les autres cadres. Nous verrons un exemple de ce cas de gure la prochaine section.
Tableau 10.2 : La mthode showDocument

Paramtre cible "_self" ou rien "_parent" "_top" "_blank" toute autre chane

Emplacement Afche le document dans le cadre courant. Afche le document dans le conteneur parent. Afche le document dans le cadre se trouvant le plus haut. Afche dans une nouvelle fentre, sans nom, place au-dessus. Afche dans le cadre dsign. Sil nexiste pas de cadre de ce nom, ouvre une nouvelle fentre en y attribuant le nom donn.

INFO
Le visualisateur dapplets de Sun nafche pas les pages Web. La mthode showDocument y est ignore.

530

Au cur de Java 2 - Notions fondamentales

Cest un applet et cest aussi une application !


Voici quelques annes, un comique la tlvision parodiait une publicit en montrant un couple en train de se disputer propos dune substance blanche glatineuse. Le mari disait : "Cest pour napper les desserts." Et sa femme : "Cest une cire parquet." Et lannonceur concluait triomphalement : "Cest les deux la fois !" Au cours de cette section, nous vous montrerons comment crire un programme Java qui soit la fois un applet et une application. Cela signie que vous pouvez charger le programme avec le visualisateur dapplet ou avec le navigateur, ou encore le charger partir dune ligne de commande avec le lanceur de programmes Java. Nous ne savons pas si cet usage est frquent, mais il est intressant que vous en connaissiez lexistence. Les Figures 10.15 et 10.16 montrent le mme programme, lanc depuis la ligne de commande et afch lintrieur du visualisateur dapplet.
Figure 10.15
Cest un applet !

Figure 10.16
Cest une application !

Une application de GUI construit un objet cadre et y appelle setVisible(true). Puisquon ne peut pas appeler setVisible pour un applet nu, il doit tre plac dans un cadre. Nous fournissons une classe AppletFrame dont le constructeur ajoute lapplet dans le volet de contenu :
public class AppletFrame extends JFrame { public AppletFrame(Applet anApplet) { applet = anApplet; add(applet); . . . } . . . }

Chapitre 10

Dployer des applets et des applications

531

Dans la mthode main de lapplet/application, nous construisons et afchons un AppletFrame :


class MyAppletApplication extends MyApplet { public static void main(String args[]) { AppletFrame frame = new AppletFrame(new MyApplet()); frame.setTitle("MyAppletApplication"); frame.setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }

Remarquez que nous pouvons simplement tendre lapplet existant. Lorsque lapplet dmarre, il faut appeler ses mthodes init et start. Nous y parvenons en surchargeant la mthode setVisible de la classe AppletFrame :
public void setVisible(boolean b) { if (b) { applet.init(); super.setVisible(true); applet.start(); } else { applet.stop(); super.setVisible(false); applet.destroy(); } }

Seulement, il y a un problme : si le programme est lanc comme une application et quil appelle getAppletContext, il obtiendra un pointeur null parce quil na pas t lanc dun navigateur. Cela provoque un plantage lexcution chaque fois que lon va rencontrer ce genre dinstruction :
getAppletContext().showStatus(message);

Nous ne dsirons pas crire un programme de navigation complet, mais il nous faut fournir le minimum pour faire en sorte que les appels fonctionnent. Lappel nafche pas de message, mais au moins il ne fait pas planter le programme. Il nous suft dimplmenter deux interfaces : AppletStub et AppletContext. Le premier intrt de linterface AppletStub est de localiser le contexte dapplet. Un applet possde toujours un applet stub (install en mme temps que la mthode setStub de la classe Applet). Nous napportons que le strict minimum des fonctionnalits ncessaires limplmentation de ces deux interfaces :
public class AppletFrame extends JFrame implements AppletStub, AppletContext { . . . // Mthodes AppletStub public boolean isActive() { return true; } public URL getDocumentBase() { return null; }

532

Au cur de Java 2 - Notions fondamentales

public public public public

URL getCodeBase() { return null; String getParameter(String name) AppletContext getAppletContext() void appletResize(int width, int

} { return ""; } { return this; } height) {}

// Mthodes AppletContext public AudioClip getAudioClip(URL url) { return null; } public Image getImage(URL url) { return Toolkit.getDefaultToolkit().getImage(url); } public Applet getApplet(String name) { return null; } public Enumeration<Applet> getApplets() { return null; } public void showDocument(URL url) {} public void showDocument(URL url, String target) {} public void showStatus(String status) {} public void setStream(String key, InputStream stream) {} public InputStream getStream(String key) { return null; } public Iterator<String> getStreamKeys() { return null; } }

Le constructeur de la classe cadre appelle ensuite setStub de lapplet pour crer lui-mme un stub :
public AppletFrame(Applet anApplet) { applet = anApplet Container contentPane = getContentPane(); contentPane.add(applet); applet.setStub(this); }

Pour le plaisir, nous avons utilis lastuce mentionne prcdemment qui consiste ajouter la balise applet en commentaire du chier source sans avoir besoin daucun autre chier HTML. Les Listings 10.5 et 10.6 prsentent le code. Lancez lapplet et lapplication :
appletviewer NotHelloAppletApplication.java java NotHelloAppletApplication

Listing 10.5 : AppletFrame.java


import import import import import import java.awt.*; java.applet.*; java.io.*; java.net.*; java.util.*; javax.swing.*;

/** * @version 1.32 2007-06-12 * @author Cay Horstmann */ public class AppletFrame extends JFrame implements AppletStub, AppletContext { public AppletFrame(Applet anApplet) { applet = anApplet; add(applet); applet.setStub(this); }

Chapitre 10

Dployer des applets et des applications

533

public void setVisible(boolean b) { if (b) { applet.init(); super.setVisible(true); applet.start(); } else { applet.stop(); super.setVisible(false); applet.destroy(); } } // Mthodes AppletStub public boolean isActive() { return true; } public URL getDocumentBase() { return null; } public URL getCodeBase() { return null; } public String getParameter(String name) { return ""; } public AppletContext getAppletContext() { return this; } public void appletResize(int width, int height) { } // Mthodes AppletContext public AudioClip getAudioClip(URL url) { return null; } public Image getImage(URL url) { return Toolkit.getDefaultToolkit().getImage(url); }

534

Au cur de Java 2 - Notions fondamentales

public Applet getApplet(String name) { return null; } public Enumeration<Applet> getApplets() { return null; } public void showDocument(URL url) { } public void showDocument(URL url, String target) { } public void showStatus(String status) { } public void setStream(String key, InputStream stream) { } public InputStream getStream(String key) { return null; } public Iterator<String> getStreamKeys() { return null; } private Applet applet; }

Listing 10.6 : AppletApplication.java


/* * Le visualisateur dapplets lit les balises ci-aprs si vous * lappelez avec appletviewer CalculatorAppletApplication.java * Aucun fichier HTML nest ncessaire. * <applet code="AppletApplication.class" * width="200" height="200"> * </applet> */ import java.awt.EventQueue; import javax.swing.*; /** * Cest un applet et cest une application! * @version 1.32 2007-04-28 * @author Cay Horstmann */

Chapitre 10

Dployer des applets et des applications

535

public class AppletApplication extends NotHelloWorldApplet { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { AppletFrame frame = new AppletFrame(new NotHelloWorldApplet()); frame.setTitle("NotHelloWorldApplet"); frame.setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); } public static final int DEFAULT_WIDTH = 200; public static final int DEFAULT_HEIGHT = 200; } java.applet.Applet 1.2

public AppletContext getAppletContext()

Renvoie un handle sur lenvironnement du navigateur de lapplet. Avec la plupart des navigateurs, on peut utiliser cette information pour agir sur le navigateur o sexcute lapplet.

void showStatus(String msg)

Afche la chane spcie dans la barre dtat du navigateur.


java.applet.AppletContext 1.0

Enumeration<Applet> getApplets()

Renvoie une numration (voir Chapitre 13) de tous les applets dun mme contexte, cest--dire dune mme page Web.

Applet getApplet(String name)

Renvoie lapplet dsign qui se trouve dans le contexte courant ou null sil nexiste pas. La recherche nest faite que dans la page Web courante.

void showDocument(URL url) void showDocument(URL url, String target)

Afchent une nouvelle page Web dans un cadre du navigateur. Dans la premire variante de cette mthode, la nouvelle page remplace la page courante. La seconde variante utilise le paramtre target pour identier le cadre cible (voir Tableau 10.2).

Stockage des prfrences dapplications


Les utilisateurs de vos applications sattendent gnralement ce que leurs prfrences et leurs personnalisations soient enregistres, puis restaures au redmarrage de lapplication. Nous nous intresserons une mthode simple permettant de stocker les informations de conguration que les applications Java adoptent gnralement. Nous verrons ensuite le mcanisme performant des prfrences prsent dans Java SE 1.4.

536

Au cur de Java 2 - Notions fondamentales

Concordances de proprits
Une concordance de proprits est une structure de donnes qui stocke les paires cl/valeur. Les concordances de proprits permettent souvent de stocker des informations de conguration. Elles afchent trois caractristiques particulires :
m m m

Les cls et les valeurs sont des chanes. Le jeu peut facilement tre enregistr dans un chier et charg partir dun chier. Il existe un tableau secondaire pour les valeurs par dfaut.

La classe de la plate-forme Java qui implmente une concordance de proprit est appele Properties. Les concordances de proprits sont utiles pour spcier des options de conguration pour les programmes. Par exemple :
Properties settings = new Properties(); settings.put("width", "200"); settings.put("title", "Hello, World");

Utilisez la mthode store pour enregistrer cette liste de proprits dans un chier. Ici, nous enregistrons simplement la concordance de proprits dans le chier program.properties. Le deuxime argument est un commentaire inclus dans le chier :
FileOutputStream out = new FileOutputStream("program.properties"); settings.store(out, "Program Properties");

Le jeu dexemple donne la sortie suivante :


#Program Properties #Mon Apr 30 07:22:52 2007 width=200 title=Hello, World

Pour charger les proprits dun chier, utilisez


FileInputStream in = new FileInputStream("program.properties"); settings.load(in);

On stocke habituellement les proprits des programmes dans un sous-rpertoire du rpertoire daccueil de lutilisateur. Le nom du rpertoire commence souvent par un point ; sur un systme UNIX, cette convention indique un rpertoire systme masqu lutilisateur. Notre exemple de programme respecte cette convention. Pour trouver le rpertoire daccueil de lutilisateur, vous pouvez appeler la mthode System.getProperties qui utilise aussi un objet Properties pour dcrire les informations du systme. Le rpertoire daccueil possde la cl "user.home". Il existe galement une mthode pratique pour lire une seule cl :
String userDir = System.getProperty("user.home");

Il est gnralement conseill de fournir des paramtres par dfaut pour nos proprits de programmes, au cas o un utilisateur modierait le chier manuellement. La classe Properties possde deux mcanismes pour fournir les paramtres par dfaut. Tout dabord, ds que vous tudiez la valeur dune chane, vous pouvez spcier un paramtre par dfaut utiliser lorsque la cl nest pas prsente :
String title = settings.getProperty("title", "Default title");

Chapitre 10

Dployer des applets et des applications

537

Sil existe une proprit "title" dans le tableau des proprits, le titre utilisera cette chane. Dans le cas contraire, title est rgl sur "Default title". Sil vous parat trop ennuyeux de spcier le paramtre par dfaut pour chaque appel getProperty, vous pouvez packager tous les paramtres par dfaut dans une concordance de proprit secondaire et fournir cette concordance dans le constructeur de votre tableau de recherche :
Properties defaultSettings = new Properties(); defaultSettings.put("width", "300"); defaultSettings.put("height", "200"); defaultSettings.put("title", "Default title"); . . . Properties settings = new Properties(defaultSettings);

Vous pouvez en effet spcier les paramtres par dfaut si vous attribuez un autre paramtre de concordance de proprits au constructeur defaultSettings, mais ce nest pas une opration trs orthodoxe. Le Listing 10.7 montre comment utiliser les proprits pour stocker et charger ltat dun programme. Le programme se souvient de la position du cadre, de sa taille et de son titre. Vous pouvez aussi modier les proprits du chier .corejava/program.properties dans votre rpertoire daccueil pour modier laspect du programme comme vous le voulez.
INFO
Les proprits sont des tableaux simples nafchant aucune structure hirarchique. Lintroduction dune fausse hirarchie avec des noms de cls comme window.main.color, window.main.title, etc., est assez frquente. Toutefois, la classe Properties ne possde pas de mthode qui permette dorganiser une telle hirarchie. Si vous stockez des informations de conguration complexes, prfrez la classe Preferences (voir la section suivante).

Listing 10.7 : PropertiesTest.java


import import import import java.awt.EventQueue; java.awt.event.*; java.io.*; java.util.Properties;

import javax.swing.*; /** * Ce programme teste les proprits. Il se souvient de * la position du cadre, de sa taille et de son titre. * @version 1.00 2007-04-29 * @author Cay Horstmann */ public class PropertiesTest { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { PropertiesFrame frame = new PropertiesFrame();

538

Au cur de Java 2 - Notions fondamentales

frame.setVisible(true); } }); } } /** * Un cadre qui restaure la position et la taille partir dun * fichier de proprits et actualise les proprits la sortie. */ class PropertiesFrame extends JFrame { public PropertiesFrame() { // rcuprer la position, la taille et le titre des proprits String userDir = System.getProperty("user.home"); File propertiesDir = new File(userDir, ".corejava"); if (!propertiesDir.exists()) propertiesDir.mkdir(); propertiesFile = new File(propertiesDir, "program.properties"); Properties defaultSettings = new Properties(); defaultSettings.put("left", "0"); defaultSettings.put("top", "0"); defaultSettings.put("width", "" + DEFAULT_WIDTH); defaultSettings.put("height", "" + DEFAULT_HEIGHT); defaultSettings.put("title", ""); settings = new Properties(defaultSettings); if (propertiesFile.exists()) try { FileInputStream in = new FileInputStream(propertiesFile); settings.load(in); } catch (IOException e) { ex.printStackTrace(); } int left = Integer.parseInt(settings.getProperty("left")); int top = Integer.parseInt(settings.getProperty("top")); int width = Integer.parseInt(settings.getProperty("width")); int height = Integer.parseInt(settings.getProperty("height")); setBounds(left, top, width, height); // si pas de titre, demander lutilisateur String title = settings.getProperty("title"); if (title.equals("")) title = JOptionPane.showInputDialog( "Please supply a frame title:"); if (title == null) title = ""; setTitle(title); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent event) { settings.put("left", "" + getX()); settings.put("top", "" + getY());

Chapitre 10

Dployer des applets et des applications

539

settings.put("width", "" + getWidth()); settings.put("height", "" + getHeight()); settings.put("title", getTitle()); try { FileOutputStream out = new FileOutputStream(propertiesFile); settings.store(out, "Program Properties"); } catch (IOException ex) { ex.printStackTrace(); } System.exit(0); } }); } private File propertiesFile; private Properties settings; public static final int DEFAULT_WIDTH = 300; public static final int DEFAULT_HEIGHT = 200; } java.util.Properties 1.0 Properties()

Cre une correspondance vide de proprits.

Properties(Properties defaults)

Cre une correspondance vide de proprits avec un jeu de paramtres par dfaut. Paramtres :

defaults

Les paramtres par dfaut utiliser pour les recherches.

String getProperty(String key)

Rcupre une concordance de proprits. Renvoie la chane associe la cl, la chane associe la cl dans le tableau par dfaut si elle tait absente du tableau ou null si la cl tait absente du tableau des paramtres par dfaut. Paramtres :

key

La cl dont il faut rcuprer la chane associe.

String getProperty(String key, String defaultValue)

Rcupre une proprit avec une valeur par dfaut si la cl nest pas trouve. Renvoie la chane associe la cl ou la chane par dfaut si elle tait absente du tableau. Paramtres :

key defaultValue

La cl dont il faut rcuprer la chane associe. La chane renvoyer si la cl nest pas prsente.

void load(InputStream in) throws IOException

Charge une concordance de proprits partir dun ux dentre. Paramtres :

in

Le ux dentre.

void store(OutputStream out, String header) 1.2

Enregistre une concordance de proprits dans un ux de sortie. Paramtres : out header Le ux de sortie. Len-tte de la premire ligne du chier stock.

540

Au cur de Java 2 - Notions fondamentales

java.lang.System 1.0

Properties getProperties()

Rcupre toutes les proprits systme. Lapplication doit avoir lautorisation de rcuprer toutes les proprits, faute de quoi une exception de scurit est dclenche.

String getProperty(String key)

Rcupre la proprit systme avec le nom de cl donn. Lapplication doit avoir lautorisation de rcuprer la proprit, faute de quoi une exception de scurit est dclenche. Les proprits suivantes peuvent toujours tre rcupres :
java.version java.vendor java.vendor.url java.class.version os.name os.version os.arch file.separator path.separator line.separator java.specification.version java.vm.specification.version java.vm.specification.vendor java.vm.specification.name java.vm.version java.vm.vendor java.vm.name

INFO
Les noms des proprits systme sont librement accessibles dans le chier security/java.policy du rpertoire runtime Java.

LAPI Preferences
Comme vous lavez vu, la classe Properties simplie le chargement et lenregistrement des informations de conguration. Toutefois, lutilisation des chiers de proprit prsente plusieurs inconvnients :
m

Les chiers de conguration ne peuvent pas toujours tre stocks dans le rpertoire de base de lutilisateur. Or certains systmes dexploitation (comme Windows 9x) ne connaissent pas les rpertoires de base. Il nexiste pas de convention de dnomination standard pour les chiers de conguration, do laugmentation de la probabilit des conits de noms lorsque les utilisateurs installent plusieurs applications Java.

Certains systmes dexploitation disposent dun rfrentiel central pour les informations de conguration. Lexemple le plus rput est la Base de registre de Microsoft Windows. La classe Preferences de Java SE 1.4 fournit ce systme sans rapport avec la plate-forme. Sous Windows, la classe Preferences utilise la Base de registre des ns de stockage ; sous Linux, les informations

Chapitre 10

Dployer des applets et des applications

541

sont stockes dans un systme de chiers local. Bien entendu, limplmentation du rfrentiel est transparente pour le programmeur qui utilise la classe Preferences. Le rfrentiel Preferences possde une structure darborescence comprenant des noms de chemin de nud, comme /com/mycompany/myapp. Comme pour les noms de packages, on vitera les conits de noms en faisant commencer les chemins par des noms de domaine inverss. En fait, les concepteurs de lAPI suggrent de faire concorder les chemins de nud de conguration avec les noms des packages. Chaque nud du rfrentiel possde une table spare de paires cl/valeur que vous pouvez utiliser pour stocker des nombres, des chanes ou des tableaux doctets. Toutefois, aucune disposition na t prise pour stocker des objets srialisables. Les concepteurs dAPI ont considr que le format de srialisation tait trop fragile pour le stockage long terme. Bien entendu, si vous pensez diffremment, vous pouvez enregistrer les objets srialiss dans des tableaux doctets. Pour plus de exibilit, plusieurs arborescences parallles ont t cres. Chaque utilisateur du programme en possde une, et il en existe une supplmentaire intitule "arborescence du systme" et destine recueillir les paramtres communs tous les utilisateurs. La classe Preferences utilise la notion "dutilisateur courant" du systme dexploitation pour accder larborescence approprie de lutilisateur. Pour accder un nud de larborescence, commencez par la racine de lutilisateur ou du systme :
Preferences root = Preferences.userRoot();

ou :
Preferences root = Preferences.systemRoot();

Puis accdez au nud. Vous pouvez simplement fournir un nom de chemin de nud :
Preferences node = root.node("/com/mycompany/myapp");

Il existe un raccourci commode pour rcuprer un nud dont le nom du chemin est gal au nom du package dune classe. Prenez simplement un objet de cette classe et appelez
Preferences node = Preferences.userNodeForPackage(obj.getClass());

ou
Preferences node = Preferences.systemNodeForPackage(obj.getClass());

Gnralement, obj sera la rfrence this. Lorsque vous avez un nud, vous avez accs la table cl/valeur avec les mthodes
String get(String key, String defval) int getInt(String key, int defval) long getLong(String key, long defval) float getFloat(String key, float defval) double getDouble(String key, double defval) boolean getBoolean(String key, boolean defval) byte[] getByteArray(String key, byte[] defval)

Sachez que vous devez spcier une valeur par dfaut lorsque vous lisez les informations, au cas o les donnes du rfrentiel ne seraient pas disponibles. Les paramtres par dfaut sont ncessaires pour diverses raisons. Il est possible que des donnes manquent si lutilisateur na jamais prcis de prfrence, certaines plates-formes contraintes par les ressources pourraient ne pas avoir de rfrentiel et des priphriques mobiles pourraient tre dconnects du rfrentiel.

542

Au cur de Java 2 - Notions fondamentales

A linverse, vous pouvez crire des donnes dans le rfrentiel avec des mthodes put, comme ceci :
put(String key, String value) putInt(String key, int value)

etc. Vous pouvez numrer toutes les touches stockes dans un nud avec la mthode
String[] keys

Mais il nexiste actuellement aucune manire de dcouvrir le type de la valeur dune cl particulire. Les rfrentiels centraux comme la Base de registre Windows souffrent gnralement de deux problmes.
m m

Ils se transforment en "dpotoirs", remplis dinformations obsoltes. Les donnes de conguration sont entremles dans le rfrentiel, ce qui complique le dplacement des prfrences vers une nouvelle plate-forme.

La classe Preferences apporte une solution au second problme. Vous pouvez exporter les prfrences dune sous-arborescence (ou, moins souvent, dun seul nud) en appelant les mthodes
void exportSubtree(OutputStream out) void exportNode(OutputStream out)

Les donnes sont enregistres au format XML. Vous pouvez les importer dans un autre rfrentiel en appelant
void importPreferences(InputStream in)

Voici un exemple de chier :


<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE preferences SYSTEM "http://java.sun.com/dtd/preferences.dtd"> <preferences EXTERNAL_XML_VERSION="1.0"> <root type="user"> <map/> <node name="com"> <map/> <node name="horstmann"> <map/> <node name="corejava"> <map> <entry key="left" value="11"/> <entry key="top" value="9"/> <entry key="width" value="453"/> <entry key="height" value="365"/> <entry key="title" value="Hello, World!"/> </map> </node> </node> </node> </root> </preferences>

Si votre programme utilise des prfrences, offrez vos utilisateurs la possibilit de les exporter et de les importer, pour quils puissent facilement migrer leurs paramtres dun ordinateur un autre. Le programme du Listing 10.8 dmontre cette technique. Le programme enregistre simplement la

Chapitre 10

Dployer des applets et des applications

543

position et la taille de la fentre principale. Tentez de redimensionner la fentre, puis quittez lapplication et redmarrez-la. La fentre safchera comme vous lavez laisse en la quittant.
Listing 10.8 : PreferencesTest.java
import import import import import java.awt.EventQueue; java.awt.event.*; java.io.*; java.util.prefs.*; javax.swing.*;

/** * Un programme pour tester les paramtres de prfrences. Le programme * se souvient de la position, de la taille et du titre du cadre. * @version 1.02 2007-06-12 * @author Cay Horstmann */ public class PreferencesTest { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { PreferencesFrame frame = new PreferencesFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); } } /** * Un cadre qui restaure la position et la taille partir des * prfrences utilisateur et actualise les prfrences la fermeture. */ class PreferencesFrame extends JFrame { public PreferencesFrame() { // rcuprer la position, la taille et le titre // partir des prfrences Preferences root = Preferences.userRoot(); final Preferences node = root.node("/com/horstmann/corejava"); int left = node.getInt("left", 0); int top = node.getInt("top", 0); int width = node.getInt("width", DEFAULT_WIDTH); int height = node.getInt("height", DEFAULT_HEIGHT); setBounds(left, top, width, height); // si pas de titre, interroger lutilisateur String title = node.get("title", ""); if (title.equals("")) title = JOptionPane.showInputDialog( "Please supply a frame title:"); if (title == null) title = ""; setTitle(title);

544

Au cur de Java 2 - Notions fondamentales

// configurer le slecteur des fichiers XML final JFileChooser chooser = new JFileChooser(); chooser.setCurrentDirectory(new File(".")); // accepter tous les fichiers se terminant par .xml chooser.setFileFilter(new javax.swing.filechooser.FileFilter() { public boolean accept(File f) { return f.getName(). toLowerCase().endsWith(".xml") || f.isDirectory(); } public String getDescription() { return "XML files"; } }); // configurer les menus JMenuBar menuBar = new JMenuBar(); setJMenuBar(menuBar); JMenu menu = new JMenu("File"); menuBar.add(menu); JMenuItem exportItem = new JMenuItem("Export preferences"); menu.add(exportItem); exportItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { if(chooser.showSaveDialog( PreferencesFrame.this) == JFileChooser.APPROVE_OPTION) { try { OutputStream out = new FileOutputStream(chooser.getSelectedFile()); node.exportSubtree(out); out.close(); } catch (Exception e) { e.printStackTrace(); } } } }); JMenuItem importItem = new JMenuItem("Import preferences"); menu.add(importItem); importItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { if(chooser.showOpenDialog( PreferencesFrame.this) == JFileChooser.APPROVE_OPTION) {

Chapitre 10

Dployer des applets et des applications

545

try { InputStream in = new FileInputStream( chooser.getSelectedFile()); Preferences.importPreferences(in); in.close(); } catch (Exception e) { e.printStackTrace(); } } } }); JMenuItem exitItem = new JMenuItem("Exit"); menu.add(exitItem); exitItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { node.putInt("left", getX()); node.putInt("top", getY()); node.putInt("width", getWidth()); node.putInt("height", getHeight()); node.put("title", getTitle()); System.exit(0); } }); } public static final int DEFAULT_WIDTH = 300; public static final int DEFAULT_HEIGHT = 200; } java.util.prefs.Preferences 1.4 Preferences userRoot()

Renvoie le nud de prfrences racine de lutilisateur du programme dappel.


Preferences systemRoot()

Renvoie le nud de prfrences racine du systme.


Preferences node(String path)

Renvoie un nud accessible depuis le nud courant par le chemin donn. Si le chemin est absolu (cest--dire quil commence par un /), cela implique que le nud commence la racine de larborescence contenant ce nud de prfrence. Sil nexiste pas de nud avec le chemin donn, il est cr.

Preferences userNodeForPackage(Class cl) Preferences systemNodeForPackage(Class cl)

Renvoient un nud dans larborescence de lutilisateur actuel ou larborescence du systme dont le chemin de nud absolu correspond au nom du package de la classe cl.

String[] keys()

Renvoie toutes les cls appartenant ce nud.


String get(String key, String defval)

546

Au cur de Java 2 - Notions fondamentales

int getInt(String key, int defval) long getLong(String key, long defval) float getFloat(String key, float defval) double getDouble(String key, double defval) boolean getBoolean(String key, boolean defval) byte[] getByteArray(String key, byte[] defval)

Ces mthodes renvoient la valeur associe la cl donne ou la valeur par dfaut fournie sil ny a pas de valeur associe la cl, ou si la valeur associe nest pas du type correct, ou encore si la banque des prfrences nest pas disponible.

void void void void void void void put(String key, String value) putInt(String key, int value) putLong(String key, long value) putFloat(String key, float value) putDouble(String key, double value) putBoolean(String key, boolean value) putByteArray(String key, byte[] value)

Ces mthodes stockent une paire cl/valeur avec ce nud.


void exportSubtree(OutputStream out)

Ecrit les prfrences de ce nud et de ses enfants dans le ux spci.


void exportNode(OutputStream out)

Ecrit les prfrences de ce nud (mais pas de ses enfants) dans le ux spci.
void importPreferences(InputStream in)

Importe les prfrences contenues dans le ux spci. Ainsi se termine notre tude du dploiement de logiciels dans Java. Au chapitre suivant, vous apprendrez utiliser les exceptions pour prciser au programme ce quil doit faire en cas de problme lors de lexcution. Nous vous fournirons galement les trucs et astuces pour tester et dboguer, de faon minimiser les difcults en cours dexcution.

11
Exceptions, consignation, assertion et mise au point
Au sommaire de ce chapitre

Le traitement des erreurs Capturer les exceptions Quelques conseils sur lutilisation des exceptions Utiliser les assertions La consignation Les techniques de mise au point Utiliser un dbogueur
Dans un monde parfait, les utilisateurs saisissent leurs donnes dans la forme attendue. Leurs chiers existent et le programme ne contient pas de bogues. Jusqu prsent, nous avons suppos que nous habitions ce monde parfait. Il est grand temps de faire connaissance avec les mcanismes que possde Java pour affronter le monde rel et les programmes bogus. La dcouverte dune erreur est toujours dplaisante. Si les travaux de lutilisateur sont perdus du fait dune erreur de programmation ou dune circonstance extrieure au programme, votre programme risque dtre purement et simplement abandonn. Il faut au minimum dans tous les cas :
m m m

envoyer un message derreur ; sauvegarder lensemble du travail en cours ; permettre une sortie correcte du programme.

Dans les situations exceptionnelles telles que des donnes dentre fausses pouvant potentiellement planter le programme, Java utilise un mcanisme dinterception des erreurs appel de faon assez image traitement des exceptions. Le traitement des exceptions en Java est trs voisin de celui de C++ et de Delphi. La premire partie de ce chapitre traite des exceptions de Java.

548

Au cur de Java 2 - Notions fondamentales

Pendant le test, vous devrez excuter de nombreuses vrications pour vous assurer que votre programme fonctionne bien. Mais ces vrications sont longues et inutiles une fois le test termin. Vous pourriez simplement les supprimer et les replacer lorsquun test supplmentaire est ncessaire, mais cela est ennuyeux. Dans la deuxime partie, nous verrons comment utiliser la fonction dassertion pour activer les vrications de manire slective. Lorsque votre programme se comporte mal, vous ne pouvez pas toujours communiquer avec lutilisateur ni y mettre n. Vous pouvez en revanche enregistrer le problme pour une analyse ultrieure. La troisime partie du chapitre traite de la fonction de consignation de Java SE. Enn, nous vous apporterons quelques conseils pour obtenir des informations utiles dune application Java en cours et pour utiliser le dbogueur dans un IDE.

Le traitement des erreurs


Supposons une erreur survenant lexcution dans un programme crit en Java. La cause de lerreur peut tre un chier contenant une information incorrecte, une connexion rseau dfaillante, un index de tableau invalide (quelle horreur !) ou lemploi dune rfrence un objet non initialise. Lutilisateur sattend toujours un comportement logique des programmes dans tous les cas derreurs. Le programme devra donc, lorsquune opration ne peut tre poursuivie en raison dune erreur :
m m

soit revenir un tat dni et offrir lutilisateur la possibilit dexcuter dautres commandes ; soit permettre lutilisateur de sauvegarder son travail et de sortir normalement du programme.

Cela nest pas toujours vident. En effet, le code qui dcle un tat derreur (ou mme celui qui en est lorigine) est en principe sans relation directe avec le code qui ramne les donnes dans un tat connu ou celui qui sauvegarde le travail de lutilisateur et procde une sortie de programme normale. Lobjectif du traitement des exceptions est prcisment de pouvoir transfrer le contrle de lendroit o se produit une erreur un gestionnaire derreur capable daffronter cette situation. Pour traiter des situations exceptionnelles, le programme doit prendre en compte chaque erreur possible et les problmes qui y sont lis. Quels types de problmes faut-il prvoir ?
m

Les fautes de saisie de lutilisateur. Non contents de faire des fautes de frappe, certains utilisateurs suivent leur ide sans tenir compte des messages les guidant. Par exemple, le programme demande un utilisateur de corriger une URL dont la syntaxe est mauvaise. Le programme devra vrier de nouveau la syntaxe, mais galement faire lhypothse que lutilisateur na pas entr de correction. Sans quoi, la couche rseau mettra son tour des messages derreur. Les erreurs du matriel. Le matriel est parfois non oprationnel. Limprimante peut tre dconnecte ; une page Web peut se trouver inaccessible de faon temporaire. Les priphriques sinterrompent souvent au milieu dun travail : par exemple, une imprimante va manquer de papier. Les contraintes physiques. Un disque peut tre plein ou on peut manquer de mmoire en cours dexcution. Les erreurs de programmation. Dans certains cas, une mthode peut tre erratique : soit elle donnera des rsultats faux, soit elle appellera dautres mthodes de faon incorrecte. Des exemples dappels errons conduisent aux situations suivantes : une mthode calcule un indice de tableau incorrect, recherche une entre inexistante dans une table de hachage ou encore essaie de dpiler une pile vide.

Chapitre 11

Exceptions, consignation, assertion et mise au point

549

La mthode classique de raction une erreur consiste renvoyer un code derreur particulier qui sera trait par la mthode appelante. Par exemple, une mthode lisant dans un chier renverra une valeur 1 pour marquer une n de chier. Ce moyen peut tre efcace pour se sortir de nombreux cas exceptionnels. Une autre valeur de retour traditionnelle pour indiquer une erreur est la rfrence null (le Chapitre 10 donne cet exemple pour la mthode getParameter de la classe Applet). Il nest pas toujours possible de renvoyer un code derreur. Par exemple, il peut ne pas exister de moyen vident pour distinguer une bonne donne dune donne incorrecte : une mthode renvoyant un entier ne peut utiliser la valeur 1 pour signaler une erreur (cette valeur pourrait tout fait tre autorise). Nous avons vu au Chapitre 5 que Java laisse la possibilit dintroduire dans une mthode une sortie anormale dans le cas o la mthode ne peut excuter ce qui a t prvu. La mthode ne renvoie pas de valeur particulire, mais utilise la clause throws avec un objet encapsulant linformation derreur. Il faut bien voir que la mthode se termine immdiatement : en particulier, elle ne renvoie pas la valeur attendue ni aucune valeur, dailleurs. De plus, Java ne rend pas la main au code appelant ; le mcanisme du traitement dexceptions va lancer une recherche dun gestionnaire dexception capable de traiter cette situation derreur. Les exceptions obissent une syntaxe particulire et se trouvent dans une hirarchie dhritage part. Commenons par observer la syntaxe, puis nous donnerons quelques exemples concrets dutilisation.

Le classement des exceptions


En Java, un objet exception est toujours une instance dune sous-classe de Throwable. En particulier, comme nous ltudierons plus loin, il est possible de crer ses propres classes dexceptions dans le cas o les classes dnies dans Java ne conviendraient pas. La Figure 11.1 donne un diagramme simpli de la hirarchie des exceptions en Java. On voit que toutes les exceptions descendent de Throwable, puis la hirarchie se spare immdiatement en une branche Error et une branche Exception. La hirarchie Error dcrit les erreurs internes ou le manque de ressources dans le systme dexcution de Java. En principe, vous ne devez pas faire un throw dun objet de ce type. On ne peut pas grand-chose dans le cas de ce type derreur, au demeurant assez rare, sauf en informer lutilisateur et tenter de mettre n au programme de faon adquate. En programmation Java, seule importe la hirarchie Exception. Elle se spare galement en deux branches : les exceptions drivant de RuntimeException et les autres. Voici la rgle gnrale : une exception RuntimeException se produit toujours en raison dune erreur de programmation. Toutes les autres exceptions ont une cause externe votre programme, par exemple une erreur dentre/ sortie. Les exceptions hritant de RuntimeException traitent des problmes comme ceux-ci :
m m m

un mauvais transtypage ; un accs un tableau en dehors des limites ; lemploi dun pointeur null.

550

Au cur de Java 2 - Notions fondamentales

Figure 11.1
La hirarchie des exceptions en Java.
Throwable

Error

Exception

IOException

Runtime Exception

Les exceptions nhritant pas de RuntimeException comprennent :


m m m

lire au-del de la n dun chier ; accder une URL incorrecte ; rechercher un objet Class en donnant un nom ne correspondant aucune classe existante.

On peut donner une rgle excellente : "Sil sagit dune RuntimeException, ce ne peut tre que de votre faute." Ainsi, vous auriez pu viter une ArrayIndexOutOfBoundsException en contrlant que lindice du tableau reste bien lintrieur du tableau. De mme, lexception NullPointerException ne peut se produire si vous vitez dutiliser une variable de valeur null. Mais que peut-on faire dans le cas dune URL incorrecte ? Comment mme dterminer ce que signie incorrecte ? Pour un navigateur donn, certains types dURL seront inappropris. Par exemple, Netscape accepte une URL mailto, alors que le visualisateur dapplet en est incapable. Cette notion dpend donc de lenvironnement dexcution et non uniquement du programme. Les spcications du langage Java baptisent toute exception drivant de la classe Error ou de la classe RuntimeException du nom dexception hors contrle (unchecked). Les autres exceptions sont dites sous contrle (checked). Nous utiliserons cette terminologie pertinente. En outre, le compilateur vrie que vous apportiez des gestionnaires dexception pour toutes les exceptions sous contrle.
INFO
Le nom RuntimeException est ambigu : il va de soi que toutes les erreurs dont nous parlons se produisent lexcution.

INFO C++
Si vous connaissez bien la hirarchie des exceptions de la bibliothque C++ standard (hirarchie beaucoup moins tendue), les choses doivent vous paratre confuses. C++ possde deux classes dexceptions de base, runtime_error et logic_error. La classe logic_error est lquivalent de notre classe RuntimeException et correspond galement des erreurs de logique du programme. La classe runtime_error est la classe de base pour les exceptions provenant de causes imprvisibles. Elle traite les exceptions qui pour Java ne sont pas du type RuntimeException.

Chapitre 11

Exceptions, consignation, assertion et mise au point

551

Signaler les exceptions sous contrle


Une mthode Java va crer une exception si elle rencontre une situation quelle ne peut traiter. Il y a deux points considrer : la mthode ne doit pas seulement informer le compilateur Java des valeurs quelle va renvoyer, mais galement linformer des erreurs possibles. Par exemple, un programme devant lire un chier reconnat que le chier pourrait ne pas exister ou encore quil pourrait tre vide. Le code devant traiter les informations dun chier va donc devoir informer le compilateur quil peut crer une variante des IOException. Cest dans len-tte dune mthode que lon informe des exceptions sous contrle. Voici titre dexemple len-tte de lun des constructeurs de la classe FileInputStream appartenant la bibliothque standard (voir Chapitre 12 sur les ux) :
public FileInputStream(String name) throws FileNotFoundException

Len-tte indique que ce constructeur renvoie un objet FileInputStream dun paramtre String, mais quil peut aussi en cas danomalie lancer une FileNotFoundException. Si le constructeur vient se trouver dans cet tat fcheux, son appel ninitialisera pas un nouvel objet FileInputStream mais excutera la clause throws sur un objet de la classe FileNotFoundException. En ce cas, le systme dexcution va lancer la recherche dun gestionnaire dexception capable de traiter les objets FileNotFoundException. Quand on crit une mthode, il nest pas ncessaire de signaler tous les objets disponibles dans un throws. Pour savoir sil faut signaler un type dexception dans la clause throws dune mthode que vous vous proposez dcrire, examinons les quatre situations suivantes qui conduisent des exceptions :
m

Vous appelez une mthode qui lance une exception sous contrle, comme le constructeur FileInputStream. Vous identiez un cas derreur et vous lancez une exception sous contrle par linstruction throw (nous verrons linstruction throw dans la section suivante). Vous faites une erreur de programmation, comme a [-1] = 0 qui fait surgir une exception hors contrle telle que ArrayIndexOutOfBoundsException. Il se produit une erreur interne dans la machine virtuelle ou dans la bibliothque de runtime.

Dans les deux premiers cas, il faut imprativement informer les programmeurs qui se serviront de votre mthode que son utilisation peut provoquer lapparition dune exception. En effet, toute mthode lanant une exception peut devenir un pige fatal, puisquen labsence dun gestionnaire de cette exception, le programme sarrtera. A linstar des mthodes qui se trouvent dans les classes fournies avec Java, on dclare quune mthode peut lancer une exception par une spcication dexception dans len-tte de mthode :
class MyAnimation { . . . public Image loadImage(String s) throws IOException { . . . } }

552

Au cur de Java 2 - Notions fondamentales

Lorsquune mthode traite plusieurs exceptions sous contrle, elles doivent toutes gurer dans lentte. Les noms des mthodes sont spars par une virgule :
class MyAnimation { . . . public Image loadImage(String s) throws EOFException, MalformedURLException { . . . } }

En revanche, il ne faut pas signaler les erreurs internes de Java, plus prcisment les exceptions hritant de Error. Ces exceptions peuvent provenir de nimporte quelle partie dun programme et sont hors de votre contrle. De mme, il ne faut pas signaler les exceptions hors contrle hritant de RuntimeException :
class MyAnimation { . . . void drawImage(int i) throws ArrayIndexOutOfBoundsException // NON!!! { . . . } }

Ces types derreurs dexcution ne peuvent tre contrls que par vous. Si vous tes obsd par les valeurs errones des indices de tableaux, rien ne vous empche de passer le temps ncessaire les traquer au lieu de les signaler. En rsum, une mthode doit dclarer toutes les exceptions sous contrle. Les exceptions hors contrle soit sont au-del de ce qui vous est accessible (Error), soit rsultent de situations que vous nauriez pas d laisser se produire (RuntimeException). Si vous ne russissez pas dclarer toutes les exceptions sous contrle de faon adapte, le compilateur afchera un message derreur. Bien sr, comme vous lavez dj remarqu travers quelques exemples, au lieu de dclarer les exceptions, vous pouvez aussi les intercepter. Dans ce cas, lexception ne sera pas expulse hors de la mthode, et il nest pas ncessaire dutiliser des spcications de lancement. Vous verrez plus loin comment dcider sil faut intercepter une exception ou laisser quelquun dautre le faire votre place.
ATTENTION
Une mthode surcharge dune superclasse ne peut pas lancer plus dexceptions sous contrle que la mthode correspondante de sa superclasse (elle peut lancer des exceptions plus spciques ou aucune dans la mthode de la sous-classe). Si la mthode de la superclasse ne lance aucune exception sous contrle, il en va de mme pour la sousclasse. Par exemple, si vous surchargez JComponent.paintComponent, votre mthode paintComponent ne pourra lancer aucune exception sous contrle si la mthode de la superclasse nen lance aucune.

Quand une mthode dclare quelle lance une exception, instance dune classe dexceptions donne, elle peut alors lancer une exception de cette classe et de nimporte laquelle de ses sous-classes. Par

Chapitre 11

Exceptions, consignation, assertion et mise au point

553

exemple, le constructeur FileInputStream aurait pu prvenir quil lance une IOException. Nous ne savons pas ce niveau de quel type de IOException il sagit : ce peut tre une simple IOException ou un objet de lune des diverses sous-classes comme une FileNotFoundException.
INFO C++
Le spcieur throws est identique au spcieur throw de C++, avec une diffrence majeure : en C++, les spcieurs throw sont pris en compte au moment de lexcution et non la compilation. Autrement dit, le compilateur C++ ne contrle pas les spcications dexceptions. Si une exception nappartenant pas la liste throw est lance, la fonction unexpected est appele et, par dfaut, le programme se termine. Une fonction C++ peut lancer nimporte quelle exception en labsence dune spcication throw. En Java, une mthode sans spcieur throws ne peut lancer aucune exception.

Comment lancer une exception


Supposons quil se produise une situation tout fait anormale pour votre programme. Vous possdez une mthode readData qui lit un chier dont len-tte afrme :
Content-length: 1024

Vous rencontrez une n de chier au bout de 733 caractres ; vous dcidez que cette situation est assez anormale pour mriter une exception. Il vous faut dcider du type dexception. Un bon choix consiste rafner IOException. En feuilletant la documentation de lAPI Java, vous lisez "Signals that an EOF has been reached unexpectedly during input" comme description dune EOFException. Cest ce quil vous faut puisque cela "signale une n de chier anormale au cours dune entre". Voici comment la lancer :
throw new EOFException();

ou encore son quivalent :


EOFException e = new EOFException(); throw e;

Voici une vue de lensemble :


String readData(Scanner in) throws EOFException { . . . while (. . .) { if (!in.hasNext()) // EOF rencontre { if (n < len) throw new EOFException(); } . . . } return s; }

EOFException possde un second constructeur qui accepte un argument de type chane. On peut en proter pour dcrire plus en dtail la situation dexception :
String gripe = "Content-length: "+len+", Received: "+n; throw new EOFException(gripe);

554

Au cur de Java 2 - Notions fondamentales

Nous voyons quil est ais de lancer une exception si lune des classes dexceptions existantes fait le travail, car en ce cas il suft de : 1. trouver la classe dexceptions approprie ; 2. faire un objet de cette classe ; 3. le lancer. Lorsquune mthode lance une exception, elle ne rend pas la main son appelant. Cela implique quil est inutile de se proccuper dune valeur de retour par dfaut ou encore dun code derreur.
INFO C++
Lancer une exception sopre de la mme faon en C++ et en Java, avec une petite diffrence. En Java, on ne peut lancer que des objets des sous-classes de Throwable. En C++, on peut lancer des valeurs de nimporte quel type.

Crer des classes dexception


Il se peut que votre code rencontre un problme qui ne soit dcrit de faon adquate par aucune des classes dexception standard. En ce cas, il est assez facile de crer votre propre classe dexception. Il suft de la faire driver de Exception ou dune de ses classes enfants telles que IOException. Il est dusage de donner la fois un constructeur par dfaut et un constructeur qui contient un message dtaill (la mthode toString de la superclasse Throwable afche ce message dtaill, qui est trs utile la mise au point) :
class FileFormatException extends IOException { public FileFormatException() {} public FileFormatException(String gripe) { super(gripe); } }

Vous pouvez maintenant lancer votre propre type dexception :


String readData(BufferedReader in) throws FileFormatException { . . . while (. . .) { if (ch == -1) // EOF rencontre { if (n < len) throw new FileFormatException(); } . . . } return s; } java.lang.Throwable 1.0

Throwable()

Construit un nouvel objet Throwable sans message dtaill.

Chapitre 11

Exceptions, consignation, assertion et mise au point

555

Throwable(String message)

Construit un nouvel objet Throwable avec le message dtaill spci. Par convention, toutes les classes dexceptions drives supportent la fois un constructeur par dfaut et un constructeur comprenant un message dtaill.

String getMessage()

Rcupre le message dtaill de lobjet Throwable.

Capturer les exceptions


Les exceptions sont faciles lancer ; il faut maintenant prvoir un code pour les capturer, ce qui demande un peu plus dattention. Si une exception se produit sans tre capture nulle part, le programme se termine et afche un message donnant le type de lexception ainsi quune trace de la pile. Un programme graphique (que ce soit un applet ou une application) intercepte les exceptions, afche les messages de trace de la pile, puis retourne sa boucle de traitement de linterface utilisateur (lorsque vous dboguez un programme graphique, il est conseill de garder lcran la fentre de lancement non rduite). Pour capturer une exception, on crit un bloc try/catch, dont la forme la plus simple est la suivante :
try { votre code plus de code et encore du code } catch(ExceptionType e) { le gestionnaire pour ce type dexception }

Si une exception est lance dans le bloc try de la classe spcie par la clause catch, alors : 1. Le programme abandonne lexcution du code restant dans le bloc try. 2. Le programme excute le code du gestionnaire situ dans la clause catch. Si aucune des instructions du bloc try ne lance une exception, le programme ignore la clause catch. Si une instruction lintrieur dune mthode lance une exception dun autre type que celui qui est spci par la clause catch, la mthode sinterrompt immdiatement (heureusement, lun de ses appelants a dj introduit une clause catch pour ce type). Pour observer cela en action, voici des instructions typiques pour lire lintrieur dun texte :
public void read(String filename) { try { InputStream in = new FileInputStream(filename); int b; while ((b = in.read())!=-1) { traitement de lentre } }

556

Au cur de Java 2 - Notions fondamentales

catch (IOException exception) { exception.printStackTrace(); } }

Les instructions de la clause try sont sans mystre : elles lisent et traitent des lignes jusqu rencontrer une n de chier. Comme vous pouvez le voir en examinant lAPI de Java, la mthode read peut lancer une IOException. En ce cas, lensemble de la boucle while est ignor et la clause catch force un traage de la pile. Pour un programme de peu dimportance, cela semble une faon adapte de grer le problme des exceptions. Quel autre choix avons-nous ? Bien souvent, il vaut mieux ne rien faire du tout et simplement passer lexception lappelant. Si une erreur se produit dans la mthode read, on laisse lappelant de la mthode read sen inquiter ! Si nous voulons suivre cette approche, nous devons signaler que la mthode peut lancer une IOException :
public void read(String filename) throws IOException { InputStream in = new FileInputStream(filename); int b; while ((b = in.read())!=-1) { traitement de lentre } }

Il faut se rappeler que le compilateur exige toujours un spcieur throws. Si vous appelez une mthode qui lance une exception sous contrle, il vous faut soit la grer soit la propager. Quelle est la meilleure faon de procder ? En rgle gnrale, vous devriez rcuprer les exceptions que vous savez grer et propager les autres. Dans ce dernier cas, il faut obligatoirement ajouter un spcieur throws pour signaler lappelant quune exception peut tre lance. Reportez-vous la documentation de lAPI de Java pour voir quelle mthode lance des exceptions, puis dcider de grer vous-mme vos exceptions ou de les ajouter la liste des throws. Il vaut bien mieux renvoyer une exception au gestionnaire ad hoc que la museler. Notez quil existe une exception cette rgle : lorsque vous crivez une mthode surchargeant une mthode de sa superclasse qui ne lance pas dexception (telle paintComponent dans JComponent), vous devez rcuprer toutes les exceptions sous contrle de la mthode. Vous navez pas le droit de dclarer une mthode de sous-classe des spcieurs throws qui ne soient dj prsents dans la mthode de sa superclasse.
INFO C++
La capture des exceptions se fait de faon quasi identique en Java et en C++. Plus prcisment,
catch(Exception e) // Java

est quivalent
catch (Exception& e) // C++

Il ny a pas dquivalent du catch(...) de C++, qui nest pas ncessaire en Java puisque toutes les exceptions drivent dune mme superclasse de base.

Chapitre 11

Exceptions, consignation, assertion et mise au point

557

Capturer des exceptions multiples


On peut capturer des exceptions multiples dans un bloc try et les grer ensuite diffremment. Il faut utiliser en ce cas une clause catch spcique chaque type, comme dans cet exemple :
try { ici du code qui est susceptible de lancer des exceptions } Catch (MalformedURLException e1) { action chaud pour les URL incorrectes } catch(UnknownHostException e2) { action chaud pour les htes inconnus } catch(IOException e3) { action chaud pour tous les autres problmes dE/S }

Lobjet exception (e1, e2, e3) peut renseigner sur la nature de lexception. Pour en savoir plus sur lobjet exception, on peut essayer
e3.getMessage()

qui renvoie un message derreur dtaill, sil existe, ou encore


e3.getClass().getName()

qui donne le type effectif de lobjet exception.

Relancer et enchaner les exceptions


Vous pouvez lancer une exception dans une clause catch. Cela se produit typiquement quand il faut en changer le type. Si vous construisez un sous-systme qui sera utilis par dautres programmeurs, vous utiliserez en toute logique un type dexception indiquant une panne du sous-systme, par exemple ServletException. Le code qui excute un servlet peut ne pas vouloir connatre le problme en dtail, mais bien savoir que le servlet tait erron. Voici comment intercepter une exception et la relancer :
try { accder la base de donnes } catch (SQLException e) { throw new ServletException("database error: " + e.getMessage()); }

Ici, lexception ServletException est construite avec le texte de son message. Depuis Java SE 1.4, vous pouvez faire encore mieux et dnir lexception dorigine comme la "cause" de la nouvelle exception :

558

Au cur de Java 2 - Notions fondamentales

try { accder la base de donnes } catch (SQLException e) { Throwable se = new ServletException("database error"); se.initCause(e); throw se; }

Au moment de linterception, il est possible de rcuprer lexception dorigine :


Throwable e = se.getCause();

Cette technique demballage est fortement recommande. Elle vous permet de lancer des exceptions de haut niveau dans des sous-systmes, sans pour autant perdre les dtails de la panne initiale.
ASTUCE
La technique demballage se rvle utile galement lorsque survient une exception sous contrle dans une mthode qui nest pas autorise le faire. Vous pouvez intercepter cette exception et lemballer dans une exception dexcution.

INFO
Plusieurs classes dexception, telles ClassNotFoundException, InvocationTargetException et RuntimeException, possdaient leurs propres schmas de chanage. Depuis Java SE 1.4, elles se conforment au mcanisme de la "cause". Vous pouvez toujours rcuprer lexception chane en utilisant la mthode prcdente, mais vous pouvez maintenant simplement appeler getCause.

La clause nally
Lorsque votre code lance une exception, il interrompt le code restant dans votre mthode et sort de celle-ci. Cette situation peut causer un problme lorsque la mthode en question dtient une ressource locale, quelle est la seule le savoir et que cette ressource doit tre dsalloue. Une solution peut tre de rcuprer et de relancer lensemble des exceptions. Mais cette solution est fastidieuse parce quil faudra dsallouer la ressource deux endroits : dans le code normal et dans le code de traitement de lexception. Java offre une meilleure solution, la clause finally. Nous montrons ici comment se dbarrasser dun objet Graphics, comme il se doit. Si vous effectuez une programmation de base de donnes en Java, vous devrez utiliser les mmes techniques pour mettre n aux connexions la base de donnes. Comme vous le verrez au Chapitre 4 du Volume II, il est trs important de clore correctement toutes les connexions, et ce mme en cas dexceptions. Le code de la clause finally sexcute, quune exception ait t intercepte ou non. Dans lexemple suivant, le programme se dbarrassera du contexte graphique dans tous les cas :
Graphics g = image.getGraphics(); try { // 1

Chapitre 11

Exceptions, consignation, assertion et mise au point

559

ici du code qui est susceptible de lancer des exceptions // 2 } catch (IOException e) { // 3 afficher la bote de dialogue derreur // 4 } finally { // 5 g.dispose(); } // 6

Examinons les trois situations o le programme risque dexcuter la clause finally. 1. Le code ne lance pas dexception. Dans ce cas, le programme commence par excuter toutes les instructions du bloc try. Il excute ensuite celles de la clause finally, puis passe lexcution de la premire ligne suivant la clause finally. Autrement dit, lexcution passe par les points 1, 2, 5 et 6. 2. Le code lance une exception capture dans une clause catch, dans notre cas une IOException. Pour cela, le programme excute lensemble des instructions du bloc try jusquau point o lexception a t lance. Le code restant du bloc est alors ignor et le programme excute le code de la clause catch correspondante, puis celui de la clause finally. Si la clause catch ne lance pas dexception, le programme excute la premire ligne suivant la clause finally. Dans ce scnario, lexcution passe par les points 1, 3, 4, 5 et 6. Si elle lance une exception, lexception est renvoye lappelant de cette mthode et lexcution passe par les points 1, 3 et 5 uniquement. 3. Le code lance une exception qui nest capture par aucune clause catch. A cet effet, le programme excute lensemble du code du bloc try jusqu ce que lexception apparaisse. Le reste du code situ dans le bloc try est ignor, et cest celui de la clause finally qui est excut. Lexception est renvoye lappelant de la mthode. Lexcution passe par les points 1 et 5 uniquement. On peut utiliser la clause finally sans clause catch, comme dans linstruction try suivante :
InputStream in = ...; try { ici du code qui est susceptible de lancer des exceptions } finally { in.close(); }

La commande in.close() de la clause finally est excute dans le bloc try, quune exception soit rencontre ou non. Bien entendu, dans le cas o une exception est rencontre, elle est renvoye et devra tre rcupre dans une autre clause catch.

560

Au cur de Java 2 - Notions fondamentales

Comme nous lexpliquons dans lastuce suivante, nous recommandons dutiliser la clause finally de cette manire ds que vous devez fermer une ressource.
ASTUCE
Nous recommandons chaudement de dcoupler les blocs try/catch et try/finally. Votre code sera alors beaucoup moins confus. Par exemple :
InputStream in = ...; try { try { code susceptible de lancer une exception } finally { in.close(); } } catch (IOException e) { afficher la bote de dialogue derreur }

Le bloc try interne na quune seule responsabilit : sassurer que le ux dentre soit ferm. Quant au bloc try extrieur, il doit vrier que les erreurs sont signales. Cette solution est plus claire, mais elle prsente aussi lavantage dtre plus fonctionnelle, puisque les erreurs de la clause finally sont bien rapportes.

ATTENTION
La clause finally provoque des rsultats inattendus lorsquelle contient des instructions return. Supposons que vous quittiez au milieu dun bloc try avec une instruction return. Avant la mthode return, le contenu du bloc finally est excut. Sil contient galement une instruction return, il masque la valeur dorigine du premier return. Voici un exemple :
public static int f(int n) { try { int r = n * n; return r; } finally { if (n == 2) return 0; } }

Si vous appelez f(2), alors le bloc try calcule r = 4 et excute linstruction return. Cependant la clause finally est excute avant que la mthode ne soit nie. La clause finally provoque un retour 0 de la mthode, ignorant la valeur dorigine qui tait 4.

Chapitre 11

Exceptions, consignation, assertion et mise au point

561

Parfois la clause finally rend la vie difcile, plus prcisment si la mthode de nettoyage peut galement lancer une exception. Un cas typique consiste fermer un ux. Pour en savoir plus, rendez-vous au Chapitre 1 du Volume II. Supposez que vous dsiriez vous assurer que vous terminez un ux lorsquune exception surgit durant le ux du code en cours :
InputStream in = ...; try { code susceptible de lancer des exceptions } finally { in.close(); }

Maintenant, supposez que le code dans le bloc try lance une exception autre quune exception IOException, ce qui est lintrt de lappelant du code. Le bloc finally sexcute et la mthode close est appele. Elle peut elle-mme lancer une exception IOException! Lorsque cela se produit, lexception dorigine est perdue et la seconde prend sa place. Cest tout fait contraire lesprit de la gestion derreur. Cest une ide excellente, autant que peu suivie par les concepteurs de la classe InputStream, que dviter de lancer des exceptions dans les oprations de nettoyage telles que dispose, close, et ainsi de suite, dont vous prvoyez lappel par les utilisateurs dans les blocs finally.
INFO C++
Il existe une diffrence fondamentale entre C++ et Java dans leur manire de grer les exceptions. Java ne possde pas de destructeur : il ny a donc pas de vidage de la pile comme en C++. Ainsi, le programmeur Java doit introduire lui-mme le code ncessaire pour grer les ressources dans les blocs finally. Comme Java possde un ramasse-miettes, moins de ressources ncessitent dtre dsalloues directement.

Analyser les traces de piles


Une trace de pile (stack trace) est une liste des appels de mthodes en attente, gurant un moment donn dans lexcution dun programme. Vous en avez certainement dj vu, puisquelles safchent lorsquun programme Java est termin par une exception non intercepte. Avant Java SE 1.4, on accdait la description textuelle de la trace de pile en appelant la mthode StrackTrace de la classe Throwable. Vous pouvez maintenant appeler la mthode getStackTrace, qui renvoie un tableau dobjets StackTraceElement analyser dans votre programme. Par exemple :
Throwable t = new Throwable(); StackTraceElement[] frames = t.getStackTrace(); for (StackTraceElement frame: frames) analyser le cadre

La classe StackTraceElement possde des mthodes lui permettant dobtenir le nom du chier et le numro de ligne, de mme que le nom de la classe et de la mthode de la ligne de code qui sexcute. La mthode toString produit une chane o toutes ces informations ont t mises en forme.

562

Au cur de Java 2 - Notions fondamentales

Java SE 5.0 ajoute la mthode statique Thread.getAllStackTraces, qui produit les traces de pile de tous les threads. Voici comment utiliser cette mthode :
Map<Thread, StackTraceElement[]> map = Thread.getAllStackTraces(); for (Thread t: map.keySet()) { StackTraceElement[] frames = map.get(t); analyser les cadres }

Reportez-vous aux Chapitres 13 et 14 pour en savoir plus sur les threads et sur linterface Map. Le Listing 11.1 afche la trace de pile dune fonction factorielle rcurrente. Si vous calculez par exemple factorial(3), lafchage sera :
factorial(3): StackTraceTest.factorial(StackTraceTest.java:18) StackTraceTest.main(StackTraceTest.java:34) factorial(2): StackTraceTest.factorial(StackTraceTest.java:18) StackTraceTest.factorial(StackTraceTest.java:24) StackTraceTest.main(StackTraceTest.java:34) factorial(1): StackTraceTest.factorial(StackTraceTest.java:18) StackTraceTest.factorial(StackTraceTest.java:24) StackTraceTest.factorial(StackTraceTest.java:24) StackTraceTest.main(StackTraceTest.java:34) return 1 return 2 return 6

Listing 11.1 : StackTraceTest.java


import java.util.*; /** * Un programme qui affiche une fonction de trace dun * appel de mthode rcurrent. * @version 1.01 2004-05-10 * @author Cay Horstmann */ public class StackTraceTest { /** * Calcule la factorielle dun nombre * @param n Un entier non-ngatif * @return n! = 1 * 2 * . . . * n */ public static int factorial(int n) { System.out.println("factorial(" + n + "):"); Throwable t = new Throwable(); StackTraceElement[] frames = t.getStackTrace(); for (StackTraceElement f: frames) System.out.println(f); int r; if (n <= 1) r = 1; else r = n * factorial(n - 1); System.out.println("return " + r);

Chapitre 11

Exceptions, consignation, assertion et mise au point

563

return r; } public static void main(String[] args) { Scanner in = new Scanner(System.in); System.out.print("Enter n: "); int n = in.nextInt(); factorial(n); } } java.lang.Throwable 1.0

Throwable(Throwable cause) 1.4 Throwable(String message, Throwable cause) 1.4

Construisent un Throwable avec la cause donne.


Throwable initCause(Throwable cause) 1.4

Dnit la cause pour cet objet ou lance une exception si cet objet possde dj une cause. Renvoie this.

Throwable getCause() 1.4

Rcupre lobjet exception qui a t dni comme cause de cet objet ou null sil nexiste pas de cause.

StackTraceElement[] getStackTrace() 1.4

Rcupre la trace de la pile dappels de la construction de cet objet.


java.lang.Exception 1.0

Exception(Throwable cause) 1.4 Exception(String message, Throwable cause)

Construisent une exception avec une cause donne.


java.lang.RuntimeException 1.0

RuntimeException(Throwable cause) 1.4 RuntimeException(String message, Throwable cause) 1.4

Construisent une exception RuntimeException avec une cause donne.


java.lang.StackTraceElement 1.4

String getFileName()

Rcupre le nom du chier source contenant le point dexcution de cet lment ou null si ces informations ne sont pas disponibles.

int getLineNumber()

Rcupre le numro de ligne du chier source contenant le point dexcution de cet lment ou 1 si ces informations ne sont pas disponibles.

String getClassName()

Rcupre le nom complet de la classe contenant le point dexcution de cet lment.


String getMethodName()

564

Au cur de Java 2 - Notions fondamentales

Rcupre le nom de la mthode contenant le point dexcution de cet lment. Le nom dun constructeur est <init>. Le nom dun initialiseur statique est <clinit>. Il est impossible de faire la distinction entre des mthodes surcharges ayant le mme nom.

boolean isNativeMethod()

Renvoie true si le point dexcution de cet lment se trouve dans une mthode native.
String toString()

Renvoie une chane mise en forme contenant le nom de la classe et de la mthode, le nom du chier ainsi que le numro de ligne lorsquil est disponible.

Quelques conseils sur lutilisation des exceptions


Les programmeurs ne sentendent pas tous sur lutilisation des exceptions. Certains considrent toutes les exceptions sous contrle comme une vritable nuisance, dautres nen ont jamais assez. Quant nous, nous considrons que les exceptions (mme celles sous contrle) ont un rle jouer ; nous vous proposons donc quelques conseils pour les utiliser correctement. 1. La gestion des exceptions nest pas cense remplacer un test simple. Pour illustrer cette afrmation, nous avons crit un code qui tente 10 millions de fois de dpiler une pile vide. Il commence par tester si la pile est vide.
if (!s.empty()) s.pop();

Ensuite nous lui indiquons de dpiler quel que soit ltat de la pile et capturons EmptyStackException qui mentionne que nous naurions pas d le faire.
try() { s.pop(); } catch(EmptyStackException e) { }

Sur notre ordinateur, le test nous donne les temps suivants (voir Tableau 11.1).
Tableau 11.1 : Dure des tests

Test 646 ms

Throw/Catch 21 739 ms

Nous voyons quil faut plus de temps pour intercepter une exception que pour effectuer un simple test. La morale de lhistoire est : il ne faut employer les exceptions que dans des cas trs particuliers. 2. Ne faites pas une gestion ultrane des exceptions. Bien souvent, on encapsule chaque instruction dans un bloc try distinct :
OutputStream out; Stack s;

Chapitre 11

Exceptions, consignation, assertion et mise au point

565

for (i = 0; i < 100; i++) { try { n = s.pop(); } catch (EmptyStackException s) { // la pile tait vide } try { out.writeInt(n); } catch (IOException e) { // problme dcriture dans le fichier } }

Cette faon de procder pnalise normment votre programme. Il faut rchir la tche que doit accomplir le programme. Nous voulons ici dpiler 100 nombres et les crire dans un chier (ce nest quun exemple). Nous ne pouvons pas en ralit intervenir en cas de problme : si la pile est vide, nous ny pouvons rien ; si une erreur apparat dans le chier, elle ne disparatra pas par magie. Il est par la suite raisonnable dencapsuler la tche tout entire dans un bloc try puisque, si lune des deux oprations est impossible, nous ne pouvons que labandonner :
try { for (i = 0; i < 100; i++) { n = s.pop(); out.writeInt(n); } } catch (IOException e) { // problme dcriture dans le fichier } catch (EmptyStackException s) { // pile vide }

Ce programme est beaucoup plus adquat. Il satisfait parfaitement lun des devoirs du traitement des exceptions, savoir sparer le traitement normal et le traitement derreur. 3. Faites bon usage de la hirarchie des exceptions. Ne vous contentez pas de lancer une RuntimeException. Trouvez une sous-classe adapte ou crez la vtre. Il ne faut pas simplement intercepter Throwable, car votre code sera alors plus difcile lire et modier. Respectez la diffrence entre les exceptions sous et hors contrle. Les exceptions sous contrle sont invitablement lourdes grer (ne les dclenchez pas pour des erreurs logiques). La bibliothque de rexion, par exemple, se trompe ce sujet. Les appelants doivent souvent intercepter des exceptions dont ils savent quelles ne peuvent jamais survenir.

566

Au cur de Java 2 - Notions fondamentales

Nhsitez pas transformer une exception en une autre qui soit mieux adapte. Par exemple, lorsque vous analysez un entier dans un chier, interceptez lexception NumberFormatException et transformez-la en sous-classe de IOException ou MySubsystemException. 4. Ne muselez pas les exceptions. En Java, cest une tentation irrsistible. Vous crivez, par exemple, une mthode qui en appelle une autre susceptible de lancer une exception une fois par sicle. Le compilateur proteste parce que vous navez pas dclar lexception dans la liste des throws de votre mthode, ce que vous ne voulez pas faire parce que le compilateur va dtecter une erreur pour toutes les mthodes appelant votre mthode. Aussi procdez-vous de la faon suivante :
public Image loadImage(String s) { try { code menaant de lancer des exceptions sous contrle } catch(Exception e) {} // et ainsi }

Votre programme se compilera maintenant sans problme ; il sexcutera parfaitement, sauf sil survient une exception. Lexception sera passe sous silence ! Donc, si vous considrez que les exceptions ont une certaine importance, il vous faut investir un peu dans leur traitement. 5. Lorsque vous dtectez une erreur, "lamour vache" vaut mieux que lindulgence. Certains programmeurs sinquitent du lancement des exceptions lorsquils dtectent une erreur. Il conviendrait peut-tre mieux de renvoyer une valeur factice plutt que de lancer une exception lorsquune mthode est appele avec des paramtres non valides. Par exemple, Stack.pop devrait-il renvoyer null plutt que lancer une exception lorsquune pile est vide ? Nous croyons quil vaut mieux lancer une EmptyStackException au point dchec que laisser une NullPointerException survenir ultrieurement. 6. Il ne faut pas avoir honte de propager les exceptions. Trs souvent, on se croit oblig de traiter toutes les exceptions rpertories. Supposons une mthode susceptible de lancer une exception, par exemple le constructeur FileInputStream ou la mthode readLine : dinstinct on va intercepter lexception ventuellement gnre, alors quil est en ce cas prfrable de propager lexception.
public void readStuff(String name) throws IOException // ne pas avoir honte! { FileInputStream in = new FileInputStream(filename); . . . }

Les mthodes des niveaux suprieurs sont en gnral mieux prpares informer lutilisateur sur les erreurs ou interrompre lexcution de commandes qui nont pas abouti.
INFO
Les rgles 5 et 6 peuvent tre rsumes dans la phrase suivante : "Dclencher dabord, intercepter plus tard."

Chapitre 11

Exceptions, consignation, assertion et mise au point

567

Les assertions
Les assertions sont une langue souvent utilise pour une programmation dite "dfensive". Vous pouvez tre convaincu quune proprit donne est remplie et vous reposer sur elle dans votre code. Vous pourriez par exemple calculer :
double y = Math.sqrt(x);

Vous tes certain que x nest pas ngatif. Cest peut-tre le rsultat dun autre calcul qui ne peut pas avoir de rsultat ngatif ou un paramtre dune mthode qui exige de ses appelants quils fournissent uniquement des entres positives. Vous souhaitez pourtant procder une deuxime vrication plutt que voir apparatre des valeurs droutantes virgule ottante dans votre calcul. Vous pourriez bien sr lancer une exception :
if (x < 0) throw new IllegalArgumentException("x < 0");

Ce code, toutefois, reste dans le programme, mme une fois le test termin. Si vous avez de nombreuses vrications de ce type, le programme tourne un peu moins vite quil ne devrait. Le mcanisme des assertions permet dinsrer des vrications au moment du test, puis de les faire disparatre automatiquement dans le code de production. Depuis Java SE 1.4, le langage Java possde le mot cl assert. Il prend deux formes :
assert condition;

et
assert condition: expression;

Ces deux instructions valuent la condition et lancent une AssertionError si elle vaut false. Dans la deuxime, lexpression est transmise au constructeur de lobjet AssertionError et transforme en une chane de message.
INFO
Le seul objectif de la partie expression est de produire une chane de message. Lobjet AssertionError ne stocke pas la valeur relle de lexpression, vous ne pouvez donc pas effectuer une requte dessus par la suite. Comme lindique la documentation du JDK avec son charme tout paternaliste, ceci "encouragerait les programmeurs tenter de rcuprer aprs une panne dassertion, ce qui est contraire lobjectif de cette structure".

Pour sassurer que x nest pas ngatif, vous pouvez simplement utiliser linstruction
assert x >=0;

Vous pouvez aussi simplement transfrer la valeur relle de x dans lobjet AssertionError, de sorte quil safche par la suite :
assert x >=0: x;

INFO C++
La macro assert du langage C transforme la condition dassertion en une chane qui safche si lassertion choue. Par exemple, si assert(x >=0) choue, elle afche que "x >=0" est la condition dfaillante. En Java, la condition ne fait pas automatiquement partie du rapport derreur. Pour la voir, il faut la transfrer sous forme de chane dans lobjet AssertionError: assert x >=0: "x >= 0".

568

Au cur de Java 2 - Notions fondamentales

Activation et dsactivation des assertions


Les assertions sont dsactives par dfaut. Pour les activer, excutez le programme avec loption -enableassert ou -ea :
java -enableassertions MyApp

Sachez quil est inutile de recompiler votre programme pour activer ou dsactiver les assertions. Lactivation ou la dsactivation des assertions est une fonction du chargeur de classe. Lorsque les assertions sont dsactives, le chargeur de classe retire le code de lassertion de sorte quil ne ralentisse pas lexcution. Vous pouvez mme activer les assertions dans des classes spciques ou dans des packages complets. Par exemple :
java -ea:MyClass -ea:com.mycompany.mylib... MyApp

Cette commande active les assertions pour la classe MyClass et toutes les classes du package com.mycompany.mylib et ses sous-packages. Loption -ea active les assertions dans toutes les classes du package par dfaut. Vous pouvez aussi dsactiver les assertions dans certains packages et classes avec loption -disableassertions ou -da :
java -ea:... -da:MyClass MyApp

Certaines classes sont charges non pas par un chargeur de classes mais directement par la machine virtuelle. Vous pouvez utiliser ces commutateurs pour activer ou dsactiver au choix les assertions dans ces classes. Toutefois, les arguments -ea et -da qui activent ou dsactivent toutes les assertions ne sappliquent pas aux "classes systme" en labsence de chargeurs de classes. Utilisez largument -enablesystemassertions/-esa pour activer les assertions dans les classes systme. Il est aussi possible de contrler, par le biais de la programmation, le statut des assertions des chargeurs de classes. Reportez-vous aux notes API de cette section.

Vrication des paramtres avec des assertions


Le langage Java propose trois mcanismes pour grer les pannes systme :
m m m

dclenchement dune exception ; consignation ; utilisation des assertions. Les pannes dassertions sont supposes tre des erreurs dnitives et irrcuprables. Les vrications dassertions ne sont actives que lors du dveloppement et du test (ceci est quelquefois compar la situation suivante : porter un gilet de sauvetage lorsque lon se trouve sur la rive et sen dbarrasser au milieu de locan).

A quel moment choisir ces assertions ? Il faut se souvenir de ceci :


m m

Il est donc dconseill dutiliser les assertions pour signaler des conditions rcuprables une autre partie du programme ou pour communiquer des problmes lutilisateur. Les assertions ne doivent tre utilises que pour retrouver des erreurs internes au programme lors du test.

Chapitre 11

Exceptions, consignation, assertion et mise au point

569

Etudions un scnario habituel : la vrication des paramtres de mthode. Faut-il utiliser les assertions pour rechercher des valeurs dindice interdites ou des rfrences null? Pour rpondre cette question, tudiez la documentation de la mthode. Supposons que vous implmentiez une mthode de tri :
/** * Trie la gamme spcifie du tableau indiqu * dans lordre numrique croissant. La plage trier * stend de fromIndex compris toIndex, exclu. * @param a Le tableau trier. * @param fromIndex Lindice du premier lment (inclus) trier. * @param toIndex Lindice du dernier lment (exclu) trier. * @throws IllegalArgumentException si fromIndex > toIndex * @throws ArrayIndexOutOfBoundsException si fromIndex < 0 * ou toIndex > a.length */ static void sort(int[] a, int fromIndex, int toIndex)

La documentation indique que la mthode lance une exception si les valeurs dindice sont fausses. Ce comportement fait partie du contrat que la mthode passe avec ses appelants. Si vous implmentez la mthode, vous devez respecter le contrat et lancer les exceptions indiques. Il ne conviendrait pas dutiliser les assertions ici. Faut-il dclarer que a nest pas null? Voil qui ne convient pas non plus. La documentation de la mthode ne mentionne rien sur son comportement lorsque a vaut null. Les appelants ont le droit de supposer que la mthode sera russie dans ce cas et quelle ne lancera pas derreur dassertion. Supposons toutefois que le contrat de la mthode ait t lgrement diffrent :
@param a Le tableau trier (ne doit pas tre null)

Les appelants de la mthode ont t avertis quil est interdit dappeler la mthode avec un tableau null. La mthode peut toutefois commencer avec lassertion
assert a!= null;

Les informaticiens dsignent ce type de contrat sous le terme de prcondition. La mthode initiale ne disposait pas de prcondition sur ses paramtres, elle promettait un comportement bien dni dans tous les cas. La mthode rvise possde une seule prcondition : que a ne soit pas null. Si lappelant ne respecte pas la prcondition, les paris ne tiennent plus et la mthode peut faire ce quelle veut. En fait, une fois lassertion en place, la mthode aura un comportement assez imprvisible en cas dappel interdit. Parfois, elle lancera une erreur dassertion, parfois une exception de pointeur nul, selon la conguration de son chargeur de classe.

Utiliser des assertions pour documenter des hypothses


De nombreux programmeurs utilisent des commentaires pour documenter leurs hypothses. Vous trouverez un bon exemple ladresse http://java.sun.com/javase/6/docs/technotes/guides/ language/assert.html :
if (i % 3 == 0) . . . else if (i % 3 == 1) . . . else // (i % 3 == 2) . . .

570

Au cur de Java 2 - Notions fondamentales

Lutilisation dune assertion prend tout son sens dans ce cas :


if (i % 3 == 0) . . . else if (i % 3 == 1) . . . else { assert i % 3 == 2; . . . }

Il serait bien entendu encore plus logique de rchir ce problme de manire plus directe. Quelles sont les valeurs possibles de i % 3? Si i est positif, le reste doit tre 0, 1 ou 2. Si i est ngatif, les restes peuvent tre 1 ou 2. Par consquent, la vritable hypothse est que i nest pas ngatif. Une meilleure assertion serait dcrire
assert i >= 0;

avant linstruction if. Dans tous les cas, cet exemple fait bon usage des assertions comme contrle automatique pour le programmeur. Comme vous pouvez le voir, les assertions constituent un outil tactique permettant de tester et de dboguer un programme, tandis que la consignation est un outil stratgique conu pour servir tout au long du cycle de vie dun programme. Nous verrons la consignation la section suivante.
java.lang.ClassLoader 1.0

void setDefaultAssertionStatus(boolean b) 1.4

Active ou dsactive les assertions pour toutes les classes charges par ce chargeur de classe qui nont pas un statut explicite dassertion de classe ou de package.

void setClassAssertionStatus(String className, boolean b) 1.4

Active ou dsactive les assertions pour la classe donne ou ses classes internes.
void setPackageAssertionStatus(String packageName, boolean b) 1.4

Active ou dsactive les assertions pour toutes les classes du package donn et ses sous-packages.
void clearAssertionStatus() 1.4

Supprime tous les paramtres explicites sur le statut dassertion de classe et de package et dsactive les assertions pour toutes les classes charges par ce chargeur de classe.

La consignation
Tous les programmeurs Java connaissent bien le processus dinsertion des appels System.out .println dans un code problmes, pour se faire une ide du comportement du programme. Bien sr, lorsque vous aurez dcouvert la cause du problme, les instructions dafchage seffaceront ; malgr tout, elles rapparatront au prochain problme. LAPI de consignation vise surmonter cela. Ses principaux avantages sont les suivants :
m

Il est facile de supprimer tous les enregistrements du journal ou seulement ceux apparaissant audel dun certain niveau, et il est aussi simple de les retrouver.

Chapitre 11

Exceptions, consignation, assertion et mise au point

571

Les journaux supprims sont trs lgers, la pnalit est donc mince les conserver dans le code de consignation de votre application. Les enregistrements des journaux peuvent tre dirigs vers diffrents gestionnaires, an dtre afchs sur la console, stocks dans un chier, etc. Les systmes de consignation et de gestion sont en mesure de ltrer les enregistrements. Selon les critres de leurs crateurs, les ltres rejettent les entres de journal inintressantes. Les enregistrements de journaux peuvent tre mis en forme de diverses manires, par exemple en texte brut ou en XML. Les applications peuvent faire appel divers systmes de consignation, utilisant des noms hirarchiques comme com.mycompany.myapp, identiques aux noms de packages. Par dfaut, la conguration de la consignation se contrle grce un chier de conguration. Les applications sont en mesure de remplacer ce mcanisme si on le souhaite.

Consignation de base
Commenons par le cas le plus simple qui soit. Le systme de consignation gre un enregistreur par dfaut, nomm Logger.global, que vous pouvez utiliser la place de System.out. Sa mthode info permet de consigner un message informatif :
Logger.global.info("File->Open menu item selected");

Par dfaut, lenregistrement safche comme suit :


May 10, 2004 10:12:15 PM LoggingImageViewer fileOpen INFO: File->Open menu item selected

Vous aurez remarqu que lheure et les noms de la classe et de la mthode dappel apparaissent automatiquement. Toutefois, si vous appelez
Logger.global.setLevel(Level.OFF);

un endroit qui convient (par exemple au dbut de main), lensemble de la consignation est supprim.

Consignation avance
Aprs cette introduction plus que sommaire, voyons quoi ressemble la consignation pour les professionnels. Dans une application professionnelle, il est inutile denregistrer toutes les entres dun enregistreur global. Il vaut mieux dnir quelques critres. Lorsque vous appelez pour la premire fois un enregistreur ayant un nom donn, celui-ci se cre :
Logger myLogger = Logger.getLogger("com.mycompany.myapp");

Les appels ultrieurs ce nom produiront le mme objet logger. A linstar des noms de packages, les noms denregistreurs afchent une structure hirarchique. Ils sont en fait plus hirarchiques que les packages. En effet, il nexiste aucune relation smantique entre un package et son parent ; or, les enfants et les parents des enregistreurs (logger) partagent certaines proprits. Par exemple, si vous dnissez le niveau de consignation sur lenregistreur "com.mycompany", ses enfants en hriteront.

572

Au cur de Java 2 - Notions fondamentales

Il existe sept niveaux de consignation :


m m m m m m m

SEVERE WARNING INFO CONFIG FINE FINER FINEST


logger.setLevel(Level.FINE);

Par dfaut, les trois niveaux suprieurs sont consigns, mais vous pouvez modier cela : Dsormais, la consignation concerne tous les niveaux partir de FINE. Vous pouvez galement utiliser Level.ALL pour consigner tous les niveaux ou Level.OFF pour annuler toute consignation. Il existe certaines mthodes de consignation pour tous les niveaux, comme :
logger.warning(message); logger.fine(message);

etc. Mais vous pouvez aussi utiliser la mthode log, en indiquant le niveau, comme suit :
logger.log(Level.FINE, message);

ASTUCE
La conguration par dfaut permet de consigner tous les enregistrements ayant le niveau INFO ou suprieur. Vous devez donc utiliser les niveaux CONFIG, FINE, FINER et FINEST pour dboguer les messages insigniants pour lutilisateur du programme et pourtant utiles au diagnostic.

ATTENTION
Si vous rglez le niveau de consignation sur une valeur plus prcise que INFO, modiez galement la conguration du gestionnaire de lenregistreur. Ce gestionnaire par dfaut supprime les messages infrieurs au niveau INFO. Consultez la section suivante pour en savoir plus.

Lenregistrement par dfaut prcise le nom de la classe et de la mthode qui contient lappel de consignation, comme cela est suggr par la pile dappel. Malgr tout, si la machine virtuelle optimise lexcution, aucune information prcise ne sera disponible. Vous pouvez alors utiliser la mthode logp pour indiquer lemplacement prcis de la classe et de la mthode dappel. La signature de mthode est la suivante :
void logp(Level l, String className, String methodName, String message)

Il existe certaines mthodes pratiques pour retracer le ux de lexcution :


void entering(String className, String methodName) void entering(String className, String methodName, Object param)

Chapitre 11

Exceptions, consignation, assertion et mise au point

573

void entering(String className, String methodName, Object[] params) void exiting(String className, String methodName) void exiting(String className, String methodName, Object result)

Par exemple :
int read(String file, String pattern) { logger.entering("com.mycompany.mylib.Reader", "read", new Object[] { file, pattern }); . . . logger.exiting("com.mycompany.mylib.Reader", "read", count); return count; }

Ces appels gnrent des enregistrements de journaux du niveau FINER, qui commencent par les chanes ENTRY et RETURN.
INFO
A lavenir, les mthodes de consignation seront rcrites pour supporter les listes de paramtres variables ("varargs"). Il sera alors possible de raliser des appels tels que logger.entering("com.mycompany.mylib .Reader", "read", file, pattern).

Les journaux sont souvent utiliss pour enregistrer les exceptions inattendues. Deux mthodes incluent une description de lexception dans lenregistrement :
void throwing(String className, String methodName, Throwable t) void log(Level 1, String message, Throwable t)

Les usages courants sont les suivants :


if (. . .) { IOException exception = new IOException(". . ."); logger.throwing("com.mycompany.mylib.Reader", "read", exception); throw exception; }

et
try { . . . } catch (IOException e) { Logger.getLogger("com.mycompany.myapp").log( Level.WARNING, "Reading image", e); }

Lappel throwing consigne un enregistrement du niveau FINER et un message commenant par THROW.

574

Au cur de Java 2 - Notions fondamentales

Modier la conguration du gestionnaire de journaux


Diverses proprits du systme de consignation peuvent tre modies par un changement apport au chier de conguration. Le chier par dfaut est situ
jre/lib/logging.properties

Pour utiliser un autre chier, faites passer la proprit java.util.logging.config.file sur lemplacement du chier, en lanant votre application avec
java -Djava.util.logging.config.file= FichierConfig ClasseMain

ATTENTION
Appeler System.setProperty("java.util.logging.config.file", file) dans main nest daucun effet car le gestionnaire de journaux est initialis au dmarrage de la machine virtuelle, avant mme lexcution de main.

Pour changer le niveau de consignation par dfaut, modiez la ligne suivante dans le chier de conguration :
.level=INFO

Vous pouvez spcier les niveaux de consignation de vos propres enregistreurs en ajoutant des lignes telles que
com.mycompany.myapp.level=FINE

Il sagit ici dajouter le sufxe .level au nom de lenregistreur. Comme vous le verrez plus loin au l de cette section, ce ne sont pas rellement les enregistreurs qui envoient un message la console, cest plutt le rle des gestionnaires. Ceux-ci possdent aussi des niveaux. Pour afcher les messages FINE sur la console, vous devez galement dnir
java.util.logging.ConsoleHandler.level=FINE

ATTENTION
Les paramtres de conguration du gestionnaire de journaux ne sont pas des proprits systme. Lancer un programme par -Dcom.mycompany.myapp.level=FINE na aucune inuence sur lenregistreur.

ATTENTION
Au moins jusqu Java SE 6, la documentation API de la classe LogManager indique que vous pouvez paramtrer les proprits java.util.logging.config.class et java.util.logging.config.file par lAPI Preferences. Or, cest faux, voir le site http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4691587.

INFO
Le chier des proprits de consignation est trait par la classe java.util.logging.LogManager. Vous pouvez choisir un autre gestionnaire de journal en dnissant la proprit systme java.util.logging.manager sur le nom dune sous-classe. Vous pouvez aussi conserver le gestionnaire de journaux standard et pourtant contourner linitialisation partir du chier de proprit de consignation. Dnissez la proprit systme java.util. logging.config.class sur le nom dune classe qui dnit autrement les proprits du gestionnaire de journal. Consultez la documentation API pour en savoir plus sur la classe LogManager.

Chapitre 11

Exceptions, consignation, assertion et mise au point

575

Il est galement possible de modier les niveaux de consignation dans un programme en cours dexcution laide du programme jconsole. Voir le site http://java.sun.com/developer/technicalArticles/ J2SE/jconsole.html#LoggingControl.

La localisation
Il est quelquefois ncessaire de localiser les messages de consignation de sorte quils puissent tre lus par des utilisateurs parlant dautres langues. Linternationalisation des applications sera traite au Chapitre 5 du Volume II. Voici toutefois quelques points garder en tte. Les applications localises contiennent des informations qui sont fonction des paramtres rgionaux, contenus dans des groupes de ressources. Un groupe de ressources est constitu dun jeu de correspondances (par exemple Etats-Unis ou Allemagne). A titre dexemple, un groupe de ressources peut mettre en correspondance la chane "readingFile" avec les chanes "Reading le" en anglais ou "Achtung ! Datei wird eingelesen" en allemand. Un programme peut contenir plusieurs groupes de ressources : un pour les menus, un autre pour les messages de consignation, etc. Chaque groupe de ressources possde un nom (par exemple "com.mycompany.logmessages"). Pour ajouter des correspondances un groupe de ressources, fournissez un chier chaque paramtre rgional. Les correspondances des messages anglais se trouvent dans le chier com/mycompany/logmessages_en.properties, et les correspondances de messages allemands, dans com/mycompany/logmessages_de.properties (les codes de, en, dsignent les langues). Regroupez ces chiers dans les chiers de classe de votre application, de sorte que la classe ResourceBundle les retrouve immdiatement. Ces chiers sont en texte brut et contiennent des entres telles que
readingFile=Achtung! Datei wird eingelesen renamingFile=Datei wird umbenannt ...

Lorsque vous demandez un enregistreur, vous pouvez spcier un groupe de ressources :


Logger logger = Logger.getLogger(loggerName, "com.mycompany.logmessages");

Vous spciez ensuite la cl du groupe de ressources, et non la chane du message :


logger.info("readingFile");

Il faut souvent inclure des arguments dans les messages localiss. Dans ce cas, le message doit contenir des caractres demplacement {0}, {1}, etc. Par exemple, pour inclure le nom du chier avec un message journal, insrez le caractre demplacement comme ceci :
Reading file {0}. Achtung! Datei {0} wird eingelesen.

Vous transfrez ensuite les valeurs au caractre demplacement en appelant lune des mthodes suivantes :
logger.log(Level.INFO, "readingFile", fileName); logger.log(Level.INFO, "renamingFile", new Object[] { oldName, newName });

576

Au cur de Java 2 - Notions fondamentales

Les gestionnaires
Par dfaut, les enregistreurs envoient leurs enregistrements un ConsoleHandler qui les afche dans le ux System.err. Plus prcisment, lenregistreur envoie lenregistrement au gestionnaire parent, et le dernier anctre (avec pour nom "") possde un ConsoleHandler. De mme, les gestionnaires disposent dun niveau de consignation. Pour quun enregistrement soit consign, son niveau de consignation doit tre suprieur au seuil de lenregistreur et celui du gestionnaire. Le chier de conguration du gestionnaire de consignation dnit le niveau de consignation du gestionnaire de console par dfaut comme suit :
java.util.logging.ConsoleHandler.level=INFO

Les enregistrements du journal ayant le niveau FINE modient la fois le niveau de lenregistreur par dfaut et celui du gestionnaire dans la conguration. Vous pouvez aussi contourner le chier de conguration et installer votre propre gestionnaire :
Logger logger = Logger.getLogger("com.mycompany.myapp"); logger.setLevel(Level.FINE); logger.setUseParentHandlers(false); Handler handler = new ConsoleHandler(); handler.setLevel(Level.FINE); logger.addHandler(handler);

Par dfaut, un enregistreur enregistre dans ses propres gestionnaires et dans ceux du parent. Ici, cest un enfant du principal enregistreur (avec le nom ""), qui envoie la console tous les enregistrements ayant le niveau INFO ou suprieur. Mais ces enregistrements ne doivent pas apparatre deux fois. Nous dnissons donc la proprit useParentHandlers sur false. Pour envoyer des enregistrements de journal partout ailleurs, ajoutez un autre gestionnaire. LAPI de consignation fournit deux gestionnaires qui se montreront fort utiles cet effet, un FileHandler et un SocketHandler. SocketHandler envoie les enregistrements vers un hte et un port spcis. FileHandler est bien plus intressant puisquil collecte des enregistrements dans un chier. Vous pouvez envoyer les enregistrements vers un gestionnaire de chiers par dfaut, comme ceci :
FileHandler handler = new FileHandler(); logger.addHandler(handler);

Les enregistrements sont envoys vers un chier javan.log dans le rpertoire de base de lutilisateur, o n est remplac par un nombre pour rendre le chier unique. Lorsquun systme ne connat pas le rpertoire de base de lutilisateur (par exemple sous Windows 95/98/Me), le chier est stock dans un emplacement par dfaut comme C:\Windows. Par dfaut, les enregistrements sont mis en forme au format XML. Un enregistrement de journal ordinaire a la forme suivante :
<record> <date>2002-02-04T07:45:15</date> <millis>1012837515710</millis> <sequence>1</sequence> <logger>com.mycompany.myapp</logger> <level>INFO</level> <class>com.mycompany.mylib.Reader</class> <method>read</method> <thread>10</thread> <message>Reading file corejava.gif</message> </record>

Chapitre 11

Exceptions, consignation, assertion et mise au point

577

Vous pouvez modier le comportement par dfaut du gestionnaire de chiers en changeant ses paramtres de conguration (voir Tableau 11.2) ou en utilisant un autre constructeur (voir les notes API en n de section). Il est souvent prfrable de changer galement le nom par dfaut du chier journal. Vous pourrez utiliser un modle diffrent, par exemple %h/myapp.log (voir Tableau 11.3 pour obtenir une explication des variables des modles). Lorsque plusieurs applications (ou plusieurs copies dune mme application) utilisent le mme chier journal, il est recommand dactiver la balise "append". Vous pouvez galement insrer %u dans le modle du nom de chier, pour que chaque application cre sa propre copie du journal. Il est galement conseill dactiver la rotation des chiers. Les chiers journaux sont conservs suivant un modle squentiel tel que myapp.log.0, myapp.log.1, myapp.log.2, etc. Ds quun chier dpasse le plafond dtermin, le plus vieux est supprim, les autres sont renomms et un nouveau chier est cr avec le numro 0.
Tableau 11.2 : Paramtres de conguration du gestionnaire de chiers

Proprit de conguration java.util.logging. FileHandler.level java.util.logging. FileHandler.append

Description Le niveau du gestionnaire. Contrle si le gestionnaire doit continuer un chier existant ou ouvrir un nouveau chier pour chaque programme. Le nombre maximal approximatif doctets crire dans un chier avant den ouvrir un autre (0 = pas de limite). Le modle de nom pour le chier journal. Voir Tableau 11.3 pour connatre ses variables. Le nombre de journaux dans une suite. La classe du ltre utiliser. Le codage de caractres utiliser. Le formateur des enregistrements.

Paramtre par dfaut Level.ALL false

java.util.logging. FileHandler.limit

0 (pas de limite) dans la classe FileHandler, 50 000 dans la conguration par dfaut du gestionnaire de journaux %h/java%u.log

java.util.logging. FileHandler.pattern java.util.logging. FileHandler.count java.util.logging. FileHandler.filter java.util.logging. FileHandler.encoding java.util.logging. FileHandler.formatter

1 (pas de rotation) Pas de ltrage Le codage de la plate-forme java.util.logging. XMLFormatter

578

Au cur de Java 2 - Notions fondamentales

ASTUCE
De nombreux programmeurs utilisent la consignation pour aider le personnel du support technique. En cas de drglement dun programme au cours dune session, lutilisateur peut renvoyer les chiers journaux pour analyse. Dans ce cas, il convient dactiver la balise "append", dutiliser la rotation des chiers, voire de faire les deux.

Tableau 11.3 : Variables du modle de chier journal

Variable %h %t %u %g %%

Description La valeur de la proprit systme user.home Le rpertoire temporaire du systme Un numro unique pour viter les conits Le numro pour les journaux par rotation (le sufxe .%g indique que la rotation est spcie, le modle ne contient pas %g) Le caractre %

Vous pouvez galement dnir vos propres gestionnaires en prolongeant la classe Handler ou StreamHandler. Nous dnissons ce gestionnaire dans le programme dexemple la n de cette section. Il afche les enregistrements dans une fentre (voir Figure 11.2). Le gestionnaire tend la classe StreamHandler et installe un ux dont les mthodes write afchent la sortie dans une zone de texte :
class WindowHandler extends StreamHandler { public WindowHandler() { . . . final JTextArea output = new JTextArea(); setOutputStream(new OutputStream() { public void write(int b) {} // non appel public void write(byte[] b, int off, int len) { output.append(new String(b, off, len)); } }); } . . . }

Figure 11.2
Un gestionnaire de journal afchant des enregistrements dans une fentre.

Chapitre 11

Exceptions, consignation, assertion et mise au point

579

Cette mthode prsente tout de mme un problme : le gestionnaire met les enregistrements en mmoire tampon et ne les crit sur le ux que lorsque le tampon est plein. Nous allons donc craser la mthode publish pour vider le tampon aprs chaque enregistrement :
class WindowHandler extends StreamHandler { . . . public void publish(LogRecord record) { super.publish(record); flush(); } }

Pour obtenir des gestionnaires un peu plus exotiques, tendez la classe Handler et dnissez les mthodes publish, flush et close.

Les ltres
Par dfaut, les enregistrements sont ltrs en fonction de leurs niveaux de consignation. Chaque enregistreur et chaque gestionnaire peuvent avoir un ltre optionnel qui ralisera un ltrage complmentaire. Pour dnir un ltre, implmentez linterface Filter et dnissez la mthode
boolean isLoggable(LogRecord record)

Analysez lenregistrement du journal laide des critres souhaits et renvoyez true pour ceux que vous voulez inclure dans le journal. Par exemple, un ltre particulier peut ntre intress que par les messages gnrs par les mthodes entering et exiting. Le ltre doit alors appeler record.getMessage() et vrier sil commence par ENTRY ou RETURN. Pour installer un ltre dans un enregistreur ou un gestionnaire, appelez simplement la mthode setFilter. Sachez que vous pouvez installer plusieurs ltres la fois.

Les formateurs
Les classes ConsoleHandler et FileHandler dlivrent les enregistrements des journaux aux formats texte et XML. Mais vous pouvez aussi dnir vos propres formats. Pour cela, prolongez la classe Formatter et surchargez la mthode
String format(LogRecord record)

Vous pouvez mettre en forme les informations contenues dans lenregistrement selon vos prfrences et renvoyer la chane de rsultat. Dans votre mthode format, vous pouvez appeler la mthode
String formatMessage(LogRecord record)

Celle-ci met en forme le message qui fait partie de lenregistrement, en remplaant les paramtres et en appliquant la localisation. De nombreux formats de chiers (comme XML) exigent un bloc de dbut et un bloc de n pour entourer les enregistrements mis en forme. Dans ce cas, surchargez les mthodes
String getHead(Handler h) String getTail(Handler h)

Enn, appelez la mthode setFormatter pour installer le formateur dans le gestionnaire.

580

Au cur de Java 2 - Notions fondamentales

Une astuce de consignation


Convenons quavec autant doptions, il est facile de se perdre quand il sagit de la consignation. Lastuce suivante rsume les oprations les plus communes. 1. Pour une application simple, optez pour un enregistreur unique. Donnez-lui le nom de votre package dapplication principale, comme com.mycompany.myprog. Vous pouvez toujours rcuprer lenregistreur en appelant
Logger logger = Logger.getLogger("com.mycompany.myprog");

Pour plus de commodit, vous pourrez ajouter les champs statiques


private static final Logger logger = Logger.getLogger("com.mycompany.myprog");

aux classes ayant une grande activit de consignation. 2. La conguration par dfaut enregistre dans la console tous les messages du niveau INFO ou suprieur. Les utilisateurs peuvent surcharger la conguration par dfaut mais, comme vous lavez vu, le processus est un peu plus long. Choisissez donc un paramtre plus raisonnable pour votre application. Le code suivant vrie que tous les messages sont consigns dans un chier spcique lapplication. Placez le code dans la mthode main de votre application :
if (System.getProperty("java.util.logging.config.class") == null && System.getProperty("java.util.logging.config.file") == null) { try { Logger.getLogger("").setLevel(Level.ALL); final int LOG_ROTATION_COUNT = 10; Handler handler = new FileHandler("%h/myapp.log", 0, LOG_ROTATION_COUNT); Logger.getLogger("").addHandler(handler); } catch (IOException e) { logger.log(Level.SEVERE, "Cant create log file handler", e); } }

3. Vous tes maintenant prt consigner le contenu qui vous intresse. Noubliez pas que tous les messages ayant le niveau INFO, WARNING et SEVERE safcheront dans la console. Rservez donc ces niveaux aux messages signiants pour les utilisateurs du programme. Le niveau FINE convient bien aux messages destins aux programmeurs. Ds que vous tes tent dappeler System.out.println, mettez plutt un message journal :
logger.fine("File open dialog cancelled");

Vous devriez aussi consigner les exceptions inattendues, par exemple :


try { . . . } catch (SomeException e) { logger.log(Level.FINE, "explanation", e); }

Chapitre 11

Exceptions, consignation, assertion et mise au point

581

Le Listing 11.2 prsente une astuce pour ajouter un aspect supplmentaire : la consignation des messages safche aussi dans une fentre journal.
Listing 11.2 : LoggingImageViewer.java
import import import import import java.awt.*; java.awt.event.*; java.io.*; java.util.logging.*; javax.swing.*;

/** * Une modification du code du visualisateur dimages qui consigne * divers vnements. * @version 1.02 2007-05-31 * @author Cay Horstmann */ public class LoggingImageViewer { public static void main(String[] args) { if (System.getProperty("java.util.logging.config.class") == null && System.getProperty("java.util.logging.config.file") == null) { try { Logger.getLogger("com.horstmann.corejava"). setLevel(Level.ALL); final int LOG_ROTATION_COUNT = 10; Handler handler = new FileHandler( "%h/LoggingImageViewer.log", 0, LOG_ROTATION_COUNT); Logger.getLogger("com.horstmann.corejava").addHandler(handler); } catch (IOException e) { Logger.getLogger("com.horstmann.corejava").log(Level.SEVERE, "Cant create log file handler", e); } } EventQueue.invokeLater(new Runnable() { public void run() { Handler windowHandler = new WindowHandler(); windowHandler.setLevel(Level.ALL); Logger.getLogger("com.horstmann.corejava"). addHandler(windowHandler); JFrame frame = new ImageViewerFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); Logger.getLogger("com.horstmann.corejava"). fine("Showing frame"); frame.setVisible(true); } }); } }

582

Au cur de Java 2 - Notions fondamentales

/** * Le cadre qui affiche limage. */ class ImageViewerFrame extends JFrame { public ImageViewerFrame() { logger.entering("ImageViewerFrame", "<init>"); setTitle("LoggingImageViewer"); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); // configurer la barre de menus JMenuBar menuBar = new JMenuBar(); setJMenuBar(menuBar); JMenu menu = new JMenu("File"); menuBar.add(menu); JMenuItem openItem = new JMenuItem("Open"); menu.add(openItem); openItem.addActionListener(new FileOpenListener()); JMenuItem exitItem = new JMenuItem("Exit"); menu.add(exitItem); exitItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { logger.fine("Exiting."); System.exit(0); } }); // utiliser un intitul pour afficher les images label = new JLabel(); add(label); logger.exiting("ImageViewerFrame", "<init>"); } private class FileOpenListener implements ActionListener { public void actionPerformed(ActionEvent event) { logger.entering("ImageViewerFrame.FileOpenListener", "actionPerformed", event); // configurer le slecteur de fichiers JFileChooser chooser = new JFileChooser(); chooser.setCurrentDirectory(new File(".")); // accepter tous les fichiers se terminant par .gif chooser.setFileFilter(new javax.swing.filechooser.FileFilter() { public boolean accept(File f) { return f.getName().toLowerCase().endsWith(".gif") || f.isDirectory(); }

Chapitre 11

Exceptions, consignation, assertion et mise au point

583

public String getDescription() { return "GIF Images"; } }); // afficher la bote de dialogue du slecteur de fichiers int r = chooser.showOpenDialog(ImageViewerFrame.this); // si le fichier image est accept, le dfinir comme // icne de lintitul if (r == JFileChooser.APPROVE_OPTION) { String name = chooser.getSelectedFile().getPath(); logger.log(Level.FINE, "Reading file {0}", name); label.setIcon(new ImageIcon(name)); } else logger.fine("File open dialog canceled."); logger.exiting("ImageViewerFrame.FileOpenListener", "actionPerformed"); } } private JLabel label; private static Logger logger = Logger.getLogger("com.horstmann.corejava"); private static final int DEFAULT_WIDTH = 300; private static final int DEFAULT_HEIGHT = 400; } /** * Un gestionnaire pour afficher les enregistrements de journal * dans une fentre. */ class WindowHandler extends StreamHandler { public WindowHandler() { frame = new JFrame(); final JTextArea output = new JTextArea(); output.setEditable(false); frame.setSize(200, 200); frame.add(new JScrollPane(output)); frame.setFocusableWindowState(false); frame.setVisible(true); setOutputStream(new OutputStream() { public void write(int b) { } // non appel public void write(byte[] b, int off, int len) { output.append(new String(b, off, len)); } }); }

584

Au cur de Java 2 - Notions fondamentales

public void publish(LogRecord record) { if (!frame.isVisible()) return; super.publish(record); flush(); } private JFrame frame; } java.util.logging.Logger 1.4

Logger getLogger(String loggerName) Logger getLogger(String loggerName, String bundleName)

Rcuprent lenregistreur ayant le nom donn. Sil nexiste pas, il est cr. Paramtres : loggerName bundleName

void severe(String message) void warning(String message) void info(String message) void config(String message) void fine(String message) void finer(String message) void finest(String message)

Le nom hirarchique de lenregistreur, comme com.mycompany.myapp. Le nom du groupe de ressources permettant de rechercher les messages localiss.

Consignent un enregistrement avec le niveau indiqu par le nom de la mthode avec le message donn.

void entering(String className, String methodName) void entering(String className, String methodName, Object param) void entering(String className, String methodName, Object[] param) void exiting(String className, String methodName) void exiting(String className, String methodName, Object result)

Consignent un enregistrement dcrivant lentre ou la sortie dune mthode avec le ou les paramtres donns ou la valeur de retour.

void throwing(String className, String methodName, Throwable t)

Consigne un enregistrement dcrivant le lancement de lobjet exception donn.


void log(Level level, String message) void log(Level level, String message, Object obj) void log(Level level, String message, Object[] objs) void log(Level level, String message, Throwable t)

Consignent un enregistrement avec le niveau donn et le message, en incluant en option des objets ou un throwable. Pour inclure des objets, le message doit contenir des emplacements de mise en forme tels {0}, {1}, etc.

void logp(Level level, String className, String methodName, String message)

Chapitre 11

Exceptions, consignation, assertion et mise au point

585

void logp(Level level, String className, String methodName, String message, Object obj) void logp(Level level, String className, String methodName, String message, Object[] objs) void logp(Level level, String className, String methodName, String message, Throwable t)

Consignent un enregistrement avec le niveau donn, des informations prcises sur lappelant et un message. Incluent, en option, des objets ou un throwable.

void logrb(Level level, String className, String methodName, String bundleName, String message) void logrb(Level level, String className, String methodName, String bundleName, String message, Object obj) void logrb(Level level, String className, String methodName, String bundleName, String message, Object[] objs) void logrb(Level level, String className, String methodName, String bundleName, String message, Throwable t)

Consignent un enregistrement avec le niveau donn, des informations prcises sur lappelant, le nom du groupe de ressources et le message. Consignent, en option, des objets ou un throwable.

Level getLevel() void setLevel(Level l)

Rcuprent ou dnissent le niveau de cet enregistreur.


Logger getParent() void setParent(Logger l)

Rcuprent ou dnissent lenregistreur parent de cet enregistreur.


Handler[] getHandlers()

Rcupre tous les gestionnaires de cet enregistreur.


void addHandler(Handler h) void removeHandler(Handler h)

Ajoutent ou suppriment un gestionnaire pour cet enregistreur.


boolean getUseParentHandlers() void setUseParentHandlers(boolean b)

Rcuprent et dnissent la proprit "use parent handler" (utiliser le gestionnaire parent). Si cette proprit vaut true, lenregistreur transmet tous les enregistrements consigns aux gestionnaires de son parent.

Filter getFilter() void setFilter(Filter f)

Rcuprent et dnissent le ltre de cet enregistreur.


java.util.logging.Handler 1.4

abstract void publish(LogRecord record)

Envoie lenregistrement la destination prvue.


abstract void flush()

Efface toute donne mise en tampon.

586

Au cur de Java 2 - Notions fondamentales

abstract void close()

Efface toute donne mise en tampon et libre les ressources associes.


Filter getFilter() void setFilter(Filter f)

Rcuprent et dnissent le ltre de ce gestionnaire.


Formatter getFormatter() void setFormatter(Formatter f)

Rcuprent et dnissent le formateur de ce gestionnaire.


Level getLevel() void setLevel(Level l)

Rcuprent et dnissent le niveau de ce gestionnaire.


java.util.logging.ConsoleHandler 1.4

ConsoleHandler()

Construit un nouveau gestionnaire de console.


java.util.logging.FileHandler 1.4

FileHandler(String pattern) FileHandler(String pattern, boolean append) FileHandler(String pattern, int limit, int count) FileHandler(String pattern, int limit, int count, boolean append)

Construisent un gestionnaire de chiers. Paramtres : pattern limit count append Le motif pour construire le nom du chier journal. Voir le Tableau 11.3 pour connatre les variables du modle. Le nombre maximal approximatif doctets avant quun nouveau chier journal ne soit ouvert. Le nombre de chiers dans une rotation. Vaut true lorsquun gestionnaire de chiers nouvellement construit doit continuer un chier journal existant.

java.util.logging.LogRecord 1.4

Level getLevel()

Rcupre le niveau de consignation de cet enregistrement.


String getLoggerName()

Rcupre le nom de lenregistreur qui consigne cet enregistrement.


ResourceBundle getResourceBundle() String getResourceBundleName()

Rcuprent le groupe de ressources ou son nom pour lutiliser pour localiser le message ou null si aucun nest fourni.

String getMessage()

Rcupre le message "brut" avant la localisation ou la mise en forme.

Chapitre 11

Exceptions, consignation, assertion et mise au point

587

Object[] getParameters()

Rcupre les objets paramtre ou null si aucun nest fourni.


Throwable getThrown()

Rcupre lobjet lanc ou null si aucun nest fourni.


String getSourceClassName() String getSourceMethodName()

Rcuprent lemplacement du code qui a consign cet enregistrement. Ces informations peuvent tre fournies par le code de consignation ou automatiquement tires de la pile dexcution. Cela peut tre inexact si le code de consignation a fourni la mauvaise valeur ou si le code dexcution tait optimis et que lemplacement exact ne puisse pas tre extrait.

long getMillis()

Rcupre lheure de la cration, en millimes de seconde, depuis 1970.


long getSequenceNumber()

Rcupre le numro unique (dans la suite) de cet enregistrement.


int getThreadID()

Rcupre lID unique pour le thread dans lequel cet enregistrement a t cr. Ces ID sont attribus par la classe LogRecord et nont pas de relation avec les autres ID de thread.
java.util.logging.Filter 1.4

boolean isLoggable(LogRecord record)

Renvoie true si lenregistrement de journal donn doit tre consign.


java.util.logging.Formatter 1.4

abstract String format(LogRecord record)

Renvoie la chane ne de la mise en forme de lenregistrement du journal.


String getHead(Handler h) String getTail(Handler h)

Renvoient les chanes qui doivent apparatre au dbut et la n du document contenant les enregistrements du journal. La superclasse Formatter dnit ces mthodes de sorte quelles renvoient la chane vide ; surchargez-les si ncessaire.

String formatMessage(LogRecord record)

Renvoie la partie du message qui a t localise et mise en forme.

Les techniques de mise au point


Vous avez termin votre programme et lavez protg contre toute ventualit en capturant et en traitant effectivement toutes les exceptions. Vous lancez lexcution, et cela ne marche pas. Que faire ? (Si vous navez jamais rencontr ce problme, passez sans remords au chapitre suivant.) Il est prfrable de possder un systme de dbogage pratique et puissant. Ces outils de mise au point font en gnral partie denvironnements professionnels de dveloppement comme Eclipse et NetBeans. Nous verrons le dbogueur plus loin dans ce chapitre. Pour lheure, voici quelques astuces pour un dbogage efcace, si vous vous trouvez sans aucun environnement de mise au point :

588

Au cur de Java 2 - Notions fondamentales

1. On peut imprimer ou consigner la valeur dune variable par


System.out.println("x="+x);

ou
Logger.global.info("x=" + x);

Si x est un nombre, il sera converti en une chane de caractres. Si cest un objet, Java appellera sa mthode toString. Pour obtenir ltat de lobjet paramtre implicite, afchez ltat de cet objet :
Logger.global.info("this=" + this);

La plupart des classes de la bibliothque de classes Java sefforcent de surcharger, sil y a lieu, la mthode toString an dinformer efcacement sur la classe. Cest parfait pour le dbogage. Vous devez poursuivre le mme effort dans vos propres classes. 2. Une petite astuce apparemment peu connue consiste introduire une mthode main spare lintrieur de chaque classe. Dans celle-ci, vous pouvez inclure un stub de test an de tester la classe de faon isole :
public class MyClass { mthodes et champs . . . public static void main(String[] args) { code de test } }

Crez quelques objets, appelez toutes les mthodes et vriez que chacune fonctionne. Vous pouvez laisser toutes ces mthodes main en place et appeler la machine virtuelle Java de faon spare sur chacun des chiers an dexcuter les tests. Lorsque vous lancez un applet, aucune de ces mthodes main nest jamais appele. Lorsque vous lancez une application, la machine virtuelle Java appelle uniquement la mthode main de la classe de dmarrage. 3. Si vous avez aim lastuce prcdente, consultez JUnit ladresse http://junit.org. JUnit est un encadrement de test trs populaire qui facilite lorganisation des squences de test. Excutez les tests ds que vous modiez une classe et ajoutez un test cadre ds que vous retrouvez un bogue. 4. Un proxy de consignation est un objet dune sous-classe qui intercepte les appels de mthode, les consigne et appelle la superclasse. Si, par exemple, vous rencontrez des problmes avec la mthode setBackground dun panneau, vous pouvez crer un objet proxy comme instance dune sous-classe anonyme :
JPanel panel = new JPanel() { public void setBackground(Color c) { Logger.global.info("setBackground: c=" + c); super.setBackground(c); } };

Ds que la mthode setBackground est appele, un message de journal est gnr. Pour savoir qui a appel la mthode, gnrez une trace de pile.

Chapitre 11

Exceptions, consignation, assertion et mise au point

589

5. On peut sortir le contenu de la pile pour un objet exception grce la mthode printStackTrace de la classe Throwable. Le morceau de code suivant capture toutes les exceptions, afche lobjet exception et le contenu de la pile, puis propage lexception lintention de son gestionnaire potentiel :
try { . . . } catch (Throwable t) { t.printStackTrace(); throw t; }

Vous navez mme pas besoin dintercepter une exception pour gnrer le traage dune pile. Il vous suft dinsrer linstruction
Thread.dumpStack();

nimporte quel endroit de votre code pour lobtenir. 6. En principe, le contenu de la pile safche par System.err. On peut lenvoyer dans un chier avec la mthode void printStackTrace(PrintWriter). Ou encore, pour afcher le contenu de la pile dans une fentre, voici la faon de le placer dans une chane :
StringWriter out = new StringWriter(); new Throwable().printStackTrace(new PrintWriter(out)); String trace = out.toString();

(Voir au Chapitre 1 du Volume II les classes PrintWriter et StringWriter.) 7. Il est souvent pratique dintercepter les erreurs dun programme en les crivant dans un chier. Cependant, les erreurs sont envoyes System.err et non pas System.out. Par consquent, on ne peut pas se contenter de les capturer en lanant linstruction
java MyProgram > errors.txt

Capturez plutt le ux derreurs par


java MyProgram 2> errors.txt

Pour capturer la fois et dans le mme chier System.err et System.out, utilisez


java MyProgram 2>& errors.txt

Pour capturer System.err et System.out dans le mme chier, utilisez


java MyProgram >& errors.txt

Cela fonctionne dans bash et dans le shell Windows. 8. Faire apparatre les traces de pile des exceptions non interceptes dans System.err nest vraiment pas lidal. Ces messages sont droutants pour lutilisateur et ils ne sont pas disponibles aux ns de diagnostic lorsque vous en avez besoin. Une meilleure solution consiste les consigner dans un chier. Depuis Java SE 5.0, vous pouvez modier le gestionnaire pour les exceptions non interceptes avec la mthode statique Thread.setDefaultUncaughtExceptionHandler :
Thread.setDefaultUncaughtExceptionHandler( new Thread.UncaughtExceptionHandler()

590

Au cur de Java 2 - Notions fondamentales

{ public void uncaughtException(Thread t, Throwable e) { enregistrer les informations dans le fichier journal }; });

9. Pour observer le chargement des classes, excutez la machine virtuelle Java avec la balise verbose. Vous obtenez une premire sortie imprimante telle que
[Opened [Opened [Opened [Opened [Loaded [Loaded [Loaded [Loaded [Loaded [Loaded [Loaded [Loaded [Loaded [Loaded ... /usr/local/jdk5.0/jre/lib/rt.jar] /usr/local/jdk5.0/jre/lib/jsse.jar] /usr/local/jdk5.0/jre/lib/jce.jar] /usr/local/jdk5.0/jre/lib/charsets.jar] java.lang.Object from shared objects file] java.io.Serializable from shared objects file] java.lang.Comparable from shared objects file] java.lang.CharSequence from shared objects file] java.lang.String from shared objects file] java.lang.reflect.GenericDeclaration from shared objects file] java.lang.reflect.Type from shared objects file] java.lang.reflect.AnnotatedElement from shared objects file] java.lang.Class from shared objects file] java.lang.Cloneable from shared objects file]

Cela peut tre utile, loccasion, pour diagnostiquer des problmes de chemin de classe. 10. Pour espionner le contenu dune fentre Swing, dont les composants sont si agrablement aligns, il suft de faire Ctrl+Maj+F1 pour obtenir lafchage de tous les composants de la hirarchie :
FontDialog[frame0,0,0,300x200,layout=java.awt.BorderLayout,... javax.swing.JRootPane[,4,23,292x173,layout=javax.swing.JRootPane$RootLayout,... javax.swing.JPanel[null.glassPane,0,0,292x173,hidden,layout=java.awt.FlowLayout, ... javax.swing.JLayeredPane[null.layeredPane,0,0,292x173,... javax.swing.JPanel[null.contentPane,0,0,292x173,layout=java.awt.GridBagLayout, ... javax.swing.JList[,0,0,73x152,alignmentX=null,alignmentY=null,... javax.swing.CellRendererPane[,0,0,0x0,hidden] javax.swing.DefaultListCellRenderer$UIResource[,-73,-19,0x0,... javax.swing.JCheckBox[,157,13,50x25,layout=javax.swing.OverlayLayout,... javax.swing.JCheckBox[,156,65,52x25,layout=javax.swing.OverlayLayout,... javax.swing.JLabel[,114,119,30x17,alignmentX=0.0,alignmentY=null,... javax.swing.JTextField[,186,117,105x21,alignmentX=null,alignmentY=null,... javax.swing.JTextField[,0,152,291x21,alignmentX=null,alignmentY=null,...

11. Si vous avez crit un composant Swing spcial et si ce composant ne safche pas correctement, vous devriez apprcier le Swing graphics debugger. Mme si vous ncrivez pas de composant, il est utile et mme amusant de visualiser la faon exacte dont le contenu dun composant est dessin. Pour activer le dbogage dun composant Swing, utilisez la mthode setDebugGraphicsOptions de la classe JComponent, avec les options suivantes : DebugGraphics.FLASH_OPTION DebugGraphics.LOG_OPTION "Flashe" en rouge chaque ligne, rectangle ou texte avant de le dessiner. Afche un message chaque opration de dessin.

Chapitre 11

Exceptions, consignation, assertion et mise au point

591

DebugGraphics.BUFFERED_OPTION DebugGraphics.NONE_OPTION

Afche les oprations effectues dans le tampon hors cran. Dsactive le dbogage graphique.

Il semble que, pour faire fonctionner correctement cette option, il soit ncessaire de dsactiver la stratgie de "double buffrisation" utilise par Swing pour rduire le scintillement au moment de la mise jour dune fentre. Voici la recette pour activer cette option de "ash" :
RepaintManager.currentManager(getRootPane()) .setDoubleBufferingEnabled(false); ((JComponent) getContentPane()) .setDebugGraphicsOptions(DebugGraphics.FLASH_OPTION);

Ajoutez ces lignes la n de votre constructeur de cadre. A lexcution, vous verrez le contenu du panneau apparatre lentement. Pour un dbogage plus prcis, on peut appeler setDebugGraphicsOptions pour un composant particulier. Les puristes peuvent ajuster la dure, le nombre et la couleur des ashes consultez laide en ligne de la classe DebugGraphics pour plus de dtails. 12. Java SE 5.0 ajoute loption -Xlint au compilateur pour retrouver les problmes de code communs. Par exemple, si vous compilez avec la commande
javac -Xlint:fallthrough

le compilateur signale labsence dinstructions break dans les instructions switch (le terme "lint" dcrivait lorigine un outil permettant de retrouver des problmes potentiels dans les programmes C ; il sapplique maintenant de manire gnrique aux outils qui retrouvent les constructions douteuses, sans tre interdites). Les options suivantes sont disponibles : Xlint ou -Xlint:all Xlint:deprecation Xlint:fallthrough Xlint:finally Xlint:none Xlint:path Xlint:serial Xlint:unchecked Ralise toutes les vrications. Identique -deprecation, recherche les mthodes dprcies. Recherche les instructions break manquantes dans les instructions switch. Signale les clauses finally qui ne peuvent pas se terminer normalement. Ne ralise aucune vrication. Vrie lexistence de tous les rpertoires sur le chemin de classe et celle du chemin source. Signale les classes srialisables ne contenant pas serialVersionUID (voir Chapitre 1 du Volume II). Signale les conversions peu sres entre des types gnriques et des types bruts (voir Chapitre 12).

13. Java SE 5.0 prend maintenant en charge la surveillance et la gestion des applications Java, ce qui permet dinstaller des agents dans la machine virtuelle pour suivre la consommation de mmoire, lutilisation des threads, le chargement de classes, etc. Cette fonctionnalit est particulirement importante pour de gros programmes Java, au fonctionnement prolong, par exemple

592

Au cur de Java 2 - Notions fondamentales

les serveurs dapplications. En guise de dmonstration, le JDK est livr avec un outil graphique appel jconsole qui propose des statistiques sur les performances dune machine virtuelle (voir Figure 11.3). Retrouvez ensuite lID du process du systme dexploitation qui excute la machine virtuelle. Sous UNIX/Linux, excutez lutilitaire ps; sous Windows, utilisez le Gestionnaire de tches. Lancez ensuite le programme jconsole :
jconsole IDprocess

La console vous apporte de nombreuses informations propos de votre programme. Consultez le site http://java.sun.com/developer/technicalArticles/J2SE/jconsole.html pour en savoir plus.
INFO
Avant Java SE 6, vous devez lancer votre programme avec loption -Dcom.sun.management.jmxremote.
java -Dcom.sun.management.jmxremote MyProgram.java jconsole IDprocess

Figure 11.3
Le programme jconsole.

Chapitre 11

Exceptions, consignation, assertion et mise au point

593

14. Vous pouvez utiliser lutilitaire jmap pour rcuprer la mise au rebut du tas vous montrant chaque objet quil contient. Utilisez ces commandes :
jmap -dump:format=b,file=dumpFileName processID jhat dumpFileName

Pointez ensuite votre navigateur sur localhost:7000. Vous obtiendrez une application Web vous permettant de dtailler le contenu sur le tas au moment de la mise au rebut. 15. Si vous lancez la machine virtuelle avec le drapeau -Xprof, il excute un proler rudimentaire qui suit les mthodes de votre code ayant t le plus souvent excutes. Les informations de prolage sont envoyes System.out. La sortie vous indique aussi les mthodes compiles par le compilateur en juste--temps (just-in-time).
ATTENTION
Les options -X du compilateur ne sont pas ofciellement prises en charge et risquent de ne pas apparatre dans toutes les versions du JDK. Excutez java -X pour obtenir une liste de toutes les options non standard.

Utiliser une fentre de console


Lorsque vous dboguez un applet, les messages derreur safchent dans une fentre. Dans le panneau de conguration du plug-in Java, cochez la case Show Java Console (voir Chapitre 10). La fentre de console Java dispose dun ensemble de barres de dlement de faon vous permettre de rcuprer les messages qui ont dl en dehors de la fentre. Les utilisateurs de la fentre de la console Java bncient ainsi dun rel avantage par rapport la fentre de shell DOS dans laquelle les sorties System.out et System.err safchent normalement. Nous vous fournissons une classe de fentre identique, an que vous puissiez proter du mme avantage, en visualisant vos messages de dbogage lintrieur dune fentre lorsque vous mettez au point un programme. La Figure 11.4 prsente la classe ConsoleWindow en action.
Figure 11.4
La fentre de console.

Cette classe est dun usage facile. Il suft dappeler


ConsoleWindow.init()

Ensuite, imprimez normalement vers System.out ou System.err.

594

Au cur de Java 2 - Notions fondamentales

Le Listing 11.3 liste le code de la classe ConsoleWindow. Comme vous pouvez le voir, la classe est trs simple. Les messages sont afchs dans une JTextArea lintrieur dun panneau JScrollPane. Nous appelons les mthodes System.setOut et System.setErr pour diriger les sorties et ux derreurs vers un ux spcial qui ajoute chaque message dans la zone de texte.
Listing 11.3 : ConsoleWindow.java
import javax.swing.*; import java.io.*; /** * Une fentre qui affiche les mots (bytes) envoys System.out * et System.err * @version 1.01 2004-05-10 * @author Cay Horstmann */ public class ConsoleWindow { public static void init() { JFrame frame = new JFrame(); frame.setTitle("ConsoleWindow"); final JTextArea output = new JTextArea(); output.setEditable(false); frame.add(new JScrollPane(output)); frame.setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); frame.setLocation(DEFAULT_LEFT, DEFAULT_TOP); frame.setFocusableWindowState(false); frame.setVisible(true); // dfinir un PrintStream qui envoie ses mots la // zone de texte de sortie PrintStream consoleStream = new PrintStream(new OutputStream() { public void write(int b) {} // jamais appel public void write(byte[] b, int off, int len) { output.append(new String(b, off, len)); } }); // dfinir System.out et System.err sur ce flux System.setOut(consoleStream); System.setErr(consoleStream); } public public public public } static static static static final final final final int int int int DEFAULT_WIDTH = 300; DEFAULT_HEIGHT = 200; DEFAULT_LEFT = 200; DEFAULT_TOP = 200;

Chapitre 11

Exceptions, consignation, assertion et mise au point

595

Tracer les vnements AWT


Si vous devez crire une interface utilisateur labore en Java, il vous faut savoir quels vnements envoient AWT quels composants. Malheureusement, la documentation AWT est assez indigente sur ce point. Par exemple, nous voulons faire apparatre des phrases daide dans la ligne dtat lorsque lutilisateur dplace la souris sur les diverses parties de lcran. AWT gnre des vnements de souris et de focus quil vous faut intercepter. Nous vous fournissons une classe EventTrace trs utile pour espionner ces vnements. Elle afche toutes les mthodes de gestion des vnements avec leurs paramtres. La Figure 11.5 afche les vnements tracs. Pour espionner les messages, il suft dajouter les composants dont les vnements doivent tre suivis, lintrieur dun traceur dvnement :
EventTracer tracer = new EventTracer(); tracer.add(frame);

Cela afche un texte dcrivant tous les vnements, comme ceci :


public abstract void java.awt.event.MouseListener.mouseExited( java.awt.event.MouseEvent): java.awt.event.MouseEvent[MOUSE_EXITED,(408,14),button=0,clickCount=0] on javax.swing.JButton[,0,345,400x25,...] public abstract void java.awt.event.FocusListener.focusLost( java.awt.event.FocusEvent): java.awt.event.FocusEvent[FOCUS_LOST,temporary,opposite=null] on javax.swing.JButton[,0,345,400x25,...]

Peut-tre dsirez-vous capturer ces sorties dans un chier ou une fentre de console, comme nous lavons vu dans les sections prcdentes ?

Figure 11.5
Excution de la classe EventTracer.

596

Au cur de Java 2 - Notions fondamentales

Le Listing 11.4 correspond la classe EventTracer. Lide qui sy cache est simple, mme si son implmentation semble mystrieuse. Voici quelques tapes ralises en coulisse : 1. Lorsque vous ajoutez un composant au traceur dvnement dans la mthode add, la classe introspection des Java Beans analyse le composant la recherche des mthodes de la forme void addXxxListener(XxxListener) (voir le Chapitre 8 du Volume II). Pour chaque mthode correspondante un EventSetDescriptor est gnr. Nous passons chaque descripteur la mthode addListener. 2. Si le composant est un conteneur, nous numrons ses composants et pouvons appeler chacun dentre eux de faon rcursive. 3. addListener est appele avec deux paramtres : le composant dont nous voulons espionner les vnements et le descripteur dvnement. La mthode getListenerType de la classe EventSetDescriptor renvoie un objet Class qui dcrit linterface dcouteur dvnement telle que ActionListener ou ChangeListener. Nous crons un objet proxy pour cette interface. Le gestionnaire de proxy se contente dimprimer le nom et le paramtre dvnement de la mthode event invoque. La mthode getAddListenerMethod de la classe EventSetDescriptor renvoie un objet Method que nous utilisons pour ajouter au composant lobjet proxy en tant qucouteur dvnement. Ce programme est un bon exemple de la puissance du mcanisme de rexion. Nous navons pas besoin de coder le fait que la classe JButton possde une mthode addActionListener, puisquun JSlider possde une mthode addChangeListener. Le mcanisme de rexion dcouvre ces faits pour nous. Le Listing 11.5 teste le traceur dvnement. Le programme afche un cadre avec un bouton et une tirette et trace les vnements gnrs par ces composants.
Listing 11.4 : EventTracer.java
import java.awt.*; import java.beans.*; import java.lang.reflect.*; /** * @version 1.31 2004-05-10 * @author Cay Horstmann */ public class EventTracer { public EventTracer() { // le gestionnaire pour tous les proxies dvnement handler = new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) { System.out.println(method+":"+args[0]); return null; } }; }

Chapitre 11

Exceptions, consignation, assertion et mise au point

597

/** * Ajoute des traceurs dvnements pour tous les vnements * que ce composant et ses enfants peuvent couter * @param c Un composant */ public void add(Component c) { try { // rcuprer tous les vnements que ce composant peut couter BeanInfo info = Introspector.getBeanInfo(c.getClass()); EventSetDescriptor[] eventSets = info.getEventSetDescriptors(); for (EventSetDescriptor eventSet: eventSets) addListener(c, eventSet); } catch (IntrospectionException e) { } // ok pour ne pas ajouter dcouteurs si lexception est lance if (c instanceof Container) { // rcuprer tous les enfants et appeler add // de manire rcurrente for (Component comp: ((Container) c).getComponents()) add(comp); } } /** * Ajouter un couteur au jeu dvnements donn * @param c Un composant * @param eventSet Un descripteur dune interface dcouteur */ public void addListener(Component c, EventSetDescriptor eventSet) { // crer lobjet proxy pour ce type dcouteur // et acheminer tous les appels au gestionnaire Object proxy = Proxy.newProxyInstance(null, new Class[] { eventSet.getListenerType() }, handler); // ajouter le proxy au composant sous forme dcouteur Method addListenerMethod = eventSet.getAddListenerMethod(); try { addListenerMethod.invoke(c, proxy); } catch(InvocationTargetException e) { } catch(IllegalAccessException e) { } // ok pour ne pas ajouter dcouteur si lexception est lance } private InvocationHandler handler; }

598

Au cur de Java 2 - Notions fondamentales

Listing 11.5 : EventTracerTest.java


import java.awt.*; import javax.swing.*; /** * @version 1.13 2007-06-12 * @author Cay Horstmann */ public class EventTracerTest { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { JFrame frame = new EventTracerFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); } } class EventTracerFrame extends JFrame { public EventTracerFrame() { setTitle("EventTracerTest"); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); // ajouter un curseur et un bouton add(new JSlider(), BorderLayout.NORTH); add(new JButton("Test"), BorderLayout.SOUTH); // intercepter tous les vnements des composants dans le cadre EventTracer tracer = new EventTracer(); tracer.add(this); } public static final int DEFAULT_WIDTH = 400; public static final int DEFAULT_HEIGHT = 400; }

Le robot AWT
Java SE 1.3 ajoute une classe Robot que vous pouvez utiliser pour envoyer des frappes de clavier et des clics de souris vers nimporte quel programme AWT. Cette classe a pour vocation de permettre lautomatisation du test des interfaces utilisateur. Pour obtenir un robot, il vous faut dabord obtenir un objet GraphicsDevice. On obtient lcran par dfaut par une suite dappels :
GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment(); GraphicsDevice screen = environment.getDefaultScreenDevice();

Chapitre 11

Exceptions, consignation, assertion et mise au point

599

Puis vous construisez un robot :


Robot robot = new Robot(screen);

Pour envoyer une frappe de clavier, demandez au robot de simuler une frappe de touche suivie dune sortie de touche :
robot.keyPress(KeyEvent.VK_TAB); robot.keyRelease(KeyEvent.VK_TAB);

Pour un clic de souris, il vous faut dplacer la souris, puis appuyer et relcher le bouton :
robot.mouseMove(x, y); // x et y sont les coordonnes absolues des pixels lcran robot.mousePress(InputEvent.BUTTON1_MASK); robot.mouseRelease(InputEvent.BUTTON1_MASK);

Lide consiste simuler le clavier et la souris, puis de prendre un clich de lcran pour voir si lapplication sest comporte comme prvu. Vous capturez lcran avec la mthode createScreenCapture :
Rectangle rect = new Rectangle(x, y, width, height); BufferedImage image = robot.createScreenCapture(rect);

Les coordonnes du rectangle se rfrent galement des pixels en absolu. Pour nir, vous souhaitez en gnral ajouter un petit dlai entre les instructions du robot de telle faon que lapplication puisse tre en rythme. Utilisez la mthode delay et donnez-lui un temps dattente en millisecondes. Par exemple :
robot.delay(1000); // dlai de 1000 millisecondes

Le programme du Listing 11.6 vous montre comment utiliser le robot. Robot teste le programme de test de boutons vu au Chapitre 8. Pour commencer, appuyez sur la barre despace. Cela active le bouton gauche. Puis le robot attend deux secondes an que vous puissiez voir ce qui se passe. Aprs ce dlai, le robot simule la touche de tabulation et une autre frappe sur la barre despace pour cliquer sur le bouton suivant. Pour nir, on simule un clic du troisime bouton de la souris (peut-tre faudra-t-il ajuster les coordonnes X et Y du programme an dappuyer rellement sur ce bouton). Le programme se termine en ralisant une capture dcran et en lafchant dans un autre cadre (voir Figure 11.6).
Figure 11.6
Capture dcran du robot AWT.

600

Au cur de Java 2 - Notions fondamentales

Comme on le voit dans lexemple, la classe Robot nest pas en elle-mme adapte au test de linterface utilisateur. Au lieu de cela, elle constitue un bloc de base qui peut devenir lune des pierres de touche dun outil de test. Un outil de test professionnel peut capturer, stocker et rafcher des scnarios dinteraction dutilisateurs, et trouver les emplacements sur lcran de tous les composants. De cette faon, les clics de la souris ne tombent pas au hasard. Nous esprons voir des outils de test sophistiqus apparatre mesure que les applications Java deviennent plus populaires.
Listing 11.6 : RobotTest.java
import import import import java.awt.*; java.awt.event.*; java.awt.image.*; javax.swing.*;

/** * @version 1.03 2007-06-12 * @author Cay Horstmann */ public class RobotTest { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { // crer un cadre avec un panneau de bouton ButtonFrame frame = new ButtonFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); // joindre un robot au priphrique cran GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment(); GraphicsDevice screen = environment.getDefaultScreenDevice(); try { Robot robot = new Robot(screen); runTest(robot); } catch (AWTException e) { e.printStackTrace(); } } }); } /** * Excute un exemple de procdure de test * @param robot Le robot attach au priphrique cran */ public static void runTest(Robot robot) { // simuler une pression sur la barre espace

Chapitre 11

Exceptions, consignation, assertion et mise au point

601

robot.keyPress( ); robot.keyRelease( ); // simuler une touche de tabulation suivie dun espace robot.delay(2000); robot.keyPress(KeyEvent.VK_TAB); robot.keyRelease(KeyEvent.VK_TAB); robot.keyPress( ); robot.keyRelease( ); // simuler un clic de souris sur le bouton droit robot.delay(2000); robot.mouseMove(200, 50); robot.mousePress(InputEvent.BUTTON1_MASK); robot.mouseRelease(InputEvent.BUTTON1_MASK); // capturer lcran et afficher limage robot.delay(2000); BufferedImage image = robot.createScreenCapture( new Rectangle(0, 0, 400, 300)); ImageFrame frame = new ImageFrame(image); frame.setVisible(true); } } /** * Un cadre pour afficher une capture dcran */ class ImageFrame extends JFrame { /** * @param image Limage afficher */ public ImageFrame(Image image) { setTitle("Capture"); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); JLabel label = new JLabel(new ImageIcon(image)); add(label); } public static final int DEFAULT_WIDTH = 450; public static final int DEFAULT_HEIGHT = 350; } java.awt.GraphicsEnvironment 1.2

static GraphicsEnvironment getLocalGraphicsEnvironment()

Renvoie lenvironnement graphique local.


GraphicsDevice getDefaultScreenDevice()

Renvoie lcran par dfaut. Notez que les ordinateurs avec plusieurs moniteurs nont quun cran virtuel par moniteur. Utilisez la mthode getScreenDevices pour obtenir la liste de tous les crans.

602

Au cur de Java 2 - Notions fondamentales

java.awt.Robot 1.3

Robot(GraphicsDevice device)

Construit un robot dinteraction avec le priphrique.


void keyPress(int key) void keyRelease(int key)

Simulent lenfoncement ou le relchement dune touche. Paramtres : key Le code de touche. Voir la classe KeyStroke pour plus dinformations sur les codes de touche.

void mouseMove(int x, int y)

Simule un mouvement de la souris. Paramtres : x,y La position de la souris en coordonnes absolues et en pixels.

void mousePress(int eventMask) void mouseRelease(int eventMask)

Simulent une pression sur un bouton de la souris ou son relchement. Paramtres : eventMask Le masque dvnement dcrivant les boutons de la souris. Voir la classe InputEvent pour plus dinformations sur les masques dvnement.

void delay(int milliseconds)

Met le robot en attente pour le nombre de millisecondes donn en paramtre.


bufferedImage createScreenCapture(Rectangle rect)

Capture une partie de lcran. Paramtres : rect Le rectangle qui doit tre captur en coordonnes absolues et en pixels.

Utiliser un dbogueur
Dboguer en utilisant des instructions print est lune des plus agrables expriences qui soient. Vous vous trouvez constamment ajouter et enlever des instructions et recompiler le programme. Lusage dun dbogueur est plus efcace parce que celui-ci fait tourner votre programme jusqu ce quil atteigne un point darrt prdtermin. A ce moment-l, vous pouvez observer tout ce qui vous intresse. Le Listing 11.7 utilise une version dlibrment altre du programme ButtonTest du Chapitre 8. Lorsque vous cliquez sur lun quelconque des boutons, rien ne se produit. Regardez le code source. Les boutons sont censs dnir la couleur de fond en fonction du nom du bouton.
Listing 11.7 : BuggyButtonTest.java
import java.awt.*; import java.awt.event.*; import javax.swing.*;

Chapitre 11

Exceptions, consignation, assertion et mise au point

603

/** * @version 1.22 2007-05-14 * @author Cay Horstmann */ public class BuggyButtonTest { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { BuggyButtonFrame frame = new BuggyButtonFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); } } class BuggyButtonFrame extends JFrame { public BuggyButtonFrame() { setTitle("BuggyButtonTest"); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); // ajouter un panneau au cadre BuggyButtonPanel panel = new BuggyButtonPanel(); add(panel); } public static final int DEFAULT_WIDTH = 300; public static final int DEFAULT_HEIGHT = 200; } class BuggyButtonPanel extends JPanel { public BuggyButtonPanel() { ActionListener listener = new ButtonListener(); JButton yellowButton = new JButton("Yellow"); add(yellowButton); yellowButton.addActionListener(listener); JButton blueButton = new JButton("Blue"); add(blueButton); blueButton.addActionListener(listener); JButton redButton = new JButton("Red"); add(redButton); redButton.addActionListener(listener); }

private class ButtonListener implements ActionListener


{ public void actionPerformed(ActionEvent event) { String arg = event.getActionCommand(); if (arg.equals("yellow")) setBackground(Color.yellow);

604

Au cur de Java 2 - Notions fondamentales

else if (arg.equals("blue")) setBackground(Color.blue); else if (arg.equals("red")) setBackground(Color.red); } } }

Dans un programme aussi court, vous pouvez trouver le bogue simplement en lisant le code source. Mais nous allons imaginer quil sagit dun programme trs complexe. Nous dcrivons ci-aprs comment lancer le dbogueur Eclipse pour trouver lerreur.
INFO
Si vous utilisez un dbogueur indpendant comme JSwat (http://www.bluemarsh.com/java/jswat/) ou le trs agrable jdb, vous commencez par compiler votre programme avec loption -g, comme dans cet exemple :
javac -g BuggyButtonTest.java

Dans un environnement intgr, cela seffectue automatiquement.

Dans Eclipse, lancez le dbogueur avec loption de menu Run -> Debug As -> Java Application. Le programme se lance. Dnissez un point darrt la premire ligne de la mthode actionPerformed : faites un clic droit dans la marge gauche, prs de la ligne de code et choisissez Toggle Breakpoint (voir Figure 11.7).
Figure 11.7
Arrt un point darrt.

Chapitre 11

Exceptions, consignation, assertion et mise au point

605

Il y a deux commandes de base pour faire du pas pas dans un programme. La commande Step Into avance en pas pas en suivant compltement les appels de mthode. Il est plus sr dutiliser la commande Step Over qui passe la ligne suivante du code appelant sans entrer dans le dtail dautres appels de mthode. Eclipse utilise les options de menu Run -> Step Into et Run -> Step Over, avec les raccourcis clavier F5 et F6. Utilisez la commande "Step Over" deux fois, puis voyez o vous tes arriv.

Ce nest pas ce qui tait prvu. Normalement, le programme doit appeler setColor(Color.yellow), puis quitter la mthode. Etudiez les variables locales et vriez la valeur de la variable arg :

Nous comprenons lerreur. Largument arg est en ralit "Yellow" avec un Y majuscule, mais le test est crit ainsi :
if (arg.equals("yellow"))

avec un y minuscule. Mystre clairci ! Pour terminer la session de mise au point, slectionnez Run -> Terminate dans le menu.

606

Au cur de Java 2 - Notions fondamentales

Il existe des commandes de dbogage plus avances dans Eclipse mais vous accomplirez dj beaucoup avec les techniques simples que nous venons de voir. Dautres dbogueurs, comme NetBeans, possdent des commandes trs similaires. Ce chapitre a introduit la notion de gestion des exceptions en vous donnant quelques astuces de test et de dbogage. Le prochain traite de la programmation gnrique et de son application la plus importante : le cadre des collections Java.

12
Programmation gnrique
Au sommaire de ce chapitre

Pourquoi la programmation gnrique ? Dnition dune classe gnrique simple Mthodes gnriques Liaisons pour variables de type Code gnrique et machine virtuelle Restrictions et limites Rgles dhritage pour les types gnriques Types joker La rexion et le systme gnrique
La programmation gnrique constitue la nouveaut la plus signicative du langage de programmation Java depuis la version 1.0. Lapparition du gnrique dans Java SE 5.0 rsulte de lune des premires requtes de spcications Java, JSR 14, produite en 1999. Le groupe dexperts a pass environ cinq ans travailler sur des spcications et tester des implmentations. Le gnrique tait fort attendu car il permet dcrire un code plus sr et plus facile lire quun code parsem de variables Object et de transtypages. Il est tout particulirement utile pour les classes de collection, comme ArrayList. Le gnrique est, du moins en apparence, semblable aux modles de C++. En C++, comme en Java, les modles ont dabord t ajouts au langage pour soutenir les collections lourdes en types. Malgr tout, au cours des annes, dautres utilisations ont t dcouvertes. Lorsque vous aurez lu ce chapitre, vous en trouverez peut-tre encore dautres.

608

Au cur de Java 2 - Notions fondamentales

Pourquoi la programmation gnrique ?


La programmation gnrique implique dcrire du code qui puisse tre rutilis pour des objets de types diffrents. Il nest pas utile, par exemple, de programmer des classes diffrentes pour collecter les objets String et File, puisque la classe ArrayList collecte les objets de nimporte quelle classe. Cest un exemple de programmation gnrique. Avant Java SE 5.0, la programmation gnrique en Java sobtenait chaque fois par lhritage. La classe ArrayList conservait simplement un tableau de rfrences Object :
public class ArrayList // avant Java SE 5.0 { public Object get(int i) { . . . } public void add(Object o) { . . . } . . . private Object[] elementData; }

Cette approche prsente deux problmes. Dune part, il faut avoir recours au transtypage lorsque vous rcuprez une valeur :
ArrayList files = new ArrayList(); . . . String filename = (String) names.get(0);

Dautre part, il nexiste aucune procdure de vrication des erreurs. Vous pouvez ajouter des valeurs de nimporte quelle classe :
files.add(new File(". . ."));

Cet appel se compile et sexcute sans erreur. Partout ailleurs, transtyper le rsultat de get sur une chane produira une erreur. Le gnrique propose une meilleure solution : les paramtres de type. La classe ArrayList dispose dsormais dun paramtre de type qui prcise le type dlment utilis :
ArrayList<String> files = new ArrayList<String>();

Votre code est alors plus facile lire. Vous voyez immdiatement que cet ArrayList contient des objets String. Le compilateur utilisera galement ces informations bon escient. Aucun transtypage nest ncessaire pour appeler get : le compilateur sait que le type de retour est String, et non Object :
String filename = files.get(0);

Il sait aussi que la mthode add dun ArrayList<String> possde un paramtre de type String. Cela est bien plus sr que dutiliser un paramtre Object. Le compilateur peut alors vrier que vous ninsrez aucun objet dun type erron. Par exemple, linstruction
files.add(new File(". . .")); // ne peut ajouter que des objets String // un ArrayList<String>

ne se compilera pas. Dailleurs, une erreur de compilation vaut bien mieux quune exception de transtypage de classe au moment de lexcution. Cest lappel des paramtres de type : ils facilitent la lecture des programmes et les scurisent.

Chapitre 12

Programmation gnrique

609

Y a-t-il un programmeur gnrique dans la salle ?


Lutilisation dune classe gnrique comme ArrayList se fait sans grande difcult. La plupart des programmeurs Java utiliseront simplement des types tel ArrayList<String>, comme sils avaient t intgrs dans le langage, linstar des tableaux String[] (bien sr, les ArrayList sont plus intressants que les tableaux car ils peuvent grandir automatiquement). Mais limplmentation dune classe gnrique nest pas aussi simple quil y parat. Les programmeurs qui utilisent votre code voudront y intgrer toutes sortes de classes pour vos paramtres de type. Ils sattendent donc ce que tout fonctionne sans trop de restrictions ni messages derreurs confus. Si vous lacceptez, votre mission, en tant que programmeur gnrique, consiste anticiper toutes les utilisations potentielles de votre classe. Oui, mais jusquo ? Cest l un problme ordinaire que rencontraient les concepteurs de la bibliothque de classes standard. La classe ArrayList possde une mthode addAll qui lui permet dajouter tous les lments dune autre collection. Un programmeur peut vouloir ajouter tous les lments dun ArrayList<Manager> un ArrayList<Employee>. Bien entendu, le contraire ne doit pas tre autoris. Comment faire alors pour autoriser un appel et interdire lautre ? Les concepteurs du langage Java ont invent un nouveau concept ingnieux pour rsoudre ce problme, le type joker. Les types joker sont plutt abstraits, mais ils permettent un concepteur de bibliothque de rendre les mthodes aussi exibles que possible. La programmation gnrique aborde trois niveaux de comptences. Au niveau le plus bas, vous utilisez uniquement les classes gnriques, gnralement des collections comme ArrayList, sans penser la manire dont elles fonctionnent ni leurs raisons dtre. La plupart des programmeurs dapplications se contenteront de ce niveau jusqu ce quun problme apparaisse. Vous risquez de rencontrer un message derreur droutant lorsque vous mlangez diffrentes classes gnriques ou que vous interfacez avec du code existant qui ne connat pas les paramtres de type. A ce moment-l, il vous faudra complter votre culture du gnrique Java pour rsoudre les problmes systmatiquement, plutt que ttonner chaque fois. Enn, bien sr, vous voudrez peut-tre implmenter vos propres classes et mthodes gnriques. Les programmeurs dapplications ncriront probablement pas de grosses quantits de code gnrique. Les quipes de Sun ont dj fait le plus gros et fourni des paramtres de type pour toutes les classes de collection. En rgle gnrale, seul le code qui impliquait gnralement beaucoup de transtypages de types trs gnraux (comme Object ou linterface Comparable) protera de lutilisation des paramtres de type. Dans ce chapitre, vous dcouvrirez tout ce quil faut savoir pour implmenter votre propre code gnrique. Vous devrez toutefois utiliser ces connaissances principalement pour vous aider au moment du dpannage et satisfaire votre curiosit sur le fonctionnement interne des classes de collection avec paramtres.

610

Au cur de Java 2 - Notions fondamentales

Dnition dune classe gnrique simple


Une classe gnrique est une classe comprenant une ou plusieurs variables de type. Dans ce chapitre, nous utilisons une classe Pair simple titre dexemple. Elle nous permet de nous concentrer sur le gnrique, sans tre distraits par les dtails de stockage des donnes. Voici le code de la classe Pair gnrique :
public class Pair<T> { public Pair() { first = null; second = null; } public Pair(T first, T second) { this.first = first; this. second = second; } public T getFirst() { return first; } public T getSecond() { return second; } public void setFirst(T newValue) { first = newValue; } public void setSecond(T newValue) { second = newValue; } private T first; private T second; }

La classe Pair introduit une variable de type T, incluse entre < > aprs le nom de la classe. Une classe gnrique peut possder plusieurs variables de type. La classe Pair aurait pu, par exemple, tre dnie avec des types spars pour le premier et le deuxime champ :
public class Pair<T, U> { . . . }

Les variables de type seront utilises tout au long de la dnition de la classe pour spcier les types de retour des mthodes et les types de champs et de variables locales. Par exemple :
private T first; // utilise une variable de type

INFO
Les variables de type sont souvent indiques avec des majuscules, ce qui permet de les garder courtes. La bibliothque Java utilise la variable E pour le type dlment dune collection, K et V pour les types de cls et de valeurs dune table et T (et les lettres U et S, si ncessaire) pour "nimporte quel type".

Vous instanciez le type gnrique en remplaant les types par les variables de type, comme
Pair<String>

On peut imaginer le rsultat comme une classe ordinaire avec les constructeurs
Pair<String>() Pair<String>(String, String)

et les mthodes
String getFirst() String getSecond() void setFirst(String) void setSecond(String)

En dautres termes, la classe gnrique agit la manire dun factory pour les classes ordinaires.

Chapitre 12

Programmation gnrique

611

Le programme du Listing 12.1 met en uvre la classe Pair. La mthode minmax statique parcourt un tableau et calcule en mme temps la valeur minimale et la valeur maximale. Elle utilise un objet Pair pour renvoyer les deux rsultats. Souvenez-vous que la mthode compareTo compare deux chanes et renvoie 0 si les chanes sont identiques, un entier ngatif si la premire chane vient avant la seconde dans lordre alphabtique et un entier positif dans les autres cas.
INFO C++
Au premier abord, les classes gnriques en Java sont identiques aux classes de modles en C++. La seule diffrence vidente rside dans le fait que Java ne possde aucun mot cl template spcial. Toutefois, comme vous le verrez dans ce chapitre, il existe des diffrences considrables entre ces deux mcanismes.

Listing 12.1 : PairTest1.java


/** * @version 1.00 2004-05-10 * @author Cay Horstmann */ public class PairTest1 { public static void main(String[] args) { String[] words = { "Mary", "had", "a", "little", "lamb" }; Pair<String> mm = ArrayAlg.minmax(words); System.out.println("min = " + mm.getFirst()); System.out.println("max = " + mm.getSecond()); } } class ArrayAlg { /** * Rcupre le minimum et le maximum dun tableau de chanes. * @param a Un tableau de chane * @return Une paire avec la valeur min et la valeur max ou null * si a est nul ou vide */ public static Pair<String> minmax(String[] a) { if (a == null || a.length == 0) return null; String min = a[0]; String max = a[0]; for (int i = 1; i < a.length; i++) { if (min.compareTo(a[i]) > 0) min = a[i]; if (max.compareTo(a[i]) < 0) max = a[i]; } return new Pair<String>(min, max); } }

612

Au cur de Java 2 - Notions fondamentales

Mthodes gnriques
Dans la section prcdente, vous avez vu comment dnir une classe gnrique. Vous pouvez aussi dnir une seule mthode avec des paramtres de type :
class ArrayAlg { public static <T> T getMiddle(T[] a) { return a[a.length / 2]; } }

Cette mthode est dnie dans une classe ordinaire, et non dans une classe gnrique. Cest toutefois une mthode gnrique, comme vous pouvez le voir aux signes qui lentourent et la variable de type. Sachez que les variables de type sont insres aprs les modieurs (public static, dans ce cas) et avant le type de retour. Vous pouvez dnir des mthodes gnriques dans des classes ordinaires et dans des classes gnriques. Lorsque vous appelez une mthode gnrique, vous pouvez placer les types rels, entours des signes <>, avant le nom de la mthode :
String[] names = { "John", "Q.", "Public" }; String middle = ArrayAlg.<String>getMiddle(names);

Dans ce cas, et donc la plupart du temps, vous pouvez omettre le paramtre de type <String> de lappel de mthode. Le compilateur dispose de sufsamment dinformations pour en dduire la mthode que vous souhaitez utiliser. Il fait correspondre le type des names (donc, String[]) avec le type gnrique T[] et en dduit que T doit tre un String. Vous pouvez donc simplement appeler
String middle = ArrayAlg.getMiddle(names);

Dans la plupart des cas, linfrence de type pour les mthodes gnriques fonctionne agrablement. Parfois, cependant, le compilateur se trompe et vous devrez dchiffrer un rapport derreur. Etudiez cet exemple :
double middle = ArrayAlg.getMiddle(3.14, 1729, 0);

Le message derreur est le suivant : "found: java.lang.Number&java.lang.Comparable<? extends java.lang.Number&java.lang.Comparable<?>>, required: double". Vous verrez plus loin au cours de ce chapitre comment dchiffrer la dclaration de type "found". Bref, le compilateur a procd lautoboxing des paramtres dans un objet Double et deux objets Integer, puis tent de trouver un supertype commun ces classes. Il en a, en fait, trouv deux : Number et linterface Comparable, qui est en soi un type gnrique. Dans ce cas, le recours consiste crire tous les paramtres sous forme de valeurs double.
ASTUCE
Peter von der Ah conseille cette astuce pour dcouvrir le type du compilateur qui lance un appel de mthode gnrique : introduisez une erreur dessein et tudiez le message qui en rsulte. Envisagez par exemple lappel ArrayAlg.getMiddle("Hello", 0, null). Affectez le rsultat un JButton, ce qui ne peut pas tre juste. Vous obtiendrez un rapport derreur "found: java.lang.Object&java.io.Serializable&java.lang. Comparable<? extends java.lang.Object&java.io.Serializable&java.lang.Comparable<?>>". Autrement dit, vous pouvez affecter le rsultat Object, Serializable ou Comparable.

Chapitre 12

Programmation gnrique

613

INFO C++
En C++, les paramtres de type se placent aprs le nom de la mthode. Ceci peut amener des ambiguts danalyse dsagrables. Par exemple, g(f<a,b>(c)) peut signier "appeler g avec le rsultat de f<a,b>(c)" ou "appeler g avec les deux valeurs boolennes f<a et b>(c)".

Limites pour variables de type


Par moments, une classe ou une mthode doit placer des restrictions sur des variables de type. En voici un exemple ordinaire. Nous voulons calculer le plus petit lment dun tableau :
class ArrayAlg { public static <T> T min(T[] a) // presque correct { if (a == null || a.length == 0) return null; T smallest = a[0]; for (int i = 1; i < a.length; i++) if (smallest.compareTo(a[i]) > 0) smallest = a[i]; return smallest; } }

Mais un problme demeure. Etudiez le code de la mthode min. La variable smallest possde un type T, ce qui signie quil pourrait sagir dun objet dune classe arbitraire. Comment savoir alors que la classe laquelle appartient T possde une mthode compareTo? La solution consiste restreindre T une classe qui implmente linterface Comparable, une interface standard disposant dune seule mthode, compareTo. Pour y parvenir, vous devez donner une limite pour la variable de type T :
public static <T extends Comparable> T min(T[] a) . . .

En fait, linterface Comparable est elle-mme un type gnrique. Pour lheure, nous allons ignorer cette complexit et les avertissements gnrs par le compilateur. Les types joker sont dcrits plus loin et montrent comment utiliser correctement les paramtres de type avec linterface Comparable. Dsormais, la mthode gnrique min ne peut plus tre appele quavec des tableaux de classes qui implmentent linterface Comparable, comme String, Date, etc. Appeler min avec un tableau Rectangle produit une erreur de dlai de compilation car la classe Rectangle nimplmente pas Comparable.
INFO C++
En C++, vous ne pouvez pas restreindre les types des paramtres de modles. Lorsquun programmeur instancie un modle avec un type inadapt, un message derreur (souvent abscons) est signal dans le code du modle.

Vous vous demanderez peut-tre pourquoi utiliser le mot cl extends plutt que le mot cl implements dans cette situation (aprs tout, Comparable est une interface). La notation
<T extends BoundingType>

614

Au cur de Java 2 - Notions fondamentales

indique que T doit tre un sous-type du type limitant. T et le type limitant peuvent tre une classe ou une interface. Le mot cl extends a t choisi car il constitue une approximation raisonnable du concept de sous-type et que les concepteurs Java ne souhaitaient pas ajouter un nouveau mot cl (comme sub). Une variable de type ou joker peut avoir plusieurs limites, par exemple
T extends Comparable & Serializable

Les types limitants sont spars par des esperluettes (&) car les virgules sont utilises pour sparer les variables de type. Comme pour lhritage Java, vous pouvez disposer dautant de supertypes dinterfaces que vous le souhaitez, mais une seule des limites peut tre une classe. Si vous disposez dune classe agissant comme lment limitant, elle doit gurer en premire place dans la liste. Dans le prochain exemple de programme (voir Listing 12.2), nous rcrivons la mthode minmax de manire la rendre gnrique. La mthode calcule le minimum et le maximum dun tableau gnrique et renvoie un Pair<T>.
Listing 12.2 : PairTest2.java
import java.util.*; /** * @version 1.00 2004-05-10 * @author Cay Horstmann */ public class PairTest2 { public static void main(String[] args) { GregorianCalendar[] birthdays = { new GregorianCalendar(1906, Calendar.DECEMBER, 9), // new GregorianCalendar(1815, Calendar.DECEMBER, 10),// new GregorianCalendar(1903, Calendar.DECEMBER, 3), // new GregorianCalendar(1910, Calendar.JUNE, 22), // }; Pair<GregorianCalendar> mm = ArrayAlg.minmax(birthdays); System.out.println("min = " + mm.getFirst().getTime()); System.out.println("max = " + mm.getSecond().getTime()); } }

G. A. J. K.

Hopper Lovelace von Neumann Zuse

class ArrayAlg { /** * Rcupre le minimum et le maximum dun tableau dobjets de type T. * @param a Un tableau dobjets de type T * @return Une paire avec la valeur min et la valeur max, * ou null si a est nul ou vide */ public static <T extends Comparable> Pair<T> minmax(T[] a) { if (a == null || a.length == 0) return null; T min = a[0]; T max = a[0];

Chapitre 12

Programmation gnrique

615

for (int i = 1; i < a.length; i++) { if (min.compareTo(a[i]) > 0) min = a[i]; if (max.compareTo(a[i]) < 0) max = a[i]; } return new Pair<T>(min, max); } }

Code gnrique et machine virtuelle


La machine virtuelle ne possde pas dobjet de type gnrique (tous les objets appartiennent des classes ordinaires). Une prcdente version de limplmentation gnrique pouvait mme compiler un programme utilisant le gnrique dans des chiers de classe qui sexcutaient sur les machines virtuelles 1.0 ! Cette compatibilit en amont na t abandonne que tard dans le dveloppement du gnrique Java. Si vous utilisez le compilateur Sun pour compiler un code employant le gnrique Java, les chiers de classe qui en rsultent ne sexcuteront pas sur les machines virtuelles avant la version 5.0.
INFO
Si vous souhaitez proter des fonctionnalits du gnrique tout en conservant la compatibilit des bytecodes avec danciennes machines virtuelles, consultez ladresse http://sourceforge.net/projects/retroweaver. Le programme Retroweaver rcrit les chiers de classe de sorte quils soient compatibles avec danciennes machines virtuelles.

Ds que vous dnissez un type gnrique, un type brut correspondant est automatiquement fourni. Le nom du type brut correspond simplement au nom du type gnrique, les paramtres de type ayant t supprims. Les variables de type sont effaces et remplaces par leurs types limitants (ou Object pour les variables sans limites). Par exemple, le type brut pour Pair<T> ressemble ceci :
public class Pair { public Pair(Object first, Object second) { this.first = first; this.second = second; } public Object getFirst() { return first; } public Object getSecond() { return second; } public void setFirst(Object newValue) { first = newValue; } public void setSecond(Object newValue) { second = newValue; } private Object first; private Object second; }

T tant une variable sans limites, elle est simplement remplace par Object. Il en rsulte une classe ordinaire, que vous pourriez avoir implmente avant lajout du gnrique au langage de programmation Java.

616

Au cur de Java 2 - Notions fondamentales

Vos programmes peuvent contenir diffrentes sortes de Pair, comme Pair<String> ou Pair <Gregorian-Calendar>, mais leffacement les transforme tous en types Pair bruts.
INFO C++
A cet gard, le gnrique Java est trs diffrent des modles C++. C++ produit diffrents types pour chaque instanciation de modle, un phnomne dnomm "gonement du code du modle". Java ne souffre pas de ce problme.

Le type brut remplace les variables de type par la premire limite ou par Object si aucune limite nest donne. Par exemple, la variable de type de la classe Pair<T> ne dispose pas de limites explicites, le type brut remplace donc T par Object. Supposons que nous dclarions un type lgrement diffrent :
public class Interval<T extends Comparable & Serializable> implements Serializable { public Interval(T first, T second) { if (first.compareTo(second) <= 0) { lower = first; upper = second; } else { lower = second; upper = first; } } . . . private T lower; private T upper; }

Le type brut Interval ressemble ceci :


public class Interval implements Serializable { public Interval(Comparable first, Comparable second) { . . . } . . . private Comparable lower; private Comparable upper; }

INFO
Vous vous demandez peut-tre ce qui se passe si vous inversez les limites : class Interval<Serializable & Comparable>. Dans ce cas, le type brut remplace T par Serializable et le compilateur insre des transtypages dans Comparable le cas chant. Pour des raisons defcacit, vous devrez donc placer les interfaces de balisage ( savoir des interfaces sans mthodes) la n de la liste des limites.

Traduire les expressions gnriques


Dans la programmation dun appel une mthode gnrique, le compilateur insre des transtypages au moment o le type de retour est effac. Considrons par exemple la suite dinstructions
Pair<Employee> buddies = . . .; Employee buddy = buddies.getFirst();

Chapitre 12

Programmation gnrique

617

Effacer getFirst renvoie le type Object. Le compilateur insre automatiquement le transtypage sur Employee. En fait, il traduit lappel de mthode en deux instructions de machine virtuelle :
m m

un appel la mthode brute Pair.getFirst; un transtypage de lobjet renvoy au type Employee.

Des transtypages sont galement insrs lorsque vous accdez un champ gnrique. Supposons que les premier et deuxime champs de la classe Pair soient publics (ce nest peut-tre pas un bon style de programmation, mais cest autoris dans Java). Ds lors, dans lexpression
Employee buddy = buddies.first;

un transtypage a t insr dans les bytecodes qui en rsultent.

Traduire les mthodes gnriques


Leffacement de type survient galement pour les mthodes gnriques. Les programmeurs imaginent gnralement une mthode gnrique comme
public static <T extends Comparable> T min(T[] a)

comme une famille de mthodes globales ; or, aprs leffacement, une seule mthode demeure :
public static Comparable min(Comparable[] a)

Sachez que le paramtre de type T a t effac, ne laissant que son type limite Comparable. Leffacement de la mthode met au jour deux difcults. Etudiez cet exemple :
class DateInterval extends Pair<Date> { public void setSecond(Date second) { if (second.compareTo(getFirst()) >= 0) super.setSecond(second); } . . . }

Un intervalle de date est une paire dobjets Date; nous voulons craser les mthodes pour nous assurer que la deuxime valeur ne soit jamais infrieure la premire. Cette classe est efface :
class DateInterval extends Pair // aprs effacement { public void setSecond(Date second) { . . . } . . . }

Ceci peut surprendre, mais il existe une autre mthode setSecond, hrite de Pair, savoir
public void setSecond(Object second)

Cest lvidence une mthode diffrente car elle possde un paramtre dun type diffrent, qui est Object et non Date. Or elle ne doit pas tre diffrente. Regardez cette suite dinstructions :
DateInterval interval = new DateInterval(. . .); Pair<Date> pair = interval; // OK--attribution la superclasse pair.setSecond(aDate);

618

Au cur de Java 2 - Notions fondamentales

On peut sattendre ce que lappel setSecond soit polymorphique et que la mthode approprie soit appele. Etant donn que pair fait rfrence un objet DateInterval, il doit sagir de DateInterval.setSecond. Le problme est que leffacement du type interfre avec le polymorphisme. Pour le rsoudre, le compilateur gnre une mthode bridge dans la classe DateInterval :
public void setSecond(Object second) { setSecond((Date) second); }

Pour connatre les raisons de cette russite, suivons attentivement lexcution de linstruction
pair.setSecond(aDate)

La variable pair a dclar le type Pair<Date> et ce type na quune seule mthode appele setSecond, savoir setSecond(Object). La machine virtuelle appelle cette mthode sur lobjet auquel pair fait rfrence. Cet objet est de type DateInterval. Donc, la mthode DateInterval.setSecond(Object) est appele. Cest la mthode bridge synthtise. Elle appelle DateInterval.setSecond(Date), et cest bien ce que nous voulons. Les mthodes bridge peuvent apparatre encore plus tranges. Supposons que la mthode DateInterval crase aussi la mthode getSecond :
class DateInterval extends Pair<Date> { public Date getSecond() { return (Date) super.getSecond().clone(); } . . . }

Dans le type effac, il y a deux mthodes getSecond :


Date getSecond() // dfini dans DateInterval Object getSecond() // dfini dans Pair

Ce code Java est impossible, deux mthodes ne peuvent pas avoir les mmes types de paramtres, cest--dire ici aucun. Toutefois, dans la machine virtuelle, les types de paramtres et le type de retour spcient une mthode. Le compilateur peut donc produire des bytecodes pour deux mthodes qui ne diffrent quau niveau de leur type de retour ; la machine virtuelle grera correctement cette situation.
INFO
Les mthodes bridge ne sont pas restreintes aux types gnriques. Nous avons dj not au Chapitre 5 que, depuis Java SE 5.0, une mthode peut spcier un type de retour plus restrictif lors de lcrasement dune autre mthode. Par exemple :
public class Employee implements Cloneable { public Employee clone() throws CloneNotSupportedException { ... } }

Les mthodes Object.clone et Employee.clone sont dites avoir des types de retour covariants. En fait, la classe Employee possde deux mthodes clone:
Employee clone() // dfini ci-dessus Object clone() // mthode bridge synthtise, crase Object.clone

La mthode bridge synthtise appelle la mthode nouvellement dnie.

Chapitre 12

Programmation gnrique

619

En rsum, notez bien ces points lis la traduction du gnrique Java :


m

Il nexiste pas de gnrique dans les machines virtuelles, uniquement des classes et des mthodes ordinaires. Tous les types de paramtres sont remplacs par leurs limites. Les mthodes bridge sont synthtises pour prserver le polymorphisme. Les transtypages sont insrs en fonction des besoins pour prserver la scurit du type.

m m m

Appeler un code existant


De grandes quantits de code Java ont t crites avant Java SE 5.0. Si les classes gnriques ne pouvaient pas interagir avec ce code, elles ne seraient probablement pas frquemment utilises. Heureusement, lutilisation des classes gnriques avec leurs quivalents bruts se fait en toute simplicit dans les API existantes. Etudions un exemple concret. Pour dnir les tiquettes dun JSlider, vous utilisez la mthode
void setLabelTable(Dictionary table)

Au Chapitre 9, nous avons utilis le code suivant pour remplir la table des tiquettes :
Dictionary<Integer, Component> labelTable = new Hashtable<Integer, Component> (); labelTable.put(0, new JLabel(new ImageIcon("nine.gif"))); labelTable.put(20, new JLabel(new ImageIcon("ten.gif"))); . . . slider.setLabelTable(labelTable); // ATTENTION

Dans Java SE 5.0, les classes Dictionary et Hashtable ont t transformes en classes gnriques. Nous pouvons donc former un Dictionary<Integer, Component> au lieu dutiliser un Dictionary brut. Toutefois, lorsque vous passez lobjet Dictionary<Integer, Component> setLabelTable, le compilateur met un avertissement :
Dictionary<Integer, Component> labelTable = . . .; slider.setLabelTable(labelTable); // ATTENTION

Aprs tout, le compilateur na aucune assurance des effets de setLabelTable sur lobjet Dictionary. Cette mthode pourrait remplacer toutes les cls par des chanes, ce qui annulerait la garantie que les cls ont le type Integer. Les oprations venir pourraient alors lancer de mauvaises exceptions de transtypage. Vous ne pouvez pas faire grand-chose avec cet avertissement, part ltudier et demander ce que JSlider risque de faire avec cet objet Dictionary. Ici, il est assez vident que JSlider ne fait que lire les informations, nous pouvons donc ignorer lavertissement. Envisageons maintenant le cas contraire : vous obtenez un objet dun type brut depuis une classe existante. Vous pouvez lassigner une variable de type avec paramtres, mais vous obtiendrez bien sr un avertissement. Par exemple :
Dictionary<Integer, Components> labelTable = slider.getLabelTable(); // ATTENTION

Trs bien, revoyons lavertissement et assurons-nous que la table des tiquettes contient rellement les objets Integer et Component. La garantie nest bien entendu jamais absolue. Un codeur malveillant pourrait avoir install un Dictionary diffrent dans le slider. Mais, une fois de plus, la

620

Au cur de Java 2 - Notions fondamentales

situation nest pas pire quavant Java SE 5.0. Dans le pire des cas, votre programme renverra une exception. Lorsque vous aurez tudi les avertissements, vous pourrez utiliser une annotation pour les faire disparatre. Lannotation doit tre place avant la mthode dont le code gnre lavertissement, comme ceci :
@SuppressWarnings("unchecked") public void configureSlider() { . . . }

Malheureusement, cette annotation dsactive la vrication du code dans la mthode. Mieux vaut isoler un code pouvant se rvler peu sr en mthodes spares, an quelles puissent tre rvises plus facilement.
INFO
La classe Hashtable est une sous-classe concrte de la classe abstraite Dictionary. Dictionary et Hashtable ont t dclares "obsoltes" depuis quelles ont t remplaces par linterface Map et la classe HashMap de Java SE 1.2. Or, apparemment, elles existent toujours et continuent produire des effets. Aprs tout, la classe JSlider na t ajoute qu Java SE 1.3. Les programmeurs ne connaissaient-ils pas la classe Map lpoque ? Vous esprez donc quils vont adopter le gnrique dans un avenir proche ? Cest ainsi quil en va avec le code existant.

Restrictions et limites
Dans les sections venir, nous nous attarderons sur plusieurs restrictions connatre lorsque vous travaillez avec le gnrique Java. La plupart de ces restrictions sont une consquence de leffacement des types.

Les paramtres de type ne peuvent pas tre instancis avec les types primitifs
Il est impossible de remplacer un type primitif par un paramtre de type. Il nexiste donc pas de Pair<double>, uniquement Pair<Double>. Ceci est d, bien entendu, leffacement du type. Aprs leffacement, la classe Pair possde des champs du type Object, et vous ne pouvez pas les utiliser pour stocker des valeurs double. Ceci est fort ennuyeux, mais cohrent avec le statut distinct des types primitifs dans le langage Java. Ce nest toutefois pas une limitation terrible, il nexiste que huit types primitifs, et vous pouvez toujours les grer avec des classes et des mthodes spares lorsque les types denveloppe ne constituent pas un remplacement acceptable.

Les informations sur le type dexcution ne fonctionnent quavec les types bruts
Les objets de la machine virtuelle ont toujours un type non gnrique spcique. Ainsi, toutes les demandes de renseignements sur le type ne produisent que du type brut. Par exemple,
if (a instanceof Pair<String>) // identique un instanceof Pair

ne teste rellement que si a est un Pair de quelque type que ce soit. Ceci vaut galement pour le test

Chapitre 12

Programmation gnrique

621

if (a instanceof Pair<T>) // T est ignor

ou le transtypage
Pair<String> p = (Pair<String>) a; // ATTENTION--ne peut tester // que si a est un Pair

Pour vous le rappeler, vous obtiendrez un avertissement de compilateur ds que vous utilisez instanceof ou des expressions de transtypage qui impliquent des types gnriques. Dans le mme esprit, la mthode getClass renvoie toujours le type brut. Par exemple :
Pair<String> stringPair = . . .; Pair<Employee> employeePair = . . .; if (stringPair.getClass() == employeePair.getClass()) // ils sont gaux

La comparaison se vrie car les deux appels getClass renvoient Pair.class.

Vous ne pouvez pas lancer ou intercepter des instances dune classe gnrique
Vous ne pouvez ni dclencher ni intercepter des objets dune classe gnrique. En fait, une classe gnrique na mme pas le droit dtendre Throwable. Par exemple, la dnition suivante ne pourra pas tre compile :
public class Problem<T> extends Exception { /* . . . */ } // ERREUR--impossible dtendre Throwable

Vous ne pouvez pas non plus utiliser une variable de type dans une clause catch. Par exemple, la mthode suivante ne pourra pas tre compile :
public static <T extends Throwable> void doWork(Class<T> t) { try { fonctionne } catch (T e) // ERREUR--impossible dintercepter la variable de type { Logger.global.info(...) } }

Vous pouvez toutefois utiliser des variables de type dans les spcications dexceptions. La mthode suivante est autorise :
public static <T extends Throwable> void doWork(T t) throws T // OK { try { fonctionne } catch (Throwable realCause) { t.initCause(realCause); throw t; } }

622

Au cur de Java 2 - Notions fondamentales

Les tableaux de types avec paramtres ne sont pas autoriss


Il est impossible de dclarer des tableaux de types avec paramtres comme
Pair<String>[] table = new Pair<String>[10]; // ERREUR

En effet, aprs leffacement, le type de table est Pair[]. Vous pouvez le transformer en Object[] :
Object[] objarray = table;

Un tableau se souvient de son type de composant et dclenche une exception ArrayStoreException si vous essayez de stocker un lment dun type erron :
objarray[0] = "Hello"; // ERREUR--le type de composant est Pair

Mais leffacement rend ce mcanisme inefcace pour les types gnriques. Lattribution
objarray[0] = new Pair<Employee>();

passerait la vrication de stockage du tableau mais entrane toujours une erreur de type. Pour cette raison, les tableaux de types avec paramtres sont interdits.
ASTUCE
Si vous devez collecter des objets de type avec paramtres, utilisez simplement un ArrayList:

ArrayList<Pair<String>> fonctionne de manire sre et efcace.

Vous ne pouvez pas instancier des variables de type


Vous ne pouvez pas utiliser de variables de type dans des expressions comme new T(...), new T[...] ou T.class. Par exemple, le constructeur Pair<T> suivant est interdit :
public Pair() { first = new T(); second = new T(); } // ERREUR

Leffacement du type transformerait T en Object, et vous ne voulez certainement pas appeler un nouvel Object(). Pour contourner cela, vous pouvez construire des objets gnriques par la rexion, en appelant la mthode Class.newInstance. Malheureusement, les dtails sont un peu complexes. Vous ne pouvez pas appeler :
first = T.class.newInstance(); // ERREUR

Lexpression T.class nest pas autorise. Vous devez plutt concevoir lAPI pour recevoir un objet Class, comme ceci :
public static <T> Pair<T> makePair(Class<T> cl) { try { return new Pair<T>(cl.newInstance(), cl.newInstance()) } catch (Exception ex) { return null; } }

Cette mthode pourrait tre appele comme suit :


Pair<String> p = Pair.makePair(String.class);

Chapitre 12

Programmation gnrique

623

Remarquez que la classe Class est elle-mme gnrique. Par exemple, String.class est une instance (en fait la seule instance) de Class<String>. La mthode makePair peut donc transfrer le type de la paire quelle cre. Vous ne pouvez pas construire un tableau gnrique :
public static <T extends Comparable> T[] minMax(T[] a) { T[] mm = new T[2]; . . . } // ERREUR

Leffacement du type amnerait cette mthode construire un tableau Object[2] chaque fois. Si le tableau ne sert que de champ dinstance priv dune classe, vous pouvez le dclarer sous forme dObject[] et utiliser les transtypages lorsque vous rcuprez les lments. Par exemple, la classe ArrayList pourrait tre implmente ainsi :
public class ArrayList<E> { private Object[] elements; @SuppressWarnings("unchecked") public E get(int n) { return (E) elements[n]; } public void set(int n, E e) { elements[n] = e; } // aucun transtypage ncessaire . . . }

La vritable implmentation nest pas aussi propre :


public class ArrayList<E> { private E[] elements; public ArrayList() { elements = (E[]) new Object[10]; } . . . }

Ici, le transtypage E[] est un mensonge mais leffacement de type le rend indtectable. Cette technique ne fonctionne pas pour notre mthode minmax puisque nous renvoyons un tableau T[] et quune erreur dexcution apparat si nous mentons sur son type. Supposons que nous implmentions :
public static <T extends Comparable> T[] minmax(T[] a) { Object[] mm = new Object[2]; . . .; return (T[]) mm; // se compile avec un avertissement }

Lappel
String[] ss = minmax("Tom", "Dick", "Harry");

se compile sans avertissement. Une exception ClassCastException survient lorsque la rfrence Object[] est affecte la variable String[]. Dans cette situation, vous pouvez utiliser la rexion et appeler Array.newInstance :
public static <T extends Comparable> T[] minmax(T[] a) { T[] mm = (T[]) Array.newInstance(a.getClass().getComponentType(), 2); . . . }

624

Au cur de Java 2 - Notions fondamentales

La mthode toArray de la classe ArrayList nest pas aussi chanceuse. Elle doit produire un tableau T[] mais ne possde pas le type de composant. Il existe donc deux variantes :
Object[] toArray() T[] toArray(T[] result)

La seconde mthode reoit un paramtre de tableau. Si le tableau est assez grand, il est utilis, sinon on cre un tableau de taille sufsant, laide du type de composant de result.

Les variables de type ne sont pas valables dans des contextes statiques des classes gnriques
Vous ne pouvez pas rfrencer les variables de type dans des champs ou mthodes statiques. Par exemple, cette ide brillante ne fonctionnera pas :
public class Singleton<T> { public static T getSingleInstance() // ERREUR { if (singleInstance == null) construct new instance of T return singleInstance; } private static T singleInstance; // ERREUR }

Si ctait possible, un programme pourrait dclarer un Singleton<Random> pour partager un gnrateur de nombres alatoires et un Singleton<JFileChooser> pour partager une bote de dialogue de slection de chiers. Mais cela ne fonctionnera pas. Aprs leffacement du type, il ne reste quune classe Singleton et un seul champ singleInstance. Pour cette raison, les champs et les mthodes statiques ayant des variables de type sont purement interdits.

Attention aux conits aprs un effacement


Les conditions entranant des conits lors de leffacement des types gnriques sont interdites. En voici un exemple. Supposons que nous ajoutions une mthode equals la classe Pair, comme ceci :
public class Pair<T> { public boolean equals(T value) { return first.equals(value) && second.equals(value); } . . . }

Envisagez un Pair<String>. De par son concept, il possde deux mthodes equals :


boolean equals(String) // dfini dans Pair<T> boolean equals(Object) // hrit de Object

Mais lintuition va nous garer. Leffacement de la mthode


boolean equals(T)

est
boolean equals(Object)

ce qui entre en conit avec la mthode Object.equals.

Chapitre 12

Programmation gnrique

625

La solution consiste, bien entendu, renommer la mthode lorigine du conit. La spcication du gnrique mentionne une autre rgle : "Pour prendre en charge la traduction par effacement, une restriction est impose : une classe ou une variable de type ne peut pas, dans le mme temps, tre un sous-type de deux types dinterface qui constituent des paramtrages diffrents de la mme interface." Par exemple, le code suivant est interdit :
class Calendar implements Comparable<Calendar> { . . . } class GregorianCalendar extends Calendar implements Comparable<GregorianCalendar> { . . . } // ERREUR

GregorianCalendar implmenterait alors Comparable<Calendar> et Comparable<GregorianCalendar>, des paramtrages diffrents de la mme interface. Le rapport de cette restriction avec leffacement de type nest pas clair. Aprs tout, la version non gnrique
class Calendar implements Comparable { . . . } class GregorianCalendar extends Calendar implements Comparable { . . . }

est autorise. La raison en est bien plus subtile. Il y aurait un conit avec les mthodes bridge synthtises. Une classe qui implmente Comparable<X> rcupre une mthode bridge
public int compareTo(Object other) { return compareTo((X) other); }

Vous ne pouvez pas avoir deux mthodes de ce type pour diffrents types X.

Rgles dhritage pour les types gnriques


Lorsque vous travaillez avec les classes gnriques, vous devez apprendre quelques rgles sur lhritage et les sous-types. Commenons par une situation que de nombreux programmeurs ne jugent pas intuitive. Envisagez une classe et une sous-classe comme Employee et Manager. Pair<Manager> est-il une sous-classe de Pair<Employee>? Bizarrement, la rponse est "non". Par exemple, le code suivant ne sera pas compil :
Manager[] topHonchos = . . .; Pair<Employee> result = ArrayAlg.minmax(topHonchos); // ERREUR

La mthode minmax renvoie Pair<Manager>, et non Pair<Employee>, et vous ne pouvez pas affecter lune lautre. En gnral, il nexiste pas de relation entre Pair<S> et Pair<T>, quels que soient les lments auxquels S et T sont relis (voir Figure 12.1). Cette restriction peut paratre cruelle, mais elle est ncessaire la scurit des types. Supposons que nous soyons autoriss transformer un Pair<Manager> en Pair<Employee>. Etudiez ce code :
Pair<Manager> managerBuddies = new Pair<Manager>(ceo, cfo); Pair<Employee> employeeBuddies = managerBuddies; // interdit, mais supposons que ce ne le soit pas employeeBuddies.setFirst(lowlyEmployee);

A lvidence, la dernire dclaration est autorise. Mais employeeBuddies et managerBuddies font rfrence au mme objet. Nous avons donc russi apparier le directeur nancier avec un simple employ, ce qui ne devrait pas tre possible pour un Pair<Manager>.

626

Au cur de Java 2 - Notions fondamentales

Figure 12.1
Aucune relation dhritage entre les classes de paires.
Employ Paire <Employee>

pas de relation !

Directeur

Paire <Manager>

INFO
Vous venez de voir une diffrence importante entre les types gnriques et les tableaux Java. Vous pouvez assigner un tableau Manager[] une variable de type Employee[]:
Manager[] managerBuddies = { ceo, cfo }; Employee[] employeeBuddies = managerBuddies; // OK

Toutefois, les tableaux disposent dune protection spciale. Si vous tentez de stocker un employ de bas niveau dans employeeBuddies[0], la machine virtuelle lance une exception ArrayStoreException.

Vous pouvez toujours transformer un type avec paramtres en type brut. Par exemple, Pair <Employee> est un sous-type du type brut Pair. Cette conversion est ncessaire pour linterfaage avec le code existant. Est-il possible de procder une conversion en type brut, puis de crer une erreur de type ? Malheureusement, oui. Etudiez cet exemple :
Pair<Manager> managerBuddies = new Pair<Manager>(ceo, cfo); Pair rawBuddies = managerBuddies; // OK rawBuddies.setFirst(new File(". . .")); // avertissement de temps de // compilation uniquement

Ceci est assez effrayant. Sachez toutefois que ce nest pas pire quavec les anciennes versions de Java. La scurit de la machine virtuelle nest pas remise en cause. Lorsque lobjet tranger est rcupr avec getFirst et attribu une variable Manager, une exception ClassCastException est dclenche, comme auparavant. Vous perdez juste le surcrot de scurit apport gnralement par la programmation gnrique. Enn, les classes gnriques peuvent tendre ou implmenter dautres classes gnriques. A cet gard, elles ne sont pas diffrentes des classes ordinaires. Par exemple, la classe ArrayList<T> implmente linterface List<T>. Cela signie quun ArrayList<Manager> peut tre converti en List<Manager>. Toutefois, comme vous venez de le voir, un ArrayList<Manager> nest pas un ArrayList<Employee> ni un List<Employee>. La Figure 12.2 prcise ces relations.

Chapitre 12

Programmation gnrique

627

Figure 12.2
Les relations de soustype parmi les types de liste gnriques.
<<

interface List (raw)

>>

<<

interface >> List <Directeur>

<<

interface >> List <Employ>

ArrayList (raw)

ArrayList <Directeur>

ArrayList <Employ>

pas de relation !

Types joker
Tous ceux qui ont effectu quelques recherches avec les systmes de type savent depuis quelque temps dj quun systme rigide nest pas trs agrable utiliser. Les concepteurs Java ont invent une "sortie de secours" ingnieuse (tout en tant sre) : le type joker. Par exemple, le type joker
Pair<? extends Employee>

remplace toute paire gnrique dont le paramtre de type est une sous-classe de Employee, comme Pair<Manager>, mais pas Pair<String>. Imaginons que vous vouliez crire une mthode qui afche des paires demploys, comme ceci :
public static void printBuddies(Pair<Employee> p) { Employee first = p.getFirst(); Employee second = p.getSecond(); System.out.println(first.getName() + " and " + second.getName() + " are buddies."; }

Comme vous lavez vu dans la section prcdente, vous ne pouvez pas passer un Pair<Manager> cette mthode. Voil qui est plutt limitatif. La solution est simple, utilisez un type joker :
public static void printBuddies(Pair<? extends Employee> p)

628

Au cur de Java 2 - Notions fondamentales

Le type Pair<Manager> est un sous-type de Pair<? extends Employee> (voir Figure 12.3).
Figure 12.3
Relations de sous-type avec les jokers.
Paire (raw)

Paire <? extends Employee>

Paire <Manager>

Paire <Employee>

Pouvons-nous utiliser les jokers pour corrompre un Pair<Manager> via une rfrence Pair <? extends Employee> ?
Pair<Manager> managerBuddies = new Pair<Manager>(ceo, cfo); Pair<? extends Employee> wildcardBuddies = managerBuddies; // OK wildcardBuddies.setFirst(lowlyEmployee); // Erreur de dlai de compilation

Aucune corruption nest possible. Lappel setFirst est une erreur de type. Pour en connatre les raisons, tudions plus avant le type Pair<? extends Employee>. Ses mthodes ressemblent ceci :
? extends Employee getFirst() void setFirst(? extends Employee)

Avec cela, impossible dappeler la mthode setFirst. Tout ce que sait le compilateur, cest quil a besoin dun sous-type de Employee, mais il ne sait pas de quel type. Il refuse de passer les types spciques. Aprs tout, ? pourrait ne pas convenir. Nous ne rencontrons pas ce problme avec getFirst : vous pouvez tout fait attribuer la valeur de retour de getFirst une rfrence Employee. Cest lintrt principal des jokers limits. Nous disposons maintenant dune mthode pour faire la distinction entre les mthodes daccs sres et les mthodes de modication peu sres.

Limites de supertypes pour les jokers


Les limites de jokers sont identiques aux limites de variables de type, mais elles disposent dune fonctionnalit supplmentaire : vous pouvez spcier une limite de supertype, comme ceci :
? super Manager

Ce joker est limit aux supertypes de Manager (un coup de chance que le mot cl super dcrive la relation aussi prcisment).

Chapitre 12

Programmation gnrique

629

Quelles sont les raisons de cette opration ? Un joker ayant une limite de supertype a le comportement oppos celui des jokers dcrits. Vous pouvez fournir des paramtres aux mthodes mais non utiliser les valeurs de retour. Par exemple, Pair<? super Manager> possde les mthodes
void setFirst(? super Manager) ? super Manager getFirst()

Le compilateur ne connat pas le type exact de la mthode setFirst, mais peut lappeler avec tout objet de type Manager (mais pas un sous-type comme Executive), Employee ou Object. Et, si vous appelez getFirst, vous nobtenez aucune garantie sur le type de lobjet renvoy. Vous ne pouvez lattribuer qu un Object. Voici un exemple assez commun. Nous avons un tableau de directeurs et voulons placer le directeur ayant la prime la plus petite et celui ayant la plus grosse prime dans un objet Pair. Un Pair <Employee> devrait faire laffaire ou, peut-tre, un Pair<Object> (voir Figure 12.4). La mthode suivante acceptera tout Pair adapt :
public static void minmaxBonus(Manager[] a, Pair<? super Manager> result) { if (a == null || a.length == 0) return; Manager min = a[0]; Manager max = a[0]; for (int i = 1; i < a.length; i++) { if (min.getBonus() > a[i].getBonus()) min = a[i]; if (max.getBonus() < a[i].getBonus()) max = a[i]; } result.setFirst(min); result.setSecond(max); }

Figure 12.4
Un joker avec une limite de supertype.
Paire (raw)

Paire<?>

Paire <? super Manager>

Paire <Employee>

Paire <Object>

630

Au cur de Java 2 - Notions fondamentales

Logiquement, les jokers ayant des limites de supertype vous permettent dcrire vers un objet gnrique et ceux ayant des limites de sous-type, de lire partir dobjets gnriques. Voici une autre utilisation des limites de supertype. Linterface Comparable est, en elle-mme, un type gnrique. Elle est dclare comme suit :
public interface Comparable<T> { public int compareTo(T other); }

Ici, la variable de type prcise le type du paramtre other. Par exemple, la classe String implmente Comparable<String>, et sa mthode compareTo est dclare de la manire suivante :
public int compareTo(String other)

Trs bien, le paramtre explicite possde le type correct. Avant Java SE 5.0, other tait un Object, et il fallait insrer un transtypage dans limplmentation de la mthode. Comparable tant un type gnrique, nous aurions peut-tre mieux fait dutiliser la mthode min de la classe ArrayAlg. Nous aurions pu la dclarer ainsi :
public static <T extends Comparable< T>> T min(T[] a)

Ceci vaut peut-tre mieux que de simplement utiliser T extends Comparable, et cela conviendrait bien de nombreuses classes. Par exemple, si vous calculez le minimum dun tableau String, T est le type String et String est un sous-type de Comparable<String>. Mais nous rencontrons un problme au moment de traiter un tableau dobjets GregorianCalendar. GregorianCalendar est en effet une sous-classe de Calendar et Calendar implmente Comparable<Calendar>. Ainsi, GregorianCalendar implmente Comparable<Calendar> mais pas Comparable<GregorianCalendar>. Dans une situation comme celle-ci, les supertypes viennent la rescousse :
public static <T extends Comparable<? super T>> T min(T[] a) . . .

Maintenant, la mthode compareTo a la forme


int compareTo(? super T)

Elle est peut-tre dclare pour accepter un objet de type T ou, par exemple lorsque T est GregorianCalendar, un supertype de T. Dans tous les cas, le passage dun objet de type T la mthode compareTo se fera en toute scurit. Pour les non-initis, une dclaration du type <T extends Comparable<? super T>> peut paratre intimidante. Et cest dommage car son intention est daider les programmeurs dapplications supprimer les restrictions inutiles sur les paramtres dappel. Ceux qui ne sont pas intresss par le gnrique apprendront srement rapidement passer sur ces dclarations et prendront pour acquis que les programmeurs de bibliothques ont fait le bon choix. Quant aux programmeurs de bibliothques, ils devront shabituer aux jokers, faute de quoi les utilisateurs les maudiront et lanceront des transtypages alatoires sur le code jusqu ce quil se compile.

Jokers sans limites


Il est aussi possible dutiliser des jokers sans aucune limite, comme Pair<?>. Au premier abord, cela ressemble au type brut Pair, mais ils sont en fait trs diffrents.

Chapitre 12

Programmation gnrique

631

Le type Pair<?> possde des mthodes comme


? getFirst() void setFirst(?)

La valeur de retour de getFirst ne peut tre attribue qu un Object. La mthode setFirst ne peut jamais tre appele, mme pas avec un Object. Cest l la diffrence essentielle entre Pair<?> et Pair : vous pouvez appeler la mthode setObject de la classe brute Pair avec nimporte quel Object. Mais pourquoi vouloir un type aussi timide ? Peut-tre parce quil convient pour des oprations trs simples. La mthode suivante, par exemple, teste si une paire contient un objet donn. Elle na jamais besoin du type rel :
public static boolean hasNulls(Pair<?> p) { return p.getFirst() == null || p.getSecond() == null; }

Vous auriez pu viter le type joker en transformant contains en une mthode gnrique :
public static <T> boolean hasNulls(Pair<T> p)

Toutefois, la version contenant le type joker semble plus simple lire.

Capture de caractres joker


Ecrivons une mthode qui intervertit les lments dune paire :
public static void swap(Pair<?> p)

Un joker nest pas une variable de type, nous ne pouvons donc pas crire un code qui utilise ? comme type. Autrement dit, le code suivant serait interdit :
? t = p.getFirst(); // ERREUR p.setFirst(p.getSecond()); p.setSecond(t);

Ceci pose problme car nous devons conserver temporairement le premier lment lorsque nous ralisons lchange. Heureusement, il existe une solution intressante ce problme. Nous pouvons crire une mthode dassistance, swapHelper, comme suit :
public static <T> void swapHelper(Pair<T> p) { T t = p.getFirst(); p.setFirst(p.getSecond()); p.setSecond(t); }

Remarquez que swapHelper est une mthode gnrique ; au contraire de swap, elle a un paramtre xe de type Pair<?>. Nous pouvons maintenant appeler swapHelper partir de swap :
public static void swap(Pair<?> p) { swapHelper(p); }

Dans ce cas, le paramtre T de la mthode swapHelper capture le joker. On ne connat pas le type induit par le joker, mais cest un type dni et la dnition de <T>swapHelper est tout fait logique lorsque T induit ce type.

632

Au cur de Java 2 - Notions fondamentales

Bien entendu, dans ce cas, nous navons pas t amens utiliser un joker. Nous aurions pu directement implmenter <T> void swap(Pair<T> p) comme mthode gnrique, sans les jokers. Etudiez toutefois cet exemple, dans lequel un type joker apparat naturellement au milieu dun calcul :
public static void maxminBonus(Manager[] a, Pair<? super Manager> result) { minmaxBonus(a, result); PairAlg.swapHelper(result); // OK--swapHelper capture le type joker }

Ici, le mcanisme de capture du joker est invitable. Cette capture nest autorise que dans des cas trs restreints. Le compilateur doit pouvoir garantir que le joker reprsente un seul type dni. Par exemple, le T dans ArrayList<Pair<T>> ne peut jamais capturer le joker de ArrayList<Pair<?>>. La liste de tableau pourrait en effet contenir deux Pair<?>, chacun ayant un type diffrent pour ?. Le programme de test du Listing 12.3 rassemble les diverses mthodes que nous avons vues dans les sections prcdentes, elles sont ici replaces dans leur contexte.
Listing 12.3 : PairTest3.java
import java.util.*; /** * @version 1.00 2004-05-10 * @author Cay Horstmann */ public class PairTest3 { public static void main(String[] args) { Manager ceo = new Manager("Gus Greedy", 800000, 2003, 12, 15); Manager cfo = new Manager("Sid Sneaky", 600000, 2003, 12, 15); Pair<Manager> buddies = new Pair<Manager>(ceo, cfo); printBuddies(buddies); ceo.setBonus(1000000); cfo.setBonus(500000); Manager[] managers = { ceo, cfo }; Pair<Employee> result = new Pair<Employee>(); minmaxBonus(managers, result); System.out.println("first: " + result.getFirst().getName() + ", second: " + result.getSecond().getName()); maxminBonus(managers, result); System.out.println("first: " + result.getFirst().getName() + ", second: " + result.getSecond().getName()); } public static void printBuddies(Pair<? extends Employee> p) { Employee first = p.getFirst(); Employee second = p.getSecond(); System.out.println(first.getName() + " and " + second. getName() + " are buddies."); }

Chapitre 12

Programmation gnrique

633

public static void minmaxBonus(Manager[] a, Pair<? super Manager> result) { if (a == null || a.length == 0) return; Manager min = a[0]; Manager max = a[0]; for (int i = 1; i < a.length; i++) { if (min.getBonus() > a[i].getBonus()) min = a[i]; if (max.getBonus() < a[i].getBonus()) max = a[i]; } result.setFirst(min); result.setSecond(max); } public static void maxminBonus(Manager[] a, Pair<? super Manager> result) { minmaxBonus(a, result); PairAlg.swapHelper(result); // OK--swapHelper capture le type joker } } class PairAlg { public static boolean hasNulls(Pair<?> p) { return p.getFirst() == null || p.getSecond() == null; } public static void swap(Pair<?> p) { swapHelper(p); } public static <T> void swapHelper(Pair<T> p) { T t = p.getFirst(); p.setFirst(p.getSecond()); p.setSecond(t); } } class Employee { public Employee(String n, double s, int year, int month, int day) { name = n; salary = s; GregorianCalendar calendar = new GregorianCalendar( year, month - 1, day); hireDay = calendar.getTime(); } public String getName() { return name; } public double getSalary() { return salary;

634

Au cur de Java 2 - Notions fondamentales

} public Date getHireDay() { return hireDay; } public void raiseSalary(double byPercent) { double raise = salary * byPercent / 100; salary += raise; } private String name; private double salary; private Date hireDay; } class Manager extends Employee { /** * @param n Le nom de lemploy * @param s Le salaire * @param year Lanne dembauche * @param month Le mois dembauche * @param day Le jour dembauche */ public Manager(String n, double s, int year, int month, int day) { super(n, s, year, month, day); bonus = 0; } public double getSalary() { double baseSalary = super.getSalary(); return baseSalary + bonus; } public void setBonus(double b) { bonus = b; } public double getBonus() { return bonus; } private double bonus; }

Rexion et gnrique
La classe Class est maintenant gnrique. A titre dexemple, String.class est en fait un objet (lunique objet) de la classe Class<String>.

Chapitre 12

Programmation gnrique

635

Le paramtre de type est utile car il permet aux mthodes de Class<T> dtre plus spciques sur leur type de retour. Les mthodes suivantes de Class<T> protent du paramtre de type :
T newInstance() T cast(Object obj) T[] getEnumConstants() Class<? super T> getSuperclass() Constructor<T> getConstructor(Class... parameterTypes) Constructor<T> getDeclaredConstructor(Class... parameterTypes)

La mthode newInstance renvoie une instance de la classe, obtenue partir du constructeur par dfaut. Son type de retour peut maintenant tre dclar comme tant T, le mme que celui de la classe dcrite par Class<T>. Ceci pargne un transtypage. La mthode cast renvoie lobjet donn, maintenant dclar comme type T si son type est en fait un sous-type de T. Sinon il dclenche une exception BadCastException. La mthode getEnumConstants renvoie null si cette classe nest pas une classe enum ou un tableau des valeurs dnumration, connues comme tant du type T. Enn, les mthodes getConstructor et getDeclaredConstructor renvoient un objet Constructor<T>. La classe Constructor a aussi t rendue gnrique de sorte que sa mthode newInstance possde le type de retour correct.
java.lang.Class<T> 1.0

T newInstance() 5.0

Renvoie une nouvelle instance construite avec le constructeur par dfaut.


T cast(Object obj) 5.0

Renvoie obj sil est null ou peut tre converti en type T, ou dclenche une exception BadCastException dans le cas contraire.

T[] getEnumConstants() 5.0

Renvoie un tableau de toutes les valeurs si T est un type numr, null autrement.
Class<? super T> getSuperclass() 5.0

Renvoie la superclasse de cette classe ou null si T nest pas une classe ou la classe Object.
Constructor<T> getConstructor(Class... parameterTypes) 5.0 Constructor<T> getDeclaredConstructor(Class... parameterTypes) 5.0

Rcuprent le constructeur public ou le constructeur ayant les types de paramtres donns.


java.lang.reflect.Constructor<T> 1.1

T newInstance(Object... parameters) 5.0

Renvoie une nouvelle instance construite avec les paramtres donns.

Utilisation des paramtres Class<T> pour la concordance de type


Il est parfois utile de faire concorder la variable de type dun paramtre Class<T> dans une mthode gnrique. Voici lexemple canonique :
public static <T> Pair<T> makePair(Class<T> c) throws InstantiationException, IllegalAccessException

636

Au cur de Java 2 - Notions fondamentales

{ return new Pair<T>(c.newInstance(), c.newInstance()); }

Si vous appelez
makePair(Employee.class)

Employee.class est un objet du type Class<Employee>. Le paramtre de type T de la mthode makePair concorde avec Employee et le compilateur peut en dduire que la mthode renvoie un Pair<Employee>.

Informations de type gnrique dans la machine virtuelle


Lune des fonctionnalits remarquables du gnrique Java est leffacement des types gnriques dans la machine virtuelle. Bizarrement, les classes effaces conservent une vague ide de leur origine gnrique. Par exemple, la classe brute Pair sait quelle est originaire de la classe gnrique Pair<T>, mme si un objet de type Pair ne peut pas dire sil a t construit sous forme de Pair<String> ou de Pair<Employee>. De mme, envisagez une mthode
public static Comparable min(Comparable[] a)

qui est leffacement dune mthode gnrique


public static <T extends Comparable<? super T>> T min(T[] a)

Vous pouvez utiliser les amliorations de lAPI rexion de Java SE 5.0 pour dterminer si :
m m m m m

La mthode gnrique dispose dun paramtre de type appel T. Le paramtre de type possde une limite de sous-type qui est elle-mme un type gnrique. Le type de limite possde un paramtre joker. Le paramtre joker possde une limite de supertype. La mthode gnrique possde un paramtre de tableau gnrique.

En dautres termes, vous devez tout reconstruire sur les classes et les mthodes gnriques que leurs implmenteurs ont dclares. Toutefois, vous ne saurez pas comment les paramtres de type ont t rsolus pour des appels dobjets ou de mthodes spciques.
INFO
Les informations de type contenues dans les chiers de classe pour permettre la rexion du gnrique sont incompatibles avec des machines virtuelles plus anciennes.

Pour exprimer des dclarations de type gnrique, Java SE 5.0 a introduit une nouvelle interface Type dans le package java.lang.reflect. Linterface possde les sous-types suivants :
m m

la classe Class, dcrivant des types concrets ; linterface TypeVariable, dcrivant des variables de type (comme T extends Comparable <? super T>) ; linterface WildcardType, dcrivant des jokers (comme ? super T) ;

Chapitre 12

Programmation gnrique

637

linterface ParameterizedType, dcrivant la classe gnrique ou les types dinterface (comme Comparable<? super T>) ; linterface GenericArrayType, dcrivant des tableaux gnriques (comme T[]).

La Figure 12.5 montre la hirarchie dhritage. Sachez que les quatre derniers sous-types sont des interfaces (la machine virtuelle instancie des classes adaptes qui implmentent ces interfaces).
Figure 12.5
La classe Type et ses descendants.
<<interface>> Type

Class<T>

<<interface>> TypeVariable

<<interface>> WildcardType

<<interface>> Parameterized Type

<<interface>> Generic ArrayType

Le Listing 12.4 utilise lAPI de rexion gnrique pour afcher ce quil dcouvre sur une classe donne. Si vous lexcutez avec la classe Pair, vous obtenez ce rapport :
class Pair<T> extends java.lang.Object public T getFirst() public T getSecond() public void setFirst(T) public void setSecond(T)

Si vous lexcutez avec ArrayAlg dans le rpertoire PairTest2, le rapport afche la mthode suivante :
public static <T extends java.lang.Comparable> Pair<T> minmax(T[])

Les notes API gurant la n de cette section dcrivent les mthodes utilises dans le programme dexemple.
Listing 12.4 : GenericReectionTest.java
import java.lang.reflect.*; import java.util.*; /** * @version 1.10 2004-05-15 * @author Cay Horstmann */ public class GenericReflectionTest { public static void main(String[] args) { // lire le nom de classe partir des arguments de // ligne de commande ou de la saisie utilisateur String name; if (args.length > 0) name = args[0];

638

Au cur de Java 2 - Notions fondamentales

else { Scanner in = new Scanner(System.in); System.out.println("Enter class name (e.g. java.util.Collections): "); name = in.next(); } try { // imprimer les infos de gnrique pour la classe et // les mthodes publiques Class cl = Class.forName(name); printClass(cl); for (Method m: cl.getDeclaredMethods()) printMethod(m); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static void printClass(Class cl) { System.out.print(cl); printTypes(cl.getTypeParameters(), "<", ", ", ">", true); Type sc = cl.getGenericSuperclass(); if (sc!= null) { System.out.print(" extends "); printType(sc, false); } printTypes(cl.getGenericInterfaces(), " implements ", ", ", "", false); System.out.println(); } public static void printMethod(Method m) { String name = m.getName(); System.out.print(Modifier.toString(m.getModifiers())); System.out.print(" "); printTypes(m.getTypeParameters(), "<", ", ", "> ", true); printType(m.getGenericReturnType(), false); System.out.print(" "); System.out.print(name); System.out.print("("); printTypes(m.getGenericParameterTypes(), "", ", ", "", false); System.out.println(")"); } public static void printTypes(Type[] types, String pre, String sep, String suf, boolean isDefinition) { if (pre.equals(" extends ") && Arrays.equals(types, new Type[] { Object.class })) return;

Chapitre 12

Programmation gnrique

639

if (types.length > 0) System.out.print(pre); for (int i = 0; i < types.length; i++) { if (i > 0) System.out.print(sep); printType(types[i], isDefinition); } if (types.length > 0) System.out.print(suf); } public static void printType(Type type, boolean isDefinition) { if (type instanceof Class) { Class t = (Class) type; System.out.print(t.getName()); } else if (type instanceof TypeVariable) { TypeVariable t = (TypeVariable) type; System.out.print(t.getName()); if (isDefinition) printTypes(t.getBounds(), " extends ", " & ", "", false); } else if (type instanceof WildcardType) { WildcardType t = (WildcardType) type; System.out.print("?"); printTypes(t.getUpperBounds(), " extends ", " & ", "", false); printTypes(t.getLowerBounds(), " super ", " & ", "", false); } else if (type instanceof ParameterizedType) { ParameterizedType t = (ParameterizedType) type; Type owner = t.getOwnerType(); if (owner!= null) { printType(owner, false); System.out.print("."); } printType(t.getRawType(), false); printTypes(t.getActualTypeArguments(), "<", ", ", ">", false); } else if (type instanceof GenericArrayType) { GenericArrayType t = (GenericArrayType) type; System.out.print(""); printType(t.getGenericComponentType(), isDefinition); System.out.print("[]"); } } } java.lang.Class<T> 1.0 TypeVariable[] getTypeParameters() 5.0

Rcupre les variables de type gnrique si ce type a t dclar comme un type gnrique ou un tableau de longueur 0 dans les autres cas.

640

Au cur de Java 2 - Notions fondamentales

Type getGenericSuperclass() 5.0

Rcupre le type gnrique de la superclasse qui a t dclare pour ce type ou null si ce type est un Object ou nest pas un type de classe.

Type[] getGenericInterfaces() 5.0

Rcupre les types gnriques des interfaces dclares pour ce type, dans lordre de leur dclaration, ou un tableau de longueur 0 si ce type nimplmente pas les interfaces.
java.lang.reflect.Method 1.1 TypeVariable[] getTypeParameters() 5.0

Rcupre les variables de type gnrique si cette mthode a t dclare sous forme de mthode gnrique ou un tableau de longueur 0 dans les autres cas.

Type getGenericReturnType() 5.0

Rcupre le type de retour gnrique avec lequel cette mthode a t dclare.


Type[] getGenericParameterTypes() 5.0

Rcupre les types de paramtres gnriques avec lesquels cette mthode a t dclare. Si la mthode ne possde pas de paramtres, un tableau de longueur 0 est renvoy.
java.lang.reflect.TypeVariable 5.0 String getName()

Rcupre le nom de cette variable de type.

Type[] getBounds()

Rcupre les limites de sous-classe de cette variable de type ou un tableau de longueur 0 si la variable na pas de limites.
java.lang.reflect.WildcardType 5.0 Type[] getLowerBounds()

Rcupre les limites de sous-classe (extends) de cette variable de type ou un tableau de longueur 0 si elle na pas de limites de sous-classe.

Type[] getUpperBounds()

Rcupre les limites de superclasse (super) de cette variable de type ou un tableau de longueur 0 si elle na pas de limites de superclasse.
java.lang.reflect.ParameterizedType 5.0 Type getRawType()

Rcupre le type brut de ce type avec paramtres.



Type[] getActualTypeArguments()

Rcupre les paramtres de type avec lesquels ce type a t dclar.


Type getOwnerType()

Rcupre le type de classe externe sil sagit dun type interne ou null sil sagit dun type de premier niveau.
java.lang.reflect.GenericArrayType 5.0 Type getGenericComponentType()

Rcupre le type de composant gnrique avec lequel ce type de tableau a t dclar.

Chapitre 12

Programmation gnrique

641

Vous savez maintenant comment utiliser les classes gnriques et comment les programmer tout comme vos mthodes en cas de besoin. Vous savez galement dchiffrer les dclarations de type gnrique que vous pourriez rencontrer dans la documentation dAPI et dans les messages derreur. Pour dcouvrir une tude approfondie de tout ce quil faut savoir sur le gnrique Java, consultez la liste des questions dAngelika Langer ladresse http://angelikalanger.com/GenericsFAQ/ JavaGenericsFAQ.html. Vous verrez au prochain chapitre comment le cadre des collections Java utilise le gnrique.

13
Collections
Au sommaire de ce chapitre

Les interfaces de collection Les collections concrtes Le cadre des collections Les algorithmes Les anciennes collections
La structure de donnes que vous choisirez peut engendrer une grande diffrence, la fois au moment de limplmentation des mthodes et en termes de performances. Faut-il rechercher rapidement parmi des milliers (voire des millions) dlments tris ? Devez-vous rapidement insrer et supprimer des lments au milieu dune suite trie ? Devez-vous tablir des associations entre des cls et des valeurs ? Ce chapitre met en vidence la faon dont la bibliothque Java peut vous aider trouver une structure de donnes adapte une programmation srieuse. Dans les coles dinformatique, il existe un cours appel Structures de donnes qui requiert en gnral un trimestre complet, et en consquence un grand nombre de livres sont consacrs ce sujet trs important. Notre but dans ce chapitre nest pas de fournir un cours universitaire. Nous ignorerons la thorie et vous indiquerons uniquement comment utiliser les classes de collection dans la bibliothque standard.

Les interfaces de collection


La premire version de Java ne fournissait quun petit ensemble de classes pour les structures de donnes les plus utiles : Vector, Stack, Hashtable, Bitset, ainsi que linterface Enumeration qui fournit un mcanisme abstrait pour rpertorier des lments dans un conteneur quelconque. Cela partait dune bonne intention, car la comprhension dune bibliothque complte de classes de collection ncessite la fois beaucoup de temps et de connaissances.

644

Au cur de Java 2 - Notions fondamentales

Avec larrive de Java SE 1.2, les concepteurs ont senti quil tait temps de fournir un ensemble complet de structures de donnes. Ils ont notamment d faire face un certain nombre de conits dans llaboration dune telle bibliothque. Ils voulaient en effet que cette bibliothque reste petite et simple comprendre. Ils rejetaient la complexit de la bibliothque standard de modles (ou STL, Standard Template Library) du C++, mais ils voulaient bncier de laspect gnrique des algorithmes mis en place par la STL. Ils voulaient galement que toutes les anciennes classes fassent partie du nouvel ensemble de classes. Comme tous les concepteurs de bibliothques, ils ont donc d faire quelques choix difciles et prendre des dcisions de conception qui leur sont propres. Dans cette partie, nous explorerons le cadre des collections Java, nous vous montrerons comment lexploiter et nous aborderons la logique cache derrire les caractristiques les plus controverses.

Sparer les interfaces dune collection et leur implmentation


Comme pour la plupart des bibliothques de structures de donnes rcentes, la bibliothque de collections Java fait la distinction entre les interfaces et les implmentations. Intressons-nous cette distinction avec une structure de donnes familire, la queue de donnes. Une interface de queue indique que vous pouvez ajouter des lments la n dune queue, supprimer des lments au dbut dune queue et dterminer le nombre dlments contenus dans une queue. Les queues sont utilises si vous devez enregistrer des objets et les rcuprer selon la rgle "premier entr, premier sorti" (voir Figure 13.1).
Figure 13.1
Une queue.
1 2 3 4 5

tte

queue

Une forme minimaliste dinterface pour les queues pourrait ressembler ceci :
interface Queue<E> // Une forme simplifie dinterface de la // bibliothque standard { void add(E element); E remove(); int size(); }

Linterface nindique en aucune manire la faon dont la queue est implmente. Il existe deux implmentations courantes des queues. La premire se sert dun tableau circulaire et la seconde, dune liste chane (voir Figure 13.2).
INFO
Avec Java SE 5.0, les classes de collection sont devenues des classes gnriques avec paramtres de type. Pour en savoir plus sur les classes gnriques, consultez le Chapitre 12.

Chapitre 13

Collections

645

Figure 13.2
Les implmentations dune queue.

3 2 1

tte

queue

Tableau circulaire

Lien 1 donne suivant

Lien donne suivant

Lien 3 donne suivant

Lien donne suivant

tte Liste chane

queue

Chacune de ces implmentations peut passer par une classe implmentant linterface avec la Queue :
class CircularArrayQueue<E> implements Queue<E> // pas une vraie classe // de bibliothque { CircularArrayQueue(int capacity) { . . . } public void add(E element) { . . . } public E remove() { . . . } public int size() { . . . } private E[] elements; private int head; private int tail; } class LinkedListQueue<E> implements Queue<E> // pas une vraie classe // de bibliothque { LinkedListQueue() { . . . } public void add(E element) { . . . } public E remove() { . . . } public int size() { . . . } private Link head; private Link tail; }

INFO
La bibliothque Java ne possde pas de classes nommes CircularArrayQueue et LinkedListQueue. Nous les utilisons comme exemples pour expliquer la diffrence entre les interfaces de collection et les implmentations. Si vous avez besoin dune queue de tableau circulaire, vous pouvez utiliser la classe ArrayDeque introduite dans Java SE 6. Pour une queue de liste chane, utilisez simplement la classe LinkedList: elle implmente linterface Queue.

646

Au cur de Java 2 - Notions fondamentales

Lorsque vous vous servez dune queue dans vos programmes, vous navez pas besoin de connatre limplmentation rellement utilise une fois que la collection est construite. Par consquent, il est logique davoir recours une classe existante uniquement lorsque vous construisez lobjet collection. Le type dinterface permet de faire rfrence la collection.
Queue<Customer> expressLane = new CircularArrayQueue<Customer>(100); expressLane.add(new Customer("Harry"));

Avec cette approche, si vous changez de point de vue, vous pouvez facilement utiliser une implmentation diffrente. Vous navez besoin de modier votre programme qu un seul endroit : le constructeur. Si vous dcidez quaprs tout, une LinkedListQueue constitue un meilleur choix, votre programme devient :
Queue<Customer> expressLane = new LinkedListQueue<Customer>(); expressLane.add(new Customer("Harry"));

Pourquoi choisir une implmentation plutt quune autre ? Une interface ne fournit aucun dtail quant lefcacit de son implmentation. De manire gnrale, un tableau circulaire est plus efcace quune liste chane, et il est donc prfrable cette dernire. Toutefois, comme dhabitude, il y a un prix payer. Les tableaux circulaires font partie dune collection borne, qui a une capacit dtermine. Si vous ne connaissez pas la limite maximale du nombre dobjets stocks par votre programme, vous prfrerez probablement une implmentation en liste chane. En tudiant la documentation API, vous dcouvrirez un autre jeu de classes dont le nom commence par Abstract, comme AbstractQueue. Ces classes sont destines aux implmentations de bibliothque. Pour implmenter votre propre classe de queue, il sera plus facile dtendre AbstractQueue que dimplmenter toutes les mthodes de linterface Queue.

Interfaces de collection et ditration dans la bibliothque Java


Linterface fondamentale des classes de collection de la bibliothque Java est linterface Collection. Cette interface possde deux mthodes essentielles :
public interface Collection<E> { boolean add(E element); Iterator<E> iterator(); ... }

Il existe plusieurs autres mthodes, que nous aborderons plus loin. La mthode add ajoute un lment dans la collection. La mthode add renvoie true si lajout de llment a effectivement modi la collection. Elle renvoie false si la collection na pas t modie. Par exemple, si vous essayez dajouter un objet dans un ensemble et que cet objet en fasse dj partie, la requte add est sans effet parce que les ensembles de donnes ne peuvent pas accepter deux fois la mme donne. La mthode iterator renvoie un objet qui implmente linterface Iterator. Un objet ditration peut servir parcourir les lments dune collection, un par un.

Chapitre 13

Collections

647

Itrateurs
Linterface Iterator possde trois mthodes :
public interface Iterator<E> { E next(); boolean hasNext(); void remove(); }

En appelant plusieurs fois la mthode next, vous pouvez parcourir tous les lments de la collection un par un. Lorsque la n de la collection est atteinte, la mthode next dclenche une exception NoSuchElementException. Par consquent, il convient dappeler la mthode hasNext avant la mthode next. Cette mthode renvoie true si lobjet de litration possde encore au moins un lment. Si vous souhaitez parcourir tous les lments dun conteneur, il suft de demander un objet iterator et dappeler la mthode next tant que la mthode hasNext renvoie true. Par exemple :
Collection<String> c = ...; Iterator<String> iter = c.iterator(); while (iter.hasNext()) { String element = iter.next(); utilisation de element }

Java SE 5.0 a introduit un raccourci lgant pour cette boucle. Elle est bien plus concise avec la boucle "for each".
for (String element: c) { utilisation de element }

Le compilateur se contente de traduire la boucle "for each" en boucle avec un itrateur. La boucle "for each" fonctionne comme tout objet qui implmente linterface Iterable, une interface avec une seule mthode :
public interface Iterable<E> { Iterator<E> iterator(); }

Linterface Collection tend linterface Iterable. Vous pouvez donc utiliser la boucle "for each" avec nimporte quelle collection de la bibliothque standard. Lordre de visite des lments dpend du type de la collection. Si vous parcourez un ArrayList, litrateur dmarre lindice 0 et lincrmente chaque pas. Toutefois, si vous visitez les lments dans un HashSet, vous les rencontrerez dans un ordre alatoire. Vous pouvez tre sr de rencontrer tous les lments dune collection lors de litration, mais vous ne pouvez pas savoir dans quel ordre. Cela ne pose gnralement pas problme car lordre importe peu pour des calculs comme les totaux ou les concordances.

648

Au cur de Java 2 - Notions fondamentales

INFO
Les habitus de Java reconnatront que les mthodes next et hasNext de linterface Iterator fonctionnent de la mme manire que les mthodes nextElement et hasMoreElements dune Enumeration. Les concepteurs de la bibliothque de collections Java auraient pu choisir dtendre linterface Enumeration, mais ils naimaient pas les noms de mthode trop longs et ont choisi par consquent dintroduire une nouvelle interface avec des noms plus courts.

Il existe une diffrence importante au niveau conceptuel entre les itrateurs de la bibliothque de collections Java et les itrateurs des autres bibliothques. Dans les bibliothques de collections classiques (comme la STL du C++), les itrateurs correspondent plutt des indices de tableau. A partir dun tel oprateur, il est possible de retrouver un lment qui est enregistr la position correspondant cet itrateur, de la mme manire que vous accdez un lment de tableau a[i] si vous possdez lindice de tableau i. Indpendamment de la recherche, litrateur peut tre avanc la prochaine position. Ceci revient incrmenter un indice de tableau en appelant i++ sans effectuer de recherche dans le tableau. Mais les itrateurs Java ne fonctionnent pas de cette manire. Les recherches et les changements de position sont rellement coupls et la seule faon de rechercher un lment consiste appeler la mthode next, ce qui avance la position de litrateur. Il vaut donc mieux considrer que les itrateurs Java se trouvent entre les lments. Lorsque next est appele, litrateur saute au-dessus de llment suivant et il renvoie une rfrence llment quil vient de sauter (voir Figure 13.3).
Figure 13.3
La progression dun itrateur.

itrateur

lment renvoy

INFO
Voici une autre analogie pratique. Vous pouvez considrer que Iterator.next est lquivalent de InputStream.read. La lecture dun octet partir dun ux de donnes "consomme" automatiquement cet octet. Le prochain appel read consomme et renvoie loctet suivant du ux de donnes. De la mme manire, des appels rpts next vous permettent de parcourir tous les lments dune collection.

Chapitre 13

Collections

649

Suppression dlments
La mthode remove de linterface Iterator supprime llment renvoy par le dernier appel next. Dans de nombreux cas, cette mthode est pratique si vous avez besoin dexaminer chaque lment avant de savoir si vous devez le supprimer ou non. Mais, si vous souhaitez effacer un lment en fonction de sa position, il faut malgr tout le dpasser. Par exemple, voici comment supprimer le premier lment dune collection de chanes :
Iterator<String> it = c.iterator(); it.next(); // sauter le premier lment it.remove(); // on peut maintenant le supprimer

Plus important encore, il existe une relation entre les appels aux mthodes next et remove. Il nest pas permis dappeler remove sans avoir au pralable appel next. Si vous essayez, une IllegalStateException sera dclenche. Donc, si vous voulez supprimer deux lments successifs, vous navez pas le droit dappeler
it.remove(); it.remove(); // Erreur!

Il faut appeler next pour passer au-dessus de llment supprimer.


it.remove(); it.next(); it.remove(); // Ok

Mthodes utilitaires gnriques


Comme les interfaces Iterator et Collection sont gnrales, il est possible dcrire des mthodes pratiques pouvant travailler sur nimporte quel type de collection. Par exemple, voici une mthode gnrale qui teste si une collection arbitraire contient un lment donn :
public static<E> boolean contains(Collection<E> c, Object obj) { for (E element: c) if (element.equals(obj)) return true; return false; }

Les concepteurs de la bibliothque Java ont dcid que certaines de ces mthodes gnrales taient tellement pratiques quelles devraient faire partie de la bibliothque. De cette faon, les utilisateurs nont pas besoin de rinventer la roue en permanence. La mthode contains en constitue un bon exemple. En fait, linterface Collection dclare quelques mthodes pratiques que toutes les classes implmentes doivent fournir. Parmi elles, nous trouvons :
int size() boolean isEmpty() boolean contains(Object obj) boolean containsAll(Collection<?> c) boolean equals(Object other) boolean addAll(Collection<? extends E> source) boolean remove(Object obj)

650

Au cur de Java 2 - Notions fondamentales

boolean removeAll(Collection<?> c) void clear() boolean retainAll(Collection<?> c) Object[] toArray() <T> T[] toArray(T[] arrayToFill)

La plupart de ces mthodes sont assez simples pour se passer dune explication. Vous en trouverez la documentation complte dans les notes API la n de cette section. Mais, comme il est plutt fastidieux dajouter toutes ces mthodes dans chaque classe implmentant linterface Collection, la bibliothque fournit la classe AbstractCollection, qui dnit les mthodes fondamentales (telles size et iterator) comme des mthodes abstraites et implmente le corps de ces mthodes leur place. Par exemple,
public abstract class AbstractCollection<E> implements Collection<E> { . . . public abstract Iterator<E> iterator(); public boolean contains(Object obj) { for (E element: c) // Appelle iterator() if(element.equals(obj)) return = true; return false; } . . . }

Une classe de collection concrte peut maintenant tendre la classe AbstractCollection. Cest donc la classe de collection concrte de fournir une mthode iterator, mais la mthode contains est gre par la superclasse AbstractCollection. De plus, si la sous-classe propose une meilleure implmentation de la mthode contains, elle est libre de lutiliser. Il sagit l dune bonne architecture pour un ensemble de classes. Les utilisateurs des classes de collection disposent ainsi dun ensemble de mthodes trs complet disponible au travers de linterface gnrale et les programmeurs chargs des structures de donnes nont pas besoin de se proccuper de toutes les mthodes.
java.util.Collection<E> 1.2

Iterator<E> iterator()

Renvoie un itrateur pouvant tre utilis pour parcourir les lments dune collection.
int size()

Renvoie le nombre courant dlments stocks dans une collection.


boolean isEmpty()

Renvoie true si la collection ne contient aucun lment.


boolean contains(Object obj)

Renvoie true si la collection contient un objet correspondant obj.


boolean containsAll(Collection<?> other)

Renvoie true si cette collection contient tous les lments de lautre collection.

Chapitre 13

Collections

651

boolean add(Object element)

Ajoute un lment la collection. Renvoie true si la collection a t modie par cet appel.
boolean addAll(Collection<? extends E> other)

Ajoute tous les lments de lautre collection dans cette collection. Renvoie true si la collection a t modie par cet appel.

boolean remove(Object obj)

Supprime lobjet obj de cette collection. Renvoie true si lobjet correspondant a t trouv.
boolean removeAll(Collection<?> other)

Supprime de cette collection tous les lments de lautre collection. Renvoie true si la collection a t modie par cet appel.

void clear()

Supprime tous les lments de la collection.


boolean retainAll(Collection<?> other)

Supprime dans cette collection tous les lments qui ne gurent pas dans lautre collection. Renvoie true si la collection a t modie par cet appel.

Object[] toArray()

Renvoie un tableau contenant tous les objets de la collection.


<T> T[] toArray(T[] arrayToFill)

Renvoie un tableau des objets de la collection. Si la longueur darrayToFill est sufsante, il est empli des lments de cette collection. Sil y a de la place, un lment null est annex, sinon, un nouveau tableau est allou et rempli avec le mme type de composant quarrayToFill et la mme longueur que la taille de cette collection.
java.util.Iterator<E> 1.2

boolean hasNext()

Renvoie true sil reste un lment parcourir.


E next()

Renvoie le prochain objet parcourir. Dclenche une exception NoSuchElementException si la n de la collection est atteinte.

void remove()

Supprime le dernier objet lu. Cette mthode doit imprativement tre appele aprs une lecture. Si la collection a t modie depuis la dernire lecture, cette mthode renvoie une IllegalStateException.

Les collections concrtes


Plutt que de voir en dtail chaque interface, nous avons pens quil serait plus utile de commencer par aborder les structures de donnes concrtes implmentes dans la bibliothque Java. Une fois que vous aurez correctement dcrit les classes que vous pourrez utiliser, nous reviendrons des considrations abstraites et nous verrons comment la structure des collections organise toutes ces classes. Le Tableau 13.1 prsente les collections de la bibliothque Java et dcrit brivement lobjectif de chaque classe de collection (pour des raisons de simplicit, nous ne parlerons pas des collections

652

Au cur de Java 2 - Notions fondamentales

compatibles avec les threads, traites au Chapitre 14). Toutes les classes du Tableau 13.1 implmentent linterface Collection, lexception de celles dont le nom se termine par Map. Celles-ci implmentent plutt linterface Map (nous la traiterons un peu plus loin).
Tableau 13.1 : Collections concrtes dans la bibliothque Java

Type de collection ArrayList LinkedList ArrayDeque HashSet TreeSet EnumSet LinkedHashSet PriorityQueue HashMap TreeMap EnumMap LinkedHashMap WeakHashMap IdentityHashMap

Description Une squence indexe qui grandit et se rduit de manire dynamique Une squence ordonne qui permet des insertions et des retraits effectifs nimporte quel endroit Une queue deux extrmits implmente sous forme de tableau circulaire Une collection non ordonne qui refuse les rplications Un ensemble tri Un ensemble de valeurs de type numr Un ensemble qui se souvient de lordre dinsertion des lments Une collection qui permet un retrait effectif de llment le plus petit Une structure de donnes qui stocke les associations cl/valeur Une concordance dans laquelle les cls sont tries Une concordance dans laquelle les cls appartiennent un type numr Une concordance qui se souvient de lordre dajout des entres Une concordance avec des valeurs pouvant tre rclames par le ramassemiettes si elles ne sont pas utilises ailleurs Une concordance avec des cls compares par ==, et non par equals

Listes chanes
Nous avons dj abord les tableaux et leur cousin dynamique, la classe ArrayList, dans cet ouvrage. Mais ces deux structures possdent un inconvnient majeur. La suppression dun lment au milieu dun tableau ncessite beaucoup de temps machine, car tous les lments situs aprs llment supprim doivent tre dcals dune case (voir Figure 13.4). Le mme problme se pose pour insrer des lments au milieu dun tableau. Il existe une autre structure de donnes trs rpandue, la liste chane, qui permet de rsoudre ces problmes. Alors que les objets dun tableau occupent des emplacements mmoire successifs, une liste chane stocke chaque objet avec un lien qui y fait rfrence. Chaque lien possde galement une rfrence vers le lien suivant de la liste. Avec Java, chaque lment dune liste chane possde en fait deux liens, cest--dire que chaque lment est aussi reli llment prcdent (voir Figure 13.5).

Chapitre 13

Collections

653

Figure 13.4
Supprimer un lment dun tableau.
Elment supprim

Figure 13.5
Une liste doublement chane.

Liste chane premier lment

Lien donne suivant prcdent

Lien donne suivant prcdent

Lien donne suivant prcdent

La suppression dun lment au milieu dune liste chane est une opration trs simple, car seuls les liens associs llment supprim doivent tre modis (voir Figure 13.6).
Figure 13.6
Supprimer un lment dune liste chane.

Liste chane premier lment

Lien donne Claire suivant prcdent

Lien donne Laurent suivant prcdent

Lien donne suivant prcdent Adrien

Vous avez peut-tre dj appris implmenter des listes chanes. Si vous avez oubli les manipulations de liens lors dune suppression ou dun ajout dlment dans une liste chane, vous serez soulag dapprendre que la bibliothque de collections Java possde une classe LinkedList prte lemploi.

654

Au cur de Java 2 - Notions fondamentales

Lexemple de programme suivant ajoute trois lments, puis supprime le deuxime.


List<String> staff = new LinkedList<String>(); // LinkedList // implmente List staff.add("Claire"); staff.add("Laurent"); staff.add("Adrien"); Iterator iter = staff.iterator(); String first = iter.next(); // visiter le premier lment String second = iter.next(); // visiter le deuxime lment iter.remove(); // Supprime le dernier lment parcouru

Il y a une diffrence importante entre les listes chanes et les collections gnriques. Une liste chane est en effet une collection classe dans laquelle la position des objets a une importance. La mthode LinkedList.add ajoute un objet la n de la liste. Mais vous aurez souvent besoin dajouter un lment quelque part au milieu dune liste. Cette mthode add, qui dpend de la position courante, dpend aussi dun itrateur, car ce sont les itrateurs qui sont chargs de stocker les positions dans une collection. Lemploi dun itrateur pour ajouter des lments na un sens que si les collections concernes sont classes selon un ordre implicite. Par exemple, le type de donnes set que nous aborderons dans la prochaine partie nimpose aucun ordre ses lments. Cest pourquoi il nexiste aucune mthode add dans linterface Iterator. Pour compenser, la bibliothque de collections fournit une sous-interface, ListIterator, qui contient une mthode add :
interface ListIterator<E> extends Iterator<E> { void add(E element); . . . }

Contrairement Collection.add, cette mthode ne renvoie pas un boolean, cest--dire que lopration add est cense toujours modier la liste. De plus, linterface ListIterator possde deux mthodes, dont vous pouvez vous servir pour parcourir une liste lenvers.
E previous() boolean hasPrevious()

Comme la mthode next, la mthode previous renvoie lobjet quelle a saut. La mthode ListIterator de la classe LinkedList renvoie un objet itrateur qui implmente linterface ListIterator.
ListIterator<String> iter = staff.listIterator();

La mthode add ajoute le nouvel lment avant la position de litrateur. Par exemple, le code suivant saute le premier lment de la liste chane et ajoute la chane "Juliette" avant le deuxime lment (voir Figure 13.7) :
List<String> staff = new LinkedList<String>(); staff.add("Claire"); staff.add("Laurent"); staff.add("Adrien"); ListIterator<String> iter = staff.listIterator();

Chapitre 13

Collections

655

iter.next(); // ignorer le premier lment iter.add("Juliette");

Figure 13.7
Ajouter un lment dans une liste chane.

Liste chane premier lment

Lien donne suivant prcdent

Lien donne suivant prcdent

Lien donne suivant prcdent

Claire

Laurent

Adrien

Lien donne suivant prcdent

Juliette

Si vous appelez la mthode add plusieurs fois, les lments sont simplement ajouts dans lordre dans lequel vous les passez. Ils sont tous ajouts avant la position courante de litrateur. Lorsque vous utilisez lopration add avec un itrateur qui vient juste dtre renvoy par la mthode ListIterator et qui fait rfrence au dbut de la liste chane, les lments sont ajouts au dbut de la liste chane. Lorsque litrateur dpasse le dernier lment de la liste (cest--dire que hasNext renvoie false), llment est ajout la n de la liste. Si la liste chane possde n lments, il existe n+1 emplacements pour ajouter un nouvel lment. Ces emplacements correspondent aux n+1 positions possibles de litrateur. Par exemple, si une liste chane contient trois lments, A, B et C, alors il existe quatre positions possibles pour insrer un nouvel lment (reprsentes par |) :
|ABC A|BC AB|C ABC|

INFO
Faites attention lanalogie avec un curseur de texte. Lopration remove ne fonctionne pas exactement comme la touche Retour-arrire. Juste aprs un appel next, la mthode remove supprime en fait llment gauche de litrateur, exactement comme la touche Retour-arrire. Cependant, si vous venez dappeler previous, llment situ droite de litrateur sera supprim. De plus, il nest pas possible dappeler remove deux fois de suite. Contrairement la mthode add, qui ne dpend que de la position de litrateur, la mthode remove dpend de ltat de litrateur.

656

Au cur de Java 2 - Notions fondamentales

De plus, il existe une mthode set qui remplace le dernier lment renvoy par next ou previous par un nouvel lment. Par exemple, le code suivant remplace le premier lment dune liste par une nouvelle valeur :
ListIterator<String> iter = list.listIterator(); String oldValue = iter.next(); // renvoie le premier lment iter.set(newValue); // affecte une nouvelle valeur au premier lment

Comme vous pouvez limaginer, si un itrateur parcourt une liste alors quun autre itrateur est en train de la modier, il peut en rsulter une certaine confusion. Par exemple, supposons quun itrateur fasse rfrence un lment quun autre itrateur vient juste de supprimer. Litrateur est dsormais invalide et ne devrait donc plus tre utilis. Les itrateurs de listes chanes ont t conus pour dtecter de telles modications. Si un itrateur se rend compte que sa liste a t modie par un autre itrateur ou par une mthode de la collection, il dclenche une exception ConcurrentModificationException. Par exemple, considrons le code suivant :
List<String> list = . . .; ListIterator<String> iter1 = list.listIterator(); ListIterator<String> iter2 = list.listIterator(); iter1.next(); iter1.remove(); iter2.next(); // dclenche une ConcurrentModificationException

Lappel iter2.next lance une ConcurrentModificationException, car iter2 dtecte que la liste a t modie. Pour viter ce type dexception, il suft de suivre cette rgle simple : vous pouvez attacher autant ditrateurs une collection que vous le souhaitez, tant quils effectuent uniquement des lectures dans la liste. Sinon, vous pouvez y attacher un seul itrateur qui peut la fois lire et crire. Le dclenchement de ce type dexception observe aussi une rgle simple. La collection garde une trace du nombre doprations de transformations (comme lajout ou la suppression dun lment). Chaque itrateur compte le nombre de transformations dont il est responsable. Au dbut de chaque mthode ditrateur, ce dernier vrie que son nombre de transformations est bien gal celui de la collection. Dans le cas contraire, il dclenche lexception ConcurrentModificationException.
INFO
Il existe une curieuse exception la rgle permettant de dtecter une modication simultane. La liste chane garde uniquement la trace des modications structurelles de la liste, comme lajout et la suppression de liens. La mthode set nest pas compte comme une modication structurelle. Vous pouvez attacher plusieurs itrateurs une liste chane, et ils peuvent tous appeler set pour modier le contenu des liens existants. Cette capacit est en fait ncessaire pour un certain nombre dalgorithmes de la classe Collections que nous aborderons plus loin dans ce chapitre.

Vous connaissez dsormais les mthodes fondamentales de la classe LinkedList. Vous pouvez vous servir dun ListIterator pour parcourir les lments dune liste chane dans nimporte quelle direction et pour ajouter ou supprimer des lments. Comme nous lavons vu dans la section prcdente, plusieurs autres mthodes pratiques manipulent les listes chanes, qui sont dclares dans linterface Collection. Ces mthodes, pour la plupart, sont implmentes dans la superclasse AbstractCollection de la classe LinkedList. Par exemple,

Chapitre 13

Collections

657

la mthode toString invoque toString pour chaque lment et renvoie une seule longue chane au format [A, B, C]. Cela est trs pratique pour dboguer. Utilisez la mthode contains pour vrier si un lment est prsent dans une liste chane. Par exemple, lappel staff.contains("Georges") renvoie true si la liste chane contient dj une chane gale la chane "Georges". La bibliothque fournit aussi un certain nombre de mthodes qui sont, dun point de vue thorique, quelque peu douteuses. Les listes chanes ne permettent pas daccder rapidement nimporte quel lment. Si vous voulez connatre le n-ime lment dune liste chane, il faut commencer par le dbut de la liste et sauter les n1 premiers lments. Il nexiste aucun moyen plus rapide. Pour cette raison, les programmeurs nutilisent gnralement pas de listes chanes dans des situations o les lments doivent tre rfrencs par un index entier. Nanmoins, la classe LinkedList fournit une mthode get qui vous permet daccder un lment particulier :
LinkedList<String> list = ...; String obj = list.get(n);

Bien sr, cette technique nest pas trs efcace. Si vous vous rendez compte que vous lutilisez, cest probablement que vous utilisez une structure de donnes qui nest pas adapte votre problme. Il ne faudrait jamais avoir recours cette mthode pour parcourir les lments dune liste chane. Lefcacit du code suivant doit donc tre vivement remise en question :
for (int i = 0; i < list.size(); i++) utilisation de list.get(i);

Chaque fois que vous recherchez un autre lment, vous parcourez nouveau la liste depuis le dbut. Lobjet LinkedList ne fait aucun effort pour se rappeler la position courante du dernier lment lu.
INFO
La mthode get possde une lgre optimisation : si lindice vaut au moins size() / 2, la recherche commence par la n de la liste.

Linterface de litrateur de liste possde en outre une mthode renvoyant lindice de la position courante. En fait, comme les itrateurs Java pointent entre les lments, il en existe deux : la mthode nextIndex renvoie lindice de llment qui serait renvoy par la mthode next, et la mthode previousIndex retourne lindice de llment qui serait renvoy par la mthode previous. Cet indice correspond naturellement celui de la mthode nextIndex moins un. Ces mthodes sont assez efcaces, car litrateur garde en mmoire sa position courante. Enn, si vous avez un indice n, la mthode list.listIterator(n) renvoie un itrateur qui pointe juste avant llment dont lindice vaut n. Cest--dire quun appel next donnera le mme lment que list.get(n). Lobtention de cet itrateur reste assez peu efcace. Si lune de vos listes chanes ne possde que quelques lments, vous navez pas trop de soucis vous faire propos de lefcacit des mthodes get et set. Mais dans ce cas, quel est lavantage dune liste chane ? La seule motivation permettant de choisir une liste chane consiste minimiser le cot dune insertion ou dune suppression dun lment en plein milieu de la liste. Si vous navez que quelques lments traiter, il vaut mieux utiliser une ArrayList.

658

Au cur de Java 2 - Notions fondamentales

Nous conseillons dailleurs dviter toutes les mthodes qui utilisent un indice dentier permettant de noter la position dans une liste chane. Pour obtenir un accs alatoire dans une collection, mieux vaut utiliser un tableau ou encore ArrayList. Le programme du Listing 13.1 permet de manipuler des listes chanes. Il se contente de crer deux listes chanes, de les fusionner, puis il supprime un lment sur deux dans la seconde liste, et teste nalement la mthode removeAll. Nous vous recommandons de suivre lexcution de ce programme et daccorder une attention particulire aux itrateurs. Il vous sera ventuellement pratique de dessiner un diagramme reprsentant les positions de litrateur, comme ceci :
|ACE A|CE AB|CE . . . |BDFG |BDFG B|DFG

Remarquez que lappel


System.out.println(a);

afche tous les lments de la liste chane a en appelant la mthode toString dans AbstractCollection.
Listing 13.1 : LinkedListTest.java
import java.util.*; /** * Ce programme prsente le fonctionnement des listes chanes. * @version 1.10 2004-08-02 * @author Cay Horstmann */ public class LinkedListTest { public static void main(String[] args) { List<String> a = new LinkedList<String>(); a.add("Claire"); a.add("Carl"); a.add("Erica"); List<String> b = new LinkedList<String>(); b.add("Bob"); b.add("Doug"); b.add("Frances"); b.add("Gloria"); // fusionne les mots de b dans a ListIterator<String> aIter = a.listIterator(); Iterator<String> bIter = b.iterator(); while (bIter.hasNext()) { if (aIter.hasNext()) aIter.next(); aIter.add(bIter.next()); }

Chapitre 13

Collections

659

System.out.println(a); // supprime un mot sur deux dans b bIter = b.iterator(); while (bIter.hasNext()) { bIter.next(); // saute un lment if (bIter.hasNext()) { bIter.next(); // saute un lment bIter.remove(); // supprime cet lment } } System.out.println(b); // Opration globale: supprime tous les mots dans b de a a.removeAll(b); System.out.println(a); } } java.util.List<E> 1.2

ListIterator<E> listIterator()

Renvoie un itrateur de liste permettant de parcourir les lments de la liste.


ListIterator<E> listIterator(int index)

Renvoie un itrateur de liste permettant de parcourir les lments de la liste dont le premier appel next doit renvoyer llment correspondant lindice spci.

void add(int i, E element)

Ajoute un lment la position spcie.


void addAll(int i, Collection<? Extends E> elements)

Ajoute tous les lments dune collection la position spcie.


E remove(int i)

Supprime et renvoie llment la position spcie.


E get(int i)

Rcupre llment la position spcie.


E set(int i, E element)

Remplace llment la position spcie par un nouvel lment et renvoie lancien lment.
int indexOf(Object element)

Renvoie la position de la premire occurrence dun lment gal llment spci, ou 1 si cet lment na pu tre trouv.

int lastIndexOf(Object element)

Renvoie la position de la dernire occurrence dun lment gal llment spci, ou 1 si cet lment na pu tre trouv.

660

Au cur de Java 2 - Notions fondamentales

java.util.ListIterator<E> 1.2

void add(E newElement)

Ajoute un lment avant la position courante.


void set(E newElement)

Remplace le dernier lment renvoy par next ou previous par un nouvel lment. Dclenche une IllegalStateException si la structure de la liste a t modie depuis le dernier appel next ou previous.

boolean hasPrevious()

Renvoie true sil existe un lment avant la position courante.


E previous()

Renvoie lobjet prcdent. Dclenche une NoSuchElementException lorsque le dbut de la liste est atteint.

int nextIndex()

Renvoie lindice de llment qui serait renvoy par le prochain appel next.
int previousIndex()

Renvoie lindice de llment qui serait renvoy par le prochain appel previous.
java.util.LinkedList<E> 1.2

LinkedList()

Construit une liste chane vide.


LinkedList(Collection<? extends E> elements)

Construit une liste chane et y ajoute tous les lments dune collection.
void addFirst(E element) void addLast(E element)

Ajoutent un lment au dbut ou la n dune liste.


E getFirst() E getLast()

Renvoient llment situ au dbut ou la n dune liste.


E removeFirst() E removeLast()

Suppriment et renvoient llment situ au dbut ou la n dune liste.

Listes de tableaux
Dans la section prcdente, nous avons abord linterface List et la classe LinkedList qui limplmente. Linterface List dcrit une collection classe dans laquelle la position des lments a une importance. Il existe deux protocoles pour parcourir ses lments : avec un itrateur ou avec les mthodes daccs alatoire get et set. Ces deux mthodes ne sont pas vraiment adaptes une structure en liste chane, mais get et set prennent toute leur importance avec des tableaux. La bibliothque de collections fournit une classe ArrayList qui implmente aussi linterface List. Une ArrayList encapsule un tableau dynamique dobjets relogeable.

Chapitre 13

Collections

661

INFO
Les anciens de la programmation Java auront peut-tre dj utilis la classe Vector pour crer un tableau dynamique. Pourquoi utiliser une ArrayList la place dun Vector? Il existe une raison trs simple. Toutes les mthodes de la classe Vector sont synchronises. Vous pouvez donc accder en toute tranquillit un objet Vector depuis deux threads. Mais si vous naccdez un vecteur que depuis un seul thread (ce qui est de loin le cas le plus frquent), votre code perd beaucoup de temps avec la synchronisation. Au contraire, les mthodes ArrayList ne sont pas synchronises. Cest pourquoi nous vous recommandons dutiliser une ArrayList la place dun vecteur chaque fois que vous navez pas besoin dune synchronisation.

Tables de hachage
Les listes chanes et les tableaux vous permettent de spcier lordre dans lequel vous voulez organiser vos lments. Cependant, si vous recherchez un lment particulier et que vous ne vous rappeliez pas sa position, vous aurez besoin de parcourir tous les lments jusqu ce que vous trouviez llment recherch. Cela requiert parfois beaucoup de temps, surtout lorsque la collection contient beaucoup dlments. Si lordre des lments na pas dimportance, il existe des structures de donnes qui vous permettent de retrouver un lment beaucoup plus rapidement. Linconvnient est que ces structures de donnes ne vous permettent pas de contrler lordre des lments. En effet, elles les organisent selon un ordre qui leur permet de les retrouver facilement. Une structure de donnes classique pour retrouver simplement un lment est la table de hachage. Une table de hachage calcule un nombre entier, appel code de hachage, pour chacun des lments. Un code de hachage est un entier driv des champs dinstance dun objet, pour que les objets contenant diffrentes donnes produisent des codes diffrents. Le Tableau 13.2 prsente quelques exemples de codes de hachage ns de la mthode hashCode de la classe String.
Tableau 13.2 : Codes de hachage rsultant de la fonction hashCode

Chane "Lee" "lee" "eel"

Code de hachage 76268 107020 100300

Si vous dnissez vos propres classes, vous serez responsable de limplmentation de votre propre mthode hashCode (voir Chapitre 5 pour plus dinformations). Votre implmentation doit tre compatible avec la mthode equals : si a.equals(b), a et b doivent avoir le mme code de hachage. Pour linstant, il suft de savoir que les codes de hachage peuvent tre calculs trs rapidement et que ce calcul ne dpend que de ltat de lobjet hacher, et non des autres objets de la table de hachage. En Java, les tables de hachage sont implmentes sous forme de tableaux de listes chanes. Chaque liste est appele un seau (ou panier, ou encore bucket) [Figure 13.8]. Pour retrouver la place dun lment dans la table, il faut calculer son code de hachage et le rduire par un modulo du nombre total de seaux. Le nombre rsultant correspond lindice du seau contenant llment. Par exemple,

662

Au cur de Java 2 - Notions fondamentales

si un objet possde un code de hachage de 76268 et quil existe 128 seaux, lobjet sera plac dans le seau 108 (car le reste de la division entire 76268/128 est 108). Avec un peu de chance, il nexiste aucun autre lment dans ce seau. Il suft alors dinsrer llment dans le seau. Naturellement, il est invitable de trouver parfois un seau dj plein. Cela sappelle une collision de hachage. Dans ce cas, vous comparez le nouvel objet avec tous les autres objets du seau pour dterminer sil en fait dj partie. En se fondant sur le fait que les codes de hachage sont distribus alatoirement et que le nombre de seaux est sufsamment grand, il suft gnralement deffectuer quelques comparaisons.
Figure 13.8
Une table de hachage.

Si vous voulez contrler plus nement les performances dune table de hachage, il est possible de spcier le nombre initial de seaux, qui correspond au nombre de seaux utiliss pour tous les objets ayant le mme code de hachage. Si trop dlments sont insrs dans la table de hachage, le nombre de collisions augmente et les performances de recherche baissent. Si vous connaissez approximativement le nombre dlments de la table, il convient de choisir un nombre initial de seaux. Il doit gnralement tre compris entre 75 % et 150 % du nombre dlments prvus. Un certain nombre de chercheurs pensent quil est bon de choisir un nombre premier pour le nombre de seaux, an dviter dentasser trop de valeurs dans les mmes seaux. Cette conclusion nest cependant pas forcment vidente ! La bibliothque standard utilise les nombres de seaux puissance de 2, avec un paramtre par dfaut 16 (toute valeur fournie pour la taille de la table est automatiquement arrondie au prochain nombre puissance de 2). Naturellement, vous ne pouvez pas toujours connatre le nombre dlments que la table sera amene stocker, ou vos suppositions du dpart peuvent se rvler errones. Si la table de hachage se remplit trop, elle a besoin dtre rorganise. Pour rorganiser une table de hachage, il faut crer une nouvelle table avec plus de seaux, insrer tous les lments dans la nouvelle table et supprimer lancienne table. Un facteur de charge est calcul pour savoir si une table doit tre rorganise ou non. Par exemple, si le facteur de charge vaut 0,75 (ce qui est la valeur par dfaut) et que la table soit pleine plus de 75 %, elle est automatiquement rorganise avec le double de seaux. Pour la plupart des applications, il est raisonnable de conserver un facteur de charge autour de 0,75.

Chapitre 13

Collections

663

Les tables de hachage peuvent servir implmenter plusieurs structures de donnes importantes. La plus simple dentre elles est le type set. Un set correspond simplement une collection dlments ne gurant quune seule fois chacun dans la collection. La mthode add dun set commence par essayer de retrouver lobjet ajouter et ne lajoute que sil nest pas encore prsent. La bibliothque de collections Java fournit une classe HashSet qui implmente un set partir dune table de hachage. Les lments sont ajouts avec la mthode add. La mthode contains est rednie pour effectuer une recherche rapide et vrier si un lment fait dj partie du set. Elle ne vrie les lments que dun seul seau et non tous les lments de la collection. Litrateur dun set parcourt tous les seaux un par un. Comme les lments dune table de hachage sont disperss dans toute la table, les seaux sont parcourus dans un ordre pseudo-alatoire. Cest pourquoi il ne faut utiliser un HashSet que si lordre des lments dans la collection na aucune importance. Lexemple de programme la n de cette section (voir Listing 13.2) lit des mots partir de System.in, les ajoute dans un set, puis imprime tous les mots du set. Par exemple, vous pouvez y entrer un texte issu dAlice au pays des merveilles (que vous pouvez obtenir sur www.gutenberg.net), en lexcutant partir dun shell, comme ceci :
java SetTest < alice30.txt

Le programme lit tous les mots qui lui sont passs et les ajoute dans le set. Il passe en revue tous les mots uniques et afche pour terminer le nombre de mots uniques rencontrs. Ainsi, Alice au pays des merveilles comprend 5 909 mots uniques, en comptant la mention de copyright du dbut. Ces mots apparaissent selon un ordre alatoire.
ATTENTION
Prenez garde lorsque vous faites muter les lments set. Si le code de hachage dun lment devait tre modi, llment ne se trouverait plus la position correcte dans la structure de donnes.

Listing 13.2 : SetTest.java


import java.util.*; /** * Ce programme utilise un set pour imprimer tous les mots uniques dans * System.in. */ public class SetTest { public static void main(String[] args) { Set<String> words = new HashSet<String>(); //HashSet implmente Set // dfinit un HashSet, LinkedHashSet or TreeSet long totalTime = 0; Scanner in = new Scanner(System.in); while (in.hasNext()) { String word = in.next(); long callTime = System.currentTimeMillis();

664

Au cur de Java 2 - Notions fondamentales

words.add(word); callTime = System.currentTimeMillis() - callTime; totalTime += callTime; } Iterator<String> iter = words.iterator(); for (int i = 1; I <= 20; i++) System.out.println(iter.next()); System.out.println("..."); System.out.println(words.size() + " distinct words. " + totalTime + " milliseconds."); } } java.util.HashSet<E> 1.2

HashSet()

Construit un set vide.


HashSet(Collection<? extends E> elements)

Construit un set et y ajoute tous les lments dune collection.


HashSet(int initialCapacity)

Construit un set vide avec la capacit spcie (nombre de seaux).


HashSet(int initialCapacity, float loadFactor)

Construit un set vide avec la capacit et le facteur de charge spcis (un nombre compris entre 0,0 et 1,0 qui dtermine le pourcentage de remplissage dune table de hachage au-del duquel la table sera rorganise).
java.lang.Object 1.0

int hashCode()

Renvoie un code de hachage correspondant cet objet, cest--dire nimporte quel nombre entier, positif ou ngatif. Les dnitions de equals et hashCode doivent tre compatibles. Si x.equals(y) vaut true, x.hashCode() doit avoir la mme valeur que y.hashCode().

Arbres
La classe TreeSet ressemble beaucoup la classe set, avec une amlioration supplmentaire. Les arbres de hachage reprsentent des collections tries. Vous pouvez insrer des lments dans la collection dans nimporte quel ordre, et lorsque vous parcourez ces lments, ils sont automatiquement prsents selon un ordre croissant. Par exemple, supposons que vous ajoutiez trois chanes, puis que vous parcouriez tous les lments que vous venez dajouter.
SortedSet<String> sorter = new TreeSet<String>(); // TreeSet // implmente SortedSet sorter.add("Bob"); sorter.add("Amy"); sorter.add("Carl"); for (String s: sorter) System.println(s);

Les chanes sont afches par ordre croissant : Amy Bob Carl. Comme le nom de la classe le laisse entendre, le tri est accompli par une structure de donnes en arbre. Limplmentation courante se sert dun arbre rouge-noir. Pour une description dtaille des arbres rouge-noir, consultez, par exemple,

Chapitre 13

Collections

665

le livre (en langue anglaise) Introduction to Algorithms de Thomas Cormen, Charles Leiserson, Ronald Rivest et Clifford Stein (The MIT Press, 2001). Chaque fois quun lment est ajout un arbre, il est plac un endroit correspondant son rang parmi les lments tris. Par consquent, litrateur parcourt toujours les lments dun arbre selon un ordre croissant. Lajout dun lment dans un arbre est plus lent que sil fallait lajouter dans une table de hachage, mais cela reste bien plus rapide que de lajouter dans un tableau tri ou dans une liste chane. Si larbre contient n lments, une moyenne de log2 n comparaisons sera ncessaire pour trouver la position dun nouvel lment dans larbre. Par exemple, si larbre contient dj 1 000 lments, lajout dun nouvel lment ncessitera environ 10 comparaisons. Par consquent, il est moins rapide dajouter un lment dans un TreeSet que dans un HashSet (voir le Tableau 13.3 pour une comparaison), mais le TreeSet trie automatiquement ses lments.
Tableau 13.3 : Ajouter des lments dans un HashSet et dans un TreeSet

Document Alice au pays des merveilles Le Comte de Monte-Cristo


java.util.TreeSet<E> 1.2

Nombre total de mots 28195 466300

Nombre de mots uniques 5909 37545

HashSet 5s 75 s

TreeSet 7s 98 s

TreeSet()

Construit un TreeSet vide.


TreeSet(Collection<? extends E> elements)

Construit un TreeSet et y ajoute tous les lments dune collection.

Comparaison dobjets
Comment le TreeSet sait-il dans quel ordre trier les lments ? Par dfaut, cette structure de donnes part de lhypothse que vous insrez des lments qui implmentent linterface Comparable. Cette interface dnit une seule mthode :
public interface Comparable<T> { int compareTo(T other); }

Lappel a.compareTo(b) doit renvoyer 0 si a et b sont gaux, un nombre entier ngatif si a est infrieur b (selon lordre de tri choisi) et un nombre entier positif si a est suprieur b. La valeur exacte renvoye par cette mthode na pas grande importance. Seul son signe (>0, 0 ou <0) a une signication. Plusieurs classes de la plate-forme Java standard implmentent linterface Comparable. La classe String en est un bon exemple. Sa mthode compareTo compare des chanes selon lordre du dictionnaire (aussi appel ordre lexicographique). Si vous insrez vos propres objets, il vous faut dnir un ordre de tri en implmentant linterface Comparable. Il nexiste aucune implmentation par dfaut de compareTo dans la classe Object.

666

Au cur de Java 2 - Notions fondamentales

Par exemple, voici comment trier des objets Item en fonction de leur numro de code :
class Item implements Comparable<Item> { public int compareTo(Item other) { return partNumber other.partNumber; } . . . }

Si vous comparez deux entiers positifs, comme les numros de code de notre exemple, il vous suft de renvoyer leur diffrence, qui sera ngative si le premier article est infrieur au second, nulle si les deux articles sont identiques et positive dans le dernier cas.
ATTENTION
Cette astuce ne fonctionne que si les nombres entiers proviennent dune gamme assez petite. En effet, si x est un grand nombre positif et que y soit un grand nombre ngatif, la diffrence x - y pourra provoquer un dbordement.

Il est bien vident que lutilisation de linterface Comparable pour dnir un ordre de tri possde certaines limitations. Cette interface ne peut tre implmente quune seule fois. Mais que peut-on faire pour trier un ensemble darticles selon leur numro de code dans une collection, et en fonction de leur description dans une autre ? De plus, que pouvez-vous faire pour trier des objets dune classe dont le concepteur na pas pris le soin dimplmenter linterface Comparable? Dans ces situations, il faut demander larbre de hachage de recourir une autre mthode de comparaison, en passant un objet Comparator dans le constructeur TreeSet. Linterface Comparator dclare une mthode compare, avec deux paramtres explicites :
public interface Comparator<T> { int compare(T a, T b); }

Comme la mthode compareTo, la mthode compare renvoie une valeur entire ngative si a est infrieur b, nulle si a et b sont identiques et positive dans le dernier cas. Pour trier des articles selon leur description, il suft de dnir une classe qui implmente linterface Comparator :
class ItemComparator implements Comparator<Item> { public int compare(Item a, Item b) { String descrA = a.getDescription(); String descrB = b.getDescription(); return descrA.compareTo(descrB); } }

Il faut ensuite passer un objet de cette classe au constructeur de larbre :


ItemComparator comp = new ItemComparator(); SortedSet<Item> sortByDescription = new TreeSet<Item>(comp);

Chapitre 13

Collections

667

Si vous construisez un arbre avec un comparateur, il se sert de cet objet mme sil doit comparer deux lments. Remarquez que le comparateur darticles ne possde aucune donne. Il sagit juste dun emplacement pour la mthode de comparaison. Ce type dobjet est parfois appel un objet de fonction. Les objets de fonction sont souvent dnis "en cours de route", comme des instances de classes internes anonymes :
SortedSet<Item> sortByDescription = new TreeSet<Item>(new Comparator<Item>() { public int compare(Item a, Item b) { String descrA = a.getDescription(); String descrB = b.getDescription(); return descrA.compareTo(descrB); } });

INFO
En ralit, linterface Comparator<T> est dclare comme ayant deux mthodes : compare et equals. Bien entendu, chaque classe possde une mthode equals; il y aurait donc peu davantages ajouter la mthode la dclaration de linterface. La documentation de lAPI explique que vous navez pas besoin de surcharger la mthode equals mais que cela peut amliorer les performances dans certains cas. La mthode addAll de la classe TreeSet, par exemple, fonctionnera plus efcacement si vous ajoutez des lments dun autre set qui utilise le mme comparateur.

Si vous revenez un instant au Tableau 13.3, vous pourriez vous demander sil convient dutiliser systmatiquement un TreeSet la place dun HashSet. Aprs tout, lajout dun lment na pas lair de prendre beaucoup plus de temps et les lments sont tris automatiquement. La rponse cette question dpend en fait des donnes traites. Si vous navez pas besoin que les donnes soient tries, il ny a aucune raison de ralentir votre programme pour le plaisir de les trier. De plus, il est parfois bien plus difcile de dnir un ordre de tri quune fonction de hachage. Une fonction de hachage doit simplement brouiller les objets, tandis quune fonction de comparaison doit trier les objets avec une prcision importante. Imaginons que vous vouliez collecter un ensemble de rectangles. Si vous utilisez un TreeSet, vous devez fournir un Comparator<Rectangle>. Mais comment comparer deux rectangles ? Par aires ? Cela ne fonctionne pas. Il se peut que deux rectangles distincts aient des coordonnes diffrentes, mais que leurs aires soient identiques. Lordre de tri dun arbre doit ncessairement tre un ordre total. Cela signie que deux lments quelconques doivent tre comparables et que la comparaison de deux lments ne peut renvoyer zro que si ces deux lments sont identiques. Il existe bien un tel ordre applicable aux rectangles (lordre lexicographique appliqu sur ses coordonnes), mais il nest pas trs naturel et plutt compliqu calculer. Par opposition, une fonction de hachage est dj dnie pour une classe Rectangle. Elle hache simplement les coordonnes.
INFO
Depuis Java SE 6, la classe TreeSet implmente linterface NavigableSet. Cette interface ajoute plusieurs mthodes commodes pour localiser des lments et pour un parcours inverse. Voir les notes API pour plus de dtails.

668

Au cur de Java 2 - Notions fondamentales

Le programme du Listing 13.3 gnre deux TreeSet dobjets Item. Le premier est tri par numro de code, lordre de tri par dfaut des objets Item. Le second set est tri par description, laide dun comparateur particulier.
Listing 13.3 : TreeSetTest.java
/** * @version 1.10 2004-08-02 * @author Cay Horstmann */ import java.util.*; /** * Ce programme trie un set dlments en comparant * leurs descriptions. */ public class TreeSetTest { public static void main(String[] args) { SortedSet<Item> parts = new TreeSet<Item>(); parts.add(new Item("Toaster", 1234)); parts.add(new Item("Widget", 4562)); parts.add(new Item("Modem", 9912)); System.out.println(parts); SortedSet<Item> sortByDescription = new TreeSet<Item>(new Comparator<Item>() { public int compare(Item a, Item b) { String descrA = a.getDescription(); String descrB = b.getDescription(); return descrA.compareTo(descrB); } }); sortByDescription.addAll(parts); System.out.println(sortByDescription); } } /** * Un lment avec une description et un numro de code. */ class Item implements Comparable<Item> { /** * Construit un lment. * @param aDescription la description de llment * @param aPartNumber le numro de code de llment */ public Item(String aDescription, int aPartNumber) { description = aDescription; partNumber = aPartNumber; }

Chapitre 13

Collections

669

/** * Obtient la description de cet lment. * @return la description */ public String getDescription() { return description; } public String toString() { return "[description=" + description + ", partNumber=" + partNumber + "]"; } public boolean equals(Object otherObject) { if (this == otherObject) return true; if (otherObject == null) return false; if (getClass()!= otherObject.getClass()) return false; Item other = (Item) otherObject; return description.equals(other.description) && partNumber == other.partNumber; } public int hashCode() { return 13 * description.hashCode() + 17 * partNumber; } public int compareTo(Item other) { return partNumber - other.partNumber; } private String description; private int partNumber; } java.lang.Comparable<T> 1.2

int compareTo(T other)

Compare cet objet avec un autre objet et renvoie une valeur ngative si this est infrieur other, nulle si les deux objets sont identiques au sens de lordre de tri et positive si this est suprieur other.
java.util.Comparator<T> 1.2

int compare(T a, T b)

Compare deux objets et renvoie une valeur ngative si a est infrieur b, nulle si les deux objets sont identiques au sens de lordre de tri et positive si a est suprieur b.
java.util.SortedSet<E> 1.2

Comparator<? super E> comparator()

Renvoie le comparateur utilis pour trier les lments ou null si les lments sont compars avec la mthode compareTo de linterface Comparable.

670

Au cur de Java 2 - Notions fondamentales

E first() E last()

Renvoient le plus grand ou le plus petit lment de la collection trie.


java.util.NavigableSet<E> 6

E higher(E value) E lower(E value)

Renvoient la > value du plus petit lment ou la < value du plus grand lment ou encore null si cet lment nexiste pas.

E ceiling(E value) E floor(E value)

Renvoient la value du plus petit lment ou la null si cet lment nexiste pas.

E pollFirst() E pollLast

value du plus grand lment ou encore

Suppriment et renvoient le plus petit ou le plus grand lment de ce set ou encore null si le set est vide.

Iterator<E> descendingIterator()

Renvoie un itrateur qui parcourt ce set par ordre dcroissant.


java.util.TreeSet<E> 1.2

TreeSet()

Construit un TreeSet pour stocker les objets Comparable.


TreeSet(Comparator<? super E> c)

Construit un TreeSet et se sert du comparateur spci pour trier ses lments.


TreeSet(SortedSet<? extends E> elements)

Construit un TreeSet, ajoute tous les lments dun ensemble tri et se sert du mme comparateur que celui de lensemble spci.

Queues et deques
Nous lavons vu, une queue permet dajouter efcacement des lments la n et den supprimer au dbut. Une queue deux extrmits ou deque permet dajouter ou de supprimer efcacement des lments au dbut et la n. Il est impossible dajouter des lments au milieu. Java SE 6 a introduit une interface Deque. Elle est implmente par les classes ArrayDeque et LinkedList, toutes deux fournissant des deques dont la taille augmente selon les besoins. Vous verrez au Chapitre 14 les queues et deques bornes.
java.util.Queue<E> 5.0

boolean add(E element) boolean offer(E element)

Ajoutent llment donn la n de cette deque et renvoient true, condition que la queue ne soit pas pleine. Si la queue est pleine, la premire mthode dclenche une exception IllegalStateException, tandis que la seconde renvoie false.

Chapitre 13

Collections

671

E remove() E poll()

Suppriment et renvoient llment au dbut de cette queue, condition que la queue ne soit pas vide. Si la queue est vide, la premire mthode dclenche une exception NoSuchElementException, tandis que la seconde renvoie null.

E element() E peek()

Renvoient llment au dbut de cette queue sans le supprimer, condition que la queue ne soit pas vide. Si elle est vide, la premire mthode dclenche une exception NoSuchElementException, tandis que la seconde renvoie null.
java.util.Deque<E> 6 void addFirst(E element) void addLast(E element) boolean offerFirst(E element) boolean offerLast(E element)

Ajoutent llment donn au dbut ou la n de cette deque. Si la queue est pleine, les deux premires mthodes dclenchent une exception IllegalStateException, tandis que les deux mthodes renvoient false.

E E E E removeFirst() removeLast() pollFirst() pollLast()

Suppriment et renvoient llment au dbut de cette queue, condition que la queue ne soit pas vide. Si la queue est vide, les deux premires mthodes dclenchent une exception NoSuchElementException, tandis que les deux dernires mthodes renvoient null.

E E E E getFirst() getLast() peekFirst() peekLast()

Renvoient llment au dbut de cette queue sans le supprimer, condition que la queue ne soit pas vide. Si elle est vide, les deux premires mthodes dclenchent une exception NoSuchElementException, tandis que les deux dernires mthodes renvoient null.
java.util.ArrayDeque<E> 6 ArrayDeque() ArrayDeque(int initialCapacity)

Construisent des deques sans limites avec une capacit initiale de 16 ou la capacit initiale donne.

Queues de priorit
Une queue de priorit rcupre des lments tris, aprs quils ont t insrs dans un ordre arbitraire. Ainsi, ds que vous appelez la mthode remove, vous obtenez le plus petit lment se trouvant dans la queue. Celle-ci ne trie toutefois pas tous ses lments. Si vous parcourez les lments, ils ne sont pas ncessairement tris. La queue de priorit fait usage dune structure de donnes efcace

672

Au cur de Java 2 - Notions fondamentales

et lgante, appele un tas. Il sagit dune arborescence binaire dans laquelle les oprations add et remove amnent llment le plus petit graviter autour de la racine, sans perdre du temps trier tous les lments. A linstar dun TreeSet, une queue de priorit peut contenir soit les lments dune classe qui implmente linterface Comparable, soit un objet Comparator que vous fournissez dans le constructeur. Ces queues servent gnralement planier les tches. Chaque tche se voit attribuer une priorit et elles sont ajoutes dans un ordre alatoire. Ds le dmarrage possible dune nouvelle tche, celle ayant la plus haute priorit est supprime de la queue (la priorit 1 tant gnralement la plus forte, lopration de suppression concerne le plus petit lment). Le Listing 13.4 prsente une queue de priorit. A la diffrence de litration dans un TreeSet, celleci ne parcourt pas les lments dans lordre de tri. Cependant, le retrait concerne toujours le plus petit lment restant.
Listing 13.4 : PriorityQueueTest.java
import java.util.*; /** * Ce programme prsente une queue de priorit. * @version 1.00 2004-08-03 * @author Cay Horstmann */ public class PriorityQueueTest { public static void main(String[] args) { PriorityQueue<GregorianCalendar> pq = new PriorityQueue<GregorianCalendar>(); pq.add(new GregorianCalendar(1906, Calendar.DECEMBER, 9)); // G. Hopper pq.add(new GregorianCalendar(1815, Calendar.DECEMBER, 10)); // A. Lovelace pq.add(new GregorianCalendar(1903, Calendar.DECEMBER, 3)); // J. von Neumann pq.add(new GregorianCalendar(1910, Calendar.JUNE, 22)); // K. Zuse System.out.println("Iterating over elements..."); for (GregorianCalendar date: pq) System.out.println(date.get(Calendar.YEAR)); System.out.println("Removing elements..."); while (!pq.isEmpty()) System.out.println(pq.remove().get(Calendar.YEAR)); } } java.util.PriorityQueue 5.0

PriorityQueue() PriorityQueue(int initialCapacity)

Construisent une queue de priorit pour stocker des objets Comparable.


PriorityQueue(int initialCapacity, Comparator<? super E> c)

Construit une queue de priorit et utilise le comparateur spci pour trier ses lments.

Chapitre 13

Collections

673

Cartes
Un set est une collection qui vous permet de retrouver rapidement un lment. Pour cela, il faut le connatre exactement, ce qui nest pas souvent le cas. En fait, on dispose gnralement de certaines informations importantes sur llment rechercher, et il faut le retrouver partir de ces informations. La structure de donnes cartes (ou map) permet deffectuer ce genre de recherche. Une carte enregistre des paires cl / valeur. Une valeur peut tre retrouve partir de la cl correspondante. Par exemple, vous pouvez disposer dun tableau de donnes sur des salaris, dans lequel les cls sont les numros de salaris et les valeurs des objets Employee. La bibliothque Java propose deux implmentations gnrales des cartes : HashMap et TreeMap. Les deux classes implmentent linterface Map. Une HashMap rpartit les cls dans plusieurs catgories, alors que la TreeMap se sert dun ordre total pour organiser les cls dans un arbre de recherche. Les fonctions de hachage ou de comparaison ne sont appliques quaux cls. Les valeurs associes ces cls ne sont ni compares ni rparties. Comment choisir entre une HashMap et une TreeMap? Comme pour les sets, les cartes de hachage sont lgrement plus rapides et elles sont privilgier si vous navez pas besoin de parcourir les lments selon un ordre particulier. Voici comment mettre en place une carte de hachage pour enregistrer des donnes sur des salaris :
Map<String, Employee> staff = new HashMap<String, Employee>(); // HashMap implmente Map Employee claire = new Employee("Harry Hacher"); staff.put("987-98-9996", harry); . . .

Ds quun nouvel objet est ajout une carte, il faut galement fournir une cl associe. Dans le cas qui nous intresse, la cl est constitue dune chane et la valeur correspondante est un objet employee. Pour retrouver un objet, il faut se servir de la cl (et par consquent, il faut la connatre).
String s = "987-98-9996"; e = staff.get(s); // retrouve les informations sur Harry

Si aucune information nest stocke dans la carte pour une cl spcie, get renvoie null. Les cls doivent tre uniques. Il nest pas permis dassocier deux valeurs la mme cl. Si vous appelez la mthode put deux fois avec la mme cl, la seconde valeur remplace la premire. En fait, put renvoie la dernire valeur correspondant la cl fournie. La mthode remove supprime de la carte un lment ayant une cl donne. La mthode size renvoie quant elle le nombre dlments de la carte. Les cartes ne sont pas vraiment considres comme des collections. Au sens de certains ensembles de structures de donnes, les cartes sont des collections de paires, cest--dire des collections de valeurs indexes par des cls. Il reste nanmoins possible dobtenir une vue dune carte, cest--dire un objet qui implmente linterface Collection ou lune de ses sous-interfaces. Il existe trois vues diffrentes : lensemble des cls, lensemble des valeurs et lensemble des paires cl/valeur. Les ensembles des cls et des paires cl/valeur forment un set parce quil ne peut exister quun seul exemplaire dune cl dans une carte. Les mthodes

674

Au cur de Java 2 - Notions fondamentales

Set<K> keySet() Collection<K> values() Set<Map.Entry<K, V>> entrySet()

permettent dafcher ces trois vues. Les lments de la troisime mthode font partie de la classe interne statique Map.Entry. Notons que le keySet nest pas un HashSet ni un TreeSet, mais il sagit dun objet dune autre classe qui implmente linterface Set, laquelle tend linterface Collection. Vous pouvez donc utiliser un keySet comme nimporte quelle autre collection. Par exemple, vous pouvez numrer toutes les cls dune carte :
Set<String> keys = map.keySet(); for (String key: keys) { Utilisation de key }

ASTUCE
Si vous voulez examiner la fois les cls et les valeurs associes, vous pouvez viter de rechercher les valeurs en numrant les entres. Vous pouvez vous servir de lexemple suivant :
for (Map.Entry<String, Employee> entry: staff.entrySet()) { String key = entry.getKey(); Employee value = entry.getValue(); Utilisation de key et de value }

Si vous invoquez la mthode remove de litrateur, vous supprimez en fait la cl et la valeur associe. Il nest toutefois pas possible dajouter un lment lafchage du set de la cl. Lajout dune cl sans valeur associe na en effet aucun sens dans une carte. Si vous essayez dinvoquer la mthode add, cela dclenchera une UnsupportedOperationException. Le set dentres possde la mme restriction, bien que lajout dune nouvelle paire cl/valeur ait un sens pour ce set.

Le Listing 13.5 prsente un exemple dutilisation de carte. Nous commenons par ajouter des paires cl / valeur dans la carte, puis nous supprimons une des cls de la carte, ce qui supprime galement la valeur associe. Ensuite, nous modions la valeur associe une cl et nous appelons la mthode get pour retrouver la valeur. Pour terminer, toutes les entres du set sont passes en revue.
Listing 13.5 : MapTest.java
import java.util.*; /** * Ce programme montre lutilisation dune carte avec le type de cl * String et le type de valeur Employee. * @version 1.10 2004-08-02 * @Cay Horstmann */ public class MapTest { public static void main(String[] args)

Chapitre 13

Collections

675

{ Map<String, Employee> staff = new HashMap<String, Employee>(); staff.put("144-25-5464", new Employee("Amy Lee")); staff.put("567-24-2546", new Employee("Harry Hacker")); staff.put("157-62-7935", new Employee("Gary Cooper")); staff.put("456-62-5527", new Employee("Francesca Cruz")); // imprime toutes les entres System.out.println(staff); // supprime une entre staff.remove("567-24-2546"); // remplace une entre staff.put("456-62-5527", new Employee("Francesca Miller")); // recherche une valeur System.out.println(staff.get("157-62-7935")); // parcourt toutes les entres for (Map.Entry<String, Employee> entry: staff.entrySet(); { String key = entry.getKey(); Employee value = entry.getValue(); System.out.println("key=" + key + ", value=" + value); } } } /** * Une classe demploys minimaliste pour un objectif de test. */ class Employee { /** * Gnre un employ avec un salaire de $0. * @param n le nom de lemploy */ public Employee(String n) { name = n; salary = 0; } public String toString() { return "[name=" + name + ", salary=" + salary + "]"; } private String name; private double salary; }

676

Au cur de Java 2 - Notions fondamentales

java.util.Map<K, V> 1.2

V get(K key)

Cherche la valeur associe une cl, puis renvoie lobjet associ ou null si la cl na pas t trouve dans la carte. La cl peut tre nulle.

V put(K key, V value)

Associe une cl et une valeur dans une carte. Si la cl est dj prsente dans la carte, le nouvel objet remplace celui qui tait associ la cl. Cette mthode renvoie lancienne valeur associe la cl ou null si la cl nexistait pas dans la carte. La cl peut tre nulle, mais la valeur doit tre non nulle.

void putAll(Map<? extends K,? extends V> entries)

Ajoute toutes les entres dune carte spcie cette carte.


boolean containsKey(Object key)

Renvoie true si la cl est prsente dans la carte.


boolean containsValue(Object value)

Renvoie true si la valeur est prsente dans la carte.


Set<Map, Entry<K, V>> entrySet()

Renvoie une vue des objets Map.Entry, contenant les paires cl/valeur de la carte. Vous pouvez supprimer des lments de cette vue (ils sont alors limins de la carte), mais vous ne pouvez pas en ajouter de nouveaux.

Set<K> keySet()

Renvoie une vue de toutes les cls de la carte. Lorsque vous supprimez des lments de ce set, la cl et la valeur associe sont supprimes de la carte. En revanche, vous ne pouvez pas ajouter de nouveaux lments.

Collection<V> values()

Renvoie une vue de toutes les valeurs de la carte. Vous pouvez supprimer des lments de ce set (la cl et la valeur associe sont alors supprimes de la carte), mais vous navez pas le droit dy ajouter de nouveaux lments.
java.util.Map.Entry<K, V> 1.2

K getKey() V getValue()

Renvoient la cl ou la valeur de cette entre.


V setValue(V newValue)

Modie la valeur de la carte associe avec la nouvelle valeur et renvoie lancienne valeur.
java.util.HashMap<K, V> 1.2

HashMap() HashMap(int initialCapacity) HashMap(int initialCapacity, float loadFactor)

Construisent une carte de hachage vide avec la capacit et le facteur de charge spcis (un nombre compris entre 0,0 et 1,0 qui dtermine le pourcentage de remplissage au-del duquel la table de hachage sera rorganise). Le facteur de charge par dfaut vaut 0,75.

Chapitre 13

Collections

677

java.util.TreeMap<K, V> 1.2

TreeMap(Comparator<? super K> c)

Construit une carte en arbre et se sert du comparateur spci pour trier ses cls.
TreeMap(Map<? extends K,? extends V> entries)

Construit une carte en arbre et y ajoute toutes les entres de la carte spcie.
TreeMap(SortedMap<? extends K,? extends V> entries)

Construit un arbre, ajoute toutes les entres de la carte trie et se sert du mme comparateur dlments que la carte trie.
java.util.SortedMap<K, V> 1.2

Comparator<? super K> comparator()

Renvoie le comparateur servant trier les cls ou null si les cls sont compares laide de la mthode compareTo de linterface Comparable.

K firstKey() K lastKey()

Renvoient la plus petite ou la plus grande cl de la carte.

Classes de cartes et de set spcialises


La bibliothque de classes de collection possde plusieurs classes de cartes pour les besoins spcialiss que nous traiterons brivement dans cette section.

Cartes de hachage faibles


La classe WeakHashMap a t conue dans le but de rsoudre un problme intressant. Que se passet-il avec une valeur lorsque sa cl nest plus utilise nulle part dans votre programme ? Supposons que la dernire rfrence une cl ait disparu. Il ny a alors plus aucune manire de se rfrer lobjet de la valeur. Toutefois, tant donn quaucune partie du programme ne possde plus la cl, la paire cl/valeur ne peut tre supprime de la carte. Pourquoi alors le ramasse-miettes ne peut-il pas la supprimer ? Ce serait en effet son rle de supprimer les objets inutiliss. Malheureusement, la situation nest pas aussi simple. Le ramasse-miettes suit les objets vivants. Tant que lobjet de carte est vivant, tous les seaux quil contient sont galement vivants et ils ne seront pas rclams. Ainsi, votre programme doit supprimer les valeurs inutilises des cartes vivantes. Vous pouvez galement utiliser la place WeakHashMap. Cette structure de donnes coopre avec le ramasse-miettes pour supprimer les paires cl /valeur lorsque la seule rfrence la cl est la rfrence de lentre de la table de hachage. Voici en fait le fonctionnement interne de ce mcanisme. WeakHashMap utilise des rfrences faibles pour conserver les cls. Un objet WeakReference contient une rfrence un autre objet, dans notre cas, une cl de table de hachage. Les objets de ce type sont considrs de manire particulire par le ramasse-miettes. Normalement, sil dcouvre quun objet particulier ne possde pas de rfrences vers lui, il rclame simplement lobjet. Toutefois, si lobjet peut tre atteint uniquement par un WeakReference, le ramasse-miettes rclame toujours lobjet, mais place la rfrence faible qui y menait dans une queue. Les oprations du WeakHashMap vrient priodiquement cette queue pour y retrouver des rfrences faibles nouvellement arrives. Larrive dune rfrence faible dans la

678

Au cur de Java 2 - Notions fondamentales

queue signie que la cl nest plus utilise par personne et quelle a t collecte. WeakHashMap supprime alors lentre associe.

Cartes et sets de hachage lis


Java SE 1.4 a ajout les classes LinkedHashSet et LinkedHashMap qui se souviennent de lordre dans lequel vous avez insr des lments. De cette manire, vous vitez lordre assez alatoire des lments dans une table de hachage. A mesure que des entres sont insres dans la table, elles sont simplement relies ensemble dans une liste doublement lie (voir Figure 13.9).
Figure 13.9
Une table de hachage lie.
2

Par exemple, considrez les insertions de cartes suivantes du Listing 13.5 :


Map staff = new LinkedHashMap(); staff.put("144-25-5464", new Employee("Amy Lee")); staff.put("567-24-2546", new Employee("Harry Hacker")); staff.put("157-62-7935", new Employee("Garry Cooper")); staff.put("456-62-5527", new Employee("Francesca Cruz"));

A ce moment-l, staff.keySet().iterator() numre les cls dans cet ordre :


144-25-5464 567-24-2546 157-62-7935 456-62-5527

et staff.values().iterator() numre les valeurs dans lordre :


Amy Lee Harry Hacker Gary Cooper Francesca Cruz

Une carte de hachage lie peut utiliser lordre daccs, et non lordre dinsertion, pour parcourir les entres de la carte. Chaque fois que vous appelez get ou put, lentre affecte est supprime de sa

Chapitre 13

Collections

679

position et place la n de la liste chane des entres (seule la position dans la liste chane est concerne, et non le seau de la table de hachage. Une entre reste toujours dans le seau qui correspond au code de hachage de la cl). Pour construire une carte de hachage, appelez
LinkedHashMap<K, V>(initialCapacity, loadFactor, true)

Lordre daccs est utile pour implmenter le systme de "la dernire utilise" pour un cache. Par exemple, vous pouvez vouloir conserver les entres frquemment lues en mmoire et lire des objets lus moins souvent partir dune base de donnes. Lorsquune entre dune table est introuvable et que la table est dj assez pleine, vous pouvez obtenir un itrateur dans la table, puis supprimer les premiers lments quelle numre. Ces entres taient les dernires utilises. Vous pouvez mme automatiser cette procdure. Formez une sous-classe de LinkedHashMap et surchargez la mthode
protected boolean removeEldestEntry(Map.Entry<K, V> eldest)

Ajoutez ensuite une nouvelle entre pour supprimer lentre la plus ancienne lorsque votre mthode renvoie true. Par exemple, le cache suivant est conserv une taille maximale de 100 lments.
Map<K, V> cache = new LinkedHashMap<K, V>(128, 0.75F, true) { protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { return size() > 100; } };

Vous pouvez galement envisager lentre eldest pour dcider si vous devez le supprimer. Par exemple, vous voudrez peut-tre vrier une indication de la date et de lheure stocke avec lentre.

Sets et cartes dnumration


EnumSet est une implmentation efcace de sets, dont les lments appartiennent un type numr. Un type numr ayant un nombre ni dinstances, EnumSet est implment en interne sous la forme dune suite de bits. Un bit est activ si la valeur correspondante est prsente dans le set. La classe EnumSet ne possde pas de constructeurs publics. Utilisez une mthode factory statique pour construire le set :
enum Weekday { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY }; EnumSet<Weekday> always = EnumSet.allOf(Weekday.class); EnumSet<Weekday> never = EnumSet.noneOf(Weekday.class); EnumSet<Weekday> workday = EnumSet.range(Weekday.MONDAY, Weekday.FRIDAY); EnumSet<Weekday> mwf = EnumSet.of(Weekday.MONDAY, Weekday.WEDNESDAY, Weekday.FRI DAY);

Pour modier un EnumSet, vous pouvez utiliser les mthodes habituelles de linterface Set. EnumMap est une carte dont les cls appartiennent un type numr. Il est implment de manire simple et efcace sous forme de tableau de valeurs. Le type de la cl doit tre spci dans le constructeur :
EnumMap<Weekday, Employee> personInCharge = new EnumMap<Weekday, Employee> (Weekday.class);

680

Au cur de Java 2 - Notions fondamentales

INFO
Dans la documentation API de EnumSet, vous dcouvrirez dtranges paramtres de type, de la forme E extends Enum<E>. Ceci signie simplement que "E est un type numr". Tous les types numrs tendent la classe gnrique Enum. Weekday, par exemple, tend Enum<Weekday>.

Cartes de hachage didentit


Java SE 1.4 a ajout une autre classe IdentityHashMap pour un autre objectif assez spcialis, o les valeurs de hachage pour les cls ne doivent pas tre calcules par la mthode hashCode mais par la mthode System.identityHashCode. Cest la mthode utilise par Object.hashCode pour calculer un code de hachage partir de ladresse mmoire de lobjet. De mme, pour la comparaison des objets, IdentityHashMap utilise = =, et non equals. En dautres termes, diffrents objets de cl sont considrs comme distincts, mme sils ont un contenu gal. Cette classe est utile pour implmenter des algorithmes object traversal (comme la srialisation dobjets), dans lesquels vous souhaitez effectuer le suivi des objets auxquels on a dj appliqu traversed.
java.util.WeakHashMap<K, V> 1.2

WeakHashMap() WeakHashMap(int initialCapacity) WeakHashMap(int initialCapacity, float loadFactor)

Construisent une carte de hachage vide avec la capacit et le facteur de charge spcis.
java.util.LinkedHashMap<E> 1.4

LinkedHashSet() LinkedHashSet(int initialCapacity) LinkedHashSet(int initialCapacity, float loadFactor)

Construisent un set de hachage li et vide, avec la capacit et le facteur de charge spcis.


java.util.LinkedHashMap<K, V> 1.4

LinkedHashMap() LinkedHashMap(int initialCapacity) LinkedHashMap(int initialCapacity, float loadFactor) LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)

Construisent une carte de hachage lie vide avec la capacit, le facteur de charge et lordre spcis. Le paramtre accessOrder vaut true pour lordre daccs, false pour lordre dinsertion.

protected boolean removeEldestEntry(Map.Entry<K, V> eldest)

Doit tre surcharg pour renvoyer true si vous souhaitez supprimer lentre la plus ancienne. Le paramtre le plus ancien correspond lentre dont on envisage la suppression. Cette mthode est appele aprs quune entre a t ajoute la carte. Limplmentation par dfaut renvoie false, les anciens lments ne sont pas supprims par dfaut. Vous pouvez toutefois rednir cette mthode pour renvoyer de manire slective true, par exemple si lentre la plus ancienne rpond une certaine condition ou si la carte dpasse une certaine taille.

Chapitre 13

Collections

681

java.util.EnumSet<E extends Enum<E>> 5.0

static <E extends Enum<E>> EnumSet<E> allOf(Class<E> enumType)

Renvoie un set contenant toutes les valeurs du type numr donn.


static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> enumType)

Renvoie un set vide capable de contenir des valeurs du type numr donn.
static <E extends Enum<E>> EnumSet<E> range(E from, E to)

Renvoie un set contenant toutes les valeurs comprises entre from et to (compris).
static <E extends Enum<E>> EnumSet<E> of(E value) static <E extends Enum<E>> EnumSet<E> of(E value, E... values)

Renvoient un set contenant les valeurs donnes.


java.util.EnumMap<K extends<K>, V> 5.0

EnumMap(Class<K> keyType)

Construit une carte vide dont les cls ont le type donn.
java.util.IdentityHashMap<K, V> 1.4

IdentityHashMap() IdentityHashMap(int expectedMaxSize)

Construisent une carte de hachage didentit vide dont la capacit est la plus petite puissance de 2 dpassant 1,5 expectedMaxSize (le paramtre par dfaut pour expectedMaxSize vaut 21).
java.lang.System 1.0

static int identityHashCode(Object obj) 1.1

Renvoie le mme code de hachage (driv de ladresse mmoire de lobjet) que Object.hashCode calcule, mme si la classe laquelle appartient obj a redni la mthode hashCode.

La structure des collections


Les classes sont regroupes dans une structure appele cadre (ou framework), qui constitue la base commune pour crer des fonctionnalits avances. Un cadre contient des superclasses renfermant des fonctionnalits pratiques, des mthodes et des mcanismes. Lutilisateur dun cadre cre des sous-classes pour tendre les fonctionnalits sans devoir rinventer les mcanismes de base. Par exemple, Swing est un cadre pour les interfaces utilisateur. La bibliothque de collections Java forme un cadre pour des classes de collection. Ce cadre dnit un certain nombre dinterfaces et de classes abstraites pour des implmentations de collections (voir Figure 13.10) et il propose certains mcanismes, comme un protocole ditration. Vous pouvez vous servir des classes de collection sans connatre grand-chose au cadre. Cest exactement ce que nous avons fait dans les sections prcdentes. En revanche, si vous souhaitez implmenter des algorithmes gnraux qui fonctionnent sur plusieurs types de collections ou si vous voulez crer un nouveau type de collection, il vaut mieux comprendre le fonctionnement du cadre. Il existe deux interfaces fondamentales pour les collections : Collection et Map. Des lments peuvent tre insrs dans une collection avec la mthode suivante :
boolean add(E element)

682

Au cur de Java 2 - Notions fondamentales

Iterable

Collection

Map

Iterator

RandomAccess

List

Set

Queue

SortedMap

ListIterator

SortedSet

Deque

NavigableMap

NavigableSet

Figure 13.10
Les interfaces du cadre de collections.

Les cartes renferment des paires cl/valeur, et la mthode put sert les ajouter dans une carte :
V put(K key, V value)

Pour lire les lments dune collection, il faut les parcourir avec un itrateur. Pour lire les valeurs dune carte, il faut avoir recours la mthode get :
V get(K key)

Une List est une collection trie. Les lments sont ajouts une position particulire du conteneur. Un objet peut tre insr la position adquate de deux manires : par un indice entier ou par un itrateur de liste. Linterface List dnit des mthodes pour accder nimporte quelle donne dune liste :
void add(int index, E element) E get(int index) void remove(int index)

Comme nous lavons dj indiqu, linterface List fournit ces mthodes daccs alatoire quelles soient ou non efcaces pour une implmentation particulire. Pour permettre dviter de raliser des

Chapitre 13

Collections

683

oprations daccs alatoires coteuses, Java SE 1.4 a introduit une interface de balisage, RandomAccess. Cette interface ne possde pas de mthodes, mais vous pouvez lutiliser pour tester si une collection particulire prend en charge un accs alatoire efcace :
if (c instanceof RandomAccess) { utiliser un algorithme daccs alatoire } else { utiliser un algorithme daccs squentiel }

Les classes ArrayList et Vector implmentent linterface RandomAccess.


INFO C++
Dun point de vue thorique, il aurait t intressant de possder une interface Array spare qui tendrait linterface List et qui dclarerait des mthodes daccs alatoires. Si cette interface existait, les algorithmes ncessitant ce type daccs se serviraient de paramtres Array, et il vous serait alors impossible de les appliquer accidentellement des collections pour lesquelles ce type daccs nest pas optimis. Quoi quil en soit, les concepteurs du cadre des collections ont choisi de ne pas dnir dinterface spare. Ils voulaient en effet conserver un petit nombre dinterfaces dans la bibliothque. De plus, ils souhaitaient viter de prendre une attitude paternaliste envers les programmeurs. Vous tes donc libre de passer une liste chane des algorithmes accdant des donnes par leur indice. Vous devez juste savoir que ce genre de technique nest pas trs rapide.

Linterface ListIterator dnit une mthode pour ajouter un lment juste avant la position de litrateur :
void add(E element)

Pour lire et supprimer des lments une position particulire, il suft dutiliser les mthodes next et remove de linterface Iterator. Linterface Set est identique linterface Collection, mais le comportement des mthodes y est dni de manire plus prcise. La mthode add dun set doit rejeter les valeurs doubles. La mthode equals dun set doit tre dnie de telle sorte que deux sets sont identiques sils possdent les mmes lments, mais pas ncessairement disposs dans le mme ordre. La mthode hashCode doit tre dnie pour que deux sets possdant les mmes lments soient associs au mme code de hachage. Pourquoi dnir une interface spare si les signatures des mthodes sont les mmes ? Conceptuellement, toutes les collections ne sont pas des sets. La dnition dune interface Set permet aux programmeurs dcrire des mthodes qui nacceptent que des sets. En somme, les interfaces SortedSet et SortedMap mettent en vidence lobjet de comparaison utilis pour trier les lments et elles dnissent des mthodes pour obtenir des vues des sousensembles des collections. Nous reviendrons sur ces vues dans la prochaine section. Enn, Java SE 6 a introduit des interfaces NavigableSet et NavigableMap contenant des mthodes supplmentaires pour rechercher et parcourir des sets et des cartes tris (dans lidal, vous devez simplement avoir inclus ces mthodes dans les interfaces SortedSet et SortedMap). Les classes TreeSet et TreeMap implmentent ces interfaces.

684

Au cur de Java 2 - Notions fondamentales

Passons maintenant des interfaces aux classes qui les implmentent. Nous avons dj vu que les interfaces de collections possdent quelques mthodes qui peuvent tre implmentes trs simplement partir dun nombre plus important de mthodes fondamentales. Les classes abstraites fournissent certaines implmentations de ces routines :
AbstractCollection AbstractList AbstractSequentialList AbstractSet AbstractQueue AbstractMap

Si vous implmentez votre propre classe de collection, vous voudrez probablement tendre lune de ces classes pour rcuprer les implmentations de ses oprations. La bibliothque Java fournit des classes concrtes :
LinkedList ArrayList ArrayDeque HashSet TreeSet PriorityQueue HashMap TreeMap

La Figure 13.11 montre les relations entre ces classes.

Abstract Collection

AbstractList

AbstractSet

Abstract Queue

AbstractMap

Abstract SequentialList

HashSet

TreeSet

PriorityQueue

ArrayDeque

HashMap

TreeMap

LinkedList

ArrayList

Figure 13.11
Les classes du cadre des collections.

Chapitre 13

Collections

685

Finalement, il existe un certain nombre de classes conteneur "anciennes" qui sont prsentes depuis la premire version de Java, avant mme quil ny ait un cadre de collections :
Vector Stack Hashtable Properties

Elles ont t intgres dans le cadre des collections (voir Figure 13.12). Nous reviendrons sur ces classes un peu plus loin dans ce chapitre.
Figure 13.12
Les anciennes classes du cadre des collections.
Liste Carte

AbstractList

Hashtable

RandomAccess

Vecteur

Proprits

Pile

Les vues et les emballages


Si vous regardez les Figures 13.10 et 13.11, vous trouverez peut-tre quil est exagr davoir de nombreuses interfaces et classes abstraites pour implmenter un nombre modeste de collections concrtes. Mais ces gures nexpliquent pas tout. En ayant recours des vues, vous pouvez obtenir dautres objets qui implmentent les interfaces Collection ou Map. Vous en avez dj vu un exemple avec la mthode keySet des classes de cartes. Au premier abord, il apparat que la mthode cre un nouveau set, le remplit avec toutes les cls de la carte, puis renvoie le set. Mais ce nest pas exactement ce qui se passe. En fait, la mthode keySet renvoie un objet dune classe qui implmente linterface Set et dont les mthodes permettent de manipuler la carte dorigine. Ce type de collection est appel une vue. La technique des vues possde un certain nombre dapplications pratiques dans le cadre des collections. Nous verrons ces applications dans les sections suivantes.

686

Au cur de Java 2 - Notions fondamentales

Emballages de collection lgers


La mthode statique asList de la classe Arrays renvoie un emballage List autour dun tableau Java brut. Cette mthode vous permet de passer le tableau une mthode qui attend un argument de liste ou de collection. Par exemple :
Card[] cardDeck = new Card[52]; . . . List<Card> cardList = Arrays.asList(cardDeck);

Lobjet renvoy nest pas un ArrayList, cest un objet vue avec des mthodes get et set qui accdent au tableau sous-jacent. Toutes les mthodes capables de modier la taille du tableau (comme add et la mthode remove de litrateur associ) dclenchent une UnsupportedOperationException. Depuis Java SE 5.0, la mthode asList est dclare comme ayant un nombre variable darguments. Au lieu de passer un tableau, vous pouvez passer des lments individuels. Par exemple :
List<String> names = Arrays.asList("Amy", "Bob", "Carl");

Lappel de mthode
Collections.nCopies(n, anObject)

renvoie un objet inaltrable qui implmente linterface List et donne lillusion de possder n lments, chacun apparaissant comme un anObject. Par exemple, lappel suivant cre une liste contenant 100 chanes, toutes dnies sur "DEFAULT" :
List<String> settings = Collections.nCopies(100, "DEFAULT");

Le stockage se rvle peu coteux, lobjet nest stock quune fois. Cest une bonne application de la technique dafchage.
INFO
La classe Collections contient un certain nombre de mthodes commodes dont les paramtres ou les valeurs renvoyes constituent des collections. Attention ne pas les confondre avec linterface Collection.

Lappel de mthode
Collections.singleton(anObject)

renvoie un objet vue qui implmente linterface Set ( la diffrence de ncopies, qui produit une List). Lobjet renvoy implmente un set dlments uniques inaltrable, sans avoir la charge de la structure de donnes. Les mthodes singletonList et singletonMap se comportent de la mme manire.

Sous-ensembles
Vous pouvez crer des vues de sous-ensembles pour un certain nombre de collections. Par exemple, supposons que vous ayez une liste staff et que vous vouliez en extraire les lments 10 19. Vous pouvez vous servir de la mthode subList pour obtenir une vue dans le sous-ensemble de la liste.
List group2 = staff.subList(10, 20);

Chapitre 13

Collections

687

Le premier indice est inclusif et le second exclusif, exactement comme les paramtres de lopration substring de la classe String. Nimporte quelle opration peut tre applique un sous-ensemble et elle rete automatiquement la liste entire. Par exemple, vous pouvez effacer entirement un sous-ensemble :
group2.clear(); // rduction de staff

Les lments sont automatiquement effacs de la liste staff et group2 est vide. Pour les sets et les cartes tris, il faut se servir de lordre relatif et pas de la position de llment pour former un sous-ensemble. Linterface SortedSet dclare trois mthodes :
SortedSet<E> subSet(E from, E to) SortedSet<E> headSet(E to) SortedSet<E> tailSet(E from)

Celles-ci renvoient les sous-ensembles de tous les lments suprieurs ou gaux largument from et strictement infrieurs to. Pour les cartes tries, les mthodes similaires
SortedMap<K, V> subMap(K from, K to) SortedMap<K, V> headMap(K to) SortedMap<K, V> tailMap(K from)

renvoient des vues de la carte composes de toutes les entres dont les cls se trouvent dans lensemble spci. Linterface NavigableSet qui a t introduite dans Java SE 6 offre plus de contrle sur ces oprations de sous-ensembles. Vous pouvez indiquer si ces limites sont incluses :
NavigableSet<E> subSet(E from, boolean fromInclusive, E to, boolean toInclusive) NavigableSet<E> headSet(E to, boolean toInclusive) NavigableSet<E> tailSet(E from, boolean fromInclusive)

Vues non modiables


La classe Collections possde des mthodes qui produisent des vues non modiables de collections. Ces vues ajoutent une collection existante une vrication se produisant en cours dexcution. Si une tentative de modication de la collection est dtecte, une exception est dclenche et la collection demeure inchange. Les vues non modiables sobtiennent par six mthodes :
Collections.unmodifiableCollection Collections.unmodifiableList Collections.unmodifiableSet Collections.unmodifiableSortedSet Collections.unmodifiableMap Collections.unmodifiableSortedMap

Chaque mthode est dnie de manire fonctionner sur une interface. Collection.unmodifiableList, par exemple, fonctionne avec un ArrayList, un LinkedList ou toute autre classe qui implmente linterface List. Par exemple, supposons que vous souhaitiez quune partie de votre programme puisse examiner (mais pas modier) le contenu dune collection. Voici un exemple de ce que vous pourriez envisager :

688

Au cur de Java 2 - Notions fondamentales

List<String> staff = new LinkedList<String>(); ... lookAt(new Collection.unmodifiableList(staff));

La mthode Collections.unmodifiableList renvoie un objet dune classe implmentant linterface List. Sa mthode daccs recherche des valeurs dans la collection staff. Naturellement, la mthode lookAt peut appeler toutes les mthodes de linterface List, et pas seulement les mthodes daccs. Mais toutes les mthodes de modication (comme add) ont t rednies pour lancer une UnsupportedOperationException au lieu de propager lappel la collection sous-jacente. La vue non modiable ne rend pas la collection inaltrable. Vous pouvez toujours la modier via sa rfrence initiale (staff, dans notre cas). Vous pouvez toujours appeler des mthodes de modication sur les lments de la collection. Puisque les vues enveloppent linterface et non lobjet de collection rel, vous navez accs quaux mthodes dnies dans linterface. Ainsi, la classe LinkedList possde des mthodes, addFirst et addLast, qui ne font pas partie de linterface List. Ces mthodes ne sont pas accessibles par la vue non modiable.
ATTENTION
La mthode unmodifiableCollection (ainsi que les mthodes synchronizedCollection et checkedCollection, sur lesquelles nous reviendrons un peu plus tard dans cette section) renvoie une collection dont la mthode equals ninvoque pas la mthode equals de la collection sous-jacente. En fait, elle hrite de la mthode equals de la classe Object, qui se contente de tester si deux objets sont identiques. Si vous transformez un set ou une liste en simple collection, vous ne pouvez plus tester lidentit de leur contenu. La vue se comporte de cette manire parce quun test dgalit nest pas bien dni ce niveau de hirarchie. La vue traite la mthode hashCode de la mme manire. Cependant, les classes unmodifiableSet et unmodifiableList ne cachent pas les mthodes equals et hashCode des collections sous-jacentes.

Vues synchronises
Si vous accdez une collection depuis plusieurs threads, il est trs important que la collection ne soit pas accidentellement endommage. Par exemple, il serait dsastreux quun thread tente dajouter un lment dans une table de hachage alors quun autre thread essaierait den rorganiser les lments. Au lieu dimplmenter des classes de collection compatibles avec les threads, les concepteurs de la bibliothque ont cr un mcanisme qui gnre des collections ordinaires compatibles avec les threads. Par exemple, la classe statique synchronizedMap de la classe Collections peut transformer nimporte quelle carte en carte possdant des mthodes daccs synchronises :
Map<String, Employee> = Collections.synchronizedMap( new hashMap<String, Employee>());

Vous pouvez dornavant accder lobjet map depuis plusieurs threads. Les mthodes comme get et put sont synchronises, cest--dire que chaque appel lune delles doit tre entirement termin avant quun autre thread puisse appeler une autre mthode. Nous traiterons du problme de laccs synchronis aux structures de donnes plus en dtail au Chapitre 14.

Chapitre 13

Collections

689

Vues sous contrle


Java SE 5.0 ajoute un set de vues "sous contrle" destines dboguer un problme survenant avec les types gnriques. Comme nous lavons vu au Chapitre 12, il est en fait possible de faire entrer des lments du mauvais type dans une collection gnrique. Par exemple :
ArrayList<String> strings = new ArrayList rawList = strings; // // // rawList.add(new Date()); // ArrayList<String>(); un avertissement uniquement, non une erreur, pour des raisons de compatibilit avec le code existant maintenant, strings contient un objet Date!

La commande add errone nest pas dtecte au moment de lexcution. Une exception de transtypage de classe surviendra plus tard lorsquune autre partie du code appellera get et transtypera le rsultat sur un String. Une vue sous contrle peut dtecter ce problme. Dnissez une liste sre comme suit :
List<String> safeStrings = Collections.checkedList(strings, String.class);

La mthode add de la vue vrie que lobjet insr appartient la classe donne et dclenche immdiatement une ClassCastException si ce nest pas le cas. Avantage, lerreur est signale lemplacement correct :
ArrayList rawList = safeStrings; rawList.add(new Date()); // La liste sous contrle dclenche // une ClassCastException

ATTENTION
Les vues sous contrle sont limites par les vrications dexcution que la machine virtuelle peut raliser. Si vous avez par exemple un ArrayList<Pair<String>>, vous ne pouvez pas le protger de linsertion dun Pair<Date> puisque la machine virtuelle possde une seule classe Pair "brute".

Une dernire note sur les oprations optionnelles


Les vues possdent gnralement quelques restrictions : elles peuvent tre accessibles uniquement en lecture, leur taille nest parfois pas modiable. Dautres encore peuvent supprimer lun de leurs lments, mais ne peuvent pas en insrer de nouveaux, comme pour une vue de cl dune carte. Une vue restreinte dclenche une UnsupportedOperationException si vous tentez une opration inapproprie. Dans la documentation de lAPI pour les interfaces de collection et ditrateur, certaines mthodes sont dcrites comme des "oprations optionnelles". Ceci semble contredire la notion mme dinterface, car cest bien son but que de fournir les mthodes quune classe doit implmenter. Cet agencement est en fait insatisfaisant du point de vue thorique. Une meilleure solution aurait consist concevoir des interfaces spares pour les vues en lecture seule et celles qui ne peuvent pas modier la taille dune collection. Mais cela aurait tripl le nombre dinterfaces, ce que les concepteurs de la bibliothque ont jug inacceptable. Pourquoi ne pas tendre la technique des mthodes "optionnelles" vos propres interfaces ? Nous pensons que cela nest pas conseill. Bien que les collections soient utilises trs frquemment, le style de programmation inhrent leur utilisation ne correspond pas forcment tous les types de problmes. Les concepteurs dune bibliothque de classes de collection doivent gnralement

690

Au cur de Java 2 - Notions fondamentales

rsoudre un ensemble complexe de conits. Les utilisateurs veulent quune bibliothque soit simple apprendre, facile demploi, sufsamment gnrale, sans incohrences, et en mme temps aussi efcace quun algorithme optimis la main. Il est parfaitement impossible datteindre simultanment tous ces buts, voire de sen rapprocher. En fait, vous rencontrerez rarement des problmes aussi complexes lorsque vous crirez vos propres programmes. Vous devriez trouver des solutions qui ne sont pas fondes sur des mesures aussi extrmes que des oprations dinterfaces "optionnelles".
java.util.Collections 1.2

static <E> Collection unmodifiableCollection(Collection<E> c) static <E> List unmodifiableList(List<E> c) static <E> Set unmodifiableSet(Set<E> c) static <E> SortedSet unmodifiableSortedSet(SortedSet<E> c) static <K, V> Map unmodifiableMap(Map<K, V> c) static <K, V> SortedMap unmodifiableSortedMap(SortedMap<K, V> c)

Construisent une vue de la collection dont les mthodes de modication peuvent dclencher une UnsupportedOperationException.

static <E> Collection<E> synchronizedCollection(Collection<E> c) static <E> List synchronizedList(List<E> c) static <E> Set synchronizedSet(Set<E> c) static <E> SortedSet synchronizedSortedSet(SortedSet<E> c) static <K, V> Map<K, V> synchronizedMap(Map<K, V> c) static <K, V> SortedMap<K, V> synchronizedSortedMap(SortedMap<K, V> c)

Construisent une vue de la collection dont les mthodes sont synchronises.


static <E> Collection checkedCollection(Collection<E> c, Class<E> elementType) static <E> List checkedList(List<E> c, Class<E> elementType) static <E> Set checkedSet(Set<E> c, Class<E> elementType) static <E> SortedSet checkedSortedSet(SortedSet<E> c, Class<E> elementType) static <K, V> Map checkedMap(Map<K, V> c, Class<K> keyType, Class<V> valueType) static <K, V> SortedMap checkedSortedMap(SortedMap<K, V> c, Class<K> keyType, Class<V> valueType)

Construisent une vue de la collection dont les mthodes dclenchent une ClassCastException en cas dinsertion dun lment dun mauvais type.

static <E> List<E> nCopies(int n, E value) static <E> Set<E> singleton(E value)

Construisent une vue de lobjet soit comme une liste non modiable comprenant n lments identiques, soit comme un set contenant un seul lment.
java.util.Arrays 1.2

static <E> List<E> asList(E... array)

Renvoie une vue en liste des lments dun tableau, modiable mais non redimensionnable.
java.util.List<E> 1.2

List<E> subList(int firstIncluded, int firstExcluded)

Renvoie une vue de liste partir des lments compris entre deux positions.

Chapitre 13

Collections

691

java.util.SortedSet<E> 1.2

SortedSet<E> subSet(E firstIncluded, E firstExcluded) SortedSet<E> headSet(E firstExcluded) SortedSet<E> tailSet(E firstIncluded)

Renvoient une vue des lments compris entre deux positions.


java.util.NavigableSet<E> 6

NavigableSet<E> subSet(E from, boolean fromIncluded, E to, boolean toIncluded) NavigableSet<E> headSet(E to, boolean toIncluded) NavigableSet<E> tailSet(E from, boolean fromIncluded)

Renvoient une vue des lments dans une plage. Les balises boolean dterminent si la limite est incluse dans la vue.
java.util.SortedMap<K, V> 1.2

SortedMap<K, V> subMap(K firstIncluded, K firstExcluded) SortedMap<K, V> headMap(K firstExcluded) SortedMap<K, V> tailMap(K firstIncluded)

Renvoient une vue de la carte dont les entres sont comprises entre deux positions.
java.util.NavigableMap<K, V> 6

NavigableMap<K, V> subMap(K from, boolean fromIncluded, K to, boolean toIncluded) NavigableMap<K, V> headMap(K from, boolean fromIncluded) NavigableMap<K, V> tailMap(K to, boolean toIncluded)

Renvoient une vue de carte des entres dont les cls se trouvent entre deux positions. Les balises boolean dterminent si la limite est incluse dans la vue.

Les oprations de masse


Jusqu maintenant, la plupart de nos exemples se servaient dun itrateur pour parcourir un par un les lments dune collection. Mais il est possible dviter de parcourir tous ces lments en ayant recours lune des oprations de masse (ou bulk operation) de la bibliothque. Supposons que vous cherchiez lintersection de deux ensembles, cest--dire les lments communs chacun des deux ensembles. Commenons par crer un nouvel ensemble destin contenir le rsultat :
Set<String> result = new HashSet<String>(a);

Nous utilisons ici le fait que toutes les collections possdent un constructeur dont les paramtres sont une autre collection renfermant les valeurs dinitialisation. Appelons maintenant la mthode retainAll :
result.retainAll(b);

Cette mthode conserve tous les lments qui sont aussi dans b. Vous venez de dterminer lintersection sans programmer de boucle.

692

Au cur de Java 2 - Notions fondamentales

Pourquoi ne pas continuer sur cette lance et appliquer une opration de masse une vue ? Supposons par exemple que vous disposiez dune carte associant des numros de salaris des objets employee et que vous possdiez un ensemble de numros de salaris qui vont tre licencis.
Map<String, Employee> staffMap = . . .; Set<String> terminatedIDs = . . .;

Il suft de crer un ensemble de cls et de supprimer tous les numros de salaris licencis.
staffMap.keySet().removeAll(terminatedIDs);

Comme lensemble de cls constitue une vue de la carte, les cls et les noms de salaris associs sont automatiquement supprims de la carte. En utilisant une vue dun sous-ensemble, vous pouvez restreindre les oprations de masse des sous-ensembles et des sous-listes. Par exemple, supposons que vous vouliez ajouter les dix premiers lments dune liste dans un autre conteneur. Il suft de former une sous-liste correspondant aux dix premiers lments :
relocated.addAll(staff.subList(0, 10));

Le sous-ensemble peut aussi tre la cible dune opration de mutation :


staff.subList(0 , 10).clear();

Conversion entre collections et tableaux


Comme une grande partie de lAPI de la plate-forme Java a t conue avant lintroduction du cadre des collections, vous aurez parfois besoin dadapter les anciens tableaux en collections plus rcentes. Si vous possdez un tableau, vous devez le convertir en collection. Lemballage Arrays.asList sert cela. Par exemple :
String[] values = . . .; HashSet<String> staff = new HashSet<String>(Arrays.asList(values));

Lobtention dun tableau partir dune collection est lgrement plus astucieuse. Naturellement, vous pouvez vous servir de la mthode toArray :
Object[] values = staff.toArray();

Mais il en rsulte un tableau dobjets. Mme si vous savez que votre collection contient des objets dun type spcique, vous ne pouvez pas recourir une conversion de type :
String[] values = (String[]) staff.toArray(); // Erreur!

Le tableau renvoy par la mthode toArray a t cr comme un tableau Object[], et son type ne peut pas tre modi. Pour contourner ce problme, vous utilisez une variante de la mthode toArray. Passez-lui un tableau de longueur nulle et du type qui vous intresse. Le tableau cr est du mme type que le type spci :
String[] values = staff.toArray(new String[0]);

Si vous le souhaitez, vous pouvez construire le tableau, de sorte quil ait la bonne taille :
staff.toArray(new String[staff.size()]);

Dans ce cas, aucun nouveau tableau nest cr.

Chapitre 13

Collections

693

INFO
Vous vous demandez peut-tre pourquoi vous ne pouvez pas passer directement un objet Class (comme String.class) la mthode toArray. Cette mthode est double emploi, cest--dire quelle peut la fois remplir un tableau existant (en supposant quil est assez grand) et crer un nouveau tableau.

Algorithmes
Les interfaces de collection gnrales possdent un grand avantage : il suft dimplmenter vos algorithmes une seule fois. Par exemple, considrons un algorithme simple qui calcule llment maximal dune collection. Les programmeurs sont tents par lapproche traditionnelle qui consiste implmenter ce genre dalgorithme dans une boucle. Voici comment trouver le plus grand lment dun tableau :
if (a.length == 0) throw new NoSuchElementException(); T largest = a[0]; for (int i = 1; i < a.length; i++) if (largest.compareTo(a[i]) < 0) largest = a[i];

Naturellement, pour trouver llment maximal dune liste de tableau, le code doit tre lgrement modi.
if (v.size() == 0) throw new NoSuchElementException(); T largest = v.get(0); for (int i = 1; i < v.size(); i++) if (largest.compareTo(v.get(i)) < 0) largest = v.get(i);

Et pour une liste chane ? Comme vous ne disposez pas dun accs alatoire aux lments dune liste chane, vous pouvez vous servir dun itrateur :
if (l.isEmpty()) throw new NoSuchElementException(); Iterator<T> iter = l.iterator(); T largest = iter.next(); while (iter.hasNext()) { T next = iter.next(); if (largest.compareTo(next) < 0) largest = next; }

Ces boucles sont plutt pnibles crire et elles peuvent engendrer des erreurs. Nexiste-t-il pas une mthode plus simple ? Est-ce que ces boucles fonctionnent correctement avec des conteneurs vides ? Avec des conteneurs ne contenant quun seul lment ? Vous navez probablement pas envie de tester et de dboguer ce code pour chaque cas particulier, mais vous voulez probablement aussi viter dimplmenter lune des mthodes lentes suivantes :
static <T extends Comparable> T max( T[] a) static <T extends Comparable> T max( ArrayList<T> v) static <T extends Comparable> T max( LinkedList<T> l)

Cest dans ce contexte que les interfaces de collection entrent en jeu. Imaginez une interface de collection minimale pour grer efcacement lalgorithme. Les mthodes daccs alatoire comme

694

Au cur de Java 2 - Notions fondamentales

get et set sont considrer comme des mthodes de plus haut niveau quune simple itration. Comme nous lavons vu dans le calcul de la valeur maximale dune liste chane, ces mthodes daccs alatoire ne sont pas forcment ncessaires. Le calcul de llment maximal peut tre effectu par une simple itration sur tous les lments. Par consquent, vous pouvez implmenter la mthode max pour prendre en compte nimporte quel objet qui implmente linterface Collection.
public static <T extends Comparable> T max( Collection<T> c) { if (c.isEmpty()) throw new NoSuchElementException(); Iterator<T> iter = c.iterator(); T largest = iter.next(); while (iter.hasNext()) { T next = iter.next(); if (largest.compareTo(next) < 0) largest = next; } return largest; }

Vous pouvez maintenant calculer la valeur maximale dune liste chane, dune liste de tableau ou dun tableau avec une seule mthode. Il sagit dun concept puissant. En fait, la bibliothque standard du C++ possde des douzaines dalgorithmes pratiques, qui fonctionnent tous partir dune collection gnrale. La bibliothque Java nest pas aussi riche, mais elle contient les algorithmes essentiels : un tri, une recherche binaire et dautres algorithmes pratiques.

Trier et mlanger
Les vieux routiers de linformatique se rappellent peut-tre les cartes perfores quils devaient utiliser et les algorithmes de tri quils devaient programmer la main. Heureusement, de nos jours, les algorithmes de tri font partie des bibliothques standard de la plupart des langages de programmation et Java ny fait pas exception. La mthode sort de la classe Collections sert trier une collection qui implmente linterface List.
List<String> staff = new LinkedList<String>(); // remplissage de la collection . . .; Collections.sort(staff);

Cette mthode part de lhypothse que les lments de la liste implmentent linterface Comparable. Si vous voulez trier une liste dune autre manire, vous pouvez spcier un objet Comparator en second argument. Nous avons abord les comparateurs dans la section traitant des ensembles tris. Voici un exemple pour trier une liste dlments :
Comparator<Item> itemComparator = new Comparator<Item>() { public int compare(Item a, Item b) { return a.partNumber - b.partNumber; } }); Collections.sort(items, itemComparator);

Chapitre 13

Collections

695

Si vous souhaitez trier une liste par ordre dcroissant, il suft dutiliser la mthode statique Collections.reverseOrder(). Elle renvoie un comparateur qui renvoie b.compareTo(a). Par exemple,
Collections.sort(staff, Collections.reverseOrder())

trie les lments de la liste staff par ordre dcroissant, selon lordre fourni par la mthode compareTo du type dlment. De mme,
Collections.sort(items, Collections.reverseOrder(itemComparator))

inverse lordre ditemComparator. Vous vous demandez peut-tre comment la mthode sort trie une liste. Typiquement, les algorithmes de tri que vous pourrez trouver dans des livres sur les algorithmes se servent de tableaux et accdent leurs lments avec une mthode daccs alatoire. Mais nous savons maintenant que les accs alatoires ne sont pas optimiss pour les listes. En fait, vous pouvez trier efcacement une liste grce un algorithme appel tri fusion (voir, par exemple, Algorithms in C++, par Robert Sedgewick, Addison-Wesley, 1998, p. 366 369). Cependant, limplmentation Java ne se sert pas de cet algorithme. Elle se contente de recopier tous les lments de la liste dans un tableau, de trier le tableau et de recopier le tableau tri dans la liste dorigine. Lalgorithme de tri fusion utilis dans la bibliothque de collections est lgrement plus lent que le quick sort, qui est lalgorithme retenu gnralement pour trier les donnes les plus gnrales. Il prsente pourtant un avantage majeur : il est stable, cest--dire quil nintervertit pas des lments identiques. En quoi lordre des lments identiques est-il important ? Voici un scnario classique : supposons que vous disposiez dune liste demploys que vous avez dj trie par noms et que vous vouliez maintenant la trier par salaires. Que se passe-t-il pour les employs ayant le mme salaire ? Avec un tri stable, le classement dorigine par noms est conserv. En dautres termes, le rsultat est une liste trie dabord par salaires, puis par noms. Comme les collections nont pas besoin dimplmenter toutes leurs mthodes "optionnelles", toutes les mthodes recevant des collections en paramtre doivent indiquer sil est possible de passer sans danger une collection un algorithme. Par exemple, il est vident que vous ne pouvez pas passer une liste unmodifiableList lalgorithme sort. Quelle sorte de liste peut-on alors lui passer ? Si lon sen rfre la documentation, la liste doit tre modiable, mais sa taille na pas besoin dtre modiable. Voici la dnition de ces deux critres :
m m

Une liste est modiable si elle supporte la mthode set. La taille dune liste est modiable si elle supporte les oprations add et remove.

La classe Collections possde un algorithme shuffle qui effectue le contraire dun tri, cest-dire qui permute alatoirement les lments dune liste. Par exemple,
ArrayList<Card> cards = . . .; Collections.shuffle(cards);

Si vous fournissez une liste qui nimplmente pas linterface RandomAccess, la mthode shuffle copie les lments dans un tableau, mlange le tableau et recopie les lments mlangs dans la liste. Le programme du Listing 13.6 remplit une liste avec 49 objets Integer contenant les nombres 1 49. Puis il mlange la liste alatoirement et slectionne les six premires valeurs de la liste. Pour terminer, il trie les valeurs slectionnes et les afche.

696

Au cur de Java 2 - Notions fondamentales

Listing 13.6 : ShufeTest.java


import java.util.*; /** * Ce programme montre les algorithmes de tri et de mlange alatoire. * @version 1.10 2004-08-02 * @author Cay Horstmann */ public class ShuffleTest { public static void main(String[] args) { List<Integer> numbers = new ArrayList<Integer>(); for (int i = 1; i <= 49; i++) numbers.add(i); Collections.shuffle(numbers); List<Integer> winningCombination = numbers.subList(0, 6); Collections.sort(winningCombination); System.out.println(winningCombination); } } java.util.Collections 1.2

static <T extends Comparable<? super T>> void sort(List<T> elements) static <T> void sort(List<T> elements, Comparator<? super T> c)

Trient les lments de la liste avec un algorithme de tri stable. La complexit de cet algorithme est O(n log n), o n correspond au nombre dlments de la liste.

static void shuffle(List<?> elements) static void shuffle(List<?> elements, Random r)

Mlangent alatoirement les lments de la liste. La complexit de cet algorithme est O(n a(n)), o n correspond la longueur de la liste et o a(n) est le temps moyen daccs un lment.

static <T> Comparator<T> reverseOrder()

Renvoie un comparateur qui trie les lments par ordre dcroissant par rapport la mthode compareTo de linterface Comparable.

static <T> Comparator<T> reverseOrder(Comparator<T> comp)

Renvoie un comparateur qui trie les lments dans lordre inverse de celui donn par comp.

Recherche binaire
En principe, pour trouver un objet dans un tableau, il faut parcourir tous ses lments jusqu trouver celui que vous cherchez. Cependant, lorsquun tableau est tri, vous pouvez commencer par examiner la valeur de llment situ au milieu du tableau et vrier si cette valeur est suprieure celle de lobjet que vous cherchez. Dans ce cas, il vous suft de chercher lobjet dans la premire moiti du tableau. Sinon, llment cherch se trouve dans la seconde moiti du tableau. Cette opration divise le problme en deux. Vous pouvez alors continuer chercher lobjet en utilisant la mme technique. Ainsi, si le tableau comprend 1 024 lments, vous trouverez nimporte lequel de ses lments en 10 tapes au maximum, alors quune recherche squentielle aurait ncessit en moyenne 512 tapes

Chapitre 13

Collections

697

si llment cherch fait bien partie du tableau, et il aurait fallu 1 024 tapes pour conrmer labsence dun lment dans ce tableau. La recherche binaire (binarySearch) de la classe Collections implmente cet algorithme. Notons que la collection doit dj tre trie, sinon cet algorithme renvoie une rponse errone. Pour trouver un lment, il suft de fournir une collection qui doit implmenter linterface List (voir la note ci-aprs) et llment rechercher. Si la collection na pas t trie par llment compareTo de linterface Comparable, il faut galement fournir le comparateur utilis.
i = Collections.binarySearch(c, element); i = Collections.binarySearch(c, element, comparator);

Si la valeur renvoye par la recherche binaire est suprieure ou gale zro, cette valeur correspond lindice de llment trouv. Cest--dire que c.get(i) est gal element au sens du comparateur. Si la valeur est ngative, cela signie que llment na pas pu tre trouv dans la collection. De plus, vous pouvez vous servir de la valeur renvoye pour calculer lemplacement o il faudrait insrer llment pour que la collection reste trie. Cet indice correspond :
insertionPoint = -i - 1;

Ce nest pas simplement -i, car cela mnerait des rsultats ambigus si la valeur renvoye tait nulle. En dautres termes, lopration
if (i < 0) c.add(-i - 1, element);

ajoute llment la bonne place. Pour rester valable, une recherche binaire doit bncier dun accs alatoire aux donnes de la collection. Si vous devez parcourir tous les lments jusqu la moiti du tableau pour trouver llment du milieu, lavantage principal de la recherche binaire est perdu. Par consquent, lalgorithme binarySearch se transforme en recherche linaire si vous lui fournissez une liste chane.
INFO
Java SE 1.3 ne possdait aucune interface spare pour une collection trie avec un accs alatoire efcace : la mthode binarySearch se sert dune technique trs grossire pour dterminer si le paramtre de la liste prolonge la classe AbstractSequentialList. Ceci a t rsolu dans la version 1.4. Dsormais, la mthode binarySearch vrie si le paramtre de la liste implmente linterface RandomAccess. Si cest le cas, la mthode ralise une recherche binaire. Sinon, elle effectue une recherche linaire.

java.util.Collections 1.2

static <T extends Comparable<? super T>> int binarySearch(List<T> elements, T key) static <T> int binarySearch(List<T> elements, T key, Comparator<? super T> c)

Cherchent une cl dans une liste chane avec une recherche linaire si les lments tendent la classe AbstractSequentialList. Une recherche binaire est utilise dans tous les autres cas. La complexit de ces mthodes est O(a(n) log n), o n est la longueur de la liste et o a(n) correspond au temps moyen daccs un lment quelconque. Ces mthodes renvoient soit lindice de la cl dans la liste, soit une valeur ngative i si la cl ne fait pas partie de la liste. Dans ce cas, la cl devrait tre insre la position i 1 pour que la liste reste trie.

698

Au cur de Java 2 - Notions fondamentales

Algorithmes simples
La classe Collections contient plusieurs algorithmes simples, mais trs utiles. Nous retrouvons notamment parmi ces algorithmes lexemple du dbut de cette section, la recherche de la valeur maximale dune collection. Les autres algorithmes couvrent des sujets aussi varis que la copie des lments dune liste dans une autre liste, le remplissage dun conteneur avec une valeur constante, ou linversion dune liste. Pourquoi fournir des algorithmes aussi simples dans une bibliothque standard ? Il est probable que la plupart des programmeurs pourraient les implmenter facilement avec des boucles simples. Nous aimons bien ces algorithmes, car ils simplient la vie des programmeurs et surtout parce quils augmentent la lisibilit des programmes. Lorsque vous lisez une boucle qui a t implmente par un autre programmeur, vous devez commencer par dcrypter les intentions de ce programmeur. Alors que si vous voyez un appel une mthode comme Collections.max, vous devinez immdiatement ce que ralise cette instruction. Les notes dAPI suivantes dcrivent les algorithmes simples de la classe Collections.
java.util.Collections 1.2

static <T extends Comparable<? super T>> T min(Collection<T> elements) static <T extends Comparable<? super T>> T max(Collection<T> elements) static <T> min(Collection<T> elements, Comparator<? super T> c) static <T> max(Collection<T> elements, Comparator<? super T> c)

Renvoient le plus petit ou le plus grand lment de la collection (les bornes des paramtres sont simplies pour plus de clart).

static <T> void copy(List<? super T> to, List<T> from)

Copie tous les lments de la liste source la mme position dans la liste cible. La liste cible doit tre au moins aussi longue que la liste source.

static <T> void fill(List<? Super T> l, T value)

Remplit toutes les positions dune liste avec la mme valeur.


static <T> boolean addAll(Collection<? super T>, c, T values) 5.0

Ajoute toutes les valeurs la collection donne et renvoie true si la collection a chang en consquence.

static <T> boolean replaceAll(List<T> 1, T oldValue, T newValue) 1.4

Remplace tous les lments gaux oldValue par newValue.


static int indexOfSubList(List<?> 1, List<?> s) 1.4 static int lastIndexOfSubList(List<?> 1, List<?> s) 1.4

Renvoient lindice de la premire ou de la dernire sous-liste de 1, gale s ou 1 lorsque aucune sous-liste de 1 ngale s. Par exemple, si 1 est gal [s, t, a, r] et s [t, a, r], les deux mthodes renvoient lindice 1.

static void swap(List<?> l, int i, int j) 1.4

Echange les lments aux dcalages donns.


static void reverse(List<?> l)

Inverse lordre des lments dune liste. Par exemple, linversion de la liste [t, a, r] produit la liste [r, a, t]. La complexit de cette mthode est O(n), o n correspond la longueur de la liste.

Chapitre 13

Collections

699

static void rotate(List<?> l, int d) 1.4

Fait tourner les lments dans la liste, en dplaant les entres avec indice i la position (i + d) % 1.size(). Par exemple, le dplacement de la liste [t, a, r] de 2 produit la liste [a, r, t]. La complexit de cette mthode est O(n), o n correspond la longueur de la liste.

static int frequency(Collection<?> c, Object o) 5.0

Renvoie le compte dlments dans c, gal lobjet o.


boolean disjoint(Collection<?> c1, Collection<?> c2) 5.0

Renvoie true si les collections nont pas dlments en commun.

Ecrire vos propres algorithmes


Si vous crivez vos propres algorithmes (ou en fait nimporte quelle mthode prenant une collection en paramtre), vous devriez travailler avec des interfaces, et non avec des implmentations concrtes, lorsque cela est possible. Par exemple, supposons que vous vouliez remplir un JMenu avec un ensemble dlments de menu. Normalement, une telle mthode peut tre implmente comme ceci :
void fillMenu(JMenu menu, ArrayList<JMenuItem> items) { for (JMenuItem item: items) menu.addItem(item); }

Cependant, vous obligez les utilisateurs de cette mthode placer les chanes dans un ArrayList. Si ces chanes se trouvent tre dans un autre conteneur, elles devraient tre adaptes en vecteur. En fait, il vaut mieux accepter des collections beaucoup plus gnrales en paramtre. Il faut vous demander quelle est linterface de collection la plus gnrale pouvant faire laffaire. Dans notre cas, il suft de parcourir tous les lments, ce qui est lune des caractristiques de base de linterface Collection. Voici comment rcrire une version de la mthode fillMenu qui accepte nimporte quel type de collection :
void fillMenu(JMenu menu, Collection<JMenuItem> items) { for (JMenuItem item: items) menu.addItem(item); }

Dsormais, nimporte qui peut appeler cette mthode avec un ArrayList, un LinkedList ou mme un tableau, emball avec lemballage Arrays.asList.
INFO
Si lutilisation dinterfaces de collection en paramtres des mthodes est une si bonne ide, pourquoi la bibliothque Java ne suit-elle pas plus souvent cette rgle ? Par exemple, la classe JComboBox possde deux constructeurs :
JComboBox(Object[] items) JComboBox(Vector<?> items)

La raison en est trs simple. La bibliothque Swing a t conue avant la bibliothque de collections.

700

Au cur de Java 2 - Notions fondamentales

Si vous crivez une mthode qui renvoie une collection, vous pouvez aussi vouloir renvoyer une interface au lieu dune classe car vous pourrez alors changer davis et rimplanter la mthode par la suite avec une collection diffrente. Par exemple, crivons une mthode getAllItems qui renvoie tous les lments dun menu :
List<MenuItem> getAllItems(JMenu menu) { ArrayList<MenuItem> items = new ArrayList<MenuItem>(); for (int i = 0; i < menu.getItemCount(); i++) items.add(menu.getItem(i)); return items; }

Vous pouvez ensuite dcider que vous ne voulez pas copier les lments mais simplement en fournir une vue. Cela peut tre ralis en renvoyant une sous-classe anonyme de AbstractList :
List<MenuItem> getAllItems(final JMenu menu) { return new AbstractList<MenuItem>() { public MenuItem get(int i) { return item.getItem(i); } public int size() { return item.getItemCount(); } }; }

Naturellement, il sagit dune technique avance. Si vous vous en servez, prenez soin de documenter avec exactitude quelles oprations "optionnelles" sont supportes. Dans ce cas, vous devez avertir lutilisateur que lobjet renvoy est une liste non modiable.

Les anciennes collections


Dans cette partie, nous abordons les classes de collection qui sont prsentes dans le langage de programmation Java depuis son dbut : la classe Hashtable et sa sous-classe utile Properties, la sous-classe Stack de Vector, et la classe BitSet.

La classe Hashtable
La classe Hashtable a le mme objectif que la classe HashMap et possde essentiellement la mme interface. Comme pour les mthodes de la classe Vector, les mthodes Hashtable sont synchronises. Si vous navez aucun besoin dune synchronisation ou dune compatibilit avec les anciens programmes, vous devriez utiliser HashMap la place de cette classe.

Chapitre 13

Collections

701

INFO
Le nom exact de cette classe est Hashtable, avec un t minuscule. Sous Windows, vous obtiendrez des messages derreur tranges si vous crivez HashTable, parce que le systme de chiers de Windows ne fait pas de diffrence entre les majuscules et les minuscules, contrairement au compilateur Java.

Les numrations
Les anciennes collections se servent de linterface Enumeration pour parcourir des squences dlments. Linterface Enumeration possde deux mthodes, hasMoreElements et nextElement. Ces mthodes sont les quivalents des mthodes hasNext et next de linterface Iterator. Par exemple, la mthode elements de la classe Hashtable fournit un objet pour passer en revue les valeurs de la table :
Enumeration<Employee> e = staff.elements(); while (e.hasMoreElements()) { Employee e = e.nextElement(); . . . }

Vous rencontrerez parfois une ancienne mthode qui attend un paramtre dnumration. La mthode statique Collections.enumeration fournit un objet dnumration qui passe en revue les lments dune collection. Par exemple :
List<InputStream> streams = . . .; SequenceInputStream in = new SequenceInputStream(Collections.enumeration(streams)); // Le constructeur SequenceInputStream attend une numration.

INFO
En C++, il est assez courant dutiliser des itrateurs en paramtre. Heureusement, lorsque vous programmez pour la plate-forme Java, trs peu de programmeurs se servent de ce concept. Il est beaucoup plus rus de passer une collection que de passer un itrateur : une collection est bien plus utile. Les conteneurs peuvent toujours obtenir un itrateur de la collection sils en ont rellement besoin. De plus, ils ont toutes les mthodes de collection leur disposition. Cependant, vous trouverez des numrations dans certains vieux programmes parce quils taient les seuls outils disponibles pour des collections gnrales jusqu lapparition du cadre des collections dans Java SE 1.2.

java.util.Enumeration<E> 1.0

boolean hasMoreElements()

Renvoie true sil reste dautres lments parcourir.


E nextElement()

Renvoie le prochain lment parcourir. Nappelez pas cette mthode si hasMoreElements() renvoie false.
java.util.Hashtable<K, V> 1.0

Enumeration<K> keys()

Renvoie un objet numration qui parcourt les cls dune table de hachage.

702

Au cur de Java 2 - Notions fondamentales

Enumeration<V> elements()

Renvoie un objet numration qui parcourt les lments dune table de hachage.
java.util.Vector<E> 1.0

Enumeration<E> elements()

Renvoie un objet numration qui parcourt les lments dun vecteur.

Cartes de proprits
Une carte de proprits est une structure de carte dun type trs spcial, afchant trois caractristiques particulires :
m m m

Les cls et les valeurs sont des chanes. La table peut tre enregistre dans un chier et charge partir dun chier. Il existe une table secondaire contenant les paramtres par dfaut.

La classe Java qui implmente une carte de proprits est appele Properties. Les cartes de proprits permettent souvent de spcier des options de conguration pour des programmes (voir Chapitre 10).
java.util.Properties 1.0

Properties()

Cre une carte de proprits vide.


Properties(Properties defaults)

Cre une carte de proprits avec un ensemble de valeurs par dfaut.


String getProperty(String key)

Renvoie la chane associe une cl, ventuellement dans la table de valeurs par dfaut si la cl ne gure pas dans la carte spcie.

String getProperty(String key, String defaultValue)

Trouve une proprit ou sa valeur par dfaut si la cl na pas t trouve. Renvoie une chane associe la cl, ou une valeur par dfaut si la cl ne gure pas dans la carte.

void load(InputStream in)

Charge une carte de proprits partir dun InputStream.


void store(OutputStream out, String commentString)

Stocke une carte de proprits dans un OutputStream.

Piles
Depuis la version 1.0, la bibliothque standard possde une classe Stack dote des mthodes familires push et pop. Toutefois, la classe Stack prolonge la classe Vector, qui nest pas satisfaisante dun point de vue thorique : vous pouvez en effet appliquer des oprations de dsempilage comme insert et remove en vue dinsrer et de supprimer des valeurs nimporte quel endroit, et non pas simplement en haut de la pile.

Chapitre 13

Collections

703

java.util.Stack<E> 1.0

E push(E item)

Place un lment sur la pile et le renvoie.


E pop()

Rcupre llment situ sur le dessus de la pile. Nappelez pas cette mthode si la pile est vide.
E peek()

Renvoie llment situ sur le dessus de la pile sans le dpiler. Nappelez pas cette mthode si la pile est vide.

Les ensembles de bits


La classe BitSet sert stocker des squences de donnes binaires. Il ne sagit pas dun ensemble au sens mathmatique du terme. Les termes tableau de bits ou vecteur de bits auraient t plus appropris. Vous pouvez vous servir dun BitSet si vous devez enregistrer une squence de bits de manire efcace. Comme les ensembles de bits runissent leurs informations dans des octets, il est bien plus efcace dutiliser un BitSet quun ArrayList dobjets boolens. La classe BitSet fournit une interface pratique pour lire, crire ou rinitialiser un bit. Vous pouvez vous servir de cette interface pour viter les oprations de manipulation de bits (comme les masques) qui seraient ncessaires si vous stockiez les bits dans des variables int ou long. Par exemple, pour un BitSet appel BucketOfBits,
bucketOfBits.get(i)

renvoie true si le i-me bit est 1, et false dans le cas contraire. De mme,
bucketOfBits.set(i)

met le i-me bit 1. Pour terminer,


bucketOfBits.clear(i)

met le i-me bit 0.


INFO C++
Le modle bitset du C++ possde les mmes fonctionnalits que le BitSet Java.

java.util.BitSet 1.0

BitSet(int intialCapacity)

Construit un ensemble de bits.


int length()

Renvoie la longueur logique dun ensemble de bits : lindice du dernier bit plus 1.
boolean get(int bit)

Lit un bit.
void set(int bit)

Ecrit un bit.

704

Au cur de Java 2 - Notions fondamentales

void clear(int bit)

Efface un bit.
void and(BitSet set)

Effectue un ET logique entre deux ensembles de bits.


void or(BitSet set)

Effectue un OU logique entre deux ensembles de bits.


void xor(BitSet set)

Effectue un OU exclusif logique entre deux ensembles de bits.


void andNot(BitSet set)

Efface tous les bits de cet ensemble de bits qui sont 1 dans lautre ensemble de bits.

Le test du crible dEratosthne


Comme exemple dutilisation densembles de bits, nous voulons vous montrer une implmentation du crible dEratosthne, un algorithme permettant de trouver les nombres premiers. Un nombre premier est un nombre qui nest divisible que par 1 et par lui-mme, et le crible dEratosthne est lune des premires mthodes dcouvertes pour numrer ces nombres premiers. En ralit, il ne sagit pas dun excellent algorithme pour trouver des nombres premiers, mais il est devenu un test trs populaire pour mesurer les performances dun compilateur. En fait, ce nest pas vraiment un excellent test, car il effectue beaucoup doprations sur les bits. Bon, daccord, respectons la tradition : voici une implmentation de ce programme qui compte tous les nombres premiers entre 2 et 2 000 000. Comme il y en a 148 933, vous navez probablement pas envie de tous les afcher. Sans vouloir entrer dans les dtails de ce programme, lide principale est de parcourir un ensemble de bits comprenant 2 millions de bits. Nous commenons par mettre tous ces bits 1. Puis, nous mettons 0 tous les bits qui sont des multiples des nombres premiers que nous avons dj identis. La position des bits qui restent 1 la n du processus fournit directement les nombres premiers. Le Listing 13.7 illustre ce programme en Java et le Listing 13.8 correspond au code C++.
INFO
Mme si ce crible ne constitue pas un excellent test, nous navons pas pu rsister au plaisir de chronomtrer ces deux implmentations. Voici donc les performances observes sur un Thinkpad 1,66 GHz double cur avec 2 gigaoctets de RAM, fonctionnant sous Ubuntu 7.04.
C++ (g++ 4.1.2): 360 millisecondes. Java (Java SE 6): 105 millisecondes.

Nous avons effectu ce test pour sept versions de ce livre et cest la troisime fois que limplmentation Java supplante facilement la version C++. Pour rester honntes, le niveau dimplmentation du compilateur C++ bat aisment Java de soixante millisecondes. Java na pu tre au niveau que si le programme sest excut sufsamment longtemps pour dclencher le compilateur Hotspot en juste--temps.

Chapitre 13

Collections

705

Listing 13.7 : Sieve.java


import java.util.*; /** * Ce programme excute le test du crible dEratosthne. * Il calcule tous les nombres premiers jusqu 2000000. * @version 1.21 2004-08-03 * @author Cay Horstmann */ public class Sieve { public static void main(String[] s) { int n = 2000000; long start = System.currentTimeMillis(); BitSet b = new BitSet(n + 1); int count = 0; int i; for (i = 2; i <= n; i++) b.set(i); i = 2; while (i * i <= n) { if (b.get(i)) { count++; int k = 2 * i; while (k <= n) { b.clear(k); k += i; } } i++; } while (i <= n) { if (b.get(i)) count++; i++; } long end = System.currentTimeMillis(); System.out.println(count+" primes"); System.out.println((end - start)+ " milliseconds"); } }

Listing 13.8 : Sieve.cpp


/** * @version 1.21 2004-08-03 * @author Cay Horstmann */ #include <bitset> #include <iostream> #include <ctime>

706

Au cur de Java 2 - Notions fondamentales

using namespace std; int main() { const int N = 2000000; clock_t cstart = clock(); bitset<N+1> b; int count = 0; int i; for (i = 2; i <= N; i++) b.set(i); i = 2; while (i * i <= N) { if (b.test(i)) { count++ int k = 2 * i; while (k <= N) { b.reset(k); k += i; } } i++; } while (i <= N { if (b.test(i)) count++; i++; } } clock_t cend = clock(); double millis = 1000.0 * (cend - cstart) / CLOCKS_PER_SEC; cout << count << " primes\n" << millis << " milliseconds\n"; return 0; }

Cela achve notre parcours du cadre des collections Java. Vous lavez vu, la bibliothque propose de nombreuses classes de collection pour tous vos besoins de programmation. Au cours du dernier chapitre de cet ouvrage, nous verrons le sujet de la programmation simultane.

14
Multithreads
Au sommaire de ce chapitre

Quest-ce quun thread ? Interruption des threads Les tats dun thread Proprits dun thread Synchronisation Verrous morts Collections compatibles avec les threads Callable et Future Executor Synchronizer Threads et Swing
Vous connaissez probablement dj le multitche : la possibilit davoir plusieurs programmes travaillant en mme temps. Par exemple, avec un systme multitche, il vous est possible dimprimer un document en mme temps que vous en modiez un autre ou que vous tlchargez vos e-mails. Aujourdhui, vous disposez probablement dun ordinateur avec plusieurs units centrales, mais le nombre de procdures sexcutant simultanment nest pas limit par leur nombre. Le systme dexploitation affecte des tranches de temps dUC chaque procdure, donnant limpression dactivits paralllles. Les programmes utilisant plusieurs threads dveloppent lide du multitche en limplmentant un niveau plus bas : des programmes individuels peuvent effectuer plusieurs tches en mme temps. Chaque tche est traditionnellement appele un thread. Les programmes qui peuvent excuter plusieurs threads en mme temps sont appels des programmes multithreads. Quelle est donc la diffrence entre plusieurs processus et plusieurs threads ? La diffrence essentielle est quun processus possde une copie unique de ses propres variables, alors que les threads

708

Au cur de Java 2 - Notions fondamentales

partagent les mmes donnes. Cela peut paratre risqu, et cela lest parfois, comme nous allons le voir un peu plus loin dans ce chapitre. Mais il est bien plus rapide de crer et de dtruire des threads individuels que de crer un nouveau processus. Cest pourquoi tous les systmes dexploitation modernes supportent les multithreads. De plus, la communication entre les processus est bien plus lente et plus restrictive quentre des threads. Les multithreads sont trs utiles dans la pratique. Par exemple, un navigateur doit pouvoir tlcharger simultanment plusieurs pages et un serveur Web doit pouvoir rpondre plusieurs requtes simultanes. Les programmes ayant une interface utilisateur graphique possdent un thread spar pour rcuprer les vnements de linterface utilisateur. Ce chapitre vous montrera comment ajouter des capacits de multithread dans vos applications Java. Le multithread a considrablement chang dans Java SE 5.0, du fait de lajout dun grand nombre de classes et dinterfaces apportant des implmentations de grande qualit. Celles-ci sont destines aux mcanismes ncessaires la plupart des programmeurs dapplications. Dans ce chapitre, nous verrons les nouvelles fonctionnalits de Java SE 5.0 ainsi que les mcanismes classiques de synchronisation et nous vous aiderons faire votre choix. Un petit conseil : la gestion de plusieurs threads peut rapidement devenir trs complexe. Dans ce chapitre, nous prsentons tous les outils fournis par le langage de programmation Java pour programmer ces threads. Cependant, pour les situations plus dlicates, nous vous suggrons de passer par des ouvrages plus spcialiss, comme Java Concurrency in Practice de Brian Goetz (Addison-Wesley Professional, 2006).

Quest-ce quun thread ?


Commenons par tudier un programme qui ne se sert pas de plusieurs threads et qui, par consquent, empche lutilisateur deffectuer plusieurs tches avec ce programme. Une fois que nous laurons dissqu, nous vous montrerons combien il est facile de lexcuter dans plusieurs threads. Ce programme fait rebondir une balle en la dplaant en permanence. Si la balle arrive contre un mur, il la fait rebondir (voir Figure 14.1). Ds que vous cliquez sur le bouton Start, le programme lance la balle partir du coin suprieur gauche de lcran et celle-ci commence rebondir. Le gestionnaire du bouton Start appelle la mthode addBall. Cette mthode contient une boucle progressant sur 1 000 dplacements. Chaque appel move dplace lgrement la balle, ajuste la direction si elle rebondit sur un mur, puis redessine lcran.
Ball ball = new Ball(); panel.add(ball); for (int i = 1; i <= STEPS; i++) { ball.move(panel.getBounds()); panel.paint(panel.getGraphics()); Thread.sleep(DELAY); }

La mthode sleep statique de la classe Thread effectue une pause du nombre de millisecondes donn.

Chapitre 14

Multithreads

709

Figure 14.1
Utilisation dun thread pour animer une balle rebondissante.

Lappel Thread.sleep ne cre pas un nouveau thread, sleep est une mthode statique de la classe Thread qui met temporairement le thread courant en sommeil. La mthode sleep peut dclencher une exception InterruptedException. Nous reviendrons sur cette exception et sur son gestionnaire un peu plus tard. Pour linstant, nous terminons simplement le rebond si lexception survient. Si vous excutez ce programme, vous verrez que la balle rebondit trs bien, mais quelle bloque compltement lapplication. Si ces rebonds vous lassent et que vous ne vouliez pas attendre les 1 000 dplacements, cliquez sur le bouton Close. La balle continue quand mme son trajet. Il est impossible dinteragir avec le programme tant que la balle rebondit.
INFO
Si vous tudiez attentivement le code la n de cette section, vous remarquerez lappel
comp.paint(comp.getGraphics())

dans la mthode addBall de la classe BounceFrame. Cela est assez trange, vous devriez normalement appeler repaint et laisser le soin AWT dobtenir le contexte graphique et de dessiner lcran. Toutefois, si vous essayez dappeler comp.repaint() dans ce programme, vous dcouvrirez que lcran nest jamais redessin puisque la mthode addBall a totalement pris le pas sur le traitement. Remarquez galement que le composant de balle tend JPanel, ce qui simplie leffacement de larrire-plan. Dans le programme suivant, nous utiliserons un autre thread pour calculer la position de la balle et nous emploierons nouveau repaint et JComponent.

A lvidence, ce nest pas une situation acceptable et lon ne souhaite pas que les programmes utiliss se comportent de cette manire lorsquils doivent effectuer des tches longues. Aprs tout, lorsque vous lisez des donnes en passant par une connexion rseau, il est trop courant dtre bloqu par une tche gourmande en temps machine, que vous souhaiteriez vraiment interrompre. Par exemple, supposons que vous chargiez une grande image et que vous dcidiez, aprs en avoir vu une partie, que vous ne voulez pas voir le reste. Il peut alors tre trs intressant de pouvoir cliquer sur un bouton "Arrter" ou "Prcdent" pour interrompre le chargement. Dans la prochaine section, nous vous montrerons comment laisser cette possibilit lutilisateur en excutant les parties critiques dun programme dans un thread spar.

710

Au cur de Java 2 - Notions fondamentales

Les Listings 14.1 14.3 prsentent le code de ce programme.


Listing 14.1 : Bounce.java
import java.awt.*; import java.awt.event.*; import javax.swing.*; /** * Prsente une balle rebondissante anime. * @version 1.33 2007-05-17 * @author Cay Horstmann */ public class Bounce { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { JFrame frame = new BounceFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); } } /** * Le bloc avec la balle et les boutons. */ class BounceFrame extends JFrame { /** * Elabore le bloc avec le composant pour montrer la balle * rebondissante et les boutons Start et Close. */ public BounceFrame() { setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); setTitle("Bounce"); comp = new BallComponent(); add(comp, BorderLayout.CENTER); JPanel buttonPanel = new JPanel(); addButton(buttonPanel, "Start", new ActionListener() { public void actionPerformed(ActionEvent event) { addBall(); } }); addButton(buttonPanel, "Close",new ActionListener() { public void actionPerformed(ActionEvent event) {

Chapitre 14

Multithreads

711

System.exit(0); } }); add(buttonPanel, BorderLayout.SOUTH); } /** * Ajoute un bouton un conteneur. * @param c le conteneur * @param title le nom du bouton * @param listener lcouteur daction pour le bouton */ public void addButton(Container c, String title, ActionListener listener) { JButton button = new JButton(title); c.add(button); button.addActionListener(listener); } /** * Ajoute une balle rebondissante lcran et la fait * rebondir 1 000 fois. */ public void addBall() { try { Ball ball = new Ball(); panel.add(ball); for (int i = 1; i <= STEPS; i++) { ball.move(comp.getBounds()); comp.paint(comp.getGraphics()); Thread.sleep(DELAY); } } catch (InterruptedException e) { } } private BallComponent comp; public static final int DEFAULT_WIDTH = 450; public static final int DEFAULT_HEIGHT = 350; public static final int STEPS = 1000; public static final int DELAY = 3; }

Listing 14.2 : Ball.java


import java.awt.geom.*; /** * Une balle qui se dplace et rebondit sur les bords * dun rectangle * @version 1.33 2007-05-17 * @author Cay Horstmann */

712

Au cur de Java 2 - Notions fondamentales

public class Ball { /** * Dplace la balle la position suivante, en inversant la * direction si elle frappe lun des bords */ public void move(Rectangle2D bounds) { x += dx; y += dy; if (x < bounds.getMinX()) { x = bounds.getMinX(); dx = -dx; } if (x + XSIZE >= bounds.getMaxX()) { x = bounds.getMaxX() - XSIZE; dx = -dx; } if (y < bounds.getMinY()) { y = bounds.getMinY(); dy = -dy; } if (y + YSIZE >= bounds.getMaxY()) { y = bounds.getMaxY() - YSIZE; dy = -dy; } } /** * Rcupre la forme de la balle sa position actuelle. */ public Ellipse2D getShape() { return new Ellipse2D.Double(x, y, XSIZE, YSIZE); } private private private private private private } static static double double double double final int XSIZE = 15; final int YSIZE = 15; x = 0; y = 0; dx = 1; dy = 1;

Listing 14.3 : BallComponent.java


import java.awt.*; import java.util.*; import javax.swing.*; /** * Le composant qui dessine les balles. * @version 1.33 2007-05-17 * @author Cay Horstmann */

Chapitre 14

Multithreads

713

public class BallComponent extends JPanel { /** * Ajoute une balle au composant. * @param b La balle ajouter */ public void add(Ball b) { balls.add(b); } public void paintComponent(Graphics g) { super.paintComponent(g); // effacer le fond Graphics2D g2 = (Graphics2D) g; for (Ball b : balls) { g2.fill(b.getShape()); } } private ArrayList<Ball> balls = new ArrayList<Ball>(); } java.lang.Thread 1.0 static void sleep(long millis)

Mise en veille pendant le nombre donn de millisecondes. Paramtres : millis le nombre de millisecondes de mise en veille

Utiliser des threads pour laisser une chance aux autres tches
Nous allons rendre notre programme de balles plus attentif lutilisateur en excutant le code qui dplace la balle dans un thread spar. Vous pourrez en fait lancer plusieurs balles. Chacune est dplace par son propre thread. En outre, le thread de distribution dvnements AWT continue fonctionner en parallle, il soccupe des vnements de linterface utilisateur. Chaque thread ayant une occasion de sexcuter, le thread de rpartition dvnements peut savoir si lutilisateur clique sur le bouton Close alors que les balles rebondissent. Il peut alors traiter laction correspondante. Nous utilisons lexemple de la balle rebondissante pour vous donner lindice visuel du besoin de simultanit. En rgle gnrale, inquitez-vous des calculs longs. Votre calcul fera srement partie dun cadre plus gnral, par exemple linterface graphique ou le Web. Ds que le cadre appelle lune de vos mthodes, on sattend gnralement un retour rapide. Pour toutes les tches plutt longues, utilisez un thread spar. Voici une procdure simple pour excuter une tche dans un thread spar : 1. Placez le code de la tche dans la mthode run dune classe qui implmente linterface Runnable. Cette interface est trs simple, elle ne possde quune mthode :
public interface Runnable { void run(); }

714

Au cur de Java 2 - Notions fondamentales

Vous implmentez simplement une classe comme ceci :


class MyRunnable implements Runnable { public void run() { code de la tche } }

2. Construisez un objet de votre classe :


Runnable r = new MyRunnable();

3. Construisez un objet Thread partir de linterface Runnable :


Thread t = new Thread(r);

4. Dmarrez le thread :
t.start();

Pour excuter notre programme dans un thread spar, nous devons simplement implmenter une classe BallRunnable et placer le code de lanimation dans la mthode run, comme dans le code suivant :
class BallRunnable implements Runnable { . . . public void run() { try { for (int i = 1; i <= STEPS; i++) { ball.move(component.getBounds()); component.repaint(); Thread.sleep(DELAY); } } catch (InterruptedException exception) { } } . . . }

Une fois de plus, nous devons intercepter une exception appele InterruptedException que la mthode sleep menace de dclencher. Nous verrons cette exception dans la prochaine section. Typiquement, linterruption sert demander la n dun thread. De mme, notre mthode run se termine lorsquune InterruptedException se prsente. Ds que lutilisateur clique sur le bouton Start, la mthode addBall lance un nouveau thread (voir Figure 14.2) :
Ball b = new Ball(); panel.add(b); Runnable r = new BallRunnable(b, panel); Thread t = new Thread(r); t.start

Chapitre 14

Multithreads

715

Figure 14.2
Excuter plusieurs threads.

Cest tout ce quil faut savoir ! Vous savez maintenant lancer des tches en parallle. Le reste de ce chapitre vous montrera comment contrler linteraction entre les threads. Le code complet de ce programme se trouve dans le Listing 14.4.
INFO
Vous pouvez aussi dnir un thread en formant une sous-classe de la classe Thread, comme ceci :
class MyThread extends Thread { public void run() { code de la tche } }

Vous construisez ensuite un objet de la sous-classe et appelez sa mthode start. Toutefois, cette approche nest plus conseille. Il vaut mieux dcoupler la tche qui doit tre excute en parallle de son mcanisme dexcution. Si vous disposez de plusieurs tches, crer un thread spar pour chacune serait trop onreux. Vous pouvez plutt utiliser un pool de threads (voir la section Executors, plus loin dans ce chapitre).

ATTENTION
Nappelez pas la mthode run de la classe Thread ou de lobjet Runnable car cela excute directement la tche sur le mme thread. Aucun nouveau thread nest dmarr. Appelez plutt la mthode Thread.start. Elle crera un nouveau thread qui excutera la mthode run.

Listing 14.4 : BounceThread.java


import java.awt.*; import java.awt.event.*; import javax.swing.*;

716

Au cur de Java 2 - Notions fondamentales

/** * Prsente une balle rebondissante anime. * @version 1.33 2007-05-17 * @author Cay Horstmann */ public class BounceThread { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { JFrame frame = new BounceFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); } } /** * Un excutable qui anime une balle rebondissante. */ class BallRunnable implements Runnable { /** * Construit lexcutable. * @aBall la balle qui doit rebondir * @aPanel le composant dans lequel la balle rebondit */ public BallRunnable(Ball aBall, Component aComponent) { ball = aBall; component = aComponent; } public void run() { try { for (int i = 1; i <= STEPS; i++) { ball.move(component.getBounds()); component.repaint(); Thread.sleep(DELAY); } } catch (InterruptedException e) { } } private Ball ball; private Component component; public static final int STEPS = 1000; public static final int DELAY = 5; }

Chapitre 14

Multithreads

717

/** * Le cadre avec le panneau et les boutons. */ class BounceFrame extends JFrame { /** * Construit le cadre avec le composant pour afficher la * balle rebondissante et les boutons Start et Close */ public BounceFrame() { setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); setTitle("BounceThread"); panel = new BallComponent(); add(comp, BorderLayout.CENTER); JPanel buttonPanel = new JPanel(); addButton(buttonPanel, "Start", new ActionListener() { public void actionPerformed(ActionEvent event) { addBall(); } }); addButton(buttonPanel, "Close", new ActionListener() { public void actionPerformed(ActionEvent event) { System.exit(0); } }); add(buttonPanel, BorderLayout.SOUTH); } /** * Ajoute un bouton au conteneur. * @param c le conteneur * @param title le titre du bouton * @param listener lcouteur daction pour le bouton */ public void addButton(Container c, String title, ActionListener listener) { JButton button = new JButton(title); c.add(button); button.addActionListener(listener); } /** * Ajoute une balle rebondissante sur le fond et lance un thread * pour la faire rebondir */ public void addBall() { Ball b = new Ball(); comp.add(b); Runnable r = new BallRunnable(b, comp); Thread t = new Thread(r); t.start();

718

Au cur de Java 2 - Notions fondamentales

} private BallComponent comp; public static final int DEFAULT_WIDTH = 450; public static final int DEFAULT_HEIGHT = 350; public static final int STEPS = 1000; public static final int DELAY = 3; } java.lang.Thread 1.0 Thread(Runnable target)

Construit un nouveau thread qui appelle la mthode run() de la cible spcie.


void start()

Lance ce thread et appelle sa mthode run(). Cette mthode revient immdiatement. Le nouveau thread est excut en mme temps.

void run()

Appelle la mthode run de linterface Runnable associe.


java.lang.Runnable 1.0 void run()

Cette fonction doit tre surcharge et vous devez y ajouter les instructions qui doivent tre excutes dans le thread correspondant.

Interrompre des threads


Un thread sarrte lorsque sa mthode run revient, en excutant une instruction return aprs la dernire instruction du corps de la mthode ou si une exception survient qui ne soit pas intercepte dans la mthode. Dans la premire version de Java, il existait galement une mthode stop quun autre thread aurait pu appeler pour terminer un thread. Mais cette mthode est aujourdhui obsolte (voir plus loin dans ce chapitre). Il existe une mthode pour obliger un thread se terminer. Il est possible dutiliser la mthode interrupt pour exiger la terminaison dun thread. Lorsque la mthode interrupt est appele sur un thread, lindication du thread est dnie sur interrompu (interrupted). Il sagit dun indicateur boolen prsent dans chaque thread. Chaque thread doit, loccasion, vrier sil a t interrompu. Pour savoir si lindication a t dnie sur interrompu, appelez dabord la mthode Thread.currentThread pour obtenir le thread actuel, puis appelez la mthode isInterrupted :
while (!Thread.currentThread().isInterrupted() && encore du travail) { faire le travail }

Cependant, si un thread est bloqu, il ne peut pas dterminer sil est interrompu. Cest ce moment que la mthode InterruptedException intervient. Lorsque la mthode interrupt est appele sur un thread bloqu, lappel bloquant (comme sleep ou wait) est termin par une InterruptedException. Certains appels dE/S bloquants ne peuvent pas tre interrompus, envisagez dautres solutions. Voyez les Chapitres 1 et 3 du Volume II pour plus dinformations.

Chapitre 14

Multithreads

719

Il nexiste aucune spcication demandant quun thread interrompu doive tre termin. Linterruption dun thread se contente dattirer son attention. Le thread interrompu peut choisir comment ragir devant linterruption. Certains threads sont tellement importants quils peuvent tout simplement ignorer leur interruption en continuant leur travail. Mais, plus couramment, un thread essaiera dinterprter une interruption comme une demande darrt. La mthode run de ce type de thread a la forme suivante :
public void run() { try { . . . while (!Thread.currentThread().isInterrupted() && encore du travail) { faire le travail } } catch(InterruptedException e) { // le thread a t interrompu pendant sleep ou wait } finally { nettoyage, si ncessaire } // sort de la mthode run et met fin au thread }

La vrication isInterrupted nest ni ncessaire ni utile si vous appelez la mthode sleep (ou une autre mthode pouvant tre interrompue) aprs chaque itration. Si vous appelez la mthode sleep lorsque lindication est dnie sur interrompu, elle ne se met pas en veille. Elle efface le statut (!) et lance une exception InterruptedException. Ainsi, si votre boucle appelle sleep, ne vous inquitez pas de vrier linterruption et interceptez simplement lexception, comme ceci :
public void run() { try { . . . while (effectuer du travail) { effectuer du travail Thread.sleep(delay); } } catch(InterruptedException e) { // le thread a t interrompu pendant le sommeil } finally { nettoyage, si ncessaire } // quitter la mthode run met fin au thread }

720

Au cur de Java 2 - Notions fondamentales

INFO
Curieusement, il existe deux mthodes trs similaires, interrupted et isInterrupted. La mthode interrupted est une mthode statique qui vrie si le thread actuel a t interrompu. De plus, un appel la mthode interrupted rinitialise lindication "interrompu" dun thread. Dun autre ct, la mthode isInterrupted est une mthode dinstance que vous pouvez utiliser pour vrier que nimporte quel thread a t interrompu. Lindication "interrompu" de son argument nest pas modie.

Vous trouverez de nombreux exemples de publication de codes o InterruptedException est silencieux, comme ceci :
void mySubTask() { ... try { sleep(delay); } catch (InterruptedException e) {} NE LIGNOREZ PAS! ... }

Ne faites pas cela ! Si vous ne trouvez rien de bien faire dans la clause catch, il vous reste deux choix raisonnables :
m

Dans la clause catch, appelez Thread.currentThread().interrupt() pour dnir lindication sur interrompu. Lappelant peut alors le tester :
void mySubTask() { ... try { sleep(delay); } catch (InterruptedException e) { Thread().currentThread().interrupt(); } ... }

Mieux encore, vous pouvez baliser votre mthode avec throws InterruptedException et laisser le bloc try. Lappelant (ou, au nal, la mthode run) peut lintercepter :
void mySubTask() throws InterruptedException { ... sleep(delay); ... }

java.lang.Thread 1.0 void interrupt()

Envoie une demande dinterruption un thread. Lindication "interrompu" du thread est mise true. Si le thread est actuellement bloqu par un appel sleep, une InterruptedException est dclenche.

static boolean interrupted()

Regarde si le thread actuel (cest--dire le thread qui excute cette instruction) a t interrompu. Notez quil sagit dune mthode statique. Cet appel possde un effet annexe : il met lindication "interrompu" du thread courant false.

Chapitre 14

Multithreads

721

boolean isInterrupted()

Regarde si un thread a t interrompu. Contrairement la mthode static interrupted, cet appel ne change pas lindication "interrompu" du thread.

static Thread currentThread()

Renvoie lobjet Thread reprsentant le thread en cours dexcution.

Les tats dun thread


Les threads peuvent se trouver dans lun des six tats suivants :
m m m m m m

nouveau ; excutable ; bloqu ; en attente ; expir ; termin.

Chacun de ces tats est expliqu dans les paragraphes suivants. Pour dterminer ltat dun thread, appelez simplement la mthode getState.

Nouveaux threads
Lorsque vous crez un thread avec loprateur new (par exemple new Thread(r)), le thread nest pas encore excut. Cela signie quil se trouve dans ltat nouveau. Lorsquun thread se trouve dans cet tat, le programme na pas encore commenc excuter le code qui se trouve dans ce thread. Il faut encore faire quelques oprations avant que ce thread puisse tre excut.

Threads excutables
Une fois que vous avez invoqu la mthode start, le thread prend ltat excutable. Il se peut que le thread excutable soit ou non en cours dexcution. Cest au systme dexploitation de fournir au thread une fentre dexcution. La spcication Java ne dnit pas cet tat comme un tat spar. Un thread en cours dexcution reste dans ltat excutable. Lorsquun thread sexcute, il ne poursuit pas obligatoirement lopration dans le temps. En ralit, cela peut se rvler souhaitable si des threads sont mis en pause de temps autre an que dautres threads aient une occasion de sexcuter. Ce mcanisme dpend directement du systme dexploitation. Les systmes de planication premptifs accordent chaque thread excutable une tranche de temps pour effectuer sa tche. Lorsque cette tranche de temps est coule, le systme dexploitation prempte le thread et passe un autre (voir Figure 14.4). Lorsque vous slectionnez le thread suivant, le systme dexploitation prend en compte les priorits des threads (voir plus loin pour en savoir plus sur les priorits). Tous les systmes dexploitation modernes utilisent la planication premptive. Toutefois, les petits appareils comme les tlphones portables peuvent utiliser la planication cooprative. Dans

722

Au cur de Java 2 - Notions fondamentales

ces appareils, un thread ne perd le contrle que lorsquil appelle une mthode comme yield, il est bloqu ou en attente. Sur une machine multiprocesseur, chaque processeur peut excuter un thread et plusieurs threads peuvent sexcuter en parallle. Bien entendu, sil y a plus de threads que de processeurs, le gestionnaire est toujours responsable du dcoupage en tranches. Noubliez pas quun thread excutable peut ou non sexcuter tout moment.

Threads bloqus et en attente


Lorsquun thread est bloqu ou en attente, il est temporairement inactif. Il nexcute aucun code et consomme un minimum de ressources. Cest au gestionnaire de thread de le ractiver. Les dtails dpendent de la manire dont ltat dinactivit a t atteint.
m

Lorsque le thread tente dacqurir un verrou dobjet intrinsque (mais non un Lock dans la bibliothque java.util.concurrent) actuellement dtenu par un autre thread, il se bloque (les verrous java.util.concurrent et les verrous dobjets intrinsques sont abords plus loin dans ce chapitre). Le thread se dbloque lorsque tous les autres threads ont libr le verrou et que le gestionnaire a autoris ce thread le contenir. Lorsque le thread en attend un autre pour avertir le gestionnaire dune condition, il entre en tat dattente. Nous verrons les conditions au cours de ce chapitre. Cela survient en appelant la mthode Object.wait ou Thread.join ou en attendant un Lock ou une Condition dans la bibliothque java.util.Concurrent. Dans la pratique, la diffrence entre un tat bloqu ou en attente nest pas considrable. Plusieurs mthodes possdent un paramtre de temporisation. Les appeler amne le thread entrer dans un tat de temporisation. Cet tat persiste jusqu expiration de la temporisation ou rception dune notication approprie. Les mthodes ayant une temporisation comprennent Thread.sleep et les versions temporises de Object.wait, Thread.join, Lock.tryLock et Condition.await.

La Figure 14.3 montre les diffrents tats dun thread et les transitions possibles dun tat un autre. Lorsquun thread est bloqu ou en attente (ou, bien sr, lorsquil se termine), un autre thread est prvu pour tre excut. Lorsquun thread est ractiv (par exemple, parce que sa temporisation a expir ou quil a russi acqurir un verrou), le gestionnaire regarde sil possde une priorit suprieure celle du thread actuellement en cours dexcution. Dans ce cas, il interrompt le thread actuel et slectionne un nouveau thread.

Threads termins
Un thread peut se terminer pour lune des deux raisons suivantes :
m m

Il meurt dune mort naturelle parce que la mthode run sest termine normalement. Il meurt soudainement parce quune exception non rcupre a mis n la mthode run.

En particulier, il est possible de tuer un thread en invoquant sa mthode stop. Cette mthode dclenche une erreur ThreadDeath qui tue le thread. Cependant, la mthode stop nest plus utilise et il faut viter de lappeler dans votre code.

Chapitre 14

Multithreads

723

Figure 14.3
Les tats dun thread.
nouveau

bloqu dbut
tio isi nd u err nv ou is

q ac

r ve

a rou

u cq

attente de notification excutable


en ou te de de te no mp tifi ori no temp ca sa tifi or tio tio ca isa n n tio n s tion ur ou ve nu e att

en attente notification survenue

la mthode run quitte

temporise

termine

java.lang.Thread 1.0

void join()

Attend que le thread spci se termine.


void join(long millis)

Attend que le thread spci meure ou que le nombre spci de millisecondes scoule.
Thread.State getState() 5.0

Rcupre ltat de ce thread ; peut tre lune des constantes NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING ou TERMINATED.

void stop()

Arrte le thread. Cette mthode est aujourdhui obsolte.


void suspend()

Suspend lexcution du thread. Cette mthode est aujourdhui obsolte.


void resume()

Reprend ce thread. Cette mthode nest valide quaprs un appel suspend(). Cette mthode est aujourdhui obsolte.

724

Au cur de Java 2 - Notions fondamentales

Proprits dun thread


Dans les sections suivantes, nous verrons les diverses proprits des threads : priorits des threads, threads dmons, groupes de threads et gestionnaires dexceptions non rcupres.

Priorits dun thread


Dans le langage de programmation Java, chaque thread possde une priorit. Par dfaut, un thread hrite de la priorit du thread qui la construit. Vous pouvez augmenter ou baisser la priorit de nimporte quel thread avec la mthode setPriority. La priorit de nimporte quel thread peut tre choisie entre MIN_PRIORITY (correspondant 1 dans la classe Thread) et MAX_PRIORITY (correspondant 10). NORM_PRIORITY vaut 5. Lorsque le gestionnaire de threads peut choisir un nouveau thread, il choisit gnralement le thread de la plus haute priorit. Toutefois, les priorits de threads sont fortement dpendantes du systme. Lorsque la machine virtuelle compte sur limplmentation des threads de la plate-forme hte, les priorits de threads Java sont mises en correspondance avec les niveaux de priorit de la plate-forme hte, qui peut en avoir plus ou moins. A titre dexemple, Windows afche sept niveaux de priorit. Certaines priorits Java concorderont avec le mme niveau du systme dexploitation. Dans la machine virtuelle de Sun pour Linux, les priorits de threads sont totalement ignores : tous les threads ont la mme priorit. Les programmeurs dbutants font parfois trop appel aux priorits de thread. Il existe peu de raisons de tirer un trait sur les priorits mais ne structurez jamais vos programmes en fonction des niveaux de priorit.
ATTENTION
Si vous souhaitez tout de mme utiliser les priorits, soyez averti dune erreur commune aux dbutants. Si vous disposez de plusieurs threads haute priorit qui ne deviennent pas inactifs, ceux de moindre priorit risquent de ne jamais sexcuter. Lorsque le gestionnaire dcide dexcuter un nouveau thread, il choisira dabord parmi ceux de plus haute priorit, mme si cela peut totalement annihiler les threads de moindre priorit.

java.lang.Thread 1.0

void setPriority(int newPriority)

Dnit la priorit de ce thread. Cette priorit doit tre comprise entre Thread.MIN_PRIORITY et Thread.MAX_PRIORITY. Utilisez Thread.NORM_PRIORITY pour une priorit normale.

static int MIN_PRIORITY

Dnit la priorit minimale quun thread peut avoir. La valeur de la priorit minimale est 1.
static int NORM_PRIORITY

Dnit la priorit par dfaut dun thread. La valeur de la priorit par dfaut est 5.
static int MAX_PRIORITY

Dnit la priorit maximale quun thread peut avoir. La valeur de la priorit maximale est 10.

Chapitre 14

Multithreads

725

static void yield()

Cette mthode arrte le thread en cours dexcution. Sil existe dautres threads excutables dont la priorit est au moins gale celle de ce thread, ils seront traits par la suite. Notez quil sagit dune mthode statique.

Threads dmons
Un thread peut tre transform en thread dmon en appelant
t.setDaemon(true);

Il ny a rien de dmoniaque l-dedans ! Un dmon est simplement un thread qui na aucun autre but dans la vie que de servir dautres threads. On peut citer comme exemples des threads chronomtres qui envoient des "pulsations de temps" rgulires dautres threads ou des threads qui nettoient les entres de cache. Lorsquil ne reste plus que des threads dmons, la machine virtuelle se ferme. Il ny a en effet aucun intrt continuer dexcuter un programme si tous les threads restants sont des dmons. Les threads dmons sont parfois employs par erreur par les dbutants qui ne rchissent pas aux actions de fermeture. Cela peut toutefois tre dangereux. Un thread dmon ne doit jamais accder une ressource persistante comme un chier ou une base de donnes puisquil peut se terminer tout moment, mme au milieu dune opration.
java.lang.Thread 1.0 void setDaemon(boolean isDaemon)

Signale ce thread comme un dmon ou un thread utilisateur. Cette mthode doit tre appele avant le dmarrage du thread.

Gestionnaire dexceptions non rcupres


La mthode run dun thread ne peut pas dclencher dexceptions sous contrle, mais elle peut tre termine par une exception hors contrle. Dans ce cas, le thread meurt. Il nexiste toutefois pas de clause catch dans laquelle lexception peut tre propage. Juste avant que le thread ne meure, lexception est transfre un gestionnaire dexceptions non rcupres. Ce gestionnaire doit appartenir une classe qui implmente linterface Thread.UncaughtExceptionHandler, qui ne possde quune seule mthode :
void uncaughtException(Thread t, Throwable e)

Depuis Java SE 5.0, vous pouvez installer un gestionnaire dans nimporte quel thread avec la mthode setUncaughtExceptionHandler. Vous pouvez aussi installer un gestionnaire par dfaut pour tous les threads avec la mthode statique setDefaultUncaughtExceptionHandler de la classe Thread. Un gestionnaire de remplacement pourrait utiliser lAPI didentication pour envoyer des rapports sur les exceptions non rcupres un chier journal. Si vous ninstallez pas de gestionnaire par dfaut, le gestionnaire par dfaut vaut null. Mais, si vous ninstallez pas de gestionnaire pour un thread particulier, le gestionnaire correspond lobjet ThreadGroup du thread.

726

Au cur de Java 2 - Notions fondamentales

INFO
Un groupe de threads est une collection de threads pouvant tre grs ensemble. Par dfaut, tous les threads que vous crez appartiennent au mme groupe mais vous pouvez crer dautres groupements. Depuis que Java SE 5.0 prsente de meilleures fonctionnalits pour les collections de threads, vous ne devez pas utiliser de groupes de thread dans vos propres programmes.

La classe ThreadGroup implmente linterface Thread.UncaughtExceptionHandler. Sa mthode uncaughtException ralise laction suivante : 1. Si le groupe de threads a un parent, la mthode uncaughtException du groupe parent est appele. 2. Sinon, si la mthode Thread.getDefaultExceptionHandler renvoie un gestionnaire non nul, il est appel. 3. Sinon, si Throwable est une instance de ThreadDeath, il ne se passe rien. 4. Sinon, le nom du thread et la trace de Throwable sont afchs sur System.err. Cest une trace que vous avez certainement beaucoup vue dans vos programmes.
java.lang.Thread 1.0

static void setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler handler) 5.0 static Thread.UncaughtExceptionHandler getDefaultUncaughtExceptionHandler() 5.0

Dnissent ou rcuprent le gestionnaire par dfaut pour les exceptions non rcupres.
void setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler handler) 5.0 Thread.UncaughtExceptionHandler getUncaughtExceptionHandler() 5.0

Dnissent ou rcuprent le gestionnaire des exceptions non rcupres. Lorsque aucun gestionnaire nest install, lobjet du groupe de threads devient le gestionnaire.
java.lang.Thread.UncaughtExceptionHandler 5.0

void uncaughtException(Thread t, Throwable e)

Dnit pour consigner un rapport personnalis lorsquun thread se termine avec une exception non rcupre. Paramtres : t e le thread termin du fait dune exception non rcupre lobjet de lexception non rcupre

java.lang.ThreadGroup 1.0

void uncaughtException(Thread t, Throwable e)

Appelle cette mthode du groupe de threads parent (sil existe un parent) ou le gestionnaire par dfaut de la classe Thread (sil existe un gestionnaire par dfaut). Sinon afche une trace du ux derreur standard (toutefois, si e est un objet ThreadDeath, la trace est supprime. Les objets ThreadDeath sont gnrs par la mthode stop, aujourdhui obsolte).

Chapitre 14

Multithreads

727

Synchronisation
Dans la plupart des applications pratiques base de multithreads, il arrive que plusieurs threads doivent partager un accs aux mmes objets. Que se passe-t-il si deux threads ont accs au mme objet et que chacun appelle une mthode qui modie ltat de cet objet ? Comme vous pouvez limaginer, les threads se font concurrence. Selon lordre dans lequel la donne a t accde, des objets corrompus peuvent apparatre. Ce type de situation est souvent appel une condition de course.

Exemple de condition de course


Pour viter que plusieurs threads ne corrompent des donnes partages, vous devez apprendre synchroniser laccs. Dans cette section, vous verrez ce qui se passe si vous nutilisez pas de synchronisation. Dans la section suivante, vous verrez comment synchroniser les accs aux objets. Dans le prochain programme de test, nous simulons une banque possdant plusieurs comptes. Nous gnrons au hasard des transactions qui dplacent de largent entre ces comptes. Chaque compte possde un thread. Chaque transaction dplace des quantits alatoires dargent, du compte desservi par le thread vers un autre compte choisi alatoirement. Le code de cette simulation est assez simple. Il est principalement constitu de la classe Bank et de la mthode transfer. Cette mthode transfre une certaine somme dargent dun compte vers un autre (nous ne traiterons pas des soldes ngatifs). Voici le code de la mthode transfer de la classe Bank :
public void transfer(int from, int to, double amount) // ATTENTION: cette mthode nest pas sre lorsquelle est appele // partir de plusieurs threads { System.out.print(Thread.currentThread()); accounts[from] -= amount; System.out.print(" %10.2f de %d %d", amount, from, to); accounts[to] += amount; System.out.printf(" Total Balance: %10.2f%n", getTotalBalance()); }

Voici le code de la classe TransferRunnable. Sa mthode run sort en permanence de largent dun compte bancaire spci. A chaque itration, la mthode run choisit au hasard un compte cible et un montant dargent alatoire, puis elle appelle transfer sur lobjet banque et sendort.
class TransferRunnable implements Runnable { ... public void run() { try { int toAccount = (int) (bank.size() * Math.random()); double amount = maxAmount * Math.random(); bank.transfer(fromAccount, toAccount, amount); Thread.sleep(int) (DELAY * Math.random())); } catch(InterruptedException e) {} } }

728

Au cur de Java 2 - Notions fondamentales

Lorsque cette simulation est excute, nous ne savons pas combien dargent se trouve dans chaque compte bancaire. En revanche, nous savons que la somme de tous les comptes doit rester constante, puisque nous effectuerons uniquement des transferts dargent entre ces comptes. A la n de chaque transaction, la mthode transfer recalcule le total et lafche. Ce programme ne sarrte jamais. Il suft dappuyer sur Ctrl+C pour larrter. Voici un rsultat typique :
. . . Thread[Thread-11,5,main] 588.48 from 11 to 44 Total Balance: 100000.00 Thread[Thread-12,5,main] 976.11 from 12 to 22 Total Balance: 100000.00 Thread[Thread-14,5,main] 521.51 from 14 to 22 Total Balance: 100000.00 Thread[Thread-13,5,main] 359.89 from 13 to 81 Total Balance: 100000.00 . . . Thread[Thread-36,5,main] 401.71 from 36 to 73 Total Balance: 99291.06 Thread[Thread-35,5,main] 691.46 from 35 to 77 Total Balance: 99291.06 Thread[Thread-37,5,main] 78.64 from 37 to 3 Total Balance: 99291.06 Thread[Thread-34,5,main] 197.11 from 34 to 69 Total Balance: 99291.06 Thread[Thread-36,5,main] 85.96 from 36 to 4 TotalBalance: 99291.06 . . . Thread[Thread-4,5,main]Thread[Thread-33,5,main] 7.31 from 31 to 32 TotalBalance: 99979.24 627.50 from 4 to 5 TotalBalance: 99979.24 . . .

Comme vous pouvez le constater, il y a une erreur importante. Pour certaines transactions, le solde reste 100 000 $, ce qui correspond au total correct pour 100 comptes de 10 000 $ chacun. Mais aprs un certain moment, le solde total est lgrement modi. Lorsque vous excuterez ce programme, vous vous rendrez compte que des erreurs se produisent rapidement, ou au contraire quil faut un certain temps pour que le solde soit corrompu. Cette situation ninspire pas conance et vous naurez probablement pas envie de dposer votre argent difcilement gagn dans cette banque. Les Listings 14.5 14.7 fournissent le code source complet de cette simulation. Regardez si vous pouvez reprer le problme pos par ce code. Nous vous dvoilerons ce mystre la prochaine section.
Listing 14.5 : UnsynchBankTest.java
/** * Ce programme montre la corruption de donnes lorsque * plusieurs threads accdent une structure de donnes. * @version 1.30 2004-08-01 * @author Cay Horstmann */ public class UnsynchBankTest { public static void main(String[] args) { Bank b = new Bank(NACCOUNTS, INITIAL_BALANCE); int i; for (i = 0; i < NACCOUNTS; i++) { TransferRunnable r = new TransferRunnable(b, i, INITIAL_BALANCE); Thread t = new Thread(r); t.start();

Chapitre 14

Multithreads

729

} } public static final int NACCOUNTS = 100; public static final double INITIAL_BALANCE = 1000; }

Listing 14.6 : Bank.java


/** * Une banque avec plusieurs comptes bancaires. * @version 1.30 2004-08-01 * @author Cay Horstmann */ public class Bank { /** * Construit la banque * @param n le nombre de comptes * @param initialBalance le solde initial * pour chaque compte */ public Bank(int n, int initialBalance) { accounts = new double[n]; for (int i = 0; i < accounts.length; i++) accounts[i] = initialBalance; } /** * Transfre largent dun compte lautre * @param from le compte dorigine du transfert * @param to le compte vers lequel effectuer le transfert * @param amount la somme transfrer */ public void transfer(int from, int to, double amount) { if (accounts[from] < amount) return; System.out.print(Thread.currentThread()); accounts[from] -= amount; System.out.printf(" %10.2f de %d %d", amount, from, to); accounts[to] += amount; System.out.printf(" TotalBalance: %10.2f%n", getTotalBalance()); } /** * Rcupre la somme de tous les soldes. * @return le solde */ public double getTotalBalance() { double sum = 0; for (double a: accounts) sum += a; return sum; }

730

Au cur de Java 2 - Notions fondamentales

/** * Obtient le nombre de comptes dans la banque * @return le nombre de comptes */ public int size() { return accounts.length; } private final double[] accounts; }

Listing 14.7 : TransferRunnable.java


/** * Un excutable qui transfre largent dun compte lautre * dans une banque * @version 1.30 2004-08-01 * @author Cay Horstmann */ public class TransferRunnable implements Runnable { /** * Construit un excutable de transfert * @param b la banque dont les comptes sont concerns * par le transfert * @param from le compte dorigine du transfert * @param max la somme maximum dargent pour chaque transfert */ public TransferRunnable(Bank b, int from, double max) { bank = b; fromAccount = from; maxAmount = max; } public void run() { try { while (true) { int toAccount = (int) (bank.size() * Math.random()); double amount = maxAmount * Math.random(); bank.transfer(fromAccount, toAccount, amount); Thread.sleep((int) (DELAY * Math.random())); } } catch(InterruptedException e) { } } private private private private } Bank bank; int fromAccount; double maxAmount; int DELAY = 10;

Chapitre 14

Multithreads

731

Explication des conditions de course


Dans la section prcdente, nous avons excut un programme dans lequel plusieurs threads mettaient jour des soldes de comptes bancaires. Aprs un certain temps, des erreurs apparaissaient et une certaine quantit dargent tait soit perdue, soit ajoute spontanment. Ce problme se prsente lorsque deux threads essaient simultanment de mettre jour un compte. Supposons que deux threads essaient dexcuter linstruction suivante simultanment :
accounts[to] += amount;

Le problme de cette instruction est quelle nest pas compose doprations atomiques. Cette instruction peut en effet tre dcompose comme suit : 1. Charger accounts[to] dans un registre. 2. Ajouter amount. 3. Placer le rsultat dans accounts[to]. Maintenant, supposons que le premier thread excute les deux premires tapes, puis quil soit prempt. Supposons ensuite que le second thread se rveille et quil mette jour la mme entre dans le tableau account. Le premier thread se rveille alors et termine sa troisime tape. Cette action annule la modication de lautre thread. Par consquent, le total nest plus correct (voir Figure 14.4).
Figure 14.4
Deux accs simultans.
Transfer Thread 1 chargement registre du thread 1 5000 Transfer Thread 2 registre du thread 2 accounts[to]

5000

addition

5500

5000

chargement

5000

5000

addition

6000

5000

enregistrement

6000

6000

enregistrement

5500

5500

Notre programme de test dtecte cette erreur. Naturellement, il existe une lgre possibilit que de fausses alarmes se prsentent si le thread est interrompu alors quil effectue le test !
INFO
Vous pouvez en fait lire les codes doctets binaires de la machine virtuelle qui correspondent chaque instruction de notre classe. Excutez la commande
javap -c -v Bank

732

Au cur de Java 2 - Notions fondamentales

pour dcompiler le chier Bank.class. Par exemple, la ligne


accounts[to] += amount;

est traduite par les codes binaires suivants :


aload_0 getfield #2 //comptes du champ: [D iload_2 dup2 daload dload_3 dadd dastore

La signication relle de ces codes na pas dimportance. Vous voyez maintenant que linstruction daddition est compose de plusieurs petites instructions, et que le thread qui les excute peut tre interrompu au milieu de ces instructions.

Quelle est la probabilit que cette erreur se produise ? Nous avons dtermin que nous pouvions augmenter la probabilit derreur en entrelaant les instructions dafchage celles qui actualisent le solde. Si vous oubliez les instructions dafchage, le risque de corruption diminue un peu car chaque thread travaille trs peu avant de se rendormir. Il est donc peu probable que le gestionnaire le prempte au milieu du calcul. Mais le risque de corruption demeure. Si vous excutez de trs nombreux threads sur une machine trs charge, le programme chouera, mme lorsque vous aurez limin les instructions dafchage. Lchec peut survenir aprs quelques minutes, quelques heures ou quelques jours. Il ny a rien de pire, ou presque, pour un programmeur quune erreur qui ne se manifeste que tous les deux ou trois jours. Le vrai problme rside dans le fait que le travail de la mthode transfer peut tre interrompu en plein milieu. Si nous pouvions nous assurer que la mthode sexcute jusqu la n, avant la perte de contrle du thread, ltat de lobjet compte en banque ne serait jamais corrompu.

Verrous dobjet
Depuis Java SE 5.0, il existe deux mcanismes permettant de protger un bloc de code dun accs simultan. Le langage Java fournit le mot cl synchronized dans ce but. Java SE 5.0 a introduit la classe ReentrantLock. Le mot cl synchronized fournit automatiquement un verrou ainsi quune "condition" associe, qui simplie la plupart des cas ncessitant un verrou explicite. Toutefois, on comprend mieux le mot cl synchronized aprs avoir isol les verrous et les conditions. Le cadre java.util.concurrent propose des classes spares pour ces mcanismes fondamentaux, que nous expliquerons plus loin dans ce chapitre. Lorsque vous aurez compris ces bases, vous verrez le mot cl synchronized. Le code principal permettant de protger un bloc de code avec ReentrantLock est le suivant :
myLock.lock(); // un objet ReentrantLock try { Section principale } finally

Chapitre 14

Multithreads

733

{ myLock.unlock(); // vrifier que le verrou est dverrouill, mme si // une exception est dclenche }

Cette construction garantit que seul un thread la fois puisse entrer dans la section principale. Ds quun thread verrouille lobjet verrou, aucun autre thread ne peut entrer dans linstruction lock. Lorsque dautres threads appellent lock, ils sont bloqus jusqu ce que le premier thread dbloque lobjet verrou.
ATTENTION
Il est particulirement important que lopration unlock soit enserre dans une clause finally. Si le code de la section critique dclenche une exception, le verrou doit tre dverrouill, faute de quoi les autres threads seront bloqus pour toujours.

Utilisons un verrou pour protger la mthode transfer de la classe Bank :


public class Bank { public void transfer(int from, int to, int amount) { bankLock.lock(); try { System.out.print(Thread.currentThread()); accounts[from] -= amount; System.out.printf(" %10.2f from %d to %d", amount, from, to); accounts[to] += amount; System.out.printf(" TotalBalance: %10.2f%n", getTotalBalance()); } finally { bankLock.unlock(); } } . . . private Lock bankLock = new ReentrantLock(); // ReentrantLock // implmente linterface Lock }

Supposons quun thread appelle transfer et soit prempt avant davoir termin. Supposons ensuite quun deuxime thread appelle aussi transfer. Le deuxime ne peut pas acqurir le verrou, il est bloqu dans lappel la mthode lock. Il est donc dsactiv et doit attendre que le premier thread nisse dexcuter la mthode transfer. Lorsque le premier thread dbloque le verrou, le deuxime peut continuer (voir Figure 14.5). Testez-le. Ajoutez le code de verrouillage la mthode transfer et excutez nouveau le programme. Vous pouvez lexcuter linni, le solde ne sera jamais corrompu. Vous remarquerez que chaque objet Bank possde son propre objet ReentrantLock. Si deux threads tentent daccder au mme objet Bank, le verrou sert srialiser laccs. Toutefois, si deux threads tentent daccder diffrents objets Bank, chaque thread acquiert un verrou diffrent et aucun thread nest bloqu. Cest ce qui est prvu, car les threads ne doivent pas interfrer les uns avec les autres lorsquils manipulent diffrentes instances de Bank.

734

Au cur de Java 2 - Notions fondamentales

Figure 14.5
Comparaison entre threads synchroniss et non synchroniss.

Non synchronis
Thread 1 Thread 2

Synchronis
Thread 1 Thread 2

transfert

transfert

transfert

transfert

Le verrou est dit rentrant car un thread peut acqurir, plusieurs reprises, un verrou quil possde dj. Le verrou tient le compte des appels imbriqus la mthode lock. Le thread doit appeler unlock pour chaque appel lock, de manire renoncer au verrou. Du fait de cette caractristique, le code protg par un verrou peut appeler une autre mthode qui utilise les mmes verrous. Si, par exemple, la mthode transfer appelle la mthode getTotalBalance, celle-ci verrouille aussi lobjet bankLock, lequel afche maintenant un compte de 2. Lorsque la mthode getTotalBalance se termine, le compte revient 1. A la n de la mthode transfer, le compte est 0 et le thread renonce au verrou. En gnral, il est souhaitable de protger les blocs de code qui mettent jour ou inspectent un objet partag. Vous tes alors assur que ces oprations sexcutent jusquau bout avant quun autre thread ne puisse utiliser le mme objet.
ATTENTION
Vous devez prendre garde ce que le code dune section principale ne soit pas contourn par le dclenchement dune exception. Si une exception est dclenche avant la n de la section, la clause finally renoncera au verrou, mais lobjet pourra tre endommag.

java.util.concurrent.locks.Lock 5.0

void lock()

Acquiert ce verrou ; se bloque si le verrou appartient un autre thread.

Chapitre 14

Multithreads

735

void unlock()

Libre ce bloc.
java.util.concurrent.locks.ReentrantLock 5.0

ReentrantLock()

Construit un verrou rentrant pouvant tre utilis pour protger une section principale.
ReentrantLock(boolean fair)

Construit un verrou avec la rgle dquit donne. Un verrou juste favorise le thread qui attend depuis le plus longtemps. Cette garantie peut toutefois prsenter un gros inconvnient au niveau des performances. Ainsi, par dfaut, il nest pas obligatoire que les verrous soient quitables.
ATTENTION
Mme si lquit parat plus adapte, ces verrous sont bien plus lents que les verrous ordinaires. Activez-les seulement si vous voulez vritablement savoir ce que vous faites et si vous avez une bonne raison de les employer. Mme si vous utilisez un verrou quitable, vous navez aucune garantie que son gestionnaire le soit. Sil choisit de ngliger un thread qui attend le verrou depuis longtemps, il naura pas loccasion dtre trait quitablement par le verrou.

Objets de condition
Trs souvent, un thread entre dans une section principale, uniquement pour dcouvrir quil ne peut pas poursuivre tant quune condition nest pas remplie. Vous utiliserez alors un objet de condition pour grer les threads qui ont acquis un verrou mais ne peuvent pas procder un travail utile. Dans cette section, nous prsentons limplmentation dobjets de condition dans la bibliothque Java (pour des raisons historiques, les objets de condition sont souvent appels des variables de condition). Prcisons notre simulation de la banque. Il doit tre impossible de faire sortir de largent dun compte qui ne dispose pas de fonds sufsants. Sachez que nous ne pouvons pas utiliser un code du type
if (bank.getBalance(from) >= amount) bank.transfer(from, to, amount);

Il est tout fait possible que le thread actuel soit dsactiv entre le rsultat positif du test et lappel transfer.
if (bank.getBalance(from) >= amount) // le thread risque dtre dsactiv ici bank.transfer(from, to, amount);

Dici ce que le thread sexcute nouveau, le solde du compte risque dtre descendu sous le montant du retrait. Vous devez vous assurer quaucun autre thread ne puisse modier le solde entre le test et le transfert. Pour ce faire, protgez le test et laction de transfert avec un verrou :
public void transfer(int from, int to, int amount) { bankLock.lock(); try { while (accounts[from] < amount) {

736

Au cur de Java 2 - Notions fondamentales

// attendre . . . } // transfrer les fonds . . . } finally { bankLock.unlock(); } }

Que faire maintenant lorsquil ny a pas sufsamment dargent sur le compte ? Nous attendons quun autre thread ajoute des fonds. Mais ce thread vient dobtenir un accs exclusif bankLock, aucun autre thread na donc loccasion de raliser un dpt. Cest ici que les objets de condition entrent en jeu. Un objet verrou peut se voir associer un ou plusieurs objets de condition. Pour obtenir un objet de condition, utilisez la mthode newCondition. Chaque objet de condition reoit gnralement un nom qui voque la condition quil reprsente. Ici, par exemple, nous tablissons un objet de condition pour reprsenter la condition "fonds sufsants".
class Bank { public Bank() { . . . sufficientFunds = bankLock.newCondition(); } . . . private Condition sufficientFunds; }

Si la mthode transfer dcouvre que les fonds ne sont pas sufsants, elle appelle
sufficientFunds.await();

Le thread actuel est maintenant dsactiv et abandonne le verrou, ce qui permet un autre thread dentrer et, nous lesprons, daugmenter le solde. Il existe une diffrence essentielle entre un thread qui attend dacqurir un verrou et un thread qui a appel await. Lorsquun thread appelle la mthode await, il entre un jeu dattente pour cette condition. Le thread nest pas rendu excutable lorsque le verrou est disponible. Il reste dsactiv jusqu ce quun autre thread appelle la mthode signalAll sur la mme condition. Lorsquun autre thread transfre de largent, il doit appeler
sufficientFunds.signalAll();

Cet appel ractive tous les threads qui attendent la condition. Lorsque les threads sont retirs du jeu dattente, ils redeviennent excutables et le gestionnaire nira par les ractiver. A ce moment-l, ils tenteront de rentrer dans lobjet. Ds que le verrou est disponible, lun dentre eux acquerra le verrou et continuera l o il sest arrt, en revenant de lappel await. A ce moment-l, le thread doit nouveau tester la condition. Il nest pas garanti que la condition soit remplie. La mthode signalAll signale simplement aux threads en attente quelle peut tre remplie ce moment-l et quil faut nouveau vrier la condition.

Chapitre 14

Multithreads

737

INFO
En gnral, un appel await doit se trouver dans une boucle sous la forme
while (!(ok pour continuer)) condition.await();

Il est particulirement important quun autre thread appelle la mthode signalAll. Lorsquun thread appelle await, il na aucun moyen de le ractiver. Il met sa conance dans les autres threads. Si aucun dentre eux ne se proccupe de ractiver le thread en attente, il ne sera jamais rexcut. Cela peut mener des situations dplaisantes de verrous morts. Si tous les autres threads sont bloqus et que le dernier thread actif appelle await sans dbloquer lun des autres, il bloque aussi. Aucun thread nayant lautorisation de dbloquer les autres, le programme plante. A quel moment appeler signalAll? En rgle gnrale, il vaut mieux lappeler ds que ltat dun objet change dune manire qui pourrait tre avantageuse pour les threads en attente. Par exemple, ds quun solde bancaire change, les threads en attente doivent avoir une autre occasion dinspecter le solde. Dans notre exemple, nous appelons signalAll lorsque nous avons ni le transfert des fonds.
public void transfer(int from, int to, int amount) { bankLock.lock(); try { while (accounts[from] < amount) sufficientFunds.await(); // transfert des fonds . . . sufficientFunds.signalAll(); } finally { bankLock.unlock(); } }

Vous remarquerez que lappel signalAll nactive pas immdiatement un thread en attente. Il ne fait que dbloquer les threads en attente de sorte quils puissent entrer en concurrence pour entrer dans lobjet aprs que le thread actuel est sorti de la mthode synchronise. Une autre mthode, signal, ne dbloque quun thread choisi au hasard dans le jeu dattente. Ceci est plus efcace que dbloquer tous les threads, mais il y a un danger. Si le thread choisi alatoirement dcouvre quil ne peut toujours pas poursuivre, il est nouveau bloqu. Si aucun autre thread nappelle signal, le systme gnre des verrous morts.
ATTENTION
Un thread ne peut appeler que await, signalAll ou signal sur une condition lorsquil possde le verrou de la condition.

Si vous excutez le programme du Listing 14.8, vous verrez que tout va bien. Le solde reste 100 000 $. Aucun compte naura jamais de solde ngatif (appuyez sur Ctrl+C pour mettre n au

738

Au cur de Java 2 - Notions fondamentales

programme). Vous pouvez aussi remarquer que le programme est un peu plus lent : cest le prix payer pour la charge supplmentaire implique par la synchronisation. Dans la pratique, une utilisation correcte des conditions peut tre assez difcile. Avant de lancer limplmentation de vos propres objets de condition, envisagez lune des constructions dcrites plus loin, dans la section sur la synchronisation.
Listing 14.8 : Bank.java
import java.util.concurrent.locks.*; /** * Une banque avec plusieurs comptes utilisant * des verrous pour srialiser laccs. * @version 1.30 2004-08-01 * @author Cay Horstmann */ public class Bank { /** * Construit la banque. * @param n le nombre de comptes * @param initialBalance le solde initial * pour chaque compte */ public Bank(int n, double initialBalance) { accounts = new double[n]; for (int i = 0; i < accounts.length; i++) accounts[i] = initialBalance; bankLock = new ReentrantLock(); sufficientFunds = bankLock.newCondition(); } /** * Transfre largent dun compte lautre. * @param from le compte dorigine * @param to le compte de destination * @param amount le montant transfrer */ public void transfer(int from, int to, double amount) throws InterruptedException { bankLock.lock(); try { while (accounts[from] < amount) sufficientFunds.await(); System.out.print(Thread.currentThread()); accounts[from] -= amount; System.out.printf(" %10.2f from %d to %d", amount, from, to); accounts[to] += amount; System.out.printf(" TotalBalance: %10.2f%n", getTotalBalance()); sufficientFunds.signalAll(); } finally {

Chapitre 14

Multithreads

739

bankLock.unlock(); } } /** * Rcupre la somme de tous les soldes. * @return le solde total */ public double getTotalBalance() { bankLock.lock(); try { double sum = 0; for (double a: accounts) sum += a; return sum; } finally { bankLock.unlock(); } } /** * Rcupre le nombre de comptes la banque. * @return le nombre de comptes */ public int size() { return accounts.length; } private final double[] accounts; private Lock bankLock; private Condition sufficientFunds; } java.util.concurrent.locks.Lock 5.0

Condition newCondition()

Renvoie un objet de condition associ ce verrou.


java.util.concurrent.locks.Condition 5.0

void await()

Place ce thread dans le jeu dattente pour cette condition.


void signalAll()

Dbloque tous les threads du jeu dattente pour cette condition.


void signal()

Dbloque un thread choisi alatoirement dans le jeu dattente pour cette condition.

740

Au cur de Java 2 - Notions fondamentales

Le mot cl synchronized
Dans les sections prcdentes, vous avez vu comment utiliser Lock et les objets Condition. Avant de poursuivre, rsumons les points principaux concernant les verrous et les conditions :
m

Un verrou protge des sections de code, ne permettant qu un seul thread dexcuter du code un moment donn. Un verrou gre les threads qui tentent dentrer dans un segment de code protg. Un verrou peut se voir associer un ou plusieurs objets de condition. Chaque objet de condition gre les threads qui sont entrs dans une section de code protge mais ne peuvent pas poursuivre.

m m m

Les interfaces Lock et Condition ont t ajoutes Java SE 5.0 pour donner aux programmeurs un meilleur contrle sur le verrouillage. Toutefois, la plupart du temps, ce contrle est inutile et vous pouvez utiliser un mcanisme intgr dans le langage Java. Depuis la version 1.0, chaque objet de Java possde un verrou intrinsque. Si une mthode est dclare avec le mot cl synchronized, le verrou de lobjet protge toute la mthode. Pour appeler la mthode, un thread doit donc acqurir le verrou de lobjet intrinsque. Autrement dit,
public synchronized void method() { corps de la mthode }

quivaut
public void method() { this.intrinsicLock.lock(); try { corps de la mthode } finally { this.intrinsicLock.unlock(); } }

Par exemple, au lieu dutiliser un verrou explicite, nous pouvons simplement dclarer comme synchronized la mthode transfer de la classe Bank. Le verrou dobjet intrinsque ne sest vu associer quune seule condition. La mthode wait ajoute un thread au jeu dattente et les mthodes notifyAll et notify dbloquent les threads en attente. Autrement dit, appeler wait ou notifyAll quivaut
intrinsicCondition.await(); intrinsicCondition.signalAll();

INFO
Les mthodes wait et notifyAll et notify sont des mthodes final de la classe Object. Les mthodes Condition ont d tre nommes await, signal et signalAll pour ne pas entrer en conit avec les prcdentes.

Chapitre 14

Multithreads

741

Vous pouvez, par exemple, implmenter la classe Bank en Java comme suit :
class Bank { public synchronized void transfer(int from, int to, int amount) throws InterruptedException { while (accounts[from] < amount) wait(); // attend la seule condition du verrou de lobjet intrinsque accounts[from] -= amount; accounts[to] += amount; notifyAll(); // avertir tous les threads attendant la condition } public synchronized double getTotalBalance() { . . . } private double[] accounts; }

Comme vous le voyez, lutilisation du mot cl synchronized permet un code bien plus concis. Pour le comprendre, vous devez savoir que chaque objet possde un verrou intrinsque et que le verrou a une condition intrinsque. Le verrou gre les threads qui tentent dentrer dans une mthode synchronized. La condition gre les threads qui ont appel wait.
ASTUCE
Les mthodes synchronises sont relativement simples. Les dbutants rencontrent pourtant souvent des difcults avec les conditions. Avant dutiliser wait/notifyAll, envisagez aussi de vous servir de lune des constructions des sections suivantes traitant de la synchronisation.

Vous pouvez aussi dclarer des mthodes statiques comme synchronises. Lorsque cette mthode est appele, elle acquiert laspect intrinsque de lobjet de classe associ. Par exemple, si la classe Bank possde une mthode synchronise statique, le verrou de lobjet Bank.class est verrouill lorsquil est appel. En consquence, aucun autre thread ne peut appeler cette mthode statique synchronise de la mme classe ou une autre. Les verrous intrinsques et les conditions prsentent certaines limites, et parmi elles :
m m m

Vous ne pouvez pas interrompre un thread qui tente dacqurir un verrou. Vous ne pouvez pas spcier de temporisation lorsque vous tentez dacqurir un verrou. Ne disposer que dune seule condition par verrou peut se rvler insufsant.

On peut alors sinterroger sur ce quil faut utiliser dans le code : des objets Lock et Condition ou des mthodes synchronises ? Voici quelques conseils : 1. Il vaut mieux nutiliser ni Lock/Condition ni le mot cl synchronized. Dans de nombreux cas, vous pourrez utiliser lun des mcanismes du package java.util.concurrent qui gre le verrouillage. Vous verrez plus loin comment utiliser une queue de blocage pour synchroniser des threads qui agissent sur une mme tche. 2. Si le mot cl synchronized fonctionne pour vous, utilisez-le. Vous crirez moins de code, ce qui rduit les risques derreur. Le Listing 14.9 illustre lexemple de la banque, implment avec des mthodes synchronises.

742

Au cur de Java 2 - Notions fondamentales

3. Utilisez Lock/Condition si vous avez particulirement besoin de la puissance offerte par ces constructions.
Listing 14.9 : Bank.java
/** * Une banque avec plusieurs comptes qui utilise * les primitives de synchronisation. * @version 1.30 2004-08-01 * @author Cay Horstmann */ public class Bank { /** * Construit la banque. * @param n le nombre de comptes * @param initialBalance le solde initial * pour chaque compte */ public Bank(int n, double initialBalance) { accounts = new double[n]; for (int i = 0; i < accounts.length; i++) accounts[i] = initialBalance; } /** * Transfre de largent dun compte lautre. * @param from le compte dorigine du transfert * @param to le compte destinataire * @param amount le montant transfrer */ public synchronized void transfer(int from, int to, double amount) throws InterruptedException { while (accounts[from] < amount) wait(); System.out.print(Thread.currentThread()); accounts[from] -= amount; System.out.printf(" %10.2f from %d to %d", amount, from, to); accounts[to] += amount; System.out.printf(" TotalBalance: %10.2f%n", getTotalBalance()); notifyAll(); } /** * Rcupre la somme de tous les soldes. * @return le solde total */ public synchronized double getTotalBalance() { double sum = 0; for (double a: accounts) sum += a; return sum; }

Chapitre 14

Multithreads

743

/** * Rcupre le nombre de comptes de la banque. * @return le nombre de comptes */ public int size() { return accounts.length; } private final double[] accounts; } java.lang.Object 1.0

void notifyAll()

Dbloque les threads qui ont appel wait sur cet objet. Cette mthode ne peut tre appele que depuis une mthode synchronise ou un bloc. La mthode dclenche une IllegalMonitorStateException si le thread courant nest pas propritaire du verrou dobjet.

void notify()

Dbloque un thread choisi alatoirement parmi ceux qui ont appel wait sur cet objet. Cette mthode ne peut tre appele que depuis une mthode synchronise ou un bloc. La mthode dclenche une IllegalMonitorStateException si le thread courant nest pas propritaire du verrou dobjet.

void wait()

Amne un thread patienter jusqu ce quil soit noti. Cette mthode ne peut tre appele que depuis une mthode synchronise. Elle dclenche une IllegalMonitorStateException si le thread courant nest pas propritaire du verrou dobjet.

void wait(long millis)

void wait(long millis, int nanos) Amnent un thread patienter jusqu ce quil soit noti ou jusqu ce que le dlai spci se soit coul. Ces mthodes ne peuvent tre appeles que depuis une mthode synchronise. Elles dclenchent une IllegalMonitorStateException si le thread courant nest pas propritaire du verrou dobjet. Paramtres : millis Le nombre de millisecondes nanosLe nombre de nanosecondes, < 1 000 000

Blocs synchroniss
Nous lavons vu, chaque objet Java possde un verrou. Un thread peut acqurir le verrou en appelant une mthode synchronise. Il existe un second mcanisme pour cela, en entrant dans un bloc synchronis. Si un thread entre dans un bloc de la forme
synchronized (obj) // syntaxe pour un bloc synchronis { Section principale }

il acquiert le verrou pour obj. Le verrou est rentrant.

744

Au cur de Java 2 - Notions fondamentales

Vous trouverez souvent des verrous ad hoc dans un code existant, comme
public class Bank { public void transfer(int from, int to, int amount) { synchronized (lock) // un verrou ad-hoc { accounts[from] -= amount; accounts[to] += amount; } System.out.println(. . .); } . . . private double[] accounts; private Object lock = new Object(); }

Ici, lobjet lock nest cr que pour utiliser le verrou que possde chaque objet Java. Les programmeurs utilisent parfois le verrou dun objet pour implmenter dautres oprations atomiques, une pratique connue sous le terme de verrouillage ct client. Etudions par exemple la classe Vector, une liste dont les mthodes sont synchronises. Supposons maintenant que nous ayons stock nos soldes bancaires dans un Vector<Double>. Voici une implmentation simplie dune mthode transfer :
public void transfer(Vector<Double> accounts, int from, int to, int amount) // ERREUR { accounts.set(from, accounts.get(from) - amount); accounts.set(to, accounts.get(to) + amount); System.out.println(. . .); }

Les mthodes get et set de la classe Vector sont synchronises mais cela ne nous aide pas. Il est parfaitement possible quun thread soit prempt dans la mthode transfer aprs achvement du premier appel get. Un autre thread peut ensuite stocker une valeur diffrente la mme position. Nous pouvons toutefois attraper le verrou :
public void transfer(Vector<Double> accounts, int from, int to, int amount) { synchronized (accounts) { accounts.set(from, accounts.get(from) - amount); accounts.set(to, accounts.get(to) + amount); } System.out.println(. . .); }

Cette solution fonctionne mais dpend totalement du fait que la classe Vector utilise le verrou intrinsque pour toutes ses mthodes de modication. Mais est-ce rellement un fait ? La documentation de la classe Vector napporte aucune promesse de ce type. Vous devez particulirement tudier le source et esprer que les prochaines versions nintroduiront pas de mthodes de modication non synchronises. Vous le voyez, le verrouillage ct client est trs fragile et pas vraiment recommand.

Chapitre 14

Multithreads

745

Le concept des moniteurs


Les verrous et les conditions constituent des outils puissants pour la synchronisation des threads, mais ils ne sont pas vraiment orients objet. Pendant de nombreuses annes, les chercheurs ont tent de scuriser le multithread, sans obliger les programmeurs penser aux verrous explicites. Lune des solutions les plus performantes est le concept de moniteur, prsent par Per Brinch Hansen et Tony Hoare dans les annes 1970. En terminologie Java, un moniteur prsente les proprits suivantes :
m m m

Il est une classe nayant que des champs privs. Chaque objet de cette classe se voit associer un verrou. Toutes les mthodes sont verrouilles par ce verrou. Autrement dit, si un client appelle obj.method(), le verrou dobj est automatiquement acquis au dbut de lappel de mthode et abandonn la n. Tous les champs tant privs, cet arrangement permet de sassurer quaucun thread ny a accs, alors quun autre thread les manipule. Le verrou peut possder nimporte quel nombre de conditions associes.

Les prcdentes versions des moniteurs possdaient une seule condition, avec une syntaxe plutt agrable. Vous pouvez simplement appeler await accounts[from] >= balance sans utiliser de variable de condition explicite. Toutefois, les recherches ont montr quun nouveau test des conditions peut se rvler inefcace. Ce problme est rsolu avec les variables de conditions explicites, chacune grant un jeu de threads distinct. Les concepteurs de Java ont adapt le concept du moniteur. Chaque objet de Java possde un verrou intrinsque et une condition intrinsque. Si une mthode est dclare avec le mot cl synchronized, elle agit comme une mthode moniteur. La variable de condition est accessible par un appel wait/ notify/notifyAll. Toutefois, un objet Java diffre dun moniteur en trois points importants, qui compromettent la scurit des threads :
m m m

Les champs nont pas besoin dtre private. Les mthodes nont pas besoin dtre synchronized. Le verrou intrinsque est disponible pour les clients.

Ce non-respect de la scurit met en colre Per Brinch Hansen. Dans une tude acerbe des primitives du multithreading en Java, il dclare : "Je suis abasourdi par le fait que le paralllisme non scuris de Java soit pris au srieux par la communaut des programmeurs, un quart de sicle aprs linvention des moniteurs et de Concurrent Pascal. Elle na aucun mrite." (Javas Insecure Parallelism, ACM SIGPLAN Notices, 34:3845, avril 1999.)

Champs volatiles
Il est parfois excessif de payer le cot de la synchronisation simplement pour lire ou crire un champ dinstance ou deux. Aprs tout, quel pourrait tre le problme ? Malheureusement, avec les processeurs et les compilateurs modernes, les risques derreur sont multiples :
m

Les ordinateurs contenant plusieurs processeurs peuvent conserver temporairement des valeurs de mmoire dans des registres ou des caches de mmoire locale. Les threads sexcutant dans

746

Au cur de Java 2 - Notions fondamentales

diffrents processeurs peuvent donc voir les diffrentes valeurs pour le mme emplacement de mmoire !
m

Les compilateurs peuvent rordonner les instructions pour un dbit maximal. Ils ne choisiront pas lordre qui modie la signication du code, mais ils supposent que les valeurs de mmoire ne sont modies que lorsquil y a des instructions explicites dans le code. Une valeur de mmoire peut cependant tre remplace par un autre thread !

Si vous utilisez des verrous pour protger le code accessible par plusieurs threads, vous naurez donc pas ces problmes. Les compilateurs doivent respecter les verrous en vidant les caches locaux comme il se doit et en ne reclassant pas les instructions de manire errone. Les dtails gurent dans la spcication des threads et du modle de mmoire Java dveloppe par JSR 133 (voir http:// www.jcp.org/ en/jsr/detail?id=133). La majeure partie de la spcication est trs complexe et technique mais le document contient galement plusieurs exemples clairement expliqus. Un article plus accessible de Brian Goetz est disponible ladresse http://www-106.ibm.com/developerworks/ java/library/j-jtp02244.html.
INFO
Brian Goetz a invent le motif de synchronisation suivant : "Si vous crivez une variable qui peut ensuite tre lue par un autre thread ou lire une variable qui peut tre crite en dernier par un autre thread, vous devez utiliser la synchronisation."

Le mot cl volatile propose un mcanisme sans verrou pour synchroniser laccs un champ dinstance. Si vous dclarez un champ volatile, le compilateur et la machine virtuelle prennent en compte le fait que le champ peut tre simultanment mis jour par un autre thread. Supposons, par exemple, quun objet possde une balise boolenne done dnie par un thread et interrog par un autre thread. Nous lavons vu, vous pouvez utiliser un verrou :
public synchronized boolean isDone() { return done; } public synchronized void setDone() { done = true; } private boolean done;

Il nest peut-tre pas conseill dutiliser le verrou dobjet intrinsque. Les mthodes isDone et setDone peuvent se bloquer si un autre thread a verrouill lobjet. Si cela pose problme, il est possible dutiliser un Lock spar simplement pour cette variable. Mais cela va donner de grosses difcults. Dans ce cas, on peut dclarer le champ volatile :
public boolean isDone() { return done; } public void setDone() { done = true; } private volatile boolean done;

ATTENTION
Les variables volatiles napportent pas datomicit. Par exemple, la mthode
public void flipDone() { done = !done; } // non atomique

ne basculera pas forcment la valeur du champ.

Chapitre 14

Multithreads

747

Dans ce cas trs simple, il existe une troisime possibilit, celle dutiliser AtomicBoolean. Cette classe possde des mthodes get et set garanties comme atomiques (comme si elles taient synchronises). Limplmentation utilise des instructions efcaces au niveau de la machine qui garantissent latomicit sans utiliser les verrous. Il existe plusieurs classes denveloppe dans le package java.util.concurrent.atomic pour les entiers atomiques, les nombres virgule ottante, les tableaux, etc. Ces classes sont destines aux programmeurs systme qui produisent des utilitaires de simultanit, et non pour le programmeur de lapplication. En rsum, laccs simultan un champ est sr sous ces trois conditions :
m m m

Le champ est final et lon y accde une fois le constructeur termin. Chaque accs au champ est protg par un verrou commun. Le champ est volatile.
INFO

Avant Java SE 5.0, la smantique de volatile tait plutt permissive. Les concepteurs du langage ont tent de laisser de la marge aux chargs de limplmentation pour optimiser les performances du code qui utilise des champs volatiles. Toutefois, lancienne spcication tait si complexe que les implmenteurs ne lont pas toujours suivie ; de plus, elle permettait un comportement droutant et peu souhaitable, notamment des objets inaltrables qui ne ltaient pas vraiment.

Verrous morts
Les verrous et les conditions ne peuvent pas rsoudre tous les problmes qui se posent en multithreads. Considrons la situation suivante : Compte 1 : 200 $. Compte 2 : 300 $. Thread 1 : transfre 300 $ du compte 1 vers le compte 2. Thread 2 : transfre 400 $ du compte 2 vers le compte 1. Comme le montre la Figure 14.6, les threads 1 et 2 sont clairement bloqus. Aucun dentre eux ne peut continuer puisque les soldes des comptes 1 et 2 sont trop faibles. Est-il possible que tous les threads soient bloqus parce que chacun dentre eux attend un transfert dargent ? Ce type de situation est appel un verrou mort. Dans notre programme, un verrou mort ne peut pas se produire pour une raison trs simple. Chaque transfert dargent slve au plus 1 000 $. Comme il existe dix comptes et un total de 100 000 $, au moins un des comptes doit avoir plus de 1 000 $ nimporte quel moment. Le thread qui sort de largent de ce compte peut par consquent tre trait. Mais si vous modiez la mthode run des threads pour supprimer la limite de 1 000 $ pour les transactions, des verrous morts peuvent se produire rapidement. Vous pouvez essayer. Dnissez NACCOUNTS sur 10. Construisez chaque excutable de transfert avec une valeur max de 2 * INITIAL_ BALANCE et excutez le programme. Ce dernier sexcutera pendant un moment, puis il se bloquera.

748

Au cur de Java 2 - Notions fondamentales

Figure 14.6
Une situation de verrous morts.
1 2 200 300

bank.accounts

Thread 1 bank.transfer(1,2,300) bank.wait()

Thread 2

bank.transfer(2,1,400) bank.wait()

ASTUCE
Lorsque le programme se bloque, utilisez la combinaison Ctrl+\. Tous les threads seront lists. Chaque thread possde une trace, indiquant lendroit de son blocage. Vous pouvez aussi excuter jconsole, comme indiqu au Chapitre 11 et consulter le panneau Thread (voir Figure 14.7).

Une autre manire de crer un verrou mort est de demander au i-me thread de placer de largent sur le i-me compte, plutt que de lui demander de retirer de largent de ce compte. Dans ce cas, il est possible que tous les threads soient bloqus sur un compte, chacun essayant de supprimer plus dargent de ce compte quil nen contient. Vous pouvez galement essayer cela. Dans le programme SynchBankTest, examinez la mthode run de la classe TransferRunnable. Dans lappel transfer, changez fromAccount et toAccount. Excutez le programme, vous verrez quil se bloque presque immdiatement. Voici une autre situation dans laquelle un verrou mort peut se produire trs facilement : utilisez la mthode signal la place de signalAll dans le programme SynchBankTest. Vous verrez que ce programme se bloque rapidement. Contrairement signalAll, qui prvient tous les threads en attente que de nouveaux fonds sont disponibles, la mthode signal dbloque un seul thread.

Chapitre 14

Multithreads

749

Figure 14.7
Le panneau Threads dans jconsole.

Si ce thread ne peut pas continuer, tous les threads seront bloqus. Considrons le scnario suivant dun verrou mort en cours de cration : Compte 1 : 1 990 $. Tous les autres comptes : 990 $ chacun. Thread 1 : transfre 995 $ du compte 1 vers le compte 2. Tous les autres threads : transfrent 995 $ de leur compte vers un autre compte. Il est alors vident que tous les threads sauf le thread 1 sont bloqus puisquil ny a pas assez dargent sur leur compte. Le thread 1 continue. Par la suite, nous avons la situation suivante : Compte 1 : 995 $. Compte 2 : 1 985 $. Tous les autres comptes : 990 $ chacun. Le thread 1 appelle alors signal. La mthode signal choisit alatoirement un thread dbloquer. Supposons quelle choisisse le thread 3. Ce thread est rveill, il dtermine quil ny a pas assez dargent sur son compte et appelle await une nouvelle fois. Mais le thread 1 est toujours en cours dexcution. Une nouvelle transaction alatoire est gnre : Thread 1 : transfre 997 $ du compte 1 vers le compte 2.

750

Au cur de Java 2 - Notions fondamentales

Maintenant, le thread 1 appelle galement await, et tous les threads sont bloqus. Le systme se trouve alors dans un verrou mort. Le problme vient ici de la mthode signal. En effet, elle ne dbloque quun seul thread, et elle peut ne pas choisir le thread essentiel la bonne marche du programme. Dans notre scnario, le thread 2 doit tre activ pour retirer de largent du compte 2. Malheureusement, il nexiste aucun mcanisme dans le langage de programmation Java pour viter ou pour casser ces verrous morts. Vous devez concevoir des threads de sorte quune situation de verrou mort ne puisse survenir.

Test de verrous et temporisations


Un thread se bloque de manire indnie lorsquil appelle la mthode lock pour acqurir un verrou appartenant un autre thread. Vous pouvez prendre des prcautions supplmentaires pour acqurir un verrou. La mthode tryLock tente dacqurir un verrou et renvoie true si elle a russi, sinon, elle renvoie immdiatement false et le thread peut sarrter pour faire autre chose.
if (myLock.tryLock()) // le thread possde le verrou try { . . . } finally { myLock.unlock(); } else // faire autre chose

Vous pouvez appeler tryLock avec un paramtre de temporisation, comme ceci :


if (myLock.tryLock(100, TimeUnit.MILLISECONDS)) . . .

TimeUnit est une numration ayant les valeurs SECONDS, MILLISECONDS, MICROSECONDS et NANOSECONDS. La mthode lock ne peut pas tre interrompue. Si un thread est interrompu lorsquil attend dacqurir un verrou, il reste bloqu jusqu ce que le verrou soit disponible. En cas de verrou mort, la mthode lock ne peut jamais se terminer. Toutefois, si vous appelez tryLock avec une temporisation, une InterruptedException est dclenche si le thread est interrompu pendant quil attend. Cest particulirement utile car cela permet un programme de casser les verrous morts. Vous pouvez galement appeler la mthode lockInterruptibly. Elle a la mme signication que tryLock avec une temporisation innie. Lorsque vous attendez sur une condition, il est enn possible dapporter une temporisation :
myCondition.await(100, TimeUnit.MILLISECONDS))

La mthode await retourne si un autre thread a activ ce thread en appelant signalAll ou signal ou si la temporisation sest acheve ou si le thread a t interrompu. Les mthodes await dclenchent une InterruptedException si le thread en attente est interrompu. Dans le cas (peu probable) o vous prfreriez continuer attendre, utilisez plutt la mthode awaitUninterruptibly.

Chapitre 14

Multithreads

751

java.util.concurrent.locks.Lock 5.0 boolean tryLock()

Tente dacqurir le verrou sans bloquer ; renvoie true si cest russi. Cette mthode saisit le verrou sil est disponible mme sil possde une rgle de verrouillage quitable et que dautres threads attendent.

boolean tryLock(long time, TimeUnit unit)

Tente dacqurir le verrou, en le bloquant au maximum le temps donn ; renvoie true si cest russi.

void lockInterruptibly()

Acquiert le verrou, en le bloquant indniment. Si le thread est interrompu, dclenche une InterruptedException.
java.util.concurrent.locks.Condition 5.0 boolean await(long time, TimeUnit unit)

Entre le jeu dattente pour cette condition, en bloquant jusqu ce que le thread soit retir du jeu dattente ou que le dlai soit coul. Renvoie false si la mthode est renvoye car le dlai sest coul, true dans les autres cas.

void awaitUninterruptibly()

Entre le jeu dattente pour cette condition, en bloquant jusqu ce que le thread soit retir du jeu dattente. Si le thread est interrompu, cette mthode ne dclenche pas dInterruptedException.

Lire et crire des verrous


Le package java.util.concurrent.locks dnit deux classes de verrou, ReentrantLock, dj trait, et la classe ReentrantReadWriteLock. Cette dernire sert lorsque trop de threads lisent partir dune structure de donnes et quil existe moins de threads pour les modier. Dans cette situation, il est logique dautoriser laccs pour les lecteurs. Bien entendu, un systme dcriture doit toujours avoir un accs exclusif. Voici les tapes ncessaires pour utiliser la lecture et lcriture de verrous : 1. Construisez un objet ReentrantReadWriteLock :
private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

2. Extrayez les verrous de lecture et dcriture :


private Lock readLock = rwl.readLock(); private Lock writeLock = rwl.writeLock();

3. Utilisez le verrou de lecture dans toutes les mthodes daccs :


public double getTotalBalance() { readLock.lock(); try { . . . } finally { readLock.unlock(); } }

4. Utilisez le verrou dcriture dans toutes les mthodes de modication :


public void transfer(. . .)

752

Au cur de Java 2 - Notions fondamentales

{ writeLock.lock(); try { . . . } finally { writeLock.unlock(); } } java.util.concurrent.locks.ReentrantReadWriteLock 5.0 Lock readLock()

Rcupre un verrou de lecture pouvant tre acquis par plusieurs lecteurs, hors systmes dcriture.
Lock writeLock()

Rcupre un verrou dcriture qui exclut tous les lecteurs et systmes dcriture.

Pourquoi les mthodes stop et suspend ne sont plus utilises


La premire version de Java a dni une mthode stop qui se contente de mettre n un thread et une mthode suspend qui bloque un thread jusqu ce quun autre thread appelle resume. Ces deux mthodes ont quelque chose en commun : elles tentent de contrler le comportement dun thread donn sans sa coopration. Ces deux mthodes ne sont plus utilises depuis Java SE 1.2. La mthode stop est intrinsquement non sre et lexprience a montr que la mthode suspend amne souvent des situations de verrous morts. Dans cette section, nous allons voir pourquoi ces mthodes posent problme, et ce que nous pouvons faire pour viter ces problmes. Commenons par tudier la mthode stop. Cette mthode met n toutes les mthodes en attente, y compris la mthode run. Lorsquun thread est arrt, il rend immdiatement les verrous sur tous les objets quil a verrouills. Cela peut laisser les objets dans un tat incohrent. Par exemple, supposons quun TransferThread soit arrt en plein milieu dun dplacement dargent dun compte vers un autre, aprs le retrait et avant le dpt. Lobjet de banque est alors endommag. Le verrou ayant t abandonn, les dommages peuvent tre constats sur les autres threads qui nont pas t arrts. Lorsquun thread veut arrter un autre thread, il na aucun moyen de savoir quel moment lemploi de la mthode stop est sr, et quel moment il risque dendommager des objets. Par consquent, cette mthode nest plus utilise. Il est conseill dinterrompre un thread lorsque vous voulez larrter. Le thread interrompu peut alors sarrter au bon moment.
INFO
Certains auteurs dclarent que la mthode stop nest plus utilise car elle peut verrouiller de manire permanente dautres objets du fait dun thread arrt. Toutefois, ceci est faux. Un thread arrt ferme toutes les mthodes synchronises quil a appeles (par le traitement de lexception ThreadDeath). En consquence, le thread libre les verrous dobjet intrinsques quil possde.

Voyons maintenant ce qui peut poser problme avec la mthode suspend. Contrairement stop, suspend ne peut pas endommager les objets. Cependant, si vous suspendez un thread qui possde un verrou sur un objet, cet objet ne sera plus disponible jusqu ce que le thread ait repris son activit. Si le thread qui appelle la mthode suspend essaie dobtenir le verrou sur le mme objet, le

Chapitre 14

Multithreads

753

programme se bloque : le thread interrompu attend quon lui demande de reprendre son activit et le thread quil a mis en attente attend que lobjet soit dverrouill. Cette situation se produit frquemment avec des interfaces utilisateurs graphiques. Supposons que nous ayons une simulation graphique de notre banque. Nous disposons dun bouton "Pause" qui suspend les threads de transfert et dun bouton "Resume" qui les fait continuer.
pauseButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { for (int i = 0; i < threads.length; i++) threads[i].suspend(); // Ne pas faire cela } }); resumeButton.addActionListener(...); // appelle resume sur tous // les threads de transfert

Une mthode paintComponent dessine un graphique pour chaque compte, en appelant la mthode getBalances. Comme vous le verrez dans la prochaine section, les actions des boutons et laction graphique se trouvent dans le mme thread, appel thread de rpartition des vnements. Considrons maintenant le scnario suivant : 1. Lun des threads de transfert obtient un verrou sur lobjet bank. 2. Lutilisateur clique sur le bouton "Pause". 3. Tous les threads de transfert sont suspendus ; lun dentre eux possde toujours le verrou sur lobjet bank. 4. Pour une certaine raison, le graphique doit tre redessin. 5. La mthode paintComponent appelle la mthode getBalances. 6. Cette mthode tente dacqurir le verrou de lobjet bank. Le programme est alors bloqu. Le thread de rpartition des vnements ne peut pas continuer parce que le verrou appartient lun des threads suspendus. Par consquent, lutilisateur ne peut pas cliquer sur le bouton "Resume", et aucun thread ne continuera son excution. Si vous souhaitez interrompre un thread de manire sre, il convient dintroduire une variable suspendRequested et de la tester dans un lieu sr de votre mthode run, o votre thread ne verrouille pas des objets dont dautres threads ont besoin. Lorsque cette variable a t dnie, continuez attendre jusqu ce quelle soit nouveau disponible. Le code suivant implmente cette conception :
public void run() { while (. . .) { . . . if (suspendRequested) {

754

Au cur de Java 2 - Notions fondamentales

suspendLock.lock(); try { while (suspendRequested) suspendCondition.await(); } finally { suspendLock.unlock(); } } } } public void requestSuspend() { suspendRequested = true; } public void requestResume() { suspendRequested = false; suspendLock.lock(); try { suspendCondition.signalAll(); } finally { suspendLock.unlock(); } } private volatile boolean suspendRequested = false; private Lock suspendLock = new ReentrantLock(); private Condition suspendCondition = suspendLock.newCondition();

Queues de blocage
Vous venez de voir les blocs de base de la programmation simultane en Java. Toutefois, pour la programmation pratique, tenez-vous-en loign chaque fois que possible. Il est bien plus facile et sr dutiliser des structures de niveau suprieur implmentes par des experts de la simultanit. De nombreux problmes de threads peuvent tre formuls de manire lgante et sre laide dune ou de plusieurs queues. Les threads de producteurs insrent des lments dans la queue, puis les threads de consommation les rcuprent. La queue permet de transfrer en toute scurit des donnes dun thread lautre. Etudions par exemple notre programme de virement bancaire. Plutt que daccder lobjet banque directement, les threads de transfert insrent des objets dinstruction dans une queue. Un autre thread supprime les instructions de la queue et ralise les transferts. Seul ce thread a accs aux coulisses de lobjet banque. Aucune synchronisation nest ncessaire (bien entendu, les implmenteurs des classes de queue compatibles avec les threads ont d sinquiter des verrous et des conditions, mais ctait leur problme, ne vous en proccupez pas). Une queue de blocage amne un thread se bloquer lorsque vous tentez dajouter un lment alors que la queue est pleine ou de supprimer un lment alors quelle est vide. Les queues de blocage sont un outil utile pour coordonner le travail de plusieurs threads. Des threads de travail peuvent rgulirement y dposer des rsultats intermdiaires. Dautres threads de travail suppriment les rsultats intermdiaires et les modient ensuite. La queue quilibre automatiquement la charge de travail. Si le premier ensemble de threads sexcute plus lentement que le second, celui-ci se bloque en attendant les rsultats. Si le premier jeu de threads sexcute plus rapidement, la queue se remplit jusqu ce que le second ensemble rattrape son retard. Le Tableau 14.1 prsente les mthodes pour les queues de blocage. Le fonctionnement des queues de blocage est rparti en trois catgories, selon leur action lorsque la queue est pleine ou vide. Si vous utilisez la queue comme outil de gestion de threads, vous devrez utiliser les mthodes put et take. Les oprations add, remove et element dclenchent une exception lorsque vous tentez dajouter des lments une queue pleine ou de rcuprer la tte dune queue vide. Bien entendu, dans un programme multithread, la queue peut se remplir ou se vider tout moment, il faudra donc plutt utiliser les mthodes offer, poll et peek. Elles renvoient simplement un indicateur dchec au lieu de dclencher une exception si elles ne peuvent pas raliser leurs tches.

Chapitre 14

Multithreads

755

Tableau 14.1 : Fonctionnement des queues de blocage

Mthode add remove element offer poll peek put take


INFO

Action normale Ajoute un lment Supprime et renvoie llment de tte Renvoie llment de tte Ajoute un lment et renvoie true Supprime et renvoie llment de tte Renvoie llment de tte Ajoute un lment Supprime et renvoie la tte

Action en cas derreur Dclenche une IllegalStateException si la queue est pleine Dclenche une NoSuchElementException si la queue est vide Dclenche une NoSuchElementException si la queue est vide Renvoie false si la queue est pleine Renvoie null si la queue tait vide Renvoie null si la queue tait vide Bloque si la queue est pleine Bloque si la queue tait vide

Les mthodes poll et peek renvoient null pour signaler une erreur. Linsertion de valeurs null dans ces queues nest donc pas autorise.

Il existe aussi des variantes des mthodes offer et poll avec temporisation. Par exemple, lappel
boolean success = q.offer(x, 100, TimeUnit.MILLISECONDS);

tente pendant 100 millisecondes dinsrer un lment la queue de la le. Sil russit, il renvoie immdiatement true, sinon il renvoie false la n de la temporisation. De mme, lappel
Object head = q.poll(100, TimeUnit.MILLISECONDS)

renvoie true pendant 100 millisecondes pour supprimer la tte de la le. Sil russit, il renvoie la tte, sinon il renvoie null la n de la temporisation. La mthode put bloque si la queue est pleine, la mthode take bloque si la queue est vide. Ce sont les quivalents doffer et de poll, sans temporisation. Le package java.util.concurrent propose plusieurs variations des queues de blocage. Par dfaut, LinkedBlockingQueue ne possde pas de borne suprieure quant sa capacit, mais une capacit maximale peut tre spcie en option. LinkedBlockingDeque est une version double extrmit. ArrayBlockingQueue se construit avec une capacit donne et un paramtre optionnel pour obliger lquit. Si lquit est spcie, les threads qui ont attendu le plus longtemps reoivent un traitement prfrentiel. Comme toujours, lquit pnalise considrablement les performances (ne lutilisez que si vos problmes lexigent spciquement). PriorityBlockingQueue est une queue prioritaire. Les lments sont supprims dans lordre de leur priorit. La queue afche une capacit sans limites, mais la rcupration bloquera si la queue est vide (voir le Chapitre 13 pour en savoir plus sur les queues de priorit).

756

Au cur de Java 2 - Notions fondamentales

Enn, un DelayQueue contient des objets qui implmentent linterface Delayed :


interface Delayed extends Comparable<Delayed> { long getDelay(TimeUnit unit); }

La mthode getDelay renvoie le dlai restant de lobjet. Une valeur ngative indique un dlai coul. Les lments ne peuvent tre supprims dun DelayQueue que si leur dlai a expir. Vous devez aussi implmenter la mthode compareTo. DelayQueue utilise cette mthode pour trier les entres. Le programme du Listing 14.10 montre lutilisation dune queue de blocage pour contrler un ensemble de threads. Le programme cherche dans les chiers dun rpertoire et de ses sous-rpertoires et afche les lignes qui contiennent un mot cl donn. Un thread producteur recense tous les chiers de tous les sous-rpertoires et les place dans une queue de blocage. Cette opration est rapide et la queue se remplirait rapidement de tous les chiers du systme si elle ntait pas limite. Nous dmarrons galement un grand nombre de threads de recherche. Chacun prend un chier de la queue, louvre, afche toutes les lignes contenant le mot cl, puis prend le chier suivant. Nous faisons appel une astuce pour mettre n lapplication lorsque le travail nest plus ncessaire. Pour signaler la n, le thread dnumration place un objet dummy dans la queue. Lorsquun thread de recherche prend le dummy, il le replace et se termine. Sachez que la synchronisation explicite des threads nest pas ncessaire. Dans cette application, nous utilisons la structure de donnes de la queue comme mcanisme de synchronisation.
Listing 14.10 : BlockingQueueTest.java
import java.io.*; import java.util.*; import java.util.concurrent.*; /** * @version 1.0 2004-08-01 * @author Cay Horstmann */ public class BlockingQueueTest { public static void main(String[] args) { Scanner in = new Scanner(System.in); System.out.print("Enter base directory (e.g. /usr/local/jdk1.6.0/src): "); String directory = in.nextLine(); System.out.print("Enter keyword (e.g. volatile): "); String keyword = in.nextLine(); final int FILE_QUEUE_SIZE = 10; final int SEARCH_THREADS = 100; BlockingQueue<File> queue = new ArrayBlockingQueue<File>(FILE_QUEUE_SIZE);

Chapitre 14

Multithreads

757

FileEnumerationTask enumerator = new FileEnumerationTask(queue, new File(directory)); new Thread(enumerator).start(); for (int i = 1; i <= SEARCH_THREADS; i++) new Thread(new SearchTask(queue, keyword)).start(); } } /** * Cette tche recense tous les fichiers dun rpertoire et de * ses sous-rpertoires. */ class FileEnumerationTask implements Runnable { /** * Construit un FileEnumerationTask. * @param queue la file dattente de blocage laquelle sont ajouts * les fichiers recenss * @param startingDirectory le rpertoire dans lequel commencer * le recensement */ public FileEnumerationTask(BlockingQueue<File> queue, File startingDirectory) { this.queue = queue; this.startingDirectory = startingDirectory; } public void run() { try { enumerate(startingDirectory); queue.put(DUMMY); } catch (InterruptedException e) { } } /** * Recense tous les fichiers dun rpertoire donn et de * ses sous-rpertoires * @param directory le rpertoire o commencer */ public void enumerate(File directory) throws InterruptedException { File[] files = directory.listFiles(); for (File file: files) { { if (file.isDirectory()) enumerate(file); else queue.put(file); } } public static File DUMMY = new File(""); private BlockingQueue<File> queue;

758

Au cur de Java 2 - Notions fondamentales

private File startingDirectory; } /** * Cette tche recherche les fichiers pour un mot cl donn. */ class SearchTask implements Runnable { /** * Construit un SearchTask. * @param queue la file partir de laquelle prendre les fichiers * @param keyword le mot cl rechercher */ public SearchTask(BlockingQueue<File> queue, String keyword) { this.queue = queue; this.keyword = keyword; } public void run() { try { boolean done = false; while (!done) { File file = queue.take(); if (file == FileEnumerationTask.DUMMY) { queue.put(file); done = true; } else search(file); } } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { } } /** * Recherche un fichier pour un mot cl donn et affiche * toutes les lignes correspondantes. * @param file le fichier rechercher */ public void search(File file) throws IOException { Scanner in = new Scanner(new FileInputStream(file)); int lineNumber = 0; while (in.hasNextLine()) { lineNumber++; String line = in.nextLine();

Chapitre 14

Multithreads

759

if (line.contains(keyword)) System.out.printf("%s:%d:%s%n", file.getPath(), lineNumber, line); } in.close(); } private BlockingQueue<File> queue; private String keyword; } java.util.concurrent.ArrayBlockingQueue<E> 5.0

ArrayBlockingQueue(int capacity) ArrayBlockingQueue(int capacity, boolean fair)

Construisent une queue de blocage avec la capacit donne et des paramtres dquit. La queue est implmente sous forme de tableau circulaire.
java.util.concurrent.LinkedBlockingQueue<E> 5.0 java.util.concurrent.LinkedBlockingDeque<E> 6

LinkedBlockingQueue() LinkedBlockingDeque()

Construisent une queue ou deque de blocage sans bornes, implmente sous forme de liste chane.
LinkedBlockingQueue(int capacity) LinkedBlockingDeque(int capacity)

Construisent une queue ou deque de blocage avec la capacit donne, implmente sous forme de liste chane.
java.util.concurrent.DelayQueue<E extends Delayed> 5.0

DelayQueue()

Construit une queue de blocage dlments Delayed. Seuls les lments dont le dlai a expir seront supprims de la queue.
java.util.concurrent.Delayed 5.0

long getDelay(TimeUnit unit)

Rcupre le dlai pour cet objet, mesur dans lunit de temps donne.
java.util.concurrent.PriorityBlockingQueue<E> 5.0

PriorityBlockingQueue() PriorityBlockingQueue(int initialCapacity) PriorityBlockingQueue(int initialCapacity, Comparator<? super E> comparator)

Construisent une queue dattente de priorit de blocages sans bornes, implmente sous forme de tas.

760

Au cur de Java 2 - Notions fondamentales

Paramtres :

initialCapacity comparator

La capacit initiale de la queue de priorit. Vaut 11 par dfaut. Le comparateur utilis pour les lments. Sil nest pas spci, les lments doivent implmenter linterface Comparable.

java.util.concurrent.BlockingQueue<E> 5.0

void put(E element)

Ajoute llment, blocage si ncessaire.


E take()

Supprime et renvoie llment de tte, blocage si ncessaire.


boolean offer(E element, long time, TimeUnit unit)

Ajoute llment et renvoie true en cas de russite, blocage si ncessaire jusqu ce que llment ait t ajout ou que le dlai soit coul.

E poll(long time, TimeUnit unit)

Supprime et renvoie llment de tte, blocage si ncessaire jusqu ce quun lment soit disponible ou que le dlai soit coul. Renvoie null en cas dchec.
java.util.concurrent.BlockingDeque<E> 6 void putFirst(E element) void putLast(E element)

Ajoutent llment, blocage si ncessaire.



E takeFirst() E takeLast()

Suppriment et renvoient llment de tte ou de n, blocage si ncessaire.


boolean offerFirst(E element, long time, TimeUnit unit) boolean offerLast(E element, long time, TimeUnit unit)

Ajoutent llment donn et renvoient true en cas de russite, blocage si ncessaire jusqu ce que llment ait t ajout ou que le dlai se soit coul.

E pollFirst(long time, TimeUnit unit) E pollLast(long time, TimeUnit unit)

Suppriment et renvoient llment de tte et de n, blocage si ncessaire jusqu ce quun lment soit disponible ou que le dlai soit coul. Renvoie null en cas dchec.

Collections compatibles avec les threads


Si plusieurs threads modient simultanment une structure de donnes, telle quune table de hachage, cette structure de donnes peut facilement tre endommage (nous verrons les tables de hachage plus en dtail au Chapitre 13). Un thread pourrait par exemple commencer insrer un nouvel lment. Supposons quil soit alors prempt au milieu du racheminement des liens entre les seaux de la table de hachage. Si un autre thread commence parcourir la mme liste, il risque de

Chapitre 14

Multithreads

761

suivre des liens non valides et de crer un dsquilibre, par exemple en lanant des exceptions ou en sengageant dans une boucle sans n. Vous pouvez protger une structure de donnes partages en fournissant un verrou, mais il est gnralement plus simple de choisir une implmentation compatible avec les threads. Les queues de blocage vues prcdemment sont, bien entendu, des collections compatibles avec les threads. Dans les sections suivantes, nous en verrons dautres proposes par la bibliothque Java.

Cartes, jeux et queues efcaces


Le package java.util.concurrent apporte des implmentations efcaces pour les cartes, les jeux tris et les queues : ConcurrentHashMap, ConcurrentSkipListMap, ConcurrentSkipListSet et ConcurrentLinkedQueue. Ces collections utilisent des algorithmes complexes qui rduisent la contention en permettant un accs simultan plusieurs parties de la structure de donnes. A la diffrence de la plupart des collections, la mthode size ne fonctionne pas forcment en temps constant. Dterminer la taille actuelle de lune de ces collections ncessite gnralement de les parcourir. Les collections renvoient des itrateurs faiblement cohrents, qui peuvent ou non reter toutes les modications apportes aprs leur construction, mais qui renverront une valeur deux fois et ne dclencheront pas de ConcurrentModificationException.
INFO
Au contraire, un itrateur dune collection du package java.util dclenche une ConcurrentModificationException lorsque la collection a t modie aprs la construction de litrateur.

La table de hachage simultane peut efcacement supporter plusieurs lecteurs et un nombre de systmes dcriture xe. Par dfaut, on suppose quil y a jusqu 16 threads dcriture simultans. Il peut exister beaucoup dautres threads dcriture mais si plus de seize crivent en mme temps, les autres sont temporairement bloqus. Vous pouvez spcier un nombre suprieur dans le constructeur mais il est peu probable que vous deviez le faire. Les classes ConcurrentHashMap et ConcurrentSkipListMap possdent des mthodes utiles pour des associations atomiques dinsertion et de retrait. La mthode putIfAbsent ajoute automatiquement une nouvelle association condition quil ny en ait pas eu avant. Cest utile pour un cache auquel accdent plusieurs threads, pour sassurer quun seul dentre eux y ajoute un lment :
cache.putIfAbsent(key, value);

Lopration oppose est remove (qui aurait pu tre appele removeIfPresent). Lappel
cache.remove(key, value)

supprime automatiquement la cl et la valeur si elles sont prsentes dans la carte. Enn,


cache.replace(key, oldValue, newValue)

remplace automatiquement lancienne valeur par une nouvelle, condition que lancienne valeur ait t associe la cl donne.

762

Au cur de Java 2 - Notions fondamentales

java.util.concurrent.ConcurrentLinkedQueue<E> 5.0 ConcurrentLinkedQueue<E>()

Construit une queue sans limite ni blocage accessible en toute scurit par plusieurs threads.
java.util.concurrent.ConcurrentSkipListSet<E> 6 ConcurrentSkipListSet<E>() ConcurrentSkipListSet<E>(Comparator<? super E> comp)

Construisent un jeu tri accessible en toute scurit par plusieurs threads. Le premier constructeur exige que les lments implmentent linterface Comparable.
java.util.concurrent.ConcurrentHashMap<K, V> 5.0 java.util.concurrent.ConcurrentSkipListMap<K, V> 6 ConcurrentHashMap<K, V>() ConcurrentHashMap<K, V>(int initialCapacity) ConcurrentHashMap<K, V>(int initialCapacity, float loadFactor, int concurrencyLevel)

Construisent une carte de hachage qui est accessible en toute scurit par plusieurs threads. Paramtres : initialCapacity loadFactor La capacit initiale pour cette collection. Vaut 16 par dfaut. Contrle le redimensionnement : si la charge moyenne par seau dpasse ce facteur, la table est redimensionne. Vaut 0,75 par dfaut. Nombre estim de threads dcriture simultane.

concurrencyLevel

ConcurrentSkipListMap<K, V>() ConcurrentSkipListSet<K, V>(Comparator<? super K> comp)

Construisent une carte trie accessible en toute scurit par plusieurs threads. Le premier constructeur exige que les cls implmentent linterface Comparable.

V putIfAbsent(K key, V value)

Si la cl nest pas encore prsente dans la carte, associe la valeur donne la cl donne et renvoie null, sinon renvoie la valeur existante associe la cl.

boolean remove(K key, V value)

Si la cl donne est dj associe cette valeur, supprime la cl et la valeur donnes et renvoie true, sinon renvoie false.

boolean replace(K key, V oldValue, V newValue)

Si la cl donne est actuellement associe oldValue, lassocie newValue, sinon renvoie false.

CopyOnWriteArray
CopyOnWriteArray et CopyOnWriteArraySet sont des collections compatibles avec les threads, dans lesquelles toutes les mthodes de modication crent une copie du tableau sous-jacent. Cet arrangement est utile lorsque le nombre de threads parcourir la collection dpasse de loin le nombre de ceux qui la modient. Lorsque vous construisez un itrateur, il contient une rfrence au

Chapitre 14

Multithreads

763

tableau actuel. Si le tableau est modi par la suite, litrateur possde toujours lancien tableau, mais celui de la collection est remplac. Par consquent, litrateur le plus ancien possde une vue cohrente (mais peut-tre obsolte) laquelle il peut accder sans frais de synchronisation.

Anciennes collections compatibles avec les threads


Depuis la premire version de Java, les classes Vector et Hashtable offraient des implmentations compatibles avec les threads dun tableau dynamique et dune table de hachage. Dans Java SE 1.2, elles ont t dclares obsoltes et remplaces par les classes ArrayList et HashMap, lesquelles, par contre, ne sont pas compatibles avec les threads. Un autre mcanisme est plutt fourni dans la bibliothque des collections. Toute classe de collection peut tre rendue compatible avec les threads dune enveloppe de synchronisation :
List<E> synchArrayList = Collections.synchronizedList( new ArrayList<E>()); Map<K, V> synchHashMap = Collections.synchronizedMap( new HashMap<K, V>());

Les mthodes des collections qui en rsultent sont protges par un verrou, ce qui assure un accs compatible avec les threads. Vriez quaucun thread naccde la structure de donnes par les mthodes initiales non synchronises. La manire la plus simple pour sen assurer consiste nenregistrer aucune rfrence lobjet initial. Construisez simplement une collection et transmettez-la immdiatement lenveloppe, comme nous lavons fait dans nos exemples. Vous devez toujours utiliser le verrouillage "ct client" pour parcourir la collection, tandis quun autre thread a la possibilit de la modier :
synchronized (synchHashMap) { Iterator<K> iter = synchHashMap.keySet().iterator(); while (iter.hasNext()) . . .; }

Vous devez employer le mme code si vous utilisez une boucle "for each", celle-ci utilisant un itrateur. Sachez que litrateur choue avec une ConcurrentModificationException si un autre thread modie la collection pendant litration. La synchronisation est toujours obligatoire de sorte que la modication simultane puisse tre dtecte de manire able. Il vaut gnralement mieux utiliser les collections dnies dans le package java.util.concurrent au lieu des enveloppes de synchronisation. Notamment, la carte ConcurrentHashMap a t soigneusement implmente an que plusieurs threads puissent y accder sans se bloquer lun lautre, condition quils accdent diffrents seaux. Une exception est une liste de tableaux souvent modie. Dans ce cas, un ArrayList synchronis peut dpasser les performances dun CopyOnWriteArrayList.
java.util.Collections 1.2 static <E> Collection<E> synchronizedCollection(Collection<E> c) static <E> List synchronizedList(List<E> c) static <E> Set synchronizedSet(Set<E> c) static <E> SortedSet synchronizedSortedSet(SortedSet<E> c) static <K, V> Map<K, V> synchronizedMap(Map<K, V> c)

764

Au cur de Java 2 - Notions fondamentales

static <K, V> SortedMap<K, V> synchronizedSortedMap(SortedMap<K, V> c)

Construisent des vues de la collection dont les mthodes sont synchronises.

Callable et Future
Un Runnable encapsule une tche qui sexcute de manire asynchrone. Cest en quelque sorte une mthode asynchrone sans paramtres ni valeur de retour. Un Callable est identique, mais il renvoie une valeur. Linterface Callable est un type paramtres, avec une seule mthode call:
public interface Callable<V> { V call() throws Exception; }

Le paramtre de type correspond au type de la valeur renvoye. Par exemple, Callable<Integer> reprsente un calcul asynchrone qui nit par renvoyer un objet Integer. Un Future contient le rsultat dun calcul asynchrone. Un Future permet de dmarrer un calcul, de donner le rsultat quelquun, puis de loublier. Lorsquil est prt, le propritaire de lobjet Future peut obtenir le rsultat. Linterface Future possde les mthodes suivantes :
public interface Future<V> { V get() throws...; V get(long timeout, TimeUnit unit) throws...; void cancel(Boolean mayInterrupt); boolean isCancelled(); boolean isDone(); }

Un appel la premire mthode get bloque jusqu ce que le calcul soit termin. La deuxime mthode dclenche une TimeoutException si lappel est arriv expiration avant la n du calcul. Si le thread excutant le calcul est interrompu, les deux mthodes dclenchent une InterruptedException. Si le calcul est termin, get prend n immdiatement. La mthode isDone renvoie false si le calcul se poursuit, true sil est termin. Vous pouvez annuler le calcul avec la mthode cancel. Si le calcul na pas encore commenc, il est annul et ne commencera jamais. Si le calcul est en cours, il est interrompu lorsque le paramtre mayInterrupt vaut true. Lemballage FutureTask est un mcanisme commode pour transformer un Callable la fois en Future et en Runnable : il implmente les deux interfaces. Par exemple :
Callable<Integer> myComputation = ...; FutureTask<Integer> task = new FutureTask<Integer>(myComputation); Thread t = new Thread(task); // cest un Runnable t.start(); ... Integer result = task.get(); // cest un Future

Le programme du Listing 14.11 illustre ces concepts. Ce programme est identique lexemple prcdent qui trouvait les chiers contenant un mot cl. Toutefois, nous allons maintenant simplement

Chapitre 14

Multithreads

765

compter le nombre de chiers correspondants. Nous avons donc une longue tche qui produit une valeur entire ; un exemple de Callable<Integer> :
class MathCounter implements Callable<Integer> { public MathCounter(File directory, String keyword) { ... } public Integer call() { ... } // renvoie le nb de fichiers concordants }

Nous construisons ensuite un objet FutureTask depuis MathCounter et lutilisons pour dmarrer un thread :
FutureTask<Integer> task = new FutureTask<Integer>(counter); Thread t = new Thread(task); t.start();

Enn, nous afchons le rsultat :


System.out.println(task.get() + " matching files.");

Bien entendu, lappel get bloque jusqu ce que le rsultat soit disponible. Dans la mthode call, nous utilisons le mme mcanisme plusieurs reprises. Pour chaque sousrpertoire, nous produisons un nouveau MatchCounter et lanons un thread pour chacun. Nous rpartissons aussi les objets FutureTask dans un ArrayList<Future<Integer>>. A la n, nous ajoutons tous les rsultats :
for (Future<Integer> result: results) count += result.get();

Chaque appel get bloque jusqu ce que le rsultat soit disponible. Bien sr, les threads sexcutent en parallle, il y a donc de fortes chances pour que les rsultats soient tous disponibles peu prs au mme moment.
Listing 14.11 : FutureTest.java
import java.io.*; import java.util.*; import java.util.concurrent.*; /** * @version 1.0 2004-08-01 * @author Cay Horstmann */ public class FutureTest { public static void main(String[] args) { Scanner in = new Scanner(System.in); System.out.print("Enter base directory (e.g. /usr/local/jdk5.0/src): "); String directory = in.nextLine(); System.out.print("Enter keyword (e.g. volatile): "); String keyword = in.nextLine(); MatchCounter counter = new MatchCounter( new File(directory), keyword); FutureTask<Integer> task = new FutureTask<Integer>(counter); Thread t = new Thread(task); t.start();

766

Au cur de Java 2 - Notions fondamentales

try { System.out.println(task.get() + " matching files."); } catch (ExecutionException e) { e.printStackTrace(); } catch (InterruptedException e) { } } } /** * Cette tche compte les fichiers dun rpertoire et de ses * sous-rpertoires qui contiennent un mot cl donn. */ class MatchCounter implements Callable<Integer> { /** * Construit un MatchCounter. * @param directory le rpertoire dans lequel commencer la recherche * @param keyword le mot cl rechercher */ public MatchCounter(File directory, String keyword) { this.directory = directory; this.keyword = keyword; } public Integer call() { count = 0; try { File[] files = directory.listFiles(); ArrayList<Future<Integer>> results = new ArrayList<Future<Integer>>(); for (File file: files) if (file.isDirectory()) { MatchCounter counter = new MatchCounter(file, keyword); FutureTask<Integer> task = new FutureTask<Integer>(counter); results.add(task); Thread t = new Thread(task); t.start(); } else { if (search(file)) count++; } for (Future<Integer> result: results) try { count += result.get(); }

Chapitre 14

Multithreads

767

catch (ExecutionException e) { e.printStackTrace(); } } catch (InterruptedException e) { } return count; } /** * Recherche un mot cl donn dans un fichier. * @param file Le fichier parcourir * @return true si le mot cl figure dans le fichier */ public boolean search(File file) { try { Scanner in = new Scanner(new FileInputStream(file)); boolean found = false; while (!found && in.hasNextLine()) { String line = in.nextLine(); if (line.contains(keyword)) found = true; } in.close(); return found; } catch (IOException e) { return false; } } private File directory; private String keyword; private int count; } java.util.concurrent.Callable<V> 5.0

V call()

Excute une tche qui produit un rsultat.


java.util.concurrent.Future<V> 5.0

V get() V get(long time, TimeUnit unit)

Rcuprent le rsultat, en bloquant jusqu ce quil soit disponible ou que le dlai soit expir. La deuxime mthode dclenche une TimeoutException en cas dchec.

boolean cancel(boolean mayInterrupt)

Tente dannuler lexcution de cette tche. Si la tche a dj commenc et que le paramtre mayInterrupt vaut true, elle est interrompue. Renvoie true si lannulation a russi.

768

Au cur de Java 2 - Notions fondamentales

boolean isCancelled()

Renvoie true si la tche a t annule avant la n.


boolean isDone()

Renvoie true si la tche est termine, suite une n normale, une annulation ou une exception.
java.util.concurrent.FutureTask<V> 5.0

FutureTask(Callable<V> task) FutureTask(Runnable task, V result)

Construit un objet qui soit la fois un Future<V> et un Runnable.

Executors
La construction dun nouveau thread est assez onreuse car elle implique une interaction avec le systme dexploitation. Si votre programme cre un grand nombre de threads courts, il devra plutt utiliser un pool de threads. Ce pool contient plusieurs threads inactifs, prts sexcuter. Vous attribuez un Runnable au pool et lun des threads appelle la mthode run. A la n de la mthode run, le thread ne meurt pas, il demeure pour rpondre la requte suivante. Il existe une autre raison pour utiliser un pool de threads : ltranglement de plusieurs threads simultans. La cration dun grand nombre de threads peut considrablement dgrader les performances, voire bloquer la machine virtuelle. Pour un algorithme crateur de nombreux threads, utilisez un pool de threads "xe" qui limite le nombre total de threads simultans. La classe Executors possde plusieurs mthodes factory statiques destines la cration de pools de threads (voir Tableau 14.2).
Tableau 14.2 : Mthodes factory Executors

Mthode newCachedThreadPool

Description Les nouveaux threads sont crs en fonction des besoins. Les threads inactifs sont conservs pendant 60 secondes. Le pool contient un jeu xe de threads. Les threads inactifs sont conservs indniment. Un "pool" possdant un seul thread qui excute les tches envoyes de manire squentielle (comme le thread de rpartition dvnements Swing). Un pool de threads xe pour une excution programme, remplace java.util.Timer. Un "pool" dun seul thread pour une excution programme.

newFixedThreadPool newSingleThreadExecutor

newScheduledThreadPool newSingleThreadScheduledExecutor

Chapitre 14

Multithreads

769

Pools de threads
Etudions la premire des trois mthodes du Tableau 14.2. Nous verrons les autres par la suite. La mthode newCachedThreadPool construit un pool de threads qui excute chaque tche immdiatement, laide dun thread inactif existant lorsquil est disponible ou en crant un nouveau thread dans le cas contraire. La mthode newFixedThreadPool construit un pool de threads de taille xe. Si le nombre de tches envoyes est suprieur au nombre de threads inactifs, les tches non servies sont places dans une queue. Elles sont excutes la n des autres tches. newSingleThreadExecutor est un pool dgnr, de taille 1 : un seul thread excute les tches envoyes, lune aprs lautre. Ces trois mthodes renvoient un objet de la classe ThreadPoolExecutor qui implmente linterface ExecutorService. Vous pouvez envoyer un Runnable ou un Callable un ExecutorService de lune des manires suivantes :
Future<?> submit(Runnable task) Future<T> submit(Runnable task, T result) Future<T> submit(Callable<T> task)

Le pool excutera la tche envoye le plus tt possible. Lorsque vous appelez submit, vous recevez un objet Future disponible pour enquter sur ltat de la tche. La premire mthode submit renvoie un Future<?> laspect trange. Vous pouvez utiliser cet objet pour appeler isDone, cancel ou isCancelled. Or la mthode get renvoie simplement null la n. La deuxime version de submit envoie aussi un Runnable et la mthode get de Future renvoie lobjet result donn la n de lopration. La troisime version envoie un Callable. Le Future renvoy obtient le rsultat du calcul lorsquil est prt. Lorsque vous avez termin avec un pool de connexion, appelez shutdown. Cette mthode lance la squence de fermeture du pool. Un executor ferm naccepte plus les nouvelles tches. Lorsque toutes les tches sont termines, les threads du pool meurent. Vous pouvez aussi appeler shutdownNow. Le pool annule alors toutes les tches qui nont pas encore commenc et tente dinterrompre les threads en cours dexcution. Voici, en bref, ce quil faut faire pour utiliser un pool de connexion : 1. Appelez la mthode statique newCachedThreadPool ou newFixedThreadPool de la classe Executors. 2. Appelez submit pour envoyer des objets Runnable ou Callable. 3. Pour pouvoir annuler une tche ou si vous envoyez des objets Callable, restez sur les objets Future renvoys. 4. Appelez shutdown lorsque vous ne voulez plus envoyer de tches. Lexemple prcdent produisait un grand nombre de threads courts, un par rpertoire. Le programme du Listing 14.12 utilise un pool de threads pour lancer les tches. Le programme afche la taille de pool la plus grande pendant lexcution. Ces informations ne sont pas disponibles dans linterface ExecutorService. Pour cette raison, nous avons d transtyper lobjet pool sur la classe ThreadPoolExecutor.

770

Au cur de Java 2 - Notions fondamentales

Listing 14.12 : ThreadPoolTest.java


import java.io.*; import java.util.*; import java.util.concurrent.*; /** * @version 1.0 2004-08-01 * @author Cay Horstmann */ public class ThreadPoolTest { public static void main(String[] args) throws Exception { Scanner in = new Scanner(System.in); System.out.print("Enter base directory e.g. /usr/local/jdk5.0/src): "); String directory = in.nextLine(); System.out.print("Enter keyword (e.g. volatile): "); String keyword = in.nextLine(); ExecutorService pool = Executors.newCachedThreadPool(); MatchCounter counter = new MatchCounter( new File(directory), keyword, pool); Future<Integer> result = pool.submit(counter); try { System.out.println(result.get() + " matching files."); } catch (ExecutionException e) { e.printStackTrace(); } catch (InterruptedException e) { } pool.shutdown(); int largestPoolSize = ((ThreadPoolExecutor) pool).getLargestPoolSize(); System.out.println("largest pool size=" + largestPoolSize); } } /** * Cette tche compte les fichiers dun rpertoire et de ses * sous-rpertoires qui contiennent un mot cl donn. */ class MatchCounter implements Callable<Integer> { /** * Construit un MatchCounter. * @param directory Le rpertoire dans lequel commencer la recherche * @param keyword Le mot cl rechercher * @param pool Le pool de threads pour envoyer les sous-tches */

Chapitre 14

Multithreads

771

public MatchCounter(File directory, String keyword, ExecutorService pool) { this.directory = directory; this.keyword = keyword; this.pool = pool; } public Integer call() { count = 0; try { File[] files = directory.listFiles(); ArrayList<Future<Integer>> results = new ArrayList<Future<Integer>>(); for (File file: files) if (file.isDirectory()) { MatchCounter counter = new MatchCounter( file, keyword, pool); Future<Integer> result = pool.submit(counter); results.add(result); } else { if (search(file)) count++; } for (Future<Integer> result: results) try { count += result.get(); } catch (ExecutionException e) { e.printStackTrace(); } } catch (InterruptedException e) { } return count; } /** * Recherche un mot cl donn dans un fichier. * @param file Le fichier parcourir * @return true si le mot cl figure dans le fichier */ public boolean search(File file) { try { Scanner in = new Scanner(new FileInputStream(file)); boolean found = false; while (!found && in.hasNextLine()) {

772

Au cur de Java 2 - Notions fondamentales

String line = in.nextLine(); if (line.contains(keyword)) found = true; } in.close(); return found; } catch (IOException e) { return false; } } private private private private } java.util.concurrent.Executors 5.0 File directory; String keyword; ExecutorService pool; int count;

ExecutorService newCachedThreadPool()

Renvoie un pool de threads en cache qui cre des threads selon les besoins et met n aux threads inactifs depuis 60 secondes.

ExecutorService newFixedThreadPool(int threads)

Renvoie un pool de threads qui utilise le nombre donn de threads pour excuter les tches.
ExecutorService newSingleThreadExecutor()

Renvoie un Executor qui excute les tches de manire squentielle dans un seul thread.
java.util.concurrent.ExecutorService 5.0

Future<T> submit(Callable<T> task) Future<T> submit(Runnable task, T result) Future<?> submit(Runnable task)

Envoient la tche donne pour excution.


void shutdown()

Arrte le service, en terminant les tches dj envoyes mais sans accepter les nouveaux envois.
java.util.concurrent.ThreadPoolExecutor 5.0

int getLargestPoolSize()

Renvoie la plus grande taille du pool de threads pendant la vie de cet Executor.

Excution programme
Linterface ScheduledExecutorService possde des mthodes pour lexcution programme ou rpte des tches. Il sagit dune gnralisation de java.util.Timer permettant le pool de threads. Les mthodes newScheduledThreadPool et newSingleThreadScheduledExecutor de la classe Executors renvoient des objets qui implmentent linterface ScheduledExecutorService.

Chapitre 14

Multithreads

773

Vous pouvez programmer un Runnable ou un Callable pour quils ne sexcutent quune fois, aprs un dlai initial. Vous pouvez aussi programmer un Runnable pour quil sexcute intervalles rguliers. Voir les notes dAPI pour plus de dtails.
java.util.concurrent.Executors 5.0

ScheduledExecutorService newScheduledThreadPool(int threads)

Renvoie un pool de threads qui utilise le nombre donn de threads pour programmer des tches.
ScheduledExecutorService newSingleThreadScheduledExecutor()

Renvoie un Executor qui programme des tches sur un seul thread.


java.util.concurrent.ScheduledExecutorService 5.0

ScheduledFuture<V> schedule(Callable<V> task, long time, TimeUnit unit) ScheduledFuture<?> schedule(Runnable task, long time, TimeUnit unit)

Programment la tche donne aprs expiration du dlai donn.


ScheduledFuture<?> scheduleAtFixedRate(Runnable task, long initialDelay, long period, TimeUnit unit)

Programme la tche donne pour une excution priodique, chaque unit period, une fois le dlai initial expir.

ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, long initialDelay, long delay, TimeUnit unit)

Programme la tche donne pour une excution priodique, avec des units delay entre chaque ralisation dun appel et le dbut du suivant, une fois le dlai initial expir.

Contrle de groupes de tches


Vous avez vu comment utiliser un service executor sous forme de pool de threads pour augmenter lefcacit de lexcution de la tche. Un executor sert parfois des ns plus tactiques, simplement pour contrler un groupe de tches lies. Ainsi, vous pouvez annuler toutes les tches dans un executor avec la mthode shutdownNow. La mthode invokeAny envoie tous les objets dune collection dobjets Callable et renvoie le rsultat dune tche termine. Vous ne connaissez pas la tche (ctait probablement celle qui sest termine le plus rapidement). Vous utiliseriez cette mthode pour un problme de recherche dans lequel vous tes prt accepter nimporte quelle solution. Ainsi, supposons que vous deviez factoriser un grand entier (un calcul requis pour casser un code RSA). Vous pourriez envoyer plusieurs tches, chacune tentant une factorisation laide de nombres dune plage diffrente. Ds que lune de ces tches a une rponse, votre calcul peut sarrter. La mthode invokeAll envoie tous les objets dune collection dobjets Callable et renvoie une liste dobjets Future qui reprsentent les solutions toutes les tches. Vous pouvez traiter les rsultats du calcul ds quils sont disponibles, comme ceci :
List<Callable<T>> tasks = . . .; List<Future<T>> results = executor.invokeAll(tasks);

774

Au cur de Java 2 - Notions fondamentales

for (Future<T> result : results) processFurther(result.get());

Linconvnient de cette mthode, cest que vous risquez dattendre sans raison valable si la premire tche prend du temps. Il serait logique dobtenir les rsultats dans lordre de leur disponibilit. Cela peut tre arrang avec ExecutorCompletionService. Commencez par un executor, obtenu de la manire ordinaire. Construisez ensuite un ExecutorCompletionService. Envoyez les tches au service de ralisation. Le service gre une queue de blocage dobjets Future, contenant les rsultats des tches soumises lorsquelles deviennent disponibles. Une organisation plus efcace pour le calcul prcdent correspond ceci :
ExecutorCompletionService service = new ExecutorCompletionService(executor); for (Callable<T> task : tasks) service.submit(task); for (int i = 0; i < tasks.size(); i++) processFurther(service.take().get()); java.util.concurrent.ExecutorService 5.0 T invokeAny(Collection<Callable<T>> tasks) T invokeAny(Collection<Callable<T>> tasks, long timeout, TimeUnit unit)

Excutent les tches donnes et renvoient le rsultat de lune delles. La seconde mthode dclenche une TimeoutException en cas de temporisation.

List<Future<T>> invokeAll(Collection<Callable<T>> tasks) List<Future<T>> invokeAll(Collection<Callable<T>> tasks, long timeout, TimeUnit unit)

Excutent les tches donnes et renvoient les rsultats de toutes. La seconde mthode dclenche une TimeoutException en cas de temporisation.
java.util.concurrent.ExecutorCompletionService 5.0 ExecutorCompletionService(Executor e)

Construit une ralisation dexecutor qui collecte les rsultats de lexecutor donn.

Future<T> submit(Callable<T> task) Future<T> submit(Runnable task, T result)

Envoient une tche lexecutor sous-jacent.


Future<T> take()

Supprime le rsultat termin suivant, blocage au cas o aucun rsultat termin nest disponible.
Future<T> poll() Future<T> poll(long time, TimeUnit unit)

Suppriment le rsultat termin suivant ou null si aucun nest disponible. La seconde mthode attend le dlai indiqu.

Synchronizers
Le package java.util.concurrent contient plusieurs classes qui permettent de grer un jeu de threads de collaboration (voir Tableau 14.3). Ces mcanismes possdent des fonctionnalits intgres pour les patterns communs de rencontre des threads. Si vous disposez dun jeu de threads de

Chapitre 14

Multithreads

775

collaboration qui suit lun de ces motifs de comportement, rutilisez simplement la classe de bibliothque approprie au lieu de concocter une collection artisanale de verrous et de conditions.
Tableau 14.3 : Synchronizers

Classe CyclicBarrier

Action Permet un jeu de threads de patienter jusqu ce quun nombre prdni dentre eux ait atteint une barrire commune, puis excute, en option, une action de barrire. Permet un jeu de threads de patienter jusqu ce quun compteur ait t ramen 0. Permet deux threads dchanger des objets lorsque les deux sont prts pour lchange.

Quand lutiliser Lorsque plusieurs threads doivent se terminer avant que leurs rsultats ne puissent tre utiliss.

CountDownLatch

Lorsquun ou plusieurs threads doivent patienter jusqu ce quun nombre spci de rsultats soit disponible. Lorsque deux threads agissent sur deux instances de la mme structure de donnes, lun en remplissant une instance, lautre en vidant lautre instance. Pour envoyer un objet dun thread un autre lorsque les deux sont prts, sans synchronisation explicite. Pour limiter le nombre total de threads ayant accs une ressource. Si le compte des autorisations est un, utiliser pour bloquer les threads jusqu ce quun autre thread donne une autorisation.

Exchanger

SynchronousQueue

Permet un thread de donner un objet un autre thread. Permet un jeu de threads de patienter jusqu ce que les autorisations de poursuivre soient disponibles.

Semaphore

Smaphores
Un smaphore est un lment qui gre plusieurs autorisations. Pour passer au-del du smaphore, un thread demande une autorisation en appelant acquire. Seul un nombre xe dautorisations est disponible, ce qui limite le nombre de threads autoriss passer. Dautres threads peuvent mettre des autorisations en appelant release. Mais il ne sagit pas de vrais objets dautorisation. Le smaphore conserve simplement un compteur. De plus, une autorisation na pas tre libre par le thread qui lacquiert. En fait, nimporte quel thread peut mettre nimporte quel nombre dautorisations. Sil en met plus que le maximum disponible, le smaphore est simplement dni sur le compte maximal. Ils sont donc exibles, mais peuvent paratre confus. Les smaphores ont t invents par Edsger Dijkstra en 1968 pour tre utiliss sous la forme de primitives de synchronisation. M. Dijkstra a montr que les smaphores pouvaient tre efcacement implments et quils sont sufsamment performants pour rsoudre les problmes communs de

776

Au cur de Java 2 - Notions fondamentales

synchronisation des threads. Dans presque tous les systmes dexploitation, vous trouverez des implmentations de queues bornes utilisant des smaphores. Bien entendu, les programmeurs dapplications nont pas rinventer les queues bornes. Nous vous suggrons de nutiliser les smaphores que lorsque leur comportement concorde bien avec votre problme de synchronisation. Par exemple, un smaphore ayant un nombre dautorisations gal 1 servira de porte quun autre thread pourra ouvrir et fermer. Vous verrez un peu plus loin un exemple dans lequel un thread travailleur produit une animation. Parfois, le thread travailleur attend quun utilisateur appuie sur un bouton. Le thread travailleur tente dacqurir une autorisation, puis attend quun clic de bouton autorise lmission dune autorisation.

Verrous Countdown
Un CountdownLatch permet un jeu de threads de patienter jusqu ce quun compteur ait atteint zro. Le CountdownLatch ne sert quune fois. Lorsque le compteur a atteint zro, vous ne pouvez pas le rutiliser. Il existe un cas spcial, o le compteur du verrou est 1. Ceci implmente une porte dutilisation unique. Les threads sont maintenus la porte jusqu ce quun autre thread dnisse le compte sur 0. Imaginons, par exemple, un jeu de threads qui ait besoin de donnes initiales pour effectuer son travail. Les threads travailleurs dmarrent, puis patientent la porte. Un autre thread prpare les donnes. Lorsquil est prt, il appelle countDown et tous les threads travailleurs poursuivent. Vous pouvez utiliser un second verrou pour vrier lorsque tous les threads travailleurs sont effectus. Initialisez le verrou avec le nombre de threads. Chaque thread travailleur dcompte ce verrou juste avant de terminer. Un autre thread qui rcolte les rsultats du travail patiente sur le verrou et continue ds que tous les travailleurs ont termin.

Barrires
La classe CyclicBarrier implmente ce que lon appelle une barrire. Imaginons que plusieurs threads travaillent sur certaines parties dun calcul. Lorsque toutes les parties sont prtes, il faut combiner les rsultats. Ainsi, lorsquun thread a termin avec une partie, nous le laissons sexcuter contre la barrire. Lorsque tous les threads ont atteint la barrire, celle-ci cde et les threads peuvent poursuivre. Pour cela, vous construisez dabord une barrire, en indiquant le nombre de threads participants :
CyclicBarrier barrier = new CyclicBarrier(nthreads);

Chaque thread effectue un certain travail et appelle await sur la barrire lorsquil a termin :
public void run() { doWork(); barrier.await(); ... }

La mthode await prend un paramtre optionnel de temporisation :


barrier.await(100, TimeUnit.MILLISECONDS);

Chapitre 14

Multithreads

777

Si lun des threads attendant la barrire part, celle-ci se rompt (un thread peut partir sil appelle await avec une temporisation ou parce quil a t interrompu). Dans ce cas, la mthode await de tous les autres threads dclenche une BrokenBarrierException. Les threads qui attendent dj voient leur mthode await se terminer immdiatement. Vous pouvez fournir une action de barrire optionnelle, qui sexcutera lorsque tous les threads auront atteint la barrire :
Runnable barrierAction = ...; CyclicBarrier barrier = new CyclicBarrier(nthreads, barrierAction);

Laction peut rcuprer le rsultat de chaque thread. La barrire est dite cyclique car elle peut tre rutilise aprs libration de tous les threads en attente. Elle diffre en cela dun CountDownLatch, qui ne peut tre utilis quune fois.

Exchanger
Exchanger est utilis lorsque deux threads travaillent sur deux instances du mme tampon de donnes. Gnralement, un thread remplit le tampon, tandis que lautre consomme son contenu. Lorsque les deux ont termin, ils changent leurs tampons.

Queues synchrones
Une queue synchrone est un mcanisme qui apparie un thread producteur et un thread consommateur. Lorsquun thread appelle put sur un SynchronousQueue, il bloque jusqu ce quun autre thread appelle take, et vice versa. A la diffrence dExchanger, les donnes ne sont transfres que dans une direction, du producteur au consommateur. Mme si la classe SynchronousQueue implmente linterface BlockingQueue, ce nest pas vraiment une queue. Elle ne contient aucun lment. Sa mthode size renvoie toujours 0.

Exemple : pause et reprise dune animation


Envisagez un programme qui effectue un travail, actualise lcran, puis attend que lutilisateur regarde le rsultat et appuie sur un bouton pour continuer, enn, ralise la prochaine unit de travail. Un smaphore avec un compteur dautorisations de 1 peut servir synchroniser le thread travailleur et le thread de rpartition des vnements. Le thread travailleur appelle acquire ds quil est prt mettre en pause. Le thread dinterface utilisateur appelle release ds que lutilisateur clique sur le bouton Continue. Que se passe-t-il alors si lutilisateur clique plusieurs fois sur le bouton lorsque le thread travailleur est prt ? Une seule autorisation tant disponible, le compteur reste 1. Le programme du Listing 14.13 concrtise cette ide. Il anime un algorithme de tri. Un thread travailleur trie un tableau, il sarrte intervalles rguliers et attend que lutilisateur donne lautorisation de poursuivre. Lutilisateur peut admirer un dessin de ltat actuel de lalgorithme et appuyer sur le bouton Continue pour permettre au thread travailleur de raliser ltape suivante. Nous navons pas voulu vous ennuyer avec le code dun algorithme de tri, nous appelons donc simplement Arrays.sort, qui implmente lalgorithme de tri-fusion. Pour faire une pause dans

778

Au cur de Java 2 - Notions fondamentales

lalgorithme, nous fournissons un objet Comparator qui attend le smaphore. Lanimation fait donc une pause ds que lalgorithme compare deux lments. Nous dessinons les valeurs actuelles du tableau et surlignons les lments compars (voir Figure 14.8).
INFO
Lanimation montre la fusion de plages tries plus petites dans de grandes plages, mais elle nest pas totalement prcise. Lalgorithme de tri-fusion utilise un second tableau pour contenir des valeurs temporaires que nous ne verrons pas. Cet exemple na pas pour objet de prciser les algorithmes de tri, mais de montrer comment utiliser un smaphore pour faire une pause sur un thread travailleur.

Figure 14.8
Animation dun algorithme de tri.

Listing 14.13 : AlgorithmAnimation.java


import import import import import import java.awt.*; java.awt.geom.*; java.awt.event.*; java.util.*; java.util.concurrent.*; javax.swing.*;

/** * Ce programme anime un algorithme de tri. * @version 1.01 2007-05-18 * @author Cay Horstmann */ public class AlgorithmAnimation { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { JFrame frame = new AnimationFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } });

Chapitre 14

Multithreads

779

} } /** * Ce cadre montre le tableau en cours de tri, ainsi que les * boutons pour avancer dun pas dans lanimation * ou lexcuter sans interruption. */ class AnimationFrame extends JFrame { public AnimationFrame() { ArrayComponent comp = new ArrayComponent(); add(comp, BorderLayout.CENTER); final Sorter sorter = new Sorter(comp); JButton runButton = new JButton("Run"); runButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { sorter.setRun(); } }); JButton stepButton = new JButton("Step"); stepButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { sorter.setStep(); } }); JPanel buttons = new JPanel(); buttons.add(runButton); buttons.add(stepButton); add(buttons, BorderLayout.NORTH); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); Thread t = new Thread(sorter); t.start(); } private static final int DEFAULT_WIDTH = 300; private static final int DEFAULT_HEIGHT = 300; } /** * Cet excutable excute un algorithme de tri. * Lorsque deux lments sont compars, lalgorithme * fait une pause et actualise un composant. */ class Sorter implements Runnable {

780

Au cur de Java 2 - Notions fondamentales

/** * Construit un Sorter. * @param values Le tableau trier * @param panel Le composant sur lequel afficher * la progression du tri */ public Sorter(ArrayComponent comp) { values = new Double[VALUES_LENGTH]; for (int i = 0; i < values.length; i++) values[i] = new Double(Math.random()); this.component = comp; this.gate = new Semaphore(1); this.run = false; } /** * Rgle le trieur sur le mode "run". Appel * sur le thread de rpartition des vnements. */ public void setRun() { run = true; gate.release(); } /** * Rgle le trieur sur le mode "Step". Appel * sur le thread de rpartition des vnements. */ public void setStep() { run = false; gate.release(); } public void run() { Comparator<Double> comp = new Comparator<Double>() { public int compare(Double i1, Double i2) { component.setValues(values, i1, i2); try { if (run) Thread.sleep(DELAY); else gate.acquire(); } catch (InterruptedException exception) { Thread.currentThread().interrupt(); } return i1.compareTo(i2); } };

Chapitre 14

Multithreads

781

Arrays.sort(values, comp); component.setValues(values, null, null); } private private private private private private } /** * Cet cran dessine un tableau et marque deux lments dans * le tableau. */ class ArrayComponent extends JComponent { /** * Rgle les valeurs dessiner. Appel sur le thread de tri. * @param values Le tableau de valeurs afficher * @param marked1 Le premier lment marqu * @param marked2 Le second lment marqu */ public synchronized void setValues(Double[] values, Double marked1, Double marked2) { this.values = values.clone(); this.marked1 = marked1; this.marked2 = marked2; repaint(); } public synchronized void paintComponent(Graphics g) // appel sur le thread de rpartition des vnements { if (values == null) return; Graphics2D g2 = (Graphics2D) g; int width = getWidth() / values.length; for (int i = 0; i < values.length; i++) { double height = values[i] * getHeight(); Rectangle2D bar = new Rectangle2D.Double(width * i, 0, width, height); if (values[i] == marked1 || values[i] == marked2) g2.fill(bar); else g2.draw(bar); } } private Double marked1; private Double marked2; private Double[] values; } Double[] values; ArrayComponent component; Semaphore gate; static final int DELAY = 100; volatile boolean run; static final int VALUES_LENGTH = 30;

782

Au cur de Java 2 - Notions fondamentales

java.util.concurrent.CyclicBarrier 5.0

CyclicBarrier(int parties) CyclicBarrier(int parties, Runnable barrierAction)

Construisent une barrire cyclique pour le nombre de parties donn. barrierAction est excut lorsque toutes les parties ont appel await sur la barrire.

int await() int await(long time, TimeUnit unit)

Attendent que toutes les parties aient appel await sur la barrire ou jusqu la n de la temporisation, auquel cas une TimeoutException est dclenche. En cas de russite, renvoient lindice darrive de cette partie. La premire partie possde parties 1 indices, la dernire partie possde un indice 0.
java.util.concurrent.CountDownLatch 5.0

CountdownLatch(int count)

Construit un verrou avec un compte rebours donn.


void await()

Attend que le compteur de ce verrou ait atteint 0.


boolean await(long time, TimeUnit unit)

Attend que le compteur de ce verrou ait atteint 0 ou que la temporisation ait expir. Renvoie true si le compteur vaut 0, false si la temporisation a expir.

public void countDown()

Dcompte du compte rebours de ce verrou.


java.util.concurrent.Exchanger<V> 5.0

V exchange(V item) V exchange(V item, long time, TimeUnit unit)

Bloquent jusqu ce quun autre thread appelle cette mthode, puis changent llment avec lautre thread et renvoient llment de lautre thread. La deuxime mthode dclenche une TimeoutException aprs expiration de la temporisation.
java.util.concurrent.SynchronousQueue<V> 5.0

SynchronousQueue() SynchronousQueue(boolean fair)

Construisent une queue synchrone qui permet aux threads de donner des lments. Si fair vaut true, la queue favorise les threads attendant depuis le plus longtemps.

void put(V item)

Bloque jusqu ce quun autre thread appelle take pour prendre cet lment.
V take()

Bloque jusqu ce quun autre thread appelle put. Renvoie llment fourni par lautre thread.

Chapitre 14

Multithreads

783

java.util.concurrent.Semaphore 5.0

Semaphore(int permits) Semaphore(int permits, boolean fair)

Construisent un smaphore avec le nombre maximal donn dautorisations. Si fair vaut true, la queue favorise les threads attendant depuis le plus longtemps.

void acquire()

Attend dacqurir une autorisation.


boolean tryAcquire()

Tente dacqurir une autorisation ; renvoie false si aucune nest disponible.


boolean tryAcquire(long time, TimeUnit unit)

Tente dacqurir une autorisation avec le dlai donn ; renvoie false si aucune nest disponible.

void release()

Libre une autorisation.

Threads et Swing
Nous lavons vu dans lintroduction de ce chapitre, lune des raisons poussant utiliser les threads dans vos programmes est que ces derniers rpondront mieux. Lorsquun programme doit raliser une tche de longue dure, vous pouvez lancer un autre travailleur au lieu de bloquer linterface utilisateur. Attention toutefois ce que vous faites dans un thread travailleur car, et cela peut surprendre, Swing nest pas compatible avec les threads. Si vous tentez de manipuler des lments dinterface utilisateur partir de plusieurs threads, linterface utilisateur peut sendommager. Pour constater ce problme, excutez le programme de test suivant du Listing 14.14. Lorsque vous cliquez sur le bouton Bad, un nouveau thread est dmarr. Sa mthode run torture une liste droulante, en ajoutant et supprimant des valeurs de manire alatoire.
public void run() { try { while (true) { int i = Math.abs(generator.nextInt()); if (i % 2 == 0) combo.insertItemAt(new Integer(i), 0); else if (combo.getItemCount() > 0) combo.removeItemAt(i % combo.getItemCount()); sleep(1); } catch (InterruptedException e) {} } }

784

Au cur de Java 2 - Notions fondamentales

Testez-le. Cliquez sur le bouton Bad, puis cliquez plusieurs fois sur la zone droulante. Dplacez la barre de dlement. Dplacez la fentre. Cliquez nouveau sur le bouton Bad. Continuez cliquer sur la zone droulante. Enn, vous devez voir un rapport dexceptions (voir Figure 14.9). Que se passe-t-il ? Lorsquun lment est insr dans la zone droulante, celle-ci dclenche un vnement pour actualiser lafchage. Le code dafchage se met ensuite en route, il lit la taille actuelle de la zone droulante, puis se prpare afcher les valeurs. Mais le thread travailleur continue fonctionner, entranant parfois une rduction du nombre des valeurs dans la zone droulante. Le code dafchage pense alors quil y a plus de valeurs dans le modle que ce quil y en a rellement. Il demande des valeurs inexistantes, puis dclenche une exception ArrayIndexOutOfBounds. Cette situation aurait pu tre vite en permettant aux programmeurs de verrouiller lobjet zone droulante pendant son afchage. Or, les concepteurs de Swing ont dcid de ne faire aucun effort pour rendre Swing compatible avec les threads, et ce pour deux raisons. Tout dabord, la synchronisation prend du temps et personne ne voulait ralentir Swing encore plus. Et surtout, lquipe Swing a tudi lexprience que dautres quipes avaient eue avec les botes outils dinterfaces utilisateur compatibles avec les threads. Les constatations taient peu encourageantes. Les programmeurs utilisant des botes outils compatibles avec les threads ont t drouts par les demandes de synchronisation et ont souvent cr des programmes sujets aux verrous morts.
Figure 14.9
Rapports dexception dans la console.

Excution de tches longues


Lorsque vous utilisez des threads avec Swing, deux rgles simples sont respecter.
m

Si une action est trop longue, effectuez-la dans un thread travailleur spar et jamais dans le thread de rpartition dvnements. Ne touchez pas aux composants Swing dans un thread autre que le thread de rpartition des vnements.

Chapitre 14

Multithreads

785

La raison de la premire rgle est simple comprendre. Si vous passez trop de temps dans le thread de rpartition des vnements, lapplication semble "morte" car elle ne rpond aucun vnement. Notamment, le thread de rpartition des vnements ne doit jamais raliser dappels en entre/sortie, qui pourraient se bloquer indniment, et il ne doit jamais appeler sleep (si vous devez attendre un dlai spcique, utilisez les vnements de minuteur). La seconde rgle est souvent appele rgle du thread unique pour la programmation Swing. Nous la verrons un peu plus loin. Ces deux rgles semblent entrer en conit. Supposons que vous dclenchiez un thread spar pour quil excute une tche longue. Vous voulez gnralement actualiser linterface utilisateur pour signaler la progression lorsque votre thread travaille. Une fois la tche termine, vous devez actualiser nouveau linterface graphique. Vous ne pouvez toutefois pas toucher aux composants Swing partir de votre thread. Par exemple, pour actualiser une barre de progression ou une tiquette de texte, vous ne pouvez pas simplement rgler sa valeur partir de votre thread. Pour rsoudre ce problme, il existe deux mthodes dans tous les threads pour ajouter des actions arbitraires la queue des vnements. Ainsi, supposons que vous vouliez rgulirement actualiser une tiquette dans un thread pour signaler une progression. Vous ne pouvez pas appeler label.setText partir du thread. Utilisez plutt les mthodes invokeLater et invokeAndWait de la classe EventQueue pour que cet appel soit excut dans le thread de rpartition des vnements. Vous placez donc le code Swing dans la mthode run dune classe qui implmente linterface Runnable. Vous crez ensuite un objet de cette classe et vous le passez la mthode statique invokeLater ou invokeAndWait. Voici par exemple comment actualiser une tiquette de texte :
EventQueue.invokeLater(new Runnable() { public void run() { label.setText(percentage + "% complete"); } });

La mthode invokeLater retourne immdiatement lorsque lvnement est envoy dans la queue des vnements. La mthode run est excute de manire asynchrone. La mthode invokeAndWait attend que la mthode run ait rellement t excute. Dans le cas dune mise jour dune tiquette de progression, la mthode invokeLater convient mieux. Les utilisateurs prfreraient que le thread travailleur progresse plus, plutt que davoir un indicateur plus prcis. Les deux mthodes excutent la mthode run dans le thread de rpartition des vnements. Aucun nouveau thread nest cr. Le Listing 14.14 montre comment utiliser la mthode invokeLater pour modier en toute scurit le contenu dune zone droulante. Si vous cliquez sur le bouton Good, un thread insre et supprime des nombres. Toutefois, la modication relle a lieu dans le thread de rpartition des vnements.
Listing 14.14 : SwingThreadTest.java
import java.awt.*; import java.awt.event.*; import java.util.*;

786

Au cur de Java 2 - Notions fondamentales

import javax.swing.*; /** * Ce programme montre quun thread qui sexcute en * parallle avec le thread de rpartition des vnements * peut gnrer des erreurs dans les composants Swing. * @version 1.23 2007-05-17 * @author Cay Horstmann */ public class SwingThreadTest { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { SwingThreadFrame frame = new SwingThreadFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); } } /** * Ce cadre possde deux boutons pour remplir une zone * droulante partir dun thread spar. Le bouton "Good" * utilise la queue dvnement, le bouton "Bad" modifie directement * la zone droulante. */ class SwingThreadFrame extends JFrame { public SwingThreadFrame() { setTitle("SwingThreadTest"); final JComboBox combo = new JComboBox(); combo.insertItemAt(Integer.MAX_VALUE, 0); combo.setPrototypeDisplayValue(combo.getItemAt(0)); combo.setSelectedIndex(0); JPanel panel = new JPanel(); JButton goodButton = new JButton("Good"); goodButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { new Thread(new GoodWorkerRunnable(combo)).start(); } }); panel.add(goodButton); JButton badButton = new JButton("Bad"); badButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { new Thread(new BadWorkerRunnable(combo)).start();

Chapitre 14

Multithreads

787

} }); panel.add(badButton); panel.add(combo); add(panel); pack(); } } /** * Cet excutable modifie une zone droulante en ajoutant et * supprimant des nombres alatoirement. Cela peut entraner * des erreurs car les mthodes de la zone droulante ne sont * pas synchronises et parce que le thread travailleur et le thread * de rpartition des vnements accdent tous deux la liste * droulante. */ class BadWorkerRunnable implements Runnable { public BadWorkerRunnable(JComboBox aCombo) { combo = aCombo; generator = new Random(); } public void run() { try { while (true) { int i = Math.abs(generator.nextInt()); if (i % 2 == 0) combo.insertItemAt(i, 0); else if (combo.getItemCount() > 0) combo.removeItemAt(i % combo.getItemCount()); Thread.sleep(1); } } catch (InterruptedException e) { } } private JComboBox combo; private Random generator; } /** * Cet excutable modifie une zone droulante en ajoutant et * supprimant alatoirement des nombres. Pour sassurer que * la zone droulante nest pas corrompue, les oprations de * modifications sont transmises au thread de rpartition * des vnements. */ class GoodWorkerRunnable implements Runnable { public GoodWorkerRunnable(JComboBox aCombo) {

788

Au cur de Java 2 - Notions fondamentales

combo = aCombo; generator = new Random(); } public void run() { try { while (true) { EventQueue.invokeLater(new Runnable() { public void run() { int i = Math.abs(generator.nextInt()); if (i % 2 == 0) combo.insertItemAt(i, 0); else if (combo.getItemCount() > 0) combo.removeItemAt(i % combo.getItemCount()); } }); Thread.sleep(1); } } catch (InterruptedException e) { } } private JComboBox combo; private Random generator; } java.awt.EventQueue 1.1 static void invokeLater(Runnablerunnable) 1.2

Amne la mthode run de lobjet runnable sexcuter dans le thread de rpartition des vnements aprs que les vnements en attente ont t traits.

static void invokeAndWait(Runnablerunnable) 1.2

Amne la mthode run de lobjet runnable sexcuter dans le thread de rpartition des vnements aprs que les vnements en attente ont t traits. Cet appel se bloque jusqu ce que la mthode run se termine.

static boolean isDispatchThread() 1.2

Renvoie true si le thread excutant cette mthode est le thread de rpartition des vnements.

Utilisation du travailleur Swing


Lorsquun utilisateur met une commande pour laquelle le traitement est long, vous devez dclencher un nouveau thread pour quil effectue le travail. Vous lavez vu la section prcdente, ce thread doit utiliser la mthode EventQueue.invokeLater pour actualiser linterface utilisateur. Plusieurs auteurs ont produit des classes pour simplier cette tche, lune delles est arrive jusqu Java SE 6. Ici, nous dcrivons cette classe SwingWorker.

Chapitre 14

Multithreads

789

Le programme du Listing 14.15 possde des commandes pour charger un chier texte et annuler le traitement du chargement. Testez le programme avec un chier long, comme le texte complet du Comte de Monte-Cristo, fourni dans le rpertoire gutenberg du code accompagnant louvrage. Le chier est charg dans un thread spar. Lorsque le chier se lit, llment de menu Open est dsactiv et llment Cancel est activ (voir Figure 14.10). Aprs que chaque ligne a t lue, un compteur de ligne dans la barre dtat est mis jour. Une fois la procdure de lecture termine, llment Open est ractiv, llment Cancel est dsactiv et le texte de la ligne dtat passe Done. Cet exemple montre les activits dIU ordinaires dune tche en arrire-plan :
m m

aprs chaque unit de travail, actualiser lIU pour afcher la progression ; une fois le travail termin, apporter une dernire modication lIU.

La classe SwingWorkerTask simplie limplmentation de cette tche. Vous crasez la mthode doInBackground pour effectuer le travail de longue haleine et vous appelez parfois publish pour communiquer la progression du travail. Cette mthode est excute dans un thread travailleur. La mthode publish amne une mthode process sexcuter dans le thread de rpartition des vnements pour traiter les donnes de progression. A la n du travail, la mthode done est appele dans le thread de rpartition des vnements pour que vous nissiez la mise jour de lIU.
Figure 14.10
Chargement dun chier dans un thread spar.

Ds que vous souhaitez travailler dans le thread travailleur, construisez un nouveau travailleur (chaque objet travailleur a pour but dtre utilis une seule fois). Appelez ensuite la mthode execute. Vous appellerez gnralement execute sur le thread de rpartition des vnements, mais ce nest pas une obligation. On peut supposer quun travailleur produise un rsultat dun certain type ; SwingWorker<T, V> implmente donc Future<T>. Ce rsultat peut tre obtenu par la mthode get de linterface Future. Puisque la mthode get se bloque jusqu ce que le rsultat soit disponible, vous ne devez pas lappeler immdiatement aprs execute. Il est conseill de ne le faire que lorsque vous savez que le travail est achev. Gnralement, vous lappelez depuis la mthode done (il ny a aucune obligation dappeler get, parfois, le traitement des donnes de progression suft).

790

Au cur de Java 2 - Notions fondamentales

Les donnes de progression intermdiaire et le rsultat nal peuvent avoir des types arbitraires. La classe SwingWorker possde ces types comme paramtres de type. Un SwingWorker<T, V> produit un rsultat du type T et des donnes de progression de type V. Pour annuler le travail en cours, utilisez la mthode cancel de linterface Future. Lorsque le travail est annul, la mthode get dclenche une CancellationException. Nous lavons indiqu, lappel du thread travailleur publish entrane des appels process sur le thread de rpartition des vnements. Pour des raisons defcacit, les rsultats de plusieurs appels publish peuvent tre regroups en un seul appel process. La mthode process reoit un List<V> contenant tous les rsultats intermdiaires. Utilisons ce mcanisme pour lire un chier texte. Or, un JTextArea est assez lent. Ajouter des lignes dun chier texte long (comme toutes les lignes du Comte de Monte-Cristo) prend un temps considrable. Pour montrer lutilisateur quune progression est en cours, nous allons afcher le nombre de lignes lues dans une ligne dtat. Ainsi, les donnes de progression sont composes du nombre actuel de lignes et de la ligne actuelle de texte. Nous les emballons dans une classe interne triviale :
private class ProgressData { public int number; public String line; }

Le rsultat nal est le texte qui a t lu dans un StringBuilder. Nous avons donc besoin dun SwingWorker<StringBuilder, ProgressData>. Dans la mthode doInBackground, nous lisons un chier, ligne par ligne. Aprs chaque ligne, nous appelons publish pour publier le nombre de lignes et le texte de la ligne actuelle.
@Override public StringBuilder doInBackground() throws IOException, InterruptedException { int lineNumber = 0; Scanner in = new Scanner(new FileInputStream(file)); while (in.hasNextLine()) { String line = in.nextLine(); lineNumber++; text.append(line); text.append("\n"); ProgressData data = new ProgressData(); data.number = lineNumber; data.line = line; publish(data); Thread.sleep(1); // tester lannulation ; // inutile dans vos programmes } return text; }

Nous appelons galement sleep pendant un millime de seconde aprs chaque ligne pour que vous puissiez tester lannulation sans tre stress, mais vous ne voulez pas ralentir vos propres programmes avec sleep. Si vous commentez cette ligne, vous verrez que Le Comte de Monte-Cristo se charge assez rapidement, avec seulement quelques mises jour dinterface utilisateur par lot.

Chapitre 14

Multithreads

791

INFO
Vous pouvez amener ce programme se comporter de manire assez homogne en actualisant la zone de texte partir du thread travailleur, mais cela nest pas possible pour la plupart des composants Swing. Nous vous montrons lapproche gnrale dans laquelle les mises jour des composants surviennent dans le thread de rpartition des vnements.

Dans la mthode process, nous ignorons tous les numros de lignes lexception de la dernire et concatnons toutes les lignes pour une mise jour unique de la zone de texte.
@Override public void process(List<ProgressData> data) { if (isCancelled()) return; StringBuilder b = new StringBuilder(); statusLine.setText("" + data.get(data.size() - 1).number); for (ProgressData d : data) { b.append(d.line); b.append("\n"); } textArea.append(b.toString()); }

Dans la mthode done, la zone de texte est mise jour avec la totalit du texte et llment de menu Cancel est dsactiv. Remarquez que le travailleur est dmarr dans lcouteur dvnement pour llment de menu Open. Cette technique simple permet dexcuter des tches longues tout en assurant lattention de linterface utilisateur.
Listing 14.15 : SwingWorkerTest.java
import import import import import import java.awt.*; java.awt.event.*; java.io.*; java.util.*; java.util.List; java.util.concurrent.*;

import javax.swing.*; /** * Ce programme prsente un thread travailleur qui excute * une tche pouvant tre longue. * @version 1.1 2007-05-18 * @author Cay Horstmann */ public class SwingWorkerTest { public static void main(String[] args) throws Exception { EventQueue.invokeLater(new Runnable() { public void run() { JFrame frame = new SwingWorkerFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); }

792

Au cur de Java 2 - Notions fondamentales

}); } } /** * Ce cadre possde une zone de texte pour afficher le contenu dun * fichier texte, un menu pour ouvrir un fichier et annuler * le procd douverture, ainsi quune ligne dtat pour afficher * la progression de chargement du fichier. */ class SwingWorkerFrame extends JFrame { public SwingWorkerFrame() { chooser = new JFileChooser(); chooser.setCurrentDirectory(new File(".")); textArea = new JTextArea(); add(new JScrollPane(textArea)); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); statusLine = new JLabel(" "); add(statusLine, BorderLayout.SOUTH); JMenuBar menuBar = new JMenuBar(); setJMenuBar(menuBar); JMenu menu = new JMenu("File"); menuBar.add(menu); openItem = new JMenuItem("Open"); menu.add(openItem); openItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { // afficher la bote de slection de fichier int result = chooser.showOpenDialog(null); // si le fichier est slectionn, le dfinir comme // icne de ltiquette if (result == JFileChooser.APPROVE_OPTION) { textArea.setText(""); openItem.setEnabled(false); textReader = new TextReader(chooser.getSelectedFile()); textReader.execute(); cancelItem.setEnabled(true); } } }); cancelItem = new JMenuItem("Cancel"); menu.add(cancelItem); cancelItem.setEnabled(false); cancelItem.addActionListener(new ActionListener() {

Chapitre 14

Multithreads

793

public void actionPerformed(ActionEvent event) { textReader.cancel(true); } }); } private class ProgressData { public int number; public String line; } private class TextReader extends SwingWorker< StringBuilder, ProgressData> { public TextReader(File file) { this.file = file; } // la mthode suivante sexcute dans le thread travailleur ; // elle ne touche pas les composants Swing @Override public StringBuilder doInBackground() throws IOException, InterruptedException { int lineNumber = 0; Scanner in = new Scanner(new FileInputStream(file)); while (in.hasNextLine()) { String line = in.nextLine(); lineNumber++; text.append(line); text.append("\n"); ProgressData data = new ProgressData(); data.number = lineNumber; data.line = line; publish(data); Thread.sleep(1); // pour tester lannulation ; // inutile dans vos programmes } return text; } // les mthodes suivantes sexcutent dans le thread // de rpartition des vnements @Override public void process(List<ProgressData> data) { if (isCancelled()) return; StringBuilder b = new StringBuilder(); statusLine.setText("" + data.get(data.size() - 1).number); for (ProgressData d : data) { b.append(d.line); b.append("\n");

794

Au cur de Java 2 - Notions fondamentales

} textArea.append(b.toString()); } @Override public void done() { try { StringBuilder result = get(); textArea.setText(result.toString()); statusLine.setText("Done"); } catch (InterruptedException ex) { } catch (CancellationException ex) { textArea.setText(""); statusLine.setText("Cancelled"); } catch (ExecutionException ex) { statusLine.setText("" + ex.getCause()); } cancelItem.setEnabled(false); openItem.setEnabled(true); } private File file; private StringBuilder text = new StringBuilder(); }; private private private private private private JFileChooser chooser; JTextArea textArea; JLabel statusLine; JMenuItem openItem; JMenuItem cancelItem; SwingWorker<StringBuilder, ProgressData> textReader;

public static final int DEFAULT_WIDTH = 450; public static final int DEFAULT_HEIGHT = 350; } javax.swing.SwingWorker<T, V> 6 abstract T doInBackground()

Ecrase cette mthode pour raliser la tche en arrire-plan et renvoyer le rsultat du travail.
void process(List<V> data)

Ecrase cette mthode pour traiter des donnes de progression intermdiaire dans le thread de rpartition des vnements.

void publish(V... data)

Transfre les donnes de progression intermdiaires au thread de rpartition des vnements. Appelez cette mthode partir de doInBackground.

Chapitre 14

Multithreads

795

void execute()

Programme ce travailleur pour une excution sur un thread travailleur.


SwingWorker.StateValue getState()

Rcupre ltat de ce travailleur, entre PENDING, STARTED et DONE.

La rgle du thread unique


Chaque application Java dmarre par une mthode main qui sexcute dans le thread principal. Dans un programme Swing, le thread principal est court. Il programme la construction de linterface utilisateur dans le thread de rpartition des vnements puis sarrte. Une fois linterface utilisateur construite, le thread de rpartition des vnements traite les notications des vnements, comme les appels actionPerformed ou paintComponent. Dautres threads, comme celui qui envoie les vnements dans la queue des vnements, sexcutent en coulisse mais ils sont invisibles pour le programmeur. Prcdemment, au cours de ce chapitre, nous avons prsent la rgle du thread unique : "Ne touchez pas aux composants Swing dun thread autre que le thread de rpartition des vnements." Nous allons dtailler cette rgle. Il existe quelques exceptions.
m

Vous pouvez ajouter et supprimer en toute scurit des couteurs dvnements de nimporte quel thread. Bien entendu, les mthodes dcouteur seront appeles dans le thread de rpartition des vnements. Un petit nombre de mthodes Swing sont compatibles avec les threads. Elles sont dsignes dans la documentation API par la phrase "This method is thread safe, although most Swing methods are not (cette mthode est compatible avec les threads, mme si la plupart des mthodes Swing ne le sont pas). Les plus utiles sont :
JTextComponent.setText JTextArea.insert JTextArea.append JTextArea.replaceRange JComponent.repaint JComponent.revalidate

INFO
Nous avons souvent utilis la mthode repaint dans cet ouvrage ; la mthode revalidate est moins commune. Son objectif est de forcer la mise en page dun composant une fois que le contenu a chang. LAWT traditionnel possde une mthode validate pour forcer la mise en page dun composant. Pour les composants Swing, vous devez simplement appeler revalidate la place (toutefois, pour forcer la mise en page dun JFrame, vous devrez toujours appeler validate; un JFrame est un Component mais pas un JComponent).

Historiquement, la rgle du thread unique tait plus permissive. Nimporte quel thread avait le droit de construire des composants, de dnir leurs proprits et de les ajouter dans des conteneurs, tant quaucun des composants navait t ralis. Un composant est ralis sil peut recevoir des vnements de dessin ou de validation. Cest le cas ds que les mthodes setVisible(true) or pack (!) ont t appeles sur le composant ou si le composant a t ajout un conteneur qui a t ralis.

796

Au cur de Java 2 - Notions fondamentales

Cette version de la rgle du thread unique tait commode. Elle vous permettait de crer linterface graphique dans la mthode main, puis dappeler setVisible(true) sur le cadre de niveau suprieur de lapplication. Il nexistait aucune planication gnante dun Runnable sur le thread de rpartition dvnements. Malheureusement, certains implmenteurs de composants ne faisaient pas attention aux subtilits de la rgle du thread unique. Ils lanaient des activits sur le thread de rpartition des vnements sans jamais sinquiter de vrier si le composant tait ralis. Ainsi, si vous appelez setSelectionStart or setSelectionEnd sur un JTextComponent, un mouvement caret est plani dans le thread de rpartition des vnements, mme si le composant nest pas visible. Il aurait pu tre possible de dtecter et de rsoudre ces problmes, mais les concepteurs Swing ont choisi la solution de facilit. Ils ont dcrt quil ntait jamais sr daccder aux composants depuis nimporte quel thread autre que le thread de rpartition des vnements. Vous devez donc construire linterface utilisateur dans le thread de rpartition des vnements, laide de lappel EventQueue.invokeLater que vous avez vu dans tous nos exemples de programmes. Il existe bien entendu de nombreux programmes qui ne sont pas aussi attentifs et conservent lancienne version de la rgle du thread unique, en initialisant linterface utilisateur sur le thread principal. Ces programmes portent le lger risque quune partie de linitialisation de linterface utilisateur entrane des actions sur le thread de rpartition des vnements entrant en conit avec les actions du thread principal. Nous lavons dit au Chapitre 7, vous ne devez pas faire partie des malchanceux qui vont perdre leur temps dboguer un thread intermittent. Vous devez donc simplement suivre la rgle stricte du thread unique. Vous avez maintenant atteint la n du Volume I de Au cur de Java. Vous avez abord les bases du langage de programmation Java et les parties de la bibliothque standard dont vous avez besoin pour la plupart des projets de programmation. Nous esprons que vous avez prot de votre visite des bases de Java et que vous aurez rcolt des informations utiles en route. Pour les sujets avancs, comme le rseau, AWT/Swing avanc, la scurit et linternationalisation, reportez-vous au Volume II.

Annexe
Les mots cls de Java

Mots cls abstract assert boolean break byte case catch char class const continue default do double else extends

Signication Abstraite (pour une classe ou une mthode) Pour localiser des erreurs internes au programme Type boolen Sortie dun switch ou dune boucle Type entier sur 8 bits Clause case dun switch Clause dun block try interceptant une exception Le type de caractre Unicode Dnition de classe Inutilis Redmarre une boucle litration suivante Cas par dfaut dun switch Dbut dune boucle do/while Type dun nombre ottant en double prcision Clause else dune instruction if Dnit la classe parent dune classe

Voir Chapitre 5 11 3 3 3 3 11 3 4

3 3 3 3 3 4

798

Au cur de Java 2 - Notions fondamentales

Mots cls final finally float for goto if implements import instanceof int interface long native new null package private protected public return short static strictfp super

Signication Constante, classe ou mthode qui ne peut tre surcharge La partie dun bloc try qui est toujours excute Type dun nombre ottant en simple prcision Boucle Inutilis Instruction conditionnelle Dnit la ou les interfaces quune classe implmente Importe un package Teste si un objet est une instance dune classe Type dun entier sur 32 bits Type abstrait contenant des mthodes devant tre implmentes par une classe Type dun entier long sur 64 bits Mthode implmente par le systme hte Instancie un nouvel objet ou un tableau Une rfrence vide Un package de classes Restreint la visibilit aux mthodes de la classe Restreint la visibilit aux mthodes de la classe, ses sous-classes et aux autres classes du mme package Fonctionnalit accessible par les mthodes de toutes les classes Retour dune mthode Type dun entier sur 16 bits Constante ou variable instancie une seule fois, commune tous les objets de la classe Utilise les rgles strictes pour les calculs virgule ottante Accs la superclasse ou son constructeur

Voir Chapitre 5 11 3 3

3 6 4 5 3 6 3 Volume II 3 3 4 4 5 4 3 3 3 2 5

Annexe

Les mots cls de Java

799

Mots cls switch synchronized this throw throws transient try void volatile while

Signication Instruction de slection Mthode ou bloc de code atomique dans un thread Argument implicite dune mthode ou du constructeur de la classe Lance une exception Dclare les exceptions que peut lancer une mthode Modicateur des donnes non persistantes Partie de code susceptible dintercepter des exceptions Qualie une mthode qui ne renvoie rien Sassure quun champ est logiquement accessible par plusieurs threads Boucle

Voir Chapitre 3 Volume II 4 11 11 12 11 3 Volume II 3

Index

Symboles
$, caractre de sparation des classes 269 $, dans noms de variables et mthodes 271 && (et logique), oprateur 54 , (virgule) pour sparer des chiers JAR 519 < (infrieur ), oprateur 54 <= (infrieur ou gal ), oprateur 54 ==, oprateur 54 > (suprieur ), oprateur 54 >= (suprieur ou gal ), oprateur 54 || (ou logique), oprateur 54

A
Abstract Window Toolkit Voir AWT abstract, mot cl 194 AbstractAction, classe 346, 409, 411 AbstractButton, classe 394, 411, 416 AbstractCollection, classe 650 Abstraites Voir Classes ou mthodes accept, mthode 482 Accs aux champs privs 180 aux donnes prives 137 aux chiers, bote de dialogue 472 aux packages 166

aux variables locales 273 protg aux classes 197 AccessibleObject, classe 233, 236 Accessoire Voir Composants Accessor Voir Mthodes daccs Accolades dlimitant les blocs 43, 78 Action icne 411 barre doutils 422 interface 409 fonctions prdnies 351 mthodes 345 objet, actions prdnies 346 Action, classe 351 ActionEvent, classe 330, 336, 361 ActionListener, interface 275, 329, 330 ActionMap, classe 348 actionPerformed, mthode 329, 330, 331, 409 Actions 344 Activer une option de menu 416 ActiveX 11 Actualisation dune fentre 302 Adaptateurs, classes Voir Classes add, mthode 94, 122, 211, 213, 330, 373, 394, 410, 646 addItem, mthode 399, 401 addLayoutComponent, mthode 449 addWindowListener, mthode 343 AdjustmentEvent, classe 361 Adresses Web Voir Web, adresses

Afchage bote de dialogue 463 chane 317 dans un composant 300 image 324 mise en forme 72 texte 302 after, mthode 120 Agrgation de classes 115 Aide Voir Bulle Ajout bouton 330 champ de texte 380 composants 300 Algorithme 693 fdlibm 57 personnalis 699 align, attribut dapplet 519 Alignement 440 Alignement, constantes 382 alt, attribut dapplet 521 Analyse classes 229 objets lexcution 232 piles 561 Anonymes Voir Tableaux ou Classes API documentation en ligne 66 JNLP 505 notes 64 Preferences 540 Appel constructeur 155 mthode 144, 186 par valeur/par rfrence 144

802

Index

append, mthode 69, 387 Applet afcher 516 excuter 515 visualisateur 516 Applet, classe 518, 524, 526, 527, 535 AppletContext, classe 520, 531 AppletContext, mthode 535 Applets 14, 19, 37, 451, 513, 534 afchage par le navigateur 528 balises HTML 527 object 521 communication interapplets 520, 528 contexte 498, 528 conversion dune application 517 chiers JAR 498 passer des informations 521 taille 518 Applications convertir en applets 517 graphiques 35 multithread 13 Arborescence de package 166 des rpertoires Java 27 Arbre 664 rouge-noir 664 Architecture Modle-Vue-Contrleur 366, 367 neutre de Java 11 archive, attribut dapplet 519 Archive, chier 165 Arithmtiques, oprateurs 52 Array, classe 237 ArrayDeque, classe 671 arrayGrow, mthode 238 ArrayIndexOutOfBoundsExcept ion, exception 551 ArrayList 210, 213, 660 brut 216 classe 212 classe gnrique 609 et Vector, synchronisation 661 type 216

ArrayList, classe 652 ArrayList, type 609 Arrays, classe 102, 103, 202, 204, 281 ArrayStoreException, exception 622 Arrt, thread 719 Arrire-plan, couleur 313 Arrondi, erreurs 48 ASCII 317 Aspect et comportement Voir Look and feel assert, macro 567 Assertions 567 activer 568 dsactiver 568 vrications 567 Atomiques, oprations 731 AudioClip, classe 118 Augmenter la taille dun tableau 237 Autoboxing 217, 218 compilateur 218 Autorisations smaphores 775 Avertissement message 503 await, mthode 736, 776 AWT 289, 316 vnements 359 modle dvnement 327 AWTEvent, classe 359

B
Bac sable 502 code sign 503 Balises HTML 518 pour la documentation Voir javadoc Barre doutils 420 composants 422 icne action 422 sparateur 422 verticale 422 Barrires 776 cycliques 776 BasicButtonUI, classe 371

BasicService, classe 512 BasicService, interface 507 before, mthode 120 Bibliothque de routines 10 installation 26 Java 2D 305 classes 306 Java standard 159 Java, documentation 27 mathmatique fdlibm 57 publique, code source 26 Swing Voir aussi Swing BigDecimal, classe 94, 96 BigInteger, classe 94, 96 Binaire code 731 recherche 696 Binaires, oprateurs 55 binarySearch, mthode 104, 281, 697 Bits, ensembles 703 BitSet, classe 703 Blocage queue 754 Blocs 78 code source 43 dinitialisation 154 dinstructions 79 Blocs synchroniss 743 Botes de dialogue A propos 462 afchage 463 avec aperu 477 cadre propritaire 461, 467 communes 483 composant accessoire 477 conception 427 Couleurs 485 cration 461 de saisie 452 fermeture 463 Fichier 472 classes, constructeurs et mthodes 481 ltres 474 gestionnaires dvnements 461 mthodes 460

Index

803

modales/non modales 451, 462 personnalises 461 change de donnes 466 Mot de passe 466 Polices 427, 431 prdnies 452 messages 453 options 453 types doptions 453 types de messages 453 rutilisation 473 standard 452 taille 461 valeurs par dfaut 466 vue de chier 476 Boolen, oprateur 54 BorderFactory, classe 394, 397 BorderLayout classe 376 gestionnaire de mise en forme 374 Bordures angles arrondis 395 composes 394 titre 394 types de 394 Boucles 82 dtermines 86 do/while 84 for 86, 87 for each 97 quitter les 92 while 82 Bouton ajout 330 associer une action 347 cration 330 couteur 484 icne 453 label 453 Boutons radio 390 dans menus 411 groupe 391 BoxLayout, gestionnaire de mise en forme 426 break, instruction 90 bridge, mthode 618 brighter, mthode 313

bulk, operation 691 Bulle daide 345, 422 ButtonGroup, classe 391, 394 ButtonModel classe 394 interface 370, 371 ButtonPanel, classe 331 ButtonUIListener, classe 371

C
C# 18, 240 C++ 18 #include 160 appels par valeur et par rfrence 148 chanes 62 champs et mthodes statiques 141 classe string 63 classes imbriques 264 constructeur 181 virtuel 226 dclaration et dnition de variables 51 fonction membre statique 44 hritage 178 multiple 254 liste dinitialisation 152 mthodes 134 en ligne 190 modle de vecteur 212 namespace 160 pointeurs 109, 119 this 154 tableaux 100 transtypage 192 using 160 Cadres afchage 300 ajout de composants 300 collections 681 contenu 300 coordonnes 296 cration 291 icne 297 position 294 propritaires 462, 467 taille 294

par dfaut 296 Calendar, classe 120 constantes 121 Callable 764 Callback Voir Rappel Callbacks 261 classes internes anonymes 264 Caractres $, dans noms de variables et mthodes 271 $, de sparation de classes 269 ASCII 317 dcho 383 mnmoniques 414 types de 48 Unicode 50, 317 Carte 673 Carte synchronise 688 Cartes de hachage faibles 677 lies 678 case, mot cl 90 Cases cocher 387 dans menus 411 cast, mthode 635 catch, clause 226 CD-ROM daccompagnement 5 CENTER, constante 382 Centrage dune chane 319 Chane, construire 69 Chane (liste), manipulation 658 Chanes 60 afchage 317 centrage 319 concatnation 60 coordonnes 318 dhritage 184 dimensions 318 en C++ 62 formatage 72 inaltrables 61 sous-chanes 60 test dgalit 62 Champs altrables, copie 136 anchor 429 commentaires javadoc 171 contenu des 232

804

Index

Champs (suite) dinstance 113 et variables locales 134 nal 138 modication 135 de donnes 113, 228, 233 altrables 136 copie 255 initialisation 154 privs 135, 137 publics 132 de la classe 139 de mot de passe 383 de poids 428 de texte 380 initialisation 381 en lecture seule 135 ll 429 nal 189 gridheight 429 gridwidth 429 initialisation 151 explicite 152 insets 429 privs 137, 233, 271 accs 180, 197 statiques 138, 142 initialisation 155 volatiles 745 charAt, mthode 65 Chargeur de classe 280 Checked exception Voir Exception vrie Chemin dexcution 24 de classe Voir CLASSPATH Choix doptions 387 Class classe 224, 231, 241, 498, 635, 639 classe gnrique 634 objet 225 paramtre 635 class, mot cl 42 Classe AbstractCollection 650 ArrayList 652

BitSet 703 Hashtable 700 LinkedHashMap 678 LinkedHashSet 678 LinkedList 653 Stack 702 synchronizedMap 688 TreeSet 664 WeakHashMap 677 Classement, collection 654 Classes abstraites 192, 254, 380 accs protg 197 adaptateurs 341, 361 agrgation 115 ajout dans un package 161 analyse 229 des caractristiques 227 anonymes 271, 335, 343 bibliothque Java 2D 306 C++ 44 caractre de sparation ($) 269 chargeur de 280 chemin daccs Voir CLASSPATH chemin de 168 commentaires javadoc 169 conception 173 concrtes 307 et abstraites 192 ConsoleWindow 593 cration lexcution 280 dexception 554 de base Voir Superclasses dnies par lutilisateur 165 dpendance 115 drivation 178 drives Voir Sous-classes diagrammes 115 encapsulation 178 enfant Voir Sous-classes numration 221 enveloppes 281, 369 extension 113, 178 externes, rfrence 268 extraction des informations 228 nal 189

gnriques 210 ArrayList 609 dnition 610 hritage 625 hritageVoir Hritage hirarchie 184 imbriques 278 en C++ 264 inaltrables 255 internes 264, 334 anonymes 264, 274, 334, 343 dclaration 277 tat dun objet 265 locales 271 porte 269 scurit 269 statiques 276 syntaxe 265 Java 44 locales mthodes 273 visibilit 272 parent Voir Superclasses personnalises 128 proxy 280 proprits 284 relation entre classes 115 rpertoires 165 rutilisation 116 sous-classes Voir Sous-classes statiques internes 276 superclasses Voir Superclasses Swing 292 Timer 261 ClassLoader, classe 570 CLASSPATH 31 Clause nally 558 throws 549 cl/valeur, paire 673 Clonage 210 dobjets 255 et copie 256 exceptions 259 superciel 255 clone, mthode 209, 254, 255 Cloneable, interface 254, 257

Index

805

COBOL 177 Code binaire 731 de hash 661 des exemples 5 existant 619 chiers bibliothque 26 HTML 38 point 49 sign 503 units 49 code, attribut dapplet 519 codebase, attribut dapplet 519 codePointAt, mthode 64 Collection 761 cadre 681 classe 654 Collection et Map, interface 681 concrte 651 emballage lger 686 et threads 760 interface de 643, 646 interface pour les collections 681 structure 681 Collision de hash 662 Color, classe 313, 315 Color, objet 313 ColorAction, classe 332 com.sun.java, package 337 Commentaires dans code 45 pour la documentation Voir javadoc Comparable, interface 249, 252, 282, 613, 665 Comparaison dobjets 665 compareTo, mthode 63, 65, 248, 249, 252, 282, 665 Compilateur 12 installation 21 javac 28, 167 localisation de chiers 167 Compilation erreurs 34 instructions 23 Component, classe 294, 313, 359, 382

Composants 301 accessoires 477 ajout dans un cadre 300 barre doutils 422 coordonnes 296 dinterface graphique, caractristiques 367 mise en forme 372 Swing 370, 477 Concatnation de chanes 60 Conception de classes 173 Concordance de proprits 536 Concrte, collection 651 Concrtes Voir Classes ou Mthodes ConcurrentModicationExceptio n, exception 656 Condition, objet 735, 736 Conditionnelles, instructions 79 Conguration chemin dexcution 24 emplacement des chiers 540 paires cl/valeur 536 prfrences dapplication 535 Conits aprs effacement 624 Consignation 570 API 570 basique 571 exceptions 573 gestionnaires 576 hirarchie 571 localisation des messages 575 modier la conguration 574 niveaux 572 Console, classe 72 ConsoleHandler, classe 586 ConsoleWindow, classe 593 const, mot cl 52 Constantes 52 mathmatiques 56 statiques 139 Constructeurs 117, 132 appel 155 dun autre constructeur 153 bouton 330 gnralits 133 par dfaut 151

Construction dune ellipse 308 dune ligne 309 Constructor classe 227, 231, 635 classe gnrique 635 Container, classe 334, 374 contentPane, classe 374 Contenu dun cadre 300 dun champ 232 Contextes statiques 624 continue, instruction 93 Contrleur de composant dinterface 367 Conventions du livre 4 Conversion classes 122 entiers 49 mthode printf 73 type ottant 58 numrique 57, 190 primitif 217 Voir aussi Transtypage Conversion, collections et tableaux 692 Coordonnes cadre/composants 296 dune chane 318 dune ellipse 308 vnements de la souris 358 grille GridBagLayout 428 Java 2D 305 objet Graphics 302 Copie et clonage 256 intgrale 257 tableaux 99 variable 255 copyArea, mthode 323, 326 copyTo, mthode 99 Couleurs 312 darrire-plan 313 par dfaut 314 personnalises 313 slection 483 systme 314

806

Index

CountdownLatch 776 createBevelBorder, mthode 397 createCompoundBorder, mthode 394, 398 createCustomCursor, mthode 354, 359 createDialog, mthode 489 createEmptyBorder, mthode 397 createEtchedBorder, mthode 397 createLineBorder, mthode 397 createLoweredBevelBorder, mthode 397 createMatteBorder, mthode 397 createRaisedBevelBorder, mthode 397 createScreenCapture, mthode 599 createTitledBorder, mthode 394, 397 Cration bote de dialogue 461 bouton 330 curseur 402 panneau 302 systme de menu 90 Crible dEratosthne 704 Curseurs 354 cration 402 repres 403, 407 Cursor, classe 353 CyclicBarrier, classe 776

D
darker, mthode 313 Date, classe 117 DateFormatSymbols, classe 128 Dbogage assertions 567 techniques de mise au point 587 Dcalage, oprateurs 56 Dclaration classe 154, 189, 277 abstraite 193 nal 189 interne 277 locale 272

interface 249 matrice 105 mthode 134 publique 188 virtuelle 182 tableaux 97 variables 45 Dcompiler un chier 732 Dcrmentation, oprateurs 54 Deep copy Voir Copie intgrale default, mot cl 90 DefaultButtonModel, classe 371 Dnition mthodes 135 variables 51 Dlgation dvnement 330 Dlgu 240 Dmon, Thread 725 Dpendance de classes 115 Dprcies Voir Mthodes Deque 670 interface 670 Deque, classe 671 deriveFont, mthode 317 Driver une classe 178 Dsactiver une option de menu 416 Dsallouer une ressource 558 Dessiner en Java 301 Destruction des objets 158 Diagrammes de classe 115 Dichotomique, recherche 282 Dictionary, classe 619 Diffrence entre les interfaces et les implmentations 644 dispose, mthode 158 do/while 84 doclet 173 docs, rpertoire 27 Documentation API en ligne 66 commentaires Voir javadoc installation 26 Donnes change 466 prives, accs 137 publiques 137 types de 45

double, type 53 Drapeaux 74 draw, mthode 305 drawImage, mthode 323, 325 drawRectangle, mthode 305 drawString, mthode 302, 322 Dump 605 Dynamique liaison Voir Liaison tableau 237

E
EAST, constante 382 Eclipse 31 Ecouteur 328 bouton 484 case cocher 388 dvnement 328 notication 328 de changement de proprit 346, 478 de plusieurs sources dvnement 345 menus 409 sans appel de mthode 337 Ecran, taille 297 Effacement conits 624 type 617 type de retour 617 types gnriques 636 variables de type 616 Elment plus grand dun tableau 693 dune liste chane 693 dune liste de tableau 693 supprimer 649 Ellipse2D, classe 308 Ellipses, construction 308 else, mot cl 80 Emballages et vues 685 lgers de collection 686 Empcher lhritage 189 Employee, classe 133 EmployeeTest, classe 129 Encapsulation 113, 173, 178

Index

807

endsWith, mthode 65 Enregistreur 576 Ensemble de bits 703 de proprits 702 sous-ensemble 686 ensureCapacity, mthode 211 Entiers 46 Entre de texte utilisateur 380 chiers 76 lecture 70 utilisateur, bote de dialogue 466 Entres, numration 674 Enumration 59, 701 classes 221 entres 674 type 222 Enveloppe Voir Classes Enveloppes 217 Environnements de dveloppement 28, 372 Epoch, point xe 120 equals 199 equals, mthode 65, 104, 198, 284 dObject 209 proprits 200 equalsIgnoreCase, mthode 62, 65 Equit 735 Eratosthne, crible 704 Erreurs compilation 160, 181 localisation 34 recherche 30 traitement 548 Erreurs darrondi 48 Etat dun objet 113, 265 dun thread 724 Etendre une classe 113 Etiquettes 382 HTML 383 instructions 92 Evaluation optimise 54 Evnements action 329

AWT 359, 360, 595 modle 327 classes et mthodes 362 de bas niveau 361 de bouton radio 391 de case cocher 388 de la souris 352 de liste combine 399 dlgation 330 couteur sans appel de mthode 337 couteurs de plusieurs sources 345 gnralits 327 interaction 329 objets 328 smantiques 361 source des 328 souris coordonnes 358 Event listener Voir Ecouteur dvnement EventHandler, classe 336 EventObject, classe 336, 359 EventTrace, exception 595 Exception ArrayIndexOutOfBoundsExcep tion 551 capture des exceptions 555 ClassCast 237 classe 227, 563 classement 549 classes 554 Throwable, Error et Exception 549 clause catch 226 clause nally 558 ConcurrentModicationExcepti on 656 conseils pour leur utilisation 564 consignation 573 en C++ 561 enchaner 557 EventTrace 595 gnralits 548 gestionnaire 224, 226 getCause 558

hirarchie 550 IllegalAccessException 233 IllegalStateException 649 interception 226 InterruptedException 709, 714 lancement 552, 553 look and feel 338 multiples 557 non rcupre 725 non vrie 226 NoSuchElementException 647 programmation gnrique 621 relancer une exception 557 RuntimeException 552 signalement au compilateur 551 try 226 UnsupportedOperationExceptio n 674, 688 vries 224, 257 Exchanger 777 Excutable, thread 721 Excution bloc 80 de programmes dans une page Web 19, 37 identication de type 224 interrompre 92 programme 772 rexion 223 executor 774 Executors, classe 768, 769, 772 Exemples de programmes installation 26 Explicites Voir Paramtres Expressions gnriques, traduction 617 extends, mot cl 178 Extension de classes 178 Externes Voir Objets ou classes Extraction des commentaires 172

F
Factory Voir Mthodes FAQ 20 fdlibm, bibliothque mathmatique 57 Fentre, actualisation 302

808

Index

Fentres modales et non-modales 462 Fermeture bote de dialogue 463 Java 158 programme 35 Fichiers archive 165 audio 526 botes de dialogue pour slection 472 dcompiler 732 entre et sortie 76 image 526 JAR Voir JAR Fichiers journaux ltres 579 formateurs 579 Field, classe 227, 231, 232 File classe 475 objet 473 File, classe 78 FileContents, classe 512 FileFilter classe 482 interface de java.io 475 FileHandler 576 FileHandler, classe 586 FileNameExtensionFilter, classe 483 FileNotFoundException, exception 508, 513 FileOpenService, classe 512 FileSaveService, classe 513 FileView, classe 476, 483 mthodes 476 ll, mthode 104 Filter, classe 587 Filtres 474 All les (Tous chiers) 475 chiers journaux 579 journaux 571 nal champ dinstance 138 mot cl 52, 182, 189, 272, 273 nalize, mthode 158 nally, clause 558

FlowLayout classe 374 constructeur 374 gestionnaire de mise en forme 372 Focalisation composants 361 de saisie 450 ordre de 450 FocusEvent, classe 361 Fonctions de rappel 243 mathmatiques 56 membres statiques C++ 44 Font classe 315, 316, 318 constructeur 321 Fontes 315 disponibles 315 logiques 316, 317 taille 317 TrueType 317 FontMetrics, classe 322, 323 FontRenderContext, classe 318 for each, boucle 97 for, instruction 87 Formateurs, chiers journaux 579 Formes 2D 305 forName, mthode 224, 227 Frame, classe 291, 299 Frames Voir Cadres Fusion, tri 695 Future, interface 764

G
Garbage collector Voir Ramassemiettes General path 305 GenericArrayType, classe 640 Gestion dvnement, gnralits 327 Gestionnaires consignation 576 dvnement, bote de dialogue 461 dexception 224

implmentation 226 non rcupre 725 dinvocation 280 de mise en forme 372, 420 BorderLayout 374 BoxLayout 426 FlowLayout 372 GridBagLayout 426, 427 personnaliss 446 Swing 426 enregistreur 576 par dfaut 576 paramtres 577 get, mthode 122, 232 champs 126 getActionCommand, mthode 336, 392, 394 getActionMap, mthode 352 getApplet, mthode 535 getAppletContext, mthode 535 getAppletInfo, mthode 526 getApplets, mthode 535 getAscent, mthode 322 getAudioClip, mthode 527 getAvailableFontFamilyNames, mthode 315 getBaseline, mthode 440 getCause 558 getClass, mthode 209, 210, 224 getClickCount, mthode 352, 358 getCodeBase, mthode 527 getColor, mthode 488 getComponentType, mthode 238 getConstructors, mthode 228, 231 getDeclaredConstructors, mthode 228, 231 getDeclaredFields, mthode 228, 231 getDeclaredMethods, mthode 228, 231 getDeclaringClass, mthode 231 getDescent, mthode 322 getDescription, mthode 482, 483 getDocumentBase, mthode 527 getExceptionTypes, mthode 231 getFamily, mthode 321 getFields, mthode 228, 231

Index

809

getFirst, mthode 628 getFontMetrics, mthode 322 getFontName, mthode 321 getFontRenderContext, mthode 318, 322 getGregorianChange, mthode 127 getHeight, mthode 322 getIcon, mthode 483 getImage, mthode 299, 527 getInputMap, mthode 348, 352 getKeyStroke, mthode 347 getLeading, mthode 322 getLength, mthode 238 getLineMetrics, mthode 318 getLocalGraphicsEnvironment, mthode 316 getMethod, mthode 241 getMethods, mthode 228, 231 getModiers, mthode 227, 232 getModiersEx, mthode 359 getModiersExText, mthode 359 getName, mthode 224, 225, 232, 241 getName, mthode de Font 321 getParameter, mthode 524 getParameterInfo, mthode 526 getParameterTypes, mthode 232 getPassword, mthode 384 getPath, mthode 474 getPredenedCursor, mthode 353 getProxyClass, mthode 284 getScreenSize, mthode 299 getSelectedFile, mthode 474 getSelectedFiles, mthode 474 getSelectedItem, mthode 399, 401 getSelectedObjects, mthode 392 getSelection, mthode 392, 394 getSize, mthode 299 getSource, mthode 336 getStackTrace, mthode 561 getStringBounds, mthode 318 getTime, mthode 122 getType, mthode 227 getTypeDescription, mthode 483

getValue, mthode 345 getValue, mthode dAction 351 getWidth, mthode 306, 307, 319 getX, mthode 352 getY, mthode 352 GMT 120 goto, mot rserv 92 Grands nombres 45, 94 Graphic User Interface Voir GUI et Interface Graphics classe 302, 305, 315, 322, 323 objet 301 Graphics, classe 325 Graphics2D, classe 305 GraphicsEnvironment, classe 315, 316, 601 Graphiques, applications 35 GregorianCalendar classe 120, 126 constructeur 126 GridBagConstraints, classe 431, 436 contraintes 428 GridBagLayout classe daide 431 gestionnaire de mise en forme 426, 427 objet champs 428 GridLayout classe 373 constructeur 380 Groupe de boutons radio 391 Groupes de tches 773 GroupLayout 436 GroupLayout, classe 444, 445 GUI 288, 327 composants caractristiques 367 gnralits 288

hashCode 202 hashCode, mthode 284 Hashtable, classe 700 height 297 height, attributs dapplets 518 Helper Voir Mthodes Hritage 115, 177 chane d 184 classes et sous-classes 192 Java 2D 306 JFrame 294 JPanel 294 classes gnriques 625 empcher 189 equals 199 gnralits 178 hirarchie 184 utilisation 244 Hirarchie classe Shape 308 dhritage 184 des oprateurs 58 vnements AWT 359 Historique de Java 15 Holder, type 219 HTML 17 balises 518 boutons, tiquettes, options de menu 383 label 334

I
Icne 346, 477 action 411 dans barre doutils 422 bote de dialogue 452 bouton 330, 453 cadre 297 menu 410 option de menu 345 vue de chier 476 Ides fausses sur Java 17 if, instruction 79 if/else if 82 IllegalAccessException 233 IllegalStateException, exception 649

H
Handler, classe 585 Hash code 661 collision 662 table 661

810

Index

ImageIcon classe 334 constructeur 334 ImageIO, classe 325 Images 323 afchage 324 en mosaque 323 fractales 323 mise lchelle 326 Images et sons 526 Implmentation et interfaces 644 queue 644 implements, mot cl 249 Implicites Voir Paramtres import, instruction 159 Imports statiques 161 Inaltrables Voir Chanes ou Classes Incrmentation, oprateurs 54 indexOf, mthode 65 Indice, tableau 96 Informations de conguration 540 Informations relatives une classe 228 Initialisation blocs 154 champs de donnes 154 de texte 381 explicites 152 par dfaut 151 statique 156 tableaux 98 variables 51 Inlining 189 InputEvent, classe 359 insertItemAt, mthode 399, 401 insertSeparator, mthode 410 Installation bibliothque et documentation 26 compilateur et outils 21 exemples de programmes 26 instructions 23 messages 24 rpertoire par dfaut 23

instanceof, oprateur 191, 253 Instanciation 622 Instructions blocs 79 break 90 composes 80 conditionnelles 79 continue 93 dinstallation et de compilation 23 tiquetes 92 goto 92 import 159 switch 90 Integer classe 219, 282 type 45 Interception dexceptions 226 Interface 248 callbacks 261 Comparable 248, 665 ditration 646 de balisage 257 de collection 643, 646 de marquage 257 dclaration 249 couteur 328, 361 et implmentations diffrence 644 graphique activer/dsactiver des options 416 bote de dialogue 451 Fichier 472 bordures 394 boutons radio 390 cases cocher 387 champs de mot de passe 383 de texte 380 composants 367 avec GridBagLayout 428 entre de texte 380 tiquettes 382 vnements 327 listes doptions 398 menus 408 contextuels 412

mises en forme 372 sophistiques 425 raccourcis clavier 414 squence de tabulation 450 utilisateur Voir GUI zones de texte 384 graphique utilisateur 288 graphique Voir aussi Gestionnaires de mise en forme List 682 mthodes 248 pour les collections, Collection et Map 681 proprits 253 publique 180 utilisateur graphique Voir GUI Internet et Java 14 Interprteur 12 Interrompre thread 718 interrupted, mthode 720 InterruptedException, exception 709, 714, 718 Interruption du ux dexcution 92 intValue, mthode 219 InvocationHandler, interface 280 invoke, mthode 241, 280 isAccessible, mthode 236 isEnabled, mthode 351 isInterrupted, mthode 720 isJavaIdentierPart, mthode 50 isJavaIdentierStart, mthode 50 isProxyClass, mthode 284 isSelected, mthode 388, 390, 412 isTraversable, mthode 476 ItemEvent, classe 361 ItemSelectable, interface 392 Itrateurs, Java et STL 648 Itration, interface 646 Iterator, mthode 646

J
J++ 240 Jambage ascendant/descendant 318 JApplet, classe 514

Index

811

JAR 5 chiers 26, 165, 166, 492 programme darchivage 492 Java applications et applets 530 architecture neutre 11 bibliothque de routines 10 et C# 18 et C++ 18 excuter un programme dans une page Web 19 historique 15 ides fausses 17 installation des outils 21 Internet 14 livre blanc 8 machine virtuelle 15 modle de pointeur 10 orient objet 9 plug-in 513 portabilit 12 rpertoires 27 scurit 19 syntaxe 9 termes cls 8 Java 2D bibliothque 305 types de coordonnes 305 Java Web Start 508 java, package 159 java.awt, package 164 java.awt.Color, classe 313 java.awt.event, package 361 java.io, package 475 java.lang, package 64, 71, 167 java.lang.Object 204 java.math, package 94 java.sql, package 160 java.util, package 71, 159, 359 java.util.jar, package 159 JavaBeans 478 et rexion 223 javac, compilateur 167 javadoc 3, 168 balises 170 doc en ligne 173

extraction des commentaires 172 insertion des commentaires 169 JavaScript 20, 520 javax, package 159, 292 javax.swing, package 292, 337 javax.swing.event, package 416 javax.swing.lechooser, package 475, 476 javax.swing.lechooser.FileFilter, interface 474 JButton classe 334, 369, 371, 472 constructeur 334 JCheckBox, classe 390 JCheckBoxMenuItem classe 412 constructeur 412 JColorChooser, classe 483, 488 JComboBox, classe 399, 401 JComponent, classe 301, 323, 352, 371, 382, 394, 398, 414, 451, 472 JDialog classe 461, 465 constructeur 465 JDK 21 JEditorPane, classe 385 JFC 288 JFileChooser classe 452, 472, 481 constructeur 481 JFrame, classe 291, 300, 304, 313, 374 hirarchie dhritage 294 JFrame.add, mthode 301 JLabel, classe 382, 383 JLayeredPane 300 JMenu classe 410 constructeur 410 JMenu.add, mthode 409 JMenuItem classe 411, 416, 417 constructeur 416 JNLP, Java Network Launch Protocol 499 API 505

Jokers 627 capturer 631 limites 628 sans limites 630 JOptionPane 263 JOptionPane, classe 452, 460 Journaux enregistrements 570 ltres 571 mise en forme 571 JPanel, classe 391 hirarchie dhritage 294 JPasswordField, classe 383 JPopupMenu, classe 413 JRadioButton, classe 391, 394 JRadioButtonMenuItem classe 412 constructeur 412 JRoot 300 JRootPane, classe 472 JScrollPane, classe 387 JSlider, classe 407 JTextArea classe 384 composant 380 constructeur 387 JTextComponent, classe 380 JTextField, classe 369, 382 JTextField, composant 380 JTextPane, classe 385 JToolBar, classe 422 JToolbar, classe 425 JVM Voir Machine virtuelle

K
KeyEvent, classe 361 KeyStroke, classe 347, 351 Keystroke, classe 415 Kit de dveloppement Java JDK Voir JDK

L
Label bouton 453 texte 383 Voir aussi Etiquettes

812

Index

Langages de script 20 Java 53, 122, 250 appel de mthodes 144 constructeurs 117 mots cls 4 Pascal 112 procduraux 9, 112 Visual Basic 327 lastIndexOf, mthode 65 layoutContainer, mthode 449 LayoutManager classe personnalise 446 interface 449 mthodes 446 LayoutManager2, interface 446 Lecture des entres 70 Lecture seule, champs 135 LEFT, constante 382 Lgers, emballages de collection 686 length, mthode 65, 214 Lexicographique, ordre 665 Liaison dynamique 182, 186 statique 187 Libells Voir Etiquettes ou Label Ligne construction 309 de base 318 de sparation, menus 410 Ligne de commande outils 28 paramtres 101 Limites jokers 628 supertypes 628, 630 variables de types 613 LineMetrics, classe 318 LinkedHashMap, classe 678 LinkedHashSet, classe 678 LinkedList, classe 653 Linux 24 List, interface 682 Liste chane 652, 658 plus grand lment 693 pour une queue 644

de tableaux 660 plus grand lment 693 Listener interface Voir Interface couteur Listes doptions 398 suppression/ajout dlment 399 de tableaux 97 accs aux lments 212 allocation 211 capacit 211 de tableaux gnriques 210 Integer 217 listFiles, mthode 475 Localisation arguments 575 de chiers par le compilateur 167 des classes dans les packages 160 des erreurs de compilation 34 messages de consignation 575 lock, mthode 750 Logger, classe 584 LogRecord, classe 586 Longueur des tableaux 99 Look and feel 289, 369, 452 Mac 337 Metal 289, 337, 338 modication 337, 338 Motif 289, 338 Synth 291 Windows 289, 337 LookAndFeelInfo, classe 341

M
Machine virtuelle 15 architecture neutre 11 effacement de types gnriques 636 programmation gnrique 615 main, mthode 44, 141 chargement 224 makeButton, mthode 335 Manager, classe 178 Manifeste 493

Manipulation, liste chane 658 Map 673 Map, interface pour les collections 681 Masse, oprations 691 Math, classe 56 constantes 139 Math.random, mthode 102 Mathmatiques, fonctions et constantes 56 Matisse 426, 436 mise en page 440 Matrice 105 MAX_PRIORITY 724 Mcanisme de rexion 227 Mlanger 694 menuCanceled, mthode 417 menuDeselected, mthode 417 MenuListener, classe 417 Menus 408 activer et dsactiver des options 416 avec cases cocher et boutons radio 411 contextuels 412 cration 90, 408 droulants, mthodes 413 couteur 409 icne 410 imbriqus 417 ligne de sparation 410 Macintosh 409 options 410 raccourcis 414 sous-menus 408 supprimer des options 416 Windows 409 Messages linstallation 24 avertissement 503 botes de dialogue 453 derreur 31 sur plusieurs lignes 454 Metal 289, 337 Method, classe 227, 231, 241, 640 Mthodes 44, 113 abstraites 193 accs aux donnes prives 137

Index

813

add 646 appel 186 par rfrence 144 par valeur 144 binarySearch 697 botes de dialogue 460 caractres $ dans nom 271 classe locale 273 commentaires javadoc 170 compareTo 665 concrtes 193 createScreenCapture 599 daccs 121, 135 daltration 121 dune interface 248 de RectangularShape 308 dclaration 134 dnition 135 dprcies 120, 158, 293 en ligne 134 factory 141 nal 189 gnriques dnition 612 traduction 617 hashCode 202 helper 334 interrupted 720 isInterrupted 720 Iterator 646 natives 140 nombre variable de paramtres 220 notify 748 paramtres 44, 144, 219 pointeurs 240 prives 132, 137 run 714 signature 150, 187 statiques 56, 138, 142, 238, 253, 270, 316, 338 stop 752 subList 686 substring 687 superclasse/sous-classe 179 surcharge 150

suspend 752 syntaxe 44 tables 188 virtuelles 182 visibilit 188, 249 Voir aussi nom de la mthode MIN_PRIORITY 724 minimumLayoutSize, mthode 449 Mise en forme dinterfaces introduction 372 sophistiques 425 de lafchage 72 personnalise 446 sans gestionnaire 445 Mix-in 255 Modale/non modale Voir Botes de dialogue Modalit, fentres 462 Modle dvnement AWT 327 de composant dinterface 367 de conception 366 Modle-Vue-Contrleur Voir Architecture Modicateurs de visibilit 198 Modication champ dinstance 135 look and feel 338 Modier, classe 227 Moniteurs 745 Mort, verrou 747 Mosaque 323 Motif 289, 338 Mots cls abstract 194 case 90 class 42 const 52 default 90 else 80 extends 178 nal 52, 182, 189, 272, 273 implements 249 import 160 langage Java 4 package 160

private 132, 138 protected 197, 244 public 42, 132, 257 redondance 253 static 141, 155 strictfp 53 super 180 this 134, 153, 181, 268 void 44 MouseAdapter, classe 355 mouseClicked, mthode 352 mouseDragged, mthode 354 mouseEntered, mthode 355 MouseEvent, classe 358, 361, 414 mouseExited, mthode 355 MouseListener, interface 355 MouseMotionListener, interface 355 mouseMoved, mthode 354 mousePressed, mthode 352 mouseReleased, mthode 352 MouseWheelEvent, classe 361 multiply, mthode 94 Multitche 707 Multithread 13, 707 Mutator Voir Mthodes daltration

N
name, attribut dapplet 520 Natives Voir Mthodes NavigableMap, classe 691 NavigableSet, classe 670, 691 new, oprateur 133 newCondition, mthode 736 newInstance, mthode 225, 227, 280 newProxyInstance, mthode 280, 284 nextDouble, mthode 70 nextInt, mthode 70 nextLine, mthode 70 Niveaux de consignation 572 Noms de packages 158 de paramtres 153 de variables et mthodes ($) 271

814

Index

NORM_PRIORITY 724 NORTH, constante 382 NoSuchElementException 647 Notes API 64 Notication aux couteurs dvnement 328 Notify, mthode 748 Nouveau, thread 721 null, valeur 119 Number, classe 217 NumberFormat, classe 220

O
object attribut dapplet 519 balise HTML 521 Object, classe 198, 209, 210, 280 Objets 113 Action, actions prdnies 346 analyse lexcution 232 champs dinstance 113 Class 225 clonage 255 Color 313 comparaison 665 construction 117, 150 de condition 735, 736 destruction 158 et variables objet 118 tat 265 vnement 328 externes 266 File 473 Graphics 301 KeyStroke, associer aux actions 347 Modle-Vue-Contrleur, interaction 370 non-objets 198 srialiss 519 variables 117 verrou 732 Oprateurs arithmtiques 52 binaires 55 boolens 54 dincrmentation 54

de dcalage 56 de dcrmentation 54 hirarchie 58 instanceof 191, 253 new 133 relationnels 54 surcharge 95 Oprations atomiques 731 de masse 691 optionnelles 689 Optionnelles, oprations 689 Ordre lexicographique 665 org.omg.CORBA, package 219 Oriente objet Voir Programmation outer 266 Outils de ligne de commande 28

P
Package accs 166 ajout dune classe 161 arborescence 166 arithmtique 45 classes publiques/non publiques 167 commentaires javadoc 172 courant 167 dextension 292 de rexion 240 disponible 67 hirarchie 159 import 159 localisation des classes 160 look and feel 337 nom 158, 159, 171 par dfaut 161 plombage 165 prxe 159 rpertoires 161 sealing Voir Package, plombage scurit 165 sous-packages 159 standard 159 utilisation 159 verrouiller 498

visibilit 164 Voir aussi nom du package Page Web pour excuter des programmes Java 19 paintComponent, mthode 302, 304, 360, 453 PaintEvent, classe 360 Paire, cl/valeur 536, 673 Panneaux ajout dun bouton 330 cration 302 Paquets Voir Package ParameterizedType, classe 640 Paramtres de ligne de commande 101 de type 216, 608 de type primitif 147 des mthodes 44, 144, 219 explicites 134 explicites et implicites 134 implicites 134, 281 nom des 153 nombre variable 220 parseInt, mthode 220 Partager un accs entre plusieurs threads 727 Pascal UCSD 11 Pascal, langage 112 PasswordChooser, classe 466 Pattern, callback 261 PersistenceService 507 PersistenceService, classe 513 Personnalisation, algorithme 699 Piles 702 Piles, traces 561 Planication cooprative 721 Planication premptive 721 play, mthode dURL 527 Plug-in Java, panneau de contrle 501 Plus grand lment dun tableau 693 dune liste chane 693 dune liste de tableau 693 Poids, champs 428 Point2D, classe 307

Index

815

Pointeurs C++ 119 de mthodes 240 dlgu 240 Java 10 Points de code 49, 63 caractres complmentaires 49 Polices Voir Fontes Polymorphisme 185, 189, 245 Pool de threads 768, 769 Portabilit de Java 12, 53 Porte dun bloc 78 de classe interne 269 Positionner un cadre 294 pow, mthode 56, 140 Preferences API 540 classe 545 rfrentiel 541 preferredLayoutSize, mthode 449 Primitif Voir Types Principe de substitution 185 print, mthode 44 printf conversions 73 date et heure 74 drapeaux 74 mthode 72 println, mthode 44, 282 printStackTrace, mthode 227 PrintStream, classe 206 PrintWriter, classe 78 Priorit, queues 671 Priorits dun thread 724 private, mot cl 132, 138 Priv(e)s Voir Mthodes, Classes, Champs ou Donnes Processus et thread 707 Programmation vnementielle 327 oriente objet 9, 112 encapsulation Voir Encapsulation hritage Voir Hritage polymorphisme Voir Polymorphisme

Programmation gnrique dnition 608 et transtypage 608 exceptions 621 machine virtuelle 615 tableaux 622 Programmes excuter dans une page Web 19 terminer 341 Properties, classe 539 Proprits daccs, interface ButtonModel 371 dun thread 724 des classes proxy 284 des interfaces 253 ensembles 702 gnriques 254 mthode equals 200 protected, mot cl 197, 244 Protg, accs aux classes 197 Proxy 247, 280 Proxy, classe 280 mthodes 285 public, mot cl 42, 132, 257 putValue, mthode 345, 351

Q
Queue 670 de blocage 754 de priorit 671 implmentations 644 liste chane 644 synchrone 777 tableau circulaire 644 Queue, classe 670 QuickSort 102 Quitter un programme 341 une boucle 92

R
Raccourcis clavier 414 raiseSalary, mthode 134 Ramasse-miettes 119, 158, 212 Rappel, fonctions 243

Recherche derreurs 30 dichotomique 282 Recherche binaire 696 Rectangle englobant 308, 319 Rectangle2D, classe 306 RectangularShape, classe 308 ReentrantLock, classe 732 Rfrence externe, syntaxe 268 Recteur 223 Rexion 177, 232, 280 bibliothque 223 crer un tableau gnrique 237 Rgle du thread unique 785, 795 Relationnels, oprateurs 54 Relations entre les classes 115 remove, mthode 410 removeAllItems, mthode 399, 401 removeItem, mthode 399, 401 removeItemAt, mthode 399 removeLayoutComponent, mthode 449 Remplissage, tableaux 213 repaint, mthode 302 Rpertoires classes 165 de compilation 129 de Java 27 de travail 473 installation par dfaut 23 javadoc 169 package 161 slection par lutilisateur 474 replace, mthode 65 Rsolution de surcharge 150 Ressources 495 nom relatif 496 Retours covariants 187 Rutilisation botes de dialogue 473 classes 116 revalidate, mthode 381 RIGHT, constante 382 Robot, classe 602 Rouge-noir, arbre 664 run, mthode 714

816

Index

RuntimeException, classe 563 RuntimeException, exception 552

S
Saisie des donnes 70 Scanner, classe 70, 71, 78 SDK 5 Scurit 10, 19, 197, 233, 280 classes internes 269 hritage 244 Java Web Start 505 packages 165 Smaphores, autorisations 775 Spar, thread 713 Squence de tabulation 450 Srialisation 519 ServiceManager, classe 512 ServletException, exception 557 set, mthode 122, 213 setAccelerator, mthode 415, 416 setAccessible, mthode 233, 236 setAction, mthode 410 setActionCommand, mthode 392, 394 setBackground, mthode 313, 315 setBorder, mthode 398 setBounds, mthode 294, 295, 299 setColor, mthode 313, 315, 488 setColumns, mthode 381, 384, 387 setCurrentDirectory, mthode 473 setCursor, mthode 359 setDefaultCloseOperation, mthode 463 setEchoChar, mthode 384 setEditable, mthode 380, 399, 401 setEnabled, mthode 351, 416 setFileFilter, mthode 475 setFileSelectionMode, mthode 474 setFileView, mthode 476 setFirst, mthode 628 setFont, mthode 322, 381

setForeground, mthode 315 setFormatter, mthode 579 setGregorianChange, mthode 127 setHorizontalTextPosition, mthode 411 setIcon, mthode 383 setIconImage, mthode 294, 299 setJMenuBar, mthode 408 setLabelTable, mthode 403 setLayout, mthode 374 setLineWrap, mthode 387 setLocation, mthode 294 setMnemonic, mthode 415, 416 setMultiSelectionEnabled, mthode 474 setNextFocusableComponent, mthode 451 setPaintLabels, mthode 404 setPaintTicks, mthode 404 setRect, mthode 307 setResizable, mthode 294, 299 setRows, mthode 384, 387 Sets de hachage lis 678 setSelected, mthode 388, 390, 412 setSelectedFile, mthode 474, 481 setSnapToTicks, mthode 408 setTabSize, mthode 387 setText, mthode 380, 383 setTime, mthode 122 setTitle, mthode 294, 299 setToolTipText, mthode 422 setUser, mthode 466 setVisible(true) 466 setVisible, mthode 298, 466 setWrapStyleWord, mthode 387 Shallow Voir Clonage superciel Shape classe 308 interface 305 show, mthode 293, 413 showConrmMessage, mthode 453 showDocument, mthode 535 showInputDialog, mthode 453 showMessageDialog, mthode 453

showOpenDialog, mthode 473, 474 showOptionDialog, mthode 453 showSaveDialog, mthode 473, 474 showStatus, mthode 535 shutdown, mthode 769 signal, mthode 737 signalAll, mthode 736 Signature dune mthode 150, 187 du code 503 Sites Web Voir Web, adresses size, mthode 211, 212 SocketHandler 576 SoftBevelBorder classe 395, 398 constructeur 398 Solaris 24 sort, mthode 102, 104, 249 Sortie, chiers 76 Source des vnements 328 Souris vnements 352 nombre de clics 353 Sous-chanes 60 Sous-classes concrtes 306 mthodes 179, 187, 190 symtrie et galit 201 Sous-ensembles 686 sqrt, mthode 56 src, rpertoire 27 Stack, classe 702 StackTraceElement, classe 563 Standard Template Library Voir STL startsWith, mthode 66 static, mot cl 141, 155 Statiques Voir Champs, Mthodes, Constantes ou Variables STL 644 stop, mthode 752 strictfp, mot cl 53 StrictMath, classe 57 String, classe 60, 62, 65 StringBuffer, classe 69

Index

817

StringBuilder, classe 69 Structure, collections 681 subList, mthode 686 Substitution, principe de 245 substring, mthode 63, 66, 687 super, mot cl 180 super.paintComponent, mthode 303 Superclasse 178, 185, 233 abstraite 193 mthodes 179 Object 198 Suppression lments 649 de menu 416 espaces en-tte/n de chane 381 options dans une liste 399 partie dcimale dun rel 190 Surcharge de mthodes 150 rsolution de 150 suspend, mthode 752 swap, mthode 146 swapHelper, mthode gnrique 631 Swing 288, 289, 338, 426 bibliothque 365 composants 477 package 345 squence de tabulation 450 swing.defaultlaf, proprit 337 SwingConstants, interface 254, 382 SwingUtilities, classe 472 SwingUtilities.updateComponent TreeUI, mthode 338 SwingWorker, classe 794 switch, instruction 90 Synchronisation blocs 743 carte 688 communication des threads sans synchronisation 727 des accs entre plusieurs threads 731 queues 777 thread 727 vues 688

synchronized, mot cl 740, 741 synchronizedMap, classe 688 Synchronizer 774 SynchronousQueue, classe 777 Syntaxe classes internes 265 de Java 9 mthodes 44 rfrence externe 268 Synth 291 System, classe 72, 104, 540 System.exit, mthode 341 System.out, constante 139 System.runFinalizersOnExit, mthode 158 SystemColor, classe 314

T
Table de hash 661 Tableau circulaire, pour une queue 644 liste 660 plus grand lment 693 Tableaux 96 anonymes 99 autoboxing 218 C++ 100 copie 99 dclaration 97 dynamiques 237 gnriques 237 initialisation 98 irrguliers 108 liste de Voir Listes longueur 99 multidimensionnels 105 paramtres variables 221 programmation gnrique 622 remplacement dlment 213 remplissage 213 taille 210 augmenter 237 transtypage 237 tri 102, 250 Tables de mthodes 188 Tabulation, squence 450

Tches groupe 773 longues 784 Taille botes de dialogue 461 cadres 294 par dfaut 296 de lcran 297 fonte 317 tableaux 210 augmenter 237 Tampon Exchanger 777 Temporisation 750 Termes cls Java 8 Termin, thread 722 Test dgalit des chanes 62 Texte afchage 302 entre 380 mise en forme 315 this mot cl 134, 153, 181, 268 pointeur C++ 154 Thread 707 arrt 719 barrires 776 bloqu 722 collections 760 communication sans synchronisation 727 dmon 725 en attente 722 et processus 707 tats 724 excutable 721 interrompre 718 multithread 707 nouveau 721 partager un accs 727 pool 768, 769 priorits 724 proprits 724 spar 713 synchronisation 727 synchroniser des accs 731 termin 722 travailleur 789 unique 785, 795

818

Index

ThreadDeath 722 Throwable, classe 227, 554, 563 throws, clause 549 Timer, classe 261, 263 toArray, mthode 213 toBack, mthode 299 toFront, mthode 299 toLowerCase, mthode 66 Toolkit 263 Toolkit, classe 299, 300, 354, 359 toString, mthode 117, 204, 209, 219, 284, 453 gnrique 233 Touches associer une action 347 dsactiver 349 raccourcis clavier 415 toUpperCase, mthode 66 Trace 281 de piles 561 Transtypage 58, 187, 190, 305, 306 exception 237 tableaux 237 Travailleur, thread 789 TreeSet, classe 664 Tri 694 fusion 695 Tri de tableaux 102, 250 trim, mthode 66, 381 trimToSize, mthode 211, 212 TrueType 317 try, exception 226 Type brut 615 caractre 48 de donnes 45 de retour covariants 618 effacer 617 de variables 50 double 53 effacer 617, 624 entiers 46 numr 59, 222 gnrique 248 instancier 610, 622

holder 219 identication lexcution 224 interface 636 joker 627 numriques, conversion 57 paramtres 608 primitifs 147, 620 primitifs, conversion 217 virgule ottante 47 TypeVariable, classe 640 Typographie 318

U
UIManager, classe 340 UIManager.setLookAndFeel, mthode 338 UML (Unied Modeling Language) 115 Unchecked exceptions Voir Exceptions non vries Unicode 50, 317 Units de code 49, 63 caractres complmentaires 49 UNIX, make 131 UnsupportedOperationException, exception 674, 688 UTC (Coordinated Universal Time) 120 Utilitaires JAR Voir JAR javadoc Voir javadoc

V
Valeur/cl, paire 673 valueOf, mthode 94, 220 Variables 50 caractres $ dans nom 271 copie 255 de type 610 effacer 616 dclaration 45 nal 273 homonymes 79 initialisation 51 limites 613

locales 133, 151 accs 273 et champs dinstance 134 nal 273 objet 117, 119 porte 78 statiques 273 tableaux 99 Vector et ArrayList, synchronisation 661 Verrou Countdown 776 quitable 735 implicite 740 lire et crire 751 mort 747 objet 732 Verrouillage, packages 498 Virgule ottante 53 types 47 Visibilit 198 classes locales 272 dans un package 164 mthodes 188, 249 verrouiller les packages 498 Visualisateur, applet 516 void, mot cl 44 Volatiles, champs 745 Vues de composant dinterface 367 de chier 476 non-modiables 687 sous contrle 689 synchronises 688 Vues et emballages 685

W
WeakHashMap, classe 677 Web, adresses algorithmes fdlibm 57 codes source 26 didacticiel Java 31 Eclipse 31 exemples de programmes 26 index API 67 javadoc 173 livre blanc de Java 8 site de louvrage 2

Index

819

while, instruction 82 width 297 attribut dapplet 518 WildcardType>, classe 640 Window, classe 293, 294, 299 Window.show, mthode 293 WindowAdapter, classe 342 WindowClosing vnement 415 mthode 341, 342

WindowEvent classe 344, 361 vnement 341 WindowListener classe 344 interface 341 Windows 24, 294 gestion de mise en forme 425 nmake 131 WindowStateListener, classe 344

Wirth, Niklaus 11, 112

X
X11 301 XML 17

Z
Zones de texte 380, 384 retour automatique la ligne 387

Entirement mis jour pour la nouvelle plateforme Java Standard Edition 6, le dsormais classique Au cur de Java est louvrage de rfrence en programmation Java. la fois complet et concret, il traite avec prcision tous les lments importants du langage et de la bibliothque Java.
Les notions abordes sont illustres de programmes de dmonstration, aisment matrisables et adapts aux situations concrtes de programmation (gestion des clics de bouton, cration de botes de dialogue, calculs complexes, affichage dimages en mosaque, pause et reprise danimations, etc.). Ils constitueront un excellent point de dpart pour tous vos dveloppements. Ce premier volume prsente les nouveauts de Java SE 6 (compatibilits, amliorations graphiques, nouvelles classes, dveloppement des Web Services) et est entirement consacr aux notions fondamentales du langage, ainsi quaux bases de la programmation dinterfaces utilisateur. Il aborde notamment en dtail : les bases de la programmation oriente objet les structures fondamentales de Java la programmation gnrique les interfaces et les classes internes le cadre des collections le mcanisme de rflexion de Java et de proxy la simultanit le modle dcouteur dvnement la conception dinterfaces graphiques (bote outils Swing) la gestion des exceptions Louvrage intressera la fois les tudiants en programmation Java et les dveloppeurs professionnels en qute dun support didactique et pratique. Retrouvez tous les codes sources des exemples de louvrage sur le site www.pearsoneducation.fr.

propos des auteurs...

Cay Horstmann est professeur dinformatique luniversit dtat de San Jos (Californie). Il est galement consultant en dveloppement Java, C++ et Internet. Grand spcialiste de Java, il est bien connu des professionnels pour ses frquentes interventions comme confrencier. Gary Cornell crit et forme des professionnels de la programmation depuis plus de 20 ans. Docteur en informatique, il a travaill au sein des laboratoires de recherche IBM Watson et enseign luniversit du Connecticut. Il est le co-fondateur des ditions Apress, spcialises en programmation. Le traitement du code XML, la programmation rseau, les bases de donnes, linternationalisation, la scurit et la cration dinterfaces utilisateur avances font lobjet dun second volume : (Au cur de Java, 8e dition, Volume 2, Fonctions avances, Pearson Education, 2008).
Public : tudiants en informatique et dveloppeurs Java Cours : programmation objet, programmation Java Niveau : L, M

ISBN : 978-2-7440-4080-1
Pearson Education France 47 bis, rue des Vinaigriers 75010 Paris Tl. : 01 72 74 90 00 Fax : 01 42 05 22 17 www.pearson.fr

Image de couverture : Getty Images/Photodisc

Вам также может понравиться