Академический Документы
Профессиональный Документы
Культура Документы
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.
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
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
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
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
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
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
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
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
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
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
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 ;
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
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.
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.
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
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
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
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
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
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.
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
Chapitre 1
15
16
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
17
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
2006
3 777
18
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
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
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.
22
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
23
Nom Java Development Kit Java Runtime Environment Standard Edition Enterprise Edition Micro Edition Java 2 Software Development Kit Mise jour (Update) NetBeans
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
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
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
5. Excutez la commande :
jar xvf ../src.zip
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
Chapitre 2
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
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
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.
Chapitre 2
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
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); } }
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
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.
32
Figure 2.4
Bote de dialogue New Project dans Eclipse.
Figure 2.5
Conguration dun projet Eclipse.
Chapitre 2
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
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
35
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
/** * 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
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;
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
Figure 2.10
Lapplet WelcomeApplet dans lafcheur dapplets.
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
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
/** * 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
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.
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
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
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.
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
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
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
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
47
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
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
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
Nom Retour arrire Tabulation Sparation de ligne Retour chariot Guillemet Apostrophe Barre oblique inverse
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
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
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
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
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;
52
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
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
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."
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
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
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
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.
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
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
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));
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").
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
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.
signie
(a && b) || c
Chapitre 3
59
signie
a += (b += c)
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
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
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).
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
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.
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
63
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) . . .
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
64
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.
Le caractre
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
65
java.lang.String 1.0
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 lindice du point de code do pointe cpCount, depuis le point de code jusqu startIndex.
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 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 le nombre de points de code entre startIndex et endIndex-1. Les substitutions sans paires sont considres comme des points de code.
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
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.
Chapitre 3
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
Figure 3.4
Liste des mthodes de la classe String.
Figure 3.5
Description dtaille dune mthode de la classe String.
Chapitre 3
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()
Annexe un point de code, en le transformant en une ou deux units de code et renvoie this.
void setCharAt(int i, char c)
70
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.
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();
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
Chapitre 3
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.
String nextLine()
72
String next()
Lisent et transforment la prochaine suite de caractres qui reprsente un entier ou un nombre virgule ottante.
boolean hasNext()
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.
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
Chapitre 3
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
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
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());
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
75
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
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());
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.
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
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
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
java.util.Scanner 5.0
Scanner(File f)
PrintWriter(File f)
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#.
Chapitre 3
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
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).
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
81
Figure 3.8
Organigramme de linstruction if/else.
OUI
yourSales
target
NON
performance =Satisfactory
performance =Unsatisfactory
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
yourSales 2*target
OUI
performance =Excellent
bonus=1000
NON
yourSales 1.5*target
OUI
performance =Fine
bonus=500
NON
performance =Satisfactory
bonus=100
NON
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
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
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
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
lire l'entre
OUI input="N"
NON
86
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
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
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
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
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.
90
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
91
Figure 3.13
Organigramme de linstruction switch.
OUI choix = 1
...
NON
OUI choix = 2
...
NON
OUI choix = 3
...
NON
OUI choix = 4
...
92
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
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
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
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;
96
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)
Renvoie 0 si BigInteger est gal other, un rsultat ngatif sil est infrieur other et un rsultat positif sinon.
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.
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)
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
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;
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).
98
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));
Remarquez quil nest pas ncessaire dappeler new lorsque vous utilisez cette syntaxe.
Chapitre 3
99
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 };
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).
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
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 =
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
101
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.
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
102
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;
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
103
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
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 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.
Tableau de type int, long, short, char, byte, boolean, oat ou double. Valeur de mme type que les lments de a.
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)
Chapitre 3
105
Paramtres :
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
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;
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));
Chapitre 3
107
108
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] =
. . .
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
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][];
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; }
ni mme
double (*balances)[6] = new double[10][6]; // C++
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
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
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
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.
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
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
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).
Connecteur UML
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.
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
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();
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
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
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 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.
122
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
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
126
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
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
field
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).
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.
time
Date getTime()
Dtermine la position dans le temps reprsente par la valeur de cet objet calendrier.
128
java.text.DateFormatSymbols 1.1
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.
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
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; }
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
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.
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);
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.
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
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
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; }
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
harry = d=
Date
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
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.
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 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++.
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
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 }
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.
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%
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
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 la classe Employee fait partie dune plus grande application, vous dmarrez lapplication avec :
java Application
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
Chapitre 4
Objets et classes
143
144
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;
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
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
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); }
146
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
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.
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
public void raiseSalary(double byPercent) { double raise = salary * byPercent / 100; salary += raise; } private String name; private double salary; }
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
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
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();
152
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() { }
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
{ }
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.
154
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
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.
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
Random()
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.
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.*;
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
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;
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
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))
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)
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
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
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
164
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; }
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
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
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
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
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
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.
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.
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
* 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
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.
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.
174
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() { . . . }
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
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
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
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
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
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
// 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
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
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];
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() { ... }
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
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
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
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
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) . . .
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
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(. . .);
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
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
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.
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; } }
200
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
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;
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
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
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 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());
Objet s sb t tb
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
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
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 +"]"; } }
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.
206
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)
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
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.
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
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()
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
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
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>()
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).
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
void trimToSize()
Chapitre 5
Lhritage
213
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.
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 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
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].
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
Paramtres :
index obj
La position dinsertion, qui doit tre comprise entre 0 et size() -1. La nouvelle valeur
T get(int index)
index
Lindex de llment rcuprer, qui doit tre compris entre 0 et size() -1.
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.
Vous pouvez transfrer une liste de tableau type la mthode update sans autre transtypage :
ArrayList<Employee> staff = ...; employeeDB.update(staff);
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.
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.
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
Une autre innovation de Java SE 5.0 facilite lajout et la rcupration dlments de tableau. Lappel
list.add(3);
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 }
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)
Permet de renvoyer une reprsentation du nombre i dans la base spcie par le paramtre radix.
220
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)
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; }
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
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.
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
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
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;".
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
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.
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()
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.
228
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
/** * 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
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
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()
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
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.
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
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
236
// 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()
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
Dnit le champ dcrit par cet objet Field dans lobjet obj avec une nouvelle valeur.
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
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);
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
/** * 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 un nouveau tableau du type de composant donn avec les dimensions donnes.
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
| | | | |
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
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
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
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
248
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
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; } . . . }
250
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
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
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
253
chaque sous-classe remplacer rank et implmentez une seule mthode compareTo qui prendra en compte les valeurs du rang.
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
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.
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
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
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
Figure 6.1
Copie et clonage.
original = copy =
Copie Employee
copy =
Employee
Figure 6.2
Une copie "shallow".
original =
String
copy =
Date
Chapitre 6
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
// 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
Chapitre 6
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.
260
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
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); }
262
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();
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
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
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
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
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++.
{ . . . }
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
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 =
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
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.
/** * 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
/** * 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(); } } }
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
269
Notez que vous faites rfrence une classe interne de la faon suivante
ClasseExterne.ClasseInterne
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 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
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; }
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
271
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)
272
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.
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
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
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.
Chapitre 6
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
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(); } }
Chapitre 6
277
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; }
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
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
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
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
Chapitre 6
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
// 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.
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
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
if (i < args.length - 1) System.out.print(", "); } } System.out.println(")"); // appelle la mthode relle return m.invoke(target, args); } private Object target; }
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
Dnit cette mthode pour quelle contienne laction que vous voulez excuter chaque fois quune mthode a t invoque sur lobjet proxy.
Chapitre 6
285
Construit une nouvelle instance de la classe proxy qui implmente les interfaces donnes. Toutes les mthodes appellent la mthode invoke de lobjet gestionnaire donn.
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
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
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.
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
Figure 7.4
Le plus simple des cadres visibles.
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
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).
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
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
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.
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)
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
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
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
void toFront()
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
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.
300
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
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; }
304
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()
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()
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
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
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
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
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
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
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
Figure 7.11
Rectangles et ellipses.
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
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)
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)
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
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
Valeur de la composante rouge (0-255). Valeur de la composante verte (0-255). Valeur de la composante bleue (0-255).
Rcuprent ou dnissent la couleur courante. Toutes les oprations graphiques ultrieures utiliseront la nouvelle couleur. Paramtres :
Nouvelle couleur.
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)
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
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); } }
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
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
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
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.
/** * @version 1.33 2007-04-14 * @author Cay Horstmann */ public class FontTest
320
{ 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
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 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.
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.
322
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
Renvoient ou dnissent la fonte actuelle. Cette fonte sera employe dans les oprations ultrieures dafchage de texte. Paramtres :
font
Une fonte.
FontRenderContext getFontRenderContext ()
Extrait un contexte de rendu de fonte qui spcie les caractristiques de la fonte dans ce contexte graphique.
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
Renvoie la mesure de la fonte donne. La classe FontMetrics est un prcurseur de la classe LineMetrics.
java.awt.FontMetrics 1.0
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));
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
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
Dessine une image non mise lchelle. Remarque : cette mthode peut renvoyer un rsultat avant que limage ne soit compltement dessine.
326
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.
328
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
329
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
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);
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
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
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
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
javax.swing.JButton 1.2
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)
ImageIcon(String filename)
Chapitre 8
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
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 . . . } }
java.util.EventObject 1.1
Object getSource()
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
337
Or, la classe EventHandler peut crer automatiquement cet couteur, par lappel
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.
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
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
339
Figure 8.4
Modication du look and feel.
340
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
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
341
javax.swing.UIManager.LookAndFeelInfo 1.2
String getName()
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
{ 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
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); } });
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
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 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 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 maximise, transforme en icne ou restaure sa taille normale.
java.awt.event.WindowEvent 1.1
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
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)
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
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
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
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
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
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
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
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
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
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
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
javax.swing.JComponent 1.2
Renvoie laffectation daction qui attribue les frappes de touches (qui peuvent tre des objets arbitraires) aux touches daction.
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
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
Icne
Constante DEFAULT_CURSOR
Icne
Constante NE_RESIZE_CURSOR
CROSSHAIR_CURSOR
E_RESIZE_CURSOR
HAND_CURSOR
SE_RESIZE_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
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.*;
356
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
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
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
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
359
java.awt.event.InputEvent 1.1
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
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
Attribue au pointeur limage de lun des pointeurs prdnis spcis par le paramtre cursor.
360
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
361
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
m m m
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
Interface
ActionListener
Mthodes
actionPerformed
Paramtres/ accesseurs
ActionEvent getActionCommand getModifiers AdjustmentEvent getAdjustable getAdjustmentType getValue ItemEvent getItem getItemSelectable getStateChange FocusEvent
isTemporary
AdjustmentListener
adjustmentValueChanged
ItemListener
itemStateChanged
AbstractButton JComboBox
FocusListener KeyListener
Component Component
KeyEvent
getKeyChar getKeyCode getKeyModifiersText getKeyText isActionKey
MouseListener
MouseEvent
getClickCount getX getY getPoint translatePoint
Component
MouseMotionListener MouseWheelListener
Component Component
Chapitre 8
363
Interface
WindowListener
Mthodes
windowClosing windowOpened windowIconified windowDeiconified windowClosed windowActivated windowDeactivated windowGainedFocus windowLostFocus windowStateChanged
Paramtres/ accesseurs
WindowEvent
getWindow
WindowFocusListener WindowStateListener
WindowEvent
getOppositeWindow
Window Window
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
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
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
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
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
369
Figure 9.3
Deux vues spares du mme modle.
modle
P
OL
LI LI LI
vue WYSIWG
1. 2. 3.
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
Figure 9.4
Interactions entre les objets modles, vue et contrleur.
Contrleur Vue Modle
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.
Chapitre 9
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
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
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
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
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
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
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).
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
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
// 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
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
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()
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
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
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
javax.swing.JTextField 1.2
JTextField(int cols)
Construit un nouveau JTextField avec une chane initiale et le nombre spci de colonnes.
int getColumns() void setColumns(int cols)
void revalidate()
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()
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
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)
Le texte de ltiquette. Licne de ltiquette. Lune des constantes SwingConstants LEFT (par dfaut), CENTER ou RIGHT.
384
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
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.
386
} /** * 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
387
javax.swing.JTextArea 1.2
JTextArea() JTextArea(int rows, int cols) JTextArea(String text, int rows, int cols)
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
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)); }
Chapitre 9
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
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
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
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
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
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
javax.swing.JRadioButton 1.2
void add(AbstractButton b)
String getActionCommand()
void setActionCommand(String s)
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
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.
/** * @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
/** * 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
397
javax.swing.BorderFactory 1.2
static Border createLineBorder(Color color) static Border createLineBorder(Color color, int thickness)
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 avec un effet 3D. Paramtres : highlight, shadow type
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
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
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
Crent une bordure en relief avec des angles arrondis. Paramtres : type highlight, shadow
javax.swing.border.LineBorder 1.2
Cre une ligne de bordure avec lpaisseur et la couleur indiques. Si roundedCorners vaut true, les coins sont arrondis.
javax.swing.JComponent 1.2
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
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
Chapitre 9
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
402
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
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
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 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
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
// 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
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.
Dnissent les repres grands et petits comme multiples des units de curseur donnes.
void setPaintLabels(boolean b)
408
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)
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.
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);
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
409
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); } };
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
javax.swing.JMenu 1.2
JMenu(String label)
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)
index
JMenuItem(String label)
Chapitre 9
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
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
412
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 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 avec lintitul et ltat initial spcis (true signie active).
javax.swing.AbstractButton 1.2
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
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();
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
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
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.
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
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
javax.swing.JMenuItem 1.2
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
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.
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
417
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)
void menuSelected(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
{ 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
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
// 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
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
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
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
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
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()
Dnit le texte qui doit tre afch comme bulle daide lorsque la souris stationne au-dessus du composant.
426
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
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
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.
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
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.
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.
430
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
431
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
Chapitre 9
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
Chapitre 9
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
java.awt.GridBagConstraints 1.0
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 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
437
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.
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
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
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
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
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
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
441
Figure 9.33
Obliger deux composants avoir la mme largeur.
442
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
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
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)
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
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)
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
Ajoute un intervalle pour sparer les composants. Le type est LayoutStyle.ComponentPlacement.RELATED ou LayoutStyle.ComponentPlacement.UNRELATED.
446
java.awt.Component 1.0
Dplace et redimensionne un composant. Paramtres : x, y width, height Le nouveau coin suprieur gauche du composant. La nouvelle taille du composant.
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
447
448
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
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;
name comp
450
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
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.
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
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.
La Figure 9.36 prsente une bote de dialogue standard. Comme vous pouvez le voir, elle contient les composants suivants :
m m m
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
453
Figure 9.36
Une bote de dialogue doptions.
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
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
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
{ /** * 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
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
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
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
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
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.
462
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.
Chapitre 9
463
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
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
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
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
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
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
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
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
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
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
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
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
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.
Chapitre 9
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
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
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");
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
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
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
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
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
/** * 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
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)
482
void setMultiSelectionEnabled(boolean b)
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).
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)
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.
Dnit une vue de chier pour fournir des informations sur les chiers afchs par le slecteur de chier.
boolean accept(File f)
Renvoie une description du ltre de chier, par exemple "Fichiers images (*.gif, *.jpeg)".
Chapitre 9
483
javax.swing.filechooser.FileNameExtensionFilter 6
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
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
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
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
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
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
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()
Chapitre 9
489
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
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
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
493
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 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
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.
chiers ajouter
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
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.
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
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
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
public static final int DEFAULT_WIDTH = 300; public static final int DEFAULT_HEIGHT = 300; } java.lang.Class 1.0
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
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
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
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
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
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
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
503
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
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
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
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");
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
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(); . . . }
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
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
509
/** * 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
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
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
} catch (UnavailableServiceException e) { JOptionPane.showMessageDialog(this, e); } catch (IOException e) { JOptionPane.showMessageDialog(this, e); } } private CalculatorPanel panel; } javax.jnlp.ServiceManager
URL getCodeBase()
Tente dafcher lURL donne dans un navigateur. Renvoie true si la requte a russi.
javax.jnlp.FileContents
InputStream getInputStream()
Renvoie un ux de sortie pour crire dans le chier. Si overwrite vaut true, le contenu du chier est remplac.
String getName()
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
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
Cre une entre dans une banque persistante pour la cl donne. Renvoie la taille maximale accorde par la banque persistante.
void delete(URL key)
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
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
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
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
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.
518
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.
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.
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
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
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
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.
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
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.
522
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>
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
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
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
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
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
Prend la valeur dun paramtre dni par la balise param dans la page Web ayant charg lapplet. Attention aux majuscules/minuscules !
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.
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"
Chapitre 10
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 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
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()); }
Chapitre 10
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
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
531
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
URL getCodeBase() { return null; String getParameter(String name) AppletContext getAppletContext() void appletResize(int width, int
// 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
/** * @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
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
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; }
Chapitre 10
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
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.
Enumeration<Applet> getApplets()
Renvoie une numration (voir Chapitre 13) de tous les applets dun mme contexte, cest--dire dune mme page Web.
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).
536
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");
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
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).
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
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
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()
Properties(Properties defaults)
Cre une correspondance vide de proprits avec un jeu de paramtres par dfaut. Paramtres :
defaults
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
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.
in
Le ux dentre.
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
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.
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
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
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)
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
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
// 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
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 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()
546
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)
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
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.
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
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.
un mauvais transtypage ; un accs un tableau en dehors des limites ; lemploi dun pointeur null.
550
Figure 11.1
La hirarchie des exceptions en Java.
Throwable
Error
Exception
IOException
Runtime Exception
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
551
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
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
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.
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();
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
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.
Throwable()
Chapitre 11
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()
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
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
557
Lobjet exception (e1, e2, e3) peut renseigner sur la nature de lexception. Pour en savoir plus sur lobjet exception, on peut essayer
e3.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
try { accder la base de donnes } catch (SQLException e) { Throwable se = new ServletException("database error"); se.initCause(e); throw se; }
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
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
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
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.
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
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
Chapitre 11
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
Dnit la cause pour cet objet ou lance une exception si cet objet possde dj une cause. Renvoie this.
Rcupre lobjet exception qui a t dni comme cause de cet objet ou null sil nexiste pas de cause.
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()
564
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.
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
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
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
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
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.
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).
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
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.
570
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
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
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");
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
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)
Chapitre 11
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)
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
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
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 ...
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
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
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
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.
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
578
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.
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
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)
580
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");
Chapitre 11
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
/** * 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
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
public void publish(LogRecord record) { if (!frame.isVisible()) return; super.publish(record); flush(); } private JFrame frame; } java.util.logging.Logger 1.4
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)
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.
Chapitre 11
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 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)
586
ConsoleHandler()
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()
Rcuprent le groupe de ressources ou son nom pour lutiliser pour localiser le message ou null si aucun nest fourni.
String getMessage()
Chapitre 11
587
Object[] getParameters()
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 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
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.
588
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
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
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
{ 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
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
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
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.
594
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
595
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
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
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
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
599
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
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
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
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
java.awt.Robot 1.3
Robot(GraphicsDevice device)
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.
Simule un mouvement de la souris. Paramtres : x,y La position de la souris en coordonnes absolues et en pixels.
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.
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
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); }
604
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 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
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
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
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
610
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.
612
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)".
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
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.
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); } }
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
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; }
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.
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
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;
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
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(); } . . . }
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
Chapitre 12
Programmation gnrique
619
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
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
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
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
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
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:
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; } }
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 . . . }
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
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.
est
boolean equals(Object)
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.
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
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.
<<
>>
<<
<<
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
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 <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.
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 <Employee>
Paire <Object>
630
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) . . .
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.
Chapitre 12
Programmation gnrique
631
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)
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
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
} 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 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
636
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>.
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
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
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
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.
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 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()
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 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()
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.
644
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.
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
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
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.
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
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!
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
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 true si cette collection contient tous les lments de lautre collection.
Chapitre 13
Collections
651
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 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 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 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.
652
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.
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.
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
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
Figure 13.7
Ajouter un lment dans une liste chane.
Claire
Laurent
Adrien
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
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
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
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 dont le premier appel next doit renvoyer llment correspondant lindice spci.
void add(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.
Renvoie la position de la dernire occurrence dun lment gal llment spci, ou 1 si cet lment na pu tre trouv.
660
java.util.ListIterator<E> 1.2
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 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 et y ajoute tous les lments dune collection.
void addFirst(E element) void addLast(E element)
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
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
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.
664
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 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
HashSet 5s 75 s
TreeSet 7s 98 s
TreeSet()
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
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); } }
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
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
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
Renvoie le comparateur utilis pour trier les lments ou null si les lments sont compars avec la mthode compareTo de linterface Comparable.
670
E first() E last()
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
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()
TreeSet()
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
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
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
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
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
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.
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)
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()
Modie la valeur de la carte associe avec la nouvelle valeur et renvoie lancienne valeur.
java.util.HashMap<K, V> 1.2
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
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
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()
678
queue signie que la cl nest plus utilise par personne et quelle a t collecte. WeakHashMap supprime alors lentre associe.
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.
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
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>.
Construisent une carte de hachage vide avec la capacit et le facteur de charge spcis.
java.util.LinkedHashMap<E> 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.
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
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)
EnumMap(Class<K> keyType)
Construit une carte vide dont les cls ont le type donn.
java.util.IdentityHashMap<K, V> 1.4
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
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.
682
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 }
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
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
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
686
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)
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
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
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".
690
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 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
Renvoie une vue en liste des lments dun tableau, modiable mais non redimensionnable.
java.util.List<E> 1.2
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)
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.
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
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));
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()]);
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
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
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.
Renvoie un comparateur qui trie les lments par ordre dcroissant par rapport la mthode compareTo de linterface Comparable.
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
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).
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)
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
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
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
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
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
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.
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 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
Enumeration<V> elements()
Renvoie un objet numration qui parcourt les lments dune table de hachage.
java.util.Vector<E> 1.0
Enumeration<E> elements()
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()
Renvoie la chane associe une cl, ventuellement dans la table de valeurs par dfaut si la cl ne gure pas dans la carte spcie.
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)
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)
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.
renvoie true si le i-me bit est 1, et false dans le cas contraire. De mme,
bucketOfBits.set(i)
java.util.BitSet 1.0
BitSet(int intialCapacity)
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
Efface un bit.
void and(BitSet set)
Efface tous les bits de cet ensemble de bits qui sont 1 dans lautre ensemble de bits.
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
706
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
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).
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
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; }
712
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;
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
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.
716
/** * 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
} 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)
Lance ce thread et appelle sa mthode run(). Cette mthode revient immdiatement. Le nouveau thread est excut en mme temps.
void run()
Cette fonction doit tre surcharge et vous devez y ajouter les instructions qui doivent tre excutes dans le thread correspondant.
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
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); ... }
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.
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.
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
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.
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
temporise
termine
java.lang.Thread 1.0
void join()
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()
Reprend ce thread. Cette mthode nest valide quaprs un appel suspend(). Cette mthode est aujourdhui obsolte.
724
java.lang.Thread 1.0
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
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.
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
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
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
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
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.
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
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; }
730
/** * Obtient le nombre de comptes dans la banque * @return le nombre de comptes */ public int size() { return accounts.length; } private final double[] accounts; }
Chapitre 14
Multithreads
731
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
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.
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
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()
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
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
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()
void await()
Dbloque un thread choisi alatoirement dans le jeu dattente pour cette condition.
740
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
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, 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 }
744
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
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
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
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
Figure 14.6
Une situation de verrous morts.
1 2 200 300
bank.accounts
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
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.
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
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.
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.
752
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.
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
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
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
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
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
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
Rcupre le dlai pour cet objet, mesur dans lunit de temps donne.
java.util.concurrent.PriorityBlockingQueue<E> 5.0
Construisent une queue dattente de priorit de blocages sans bornes, implmente sous forme de tas.
760
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
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.
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 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.
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.
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)
remplace automatiquement lancienne valeur par une nouvelle, condition que lancienne valeur ait t associe la cl donne.
762
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
Construisent une carte trie accessible en toute scurit par plusieurs threads. Le premier constructeur exige que les cls implmentent linterface Comparable.
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.
Si la cl donne est dj associe cette valeur, supprime la cl et la valeur donnes et renvoie true, sinon renvoie false.
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.
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
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();
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
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()
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.
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
boolean isCancelled()
Renvoie true si la tche est termine, suite une n normale, une annulation ou une exception.
java.util.concurrent.FutureTask<V> 5.0
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
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
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.
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)
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
Renvoie un pool de threads qui utilise le nombre donn de threads pour programmer des tches.
ScheduledExecutorService newSingleThreadScheduledExecutor()
ScheduledFuture<V> schedule(Callable<V> task, long time, TimeUnit unit) ScheduledFuture<?> schedule(Runnable task, long time, TimeUnit unit)
Programme la tche donne pour une excution priodique, chaque unit period, une fois le dlai initial expir.
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.
774
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)
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
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(); ... }
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.
778
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.
/** * 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
/** * 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
java.util.concurrent.CyclicBarrier 5.0
Construisent une barrire cyclique pour le nombre de parties donn. barrierAction est excut lorsque toutes les parties ont appel await sur la barrire.
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)
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.
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
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.
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
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()
Tente dacqurir une autorisation avec le dlai donn ; renvoie false si aucune nest disponible.
void release()
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
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.
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
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
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.
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.
Renvoie true si le thread excutant cette mthode est le thread de rpartition des vnements.
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
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
}); } } /** * 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
} 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.
Transfre les donnes de progression intermdiaires au thread de rpartition des vnements. Appelez cette mthode partir de doInBackground.
Chapitre 14
Multithreads
795
void execute()
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
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
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
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
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
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
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.
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