™
Livre Java.book Page I Mercredi, 13. février 2008 2:34 14
Au cœur de Java
™
volume 1 Notions fondamentales 8e édition Cay S. Horstmann et Gary Cornell
Livre Java.book Page II Mercredi, 13. février 2008 2:34 14
Le présent 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 Tél. : 01 72 74 90 00
Titre original : Core Java, volume 1 Fundamentals
Mise en pages : TyPAO
ISBN original : 0-13-235476-4 Copyright © 2008 Sun Microsystems, Inc. Tous droits réservés
Traduit de l’américain par : Christiane Silhol et Nathalie Le Guillou de Penanros
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 réservés. Aucune représentation ou reproduction, même partielle, autre que celles prévues à l’article L. 122-5 2˚ et 3˚ a) du code de la propriété intellectuelle ne peut être faite sans l’autorisation expresse de Pearson Education France ou, le cas échéant, sans le respect des modalités prévues à l’article L. 122-10 dudit code.
Livre Java.book Page III Mercredi, 13. février 2008 2:34 14
Table des matières
Introduction .................................................................................................................................. Avertissement au lecteur .......................................................................................................... A propos de ce livre ................................................................................................................. Conventions .............................................................................................................................. Exemples de code .....................................................................................................................
1 1 2 4 5
Chapitre 1. Une introduction à Java ......................................................................................... Java, plate-forme de programmation ........................................................................................ Les termes clés du livre blanc de Java ...................................................................................... Simplicité ............................................................................................................................ Orienté objet ....................................................................................................................... Compatible avec les réseaux ............................................................................................... Fiabilité ............................................................................................................................... Sécurité ............................................................................................................................... Architecture neutre ............................................................................................................. Portabilité ........................................................................................................................... Interprété ............................................................................................................................. Performances élevées .......................................................................................................... Multithread ......................................................................................................................... Java, langage dynamique .................................................................................................... Les applets Java et Internet ...................................................................................................... Bref historique de Java ............................................................................................................. Les idées fausses les plus répandues concernant Java ..............................................................
7 7 8 8 9 10 10 10 11 12 12 12 13 13 14 15 17
Chapitre 2. L’environnement de programmation de Java ...................................................... Installation du kit de développement Java ................................................................................ Télécharger le JDK ............................................................................................................. Configurer le chemin d’exécution ...................................................................................... Installer la bibliothèque et la documentation ...................................................................... Installer les exemples de programmes ................................................................................ Explorer les répertoires de Java ..........................................................................................
21 21 22 24 26 26 27
Livre Java.book Page IV Mercredi, 13. février 2008 2:34 14
IV
Table des matières
Choix de l’environnement de développement .......................................................................... Utilisation des outils de ligne de commande ............................................................................ Conseils pour la recherche d’erreurs .................................................................................. Utilisation d’un environnement de développement intégré ...................................................... Localiser les erreurs de compilation ................................................................................... Exécution d’une application graphique .................................................................................... Elaboration et exécution d’applets ...........................................................................................
28 28 30 31 34 35 37
Chapitre 3. Structures fondamentales de la programmation Java ......................................... Un exemple simple de programme Java ................................................................................... Commentaires .......................................................................................................................... Types de données ..................................................................................................................... Entiers ................................................................................................................................. Types à virgule flottante ...................................................................................................... Le type char ...................................................................................................................... Type booléen ....................................................................................................................... Variables ................................................................................................................................... Initialisation des variables ....................................................................................................... Constantes ........................................................................................................................... Opérateurs ................................................................................................................................ Opérateurs d’incrémentation et de décrémentation ............................................................ Opérateurs relationnels et booléens .................................................................................... Opérateurs binaires ............................................................................................................. Fonctions mathématiques et constantes .............................................................................. Conversions de types numériques ...................................................................................... Transtypages ....................................................................................................................... Parenthèses et hiérarchie des opérateurs ............................................................................ Types énumérés .................................................................................................................. Chaînes ..................................................................................................................................... Sous-chaînes ....................................................................................................................... Concaténation ..................................................................................................................... Les chaînes sont inaltérables .............................................................................................. Test d’égalité des chaînes ................................................................................................... Points de code et unités de code ......................................................................................... L’API String .................................................................................................................... Lire la documentation API en ligne .................................................................................... Construction de chaînes ...................................................................................................... Entrées et sorties ....................................................................................................................... Lire les caractères entrés ..................................................................................................... Mise en forme de l’affichage .............................................................................................. Entrée et sortie de fichiers ..................................................................................................
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
Livre Java.book Page V Mercredi, 13. février 2008 2:34 14
Table des matières
V
Flux d’exécution ....................................................................................................................... Portée d’un bloc .................................................................................................................. Instructions conditionnelles ................................................................................................ Boucles .............................................................................................................................. Boucles déterminées ........................................................................................................... Sélections multiples — l’instruction switch .................................................................... Interrompre le flux d’exécution .......................................................................................... Grands nombres ....................................................................................................................... Tableaux ................................................................................................................................... La boucle "for each" ........................................................................................................... Initialiseurs de tableaux et tableaux anonymes .................................................................. Copie des tableaux .............................................................................................................. Paramètres de ligne de commande ..................................................................................... Tri d’un tableau ................................................................................................................... Tableaux multidimensionnels ............................................................................................. Tableaux irréguliers ............................................................................................................
78 78 79 82 86 90 92 94 96 97 98 99 101 102 105 108
Chapitre 4. Objets et classes ...................................................................................................... Introduction à la programmation orientée objet ....................................................................... Les classes .......................................................................................................................... Les objets ............................................................................................................................ Identification des classes .......................................................................................................... Relations entre les classes ................................................................................................... Utilisation des classes existantes .............................................................................................. Objets et variables objet ..................................................................................................... La classe GregorianCalendar de la bibliothèque Java ..................................................... Les méthodes d’altération et les méthodes d’accès ............................................................ Définition de vos propres classes ............................................................................................. Une classe Employee .......................................................................................................... Travailler avec plusieurs fichiers source ............................................................................. Analyser la classe Employee ............................................................................................. Premiers pas avec les constructeurs .................................................................................... Paramètres implicites et explicites ...................................................................................... Avantages de l’encapsulation ............................................................................................. Privilèges d’accès fondés sur les classes ............................................................................ Méthodes privées ................................................................................................................ Champs d’instance final .................................................................................................. Champs et méthodes statiques .................................................................................................. Champs statiques ................................................................................................................ Constantes statiques ............................................................................................................ Méthodes statiques .............................................................................................................
111 112 113 113 114 115 116 117 120 121 128 128 131 132 132 134 135 137 137 138 138 138 139 140
Livre Java.book Page VI Mercredi, 13. février 2008 2:34 14
VI
Table des matières
Méthodes "factory" ............................................................................................................. La méthode main ............................................................................................................... Paramètres des méthodes ......................................................................................................... Construction d’un objet ........................................................................................................... Surcharge ............................................................................................................................ Initialisation des champs par défaut ................................................................................... Constructeurs par défaut ..................................................................................................... Initialisation explicite de champ ......................................................................................... Noms de paramètres ........................................................................................................... Appel d’un autre constructeur ............................................................................................ Blocs d’initialisation ........................................................................................................... Destruction des objets et méthode finalize ......................................................................... Packages ................................................................................................................................... Importation des classes ....................................................................................................... Imports statiques ................................................................................................................. Ajout d’une classe dans un package ................................................................................... Visibilité dans un package .................................................................................................. Le chemin de classe .................................................................................................................. Définition du chemin de classe ........................................................................................... Commentaires pour la documentation ...................................................................................... Insertion des commentaires ................................................................................................ Commentaires de classe ...................................................................................................... Commentaires de méthode ................................................................................................. Commentaires de champ .................................................................................................... Commentaires généraux ..................................................................................................... Commentaires de package et d’ensemble ........................................................................... Extraction des commentaires .............................................................................................. Conseils pour la conception de classes ....................................................................................
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
Chapitre 5. L’héritage ................................................................................................................. Classes, superclasses et sous-classes ........................................................................................ Hiérarchie d’héritage .......................................................................................................... Polymorphisme ................................................................................................................... Liaison dynamique ............................................................................................................. Empêcher l’héritage : les classes et les méthodes final .................................................. Transtypage ......................................................................................................................... Classes abstraites ................................................................................................................ Accès protégé ..................................................................................................................... Object : la superclasse cosmique .............................................................................................. La méthode equals ........................................................................................................... Test d’égalité et héritage .....................................................................................................
177 178 184 185 186 189 190 192 197 198 198 199
Livre Java.book Page VII Mercredi, 13. février 2008 2:34 14
Table des matières
VII
La méthode hashCode ....................................................................................................... La méthode toString ....................................................................................................... Listes de tableaux génériques ................................................................................................... Accéder aux éléments d’une liste de tableaux .................................................................... Compatibilité entre les listes de tableaux brutes et typées ................................................. Enveloppes d’objets et autoboxing ........................................................................................... Méthodes ayant un nombre variable de paramètres ........................................................... Classes d’énumération .............................................................................................................. Réflexion .................................................................................................................................. La classe Class ................................................................................................................. Introduction à l’interception d’exceptions .......................................................................... La réflexion pour analyser les caractéristiques d’une classe .............................................. La réflexion pour l’analyse des objets à l’exécution .......................................................... La réflexion pour créer un tableau générique ..................................................................... Les pointeurs de méthodes ................................................................................................. Conseils pour l’utilisation de l’héritage ...................................................................................
202 204 210 212 216 217 220 221 223 224 226 227 232 237 240 244
Chapitre 6. Interfaces et classes internes ................................................................................ Interfaces .................................................................................................................................. Propriétés des interfaces ..................................................................................................... Interfaces et classes abstraites ............................................................................................ Clonage d’objets ....................................................................................................................... Interfaces et callbacks .............................................................................................................. Classes internes ........................................................................................................................ Accéder à l’état d’un objet à l’aide d’une classe interne .................................................... Règles particulières de syntaxe pour les classes internes ................................................... Utilité, nécessité et sécurité des classes internes ................................................................ Classes internes locales ...................................................................................................... Accès aux variables final à partir de méthodes externes ................................................. Classes internes anonymes ................................................................................................. Classes internes statiques .................................................................................................... Proxies ...................................................................................................................................... Propriétés des classes proxy ...............................................................................................
247 248 253 254 255 261 264 265 268 269 271 272 274 276 280 284
Chapitre 7. Programmation graphique .................................................................................... Introduction à Swing ................................................................................................................ Création d’un cadre .................................................................................................................. Positionnement d’un cadre ....................................................................................................... Propriétés des cadres .......................................................................................................... Déterminer une taille de cadre adéquate ............................................................................. Affichage des informations dans un composant .......................................................................
287 288 291 294 296 296 300
Livre Java.book Page VIII Mercredi, 13. février 2008 2:34 14
VIII
Table des matières
Formes 2D ................................................................................................................................ Couleurs ................................................................................................................................... Texte et polices ......................................................................................................................... Affichage d’images ..................................................................................................................
305 312 315 323
Chapitre 8. Gestion des événements ..........................................................................................
327
Introduction à la gestion des événements ................................................................................. Exemple : gestion d’un clic de bouton ............................................................................... Etre confortable avec les classes internes ........................................................................... Créer des écouteurs contenant un seul appel de méthode .................................................. Exemple : modification du "look and feel" ......................................................................... Classes adaptateurs ............................................................................................................. Actions ..................................................................................................................................... Evénements de la souris ..................................................................................................... Hiérarchie des événements AWT ............................................................................................. Evénements sémantiques et de bas niveau .........................................................................
327 330 334 337 337 341 344 352 359 361
Chapitre 9. Swing et les composants d’interface utilisateur ...................................................
365
Swing et l’architecture Modèle-Vue-Contrôleur ...................................................................... Modèles de conception ....................................................................................................... L’architecture Modèle-Vue-Contrôleur .............................................................................. Une analyse Modèle-Vue-Contrôleur des boutons Swing .................................................. Introduction à la gestion de mise en forme .............................................................................. Gestionnaire BorderLayout ................................................................................................ Disposition des grilles ........................................................................................................ Entrée de texte .......................................................................................................................... Champs de texte .................................................................................................................. Etiquettes et composants d’étiquetage ................................................................................ Champs de mot de passe ..................................................................................................... Zones de texte ..................................................................................................................... Volets de défilement ............................................................................................................ Composants du choix ............................................................................................................... Cases à cocher .................................................................................................................... Boutons radio ...................................................................................................................... Bordures ............................................................................................................................. Listes déroulantes ............................................................................................................... Curseurs .............................................................................................................................. Menus ....................................................................................................................................... Création d’un menu ............................................................................................................ Icônes et options de menu ..................................................................................................
366 366 367 370 372 374 376 380 380 382 383 384 385 387 387 390 394 398 402 408 408 410
Livre Java.book Page IX Mercredi, 13. février 2008 2:34 14
Table des matières
IX
Options de menu avec cases à cocher et boutons radio ...................................................... Menus contextuels .............................................................................................................. Caractères mnémoniques et raccourcis clavier ................................................................... Activation et désactivation des options de menu ................................................................ Barres d’outils .................................................................................................................... Bulles d’aide ....................................................................................................................... Mise en forme sophistiquée ...................................................................................................... Gestionnaire GridBagLayout .............................................................................................. GroupLayout ....................................................................................................................... Création sans gestionnaire de mise en forme ..................................................................... Gestionnaires de mise en forme personnalisés ................................................................... Séquence de tabulation ....................................................................................................... Boîtes de dialogue .................................................................................................................... Boîtes de dialogue d’options .............................................................................................. Création de boîtes de dialogue ............................................................................................ Echange de données ............................................................................................................ Boîtes de dialogue Fichier .................................................................................................. Sélecteurs de couleur ..........................................................................................................
411 412 414 416 420 422 425 427 436 445 446 450 451 452 461 466 472 483
Chapitre 10. Déployer des applets et des applications ............................................................. Les fichiers JAR ....................................................................................................................... Le manifeste ....................................................................................................................... Fichiers JAR exécutables .................................................................................................... Les ressources ..................................................................................................................... Verrouillage ........................................................................................................................ Java Web Start .......................................................................................................................... Le bac à sable ..................................................................................................................... Code signé .......................................................................................................................... L’API JNLP ....................................................................................................................... Les applets ................................................................................................................................ Un petit applet .................................................................................................................... Conversion d’une application en applet .............................................................................. Balises HTML et attributs pour applets .............................................................................. La balise object ................................................................................................................... Passer des informations à un applet avec des paramètres ................................................... Accéder aux images et fichiers audio ................................................................................. Le contexte d’applet ........................................................................................................... La communication interapplets .......................................................................................... Faire afficher des informations par le navigateur ............................................................... C’est un applet et c’est aussi une application ! ...................................................................
491 492 493 494 495 498 498 502 503 505 513 514 517 518 521 521 526 528 528 528 530
Livre Java.book Page X Mercredi, 13. février 2008 2:34 14
X
Table des matières
Stockage des préférences d’applications .................................................................................. Concordances de propriétés ................................................................................................ L’API Preferences ..............................................................................................................
535 536 540
Chapitre 11. Exceptions, consignation, assertion et mise au point ......................................... Le traitement des erreurs .......................................................................................................... Le classement des exceptions ............................................................................................. Signaler les exceptions sous contrôle ................................................................................. Comment lancer une exception .......................................................................................... Créer des classes d’exception ............................................................................................. Capturer les exceptions ............................................................................................................ Capturer des exceptions multiples ...................................................................................... Relancer et enchaîner les exceptions .................................................................................. La clause finally .................................................................................................................. Analyser les traces de piles ................................................................................................. Quelques conseils sur l’utilisation des exceptions ................................................................... Les assertions ........................................................................................................................... Activation et désactivation des assertions ........................................................................... Vérification des paramètres avec des assertions ................................................................. Utiliser des assertions pour documenter des hypothèses .................................................... La consignation ........................................................................................................................ Consignation de base .......................................................................................................... Consignation avancée ......................................................................................................... Modifier la configuration du gestionnaire de journaux ...................................................... La localisation .................................................................................................................... Les gestionnaires ................................................................................................................ Les filtres ............................................................................................................................ Les formateurs .................................................................................................................... Une astuce de consignation ................................................................................................ Les techniques de mise au point ............................................................................................... Utiliser une fenêtre de console ........................................................................................... Tracer les événements AWT ............................................................................................... Le robot AWT ..................................................................................................................... Utiliser un débogueur ...............................................................................................................
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
Chapitre 12. Programmation générique ................................................................................... Pourquoi la programmation générique ? .................................................................................. Y a-t-il un programmeur générique dans la salle ? ............................................................. Définition d’une classe générique simple ................................................................................. Méthodes génériques ................................................................................................................ Limites pour variables de type .................................................................................................
607 608 609 610 612 613
Livre Java.book Page XI Mercredi, 13. février 2008 2:34 14
Table des matières
XI
Code générique et machine virtuelle ........................................................................................ Traduire les expressions génériques ................................................................................... Traduire les méthodes génériques ...................................................................................... Appeler un code existant .................................................................................................... Restrictions et limites ............................................................................................................... Les paramètres de type ne peuvent pas être instanciés avec les types primitifs ................. Les informations sur le type d’exécution ne fonctionnent qu’avec les types bruts ............ Vous ne pouvez pas lancer ou intercepter des instances d’une classe générique ............... Les tableaux de types avec paramètres ne sont pas autorisés ............................................. Vous ne pouvez pas instancier des variables de type .......................................................... Les variables de type ne sont pas valables dans des contextes statiques des classes génériques ........................................................................................................ Attention aux conflits après un effacement ........................................................................ Règles d’héritage pour les types génériques ............................................................................ Types joker ............................................................................................................................... Limites de supertypes pour les jokers ................................................................................. Jokers sans limites .............................................................................................................. Capture de caractères joker ................................................................................................. Réflexion et générique .............................................................................................................. Utilisation des paramètres Class pour la concordance de type ................................... Informations de type générique dans la machine virtuelle .................................................
615 616 617 619 620 620 620 621 622 622
Chapitre 13. Collections ............................................................................................................. Les interfaces de collection ...................................................................................................... Séparer les interfaces d’une collection et leur implémentation .......................................... Interfaces de collection et d’itération dans la bibliothèque Java ........................................ Suppression d’éléments ...................................................................................................... Méthodes utilitaires génériques .......................................................................................... Les collections concrètes .......................................................................................................... Listes chaînées .................................................................................................................... Listes de tableaux ............................................................................................................... Tables de hachage ............................................................................................................... Arbres ................................................................................................................................. Queues et deques ................................................................................................................ Queues de priorité ............................................................................................................... Cartes .................................................................................................................................. Classes de cartes et de set spécialisées ............................................................................... La structure des collections ...................................................................................................... Les vues et les emballages .................................................................................................. Les opérations de masse ..................................................................................................... Conversion entre collections et tableaux ............................................................................
643 643 644 646 649 649 651 652 660 661 664 670 671 673 677 681 685 691 692
624 624 625 627 628 630 631 634 635 636
Livre Java.book Page XII Mercredi, 13. février 2008 2:34 14
XII
Table des matières
Algorithmes .............................................................................................................................. Trier et mélanger ................................................................................................................. Recherche binaire ............................................................................................................... Algorithmes simples ........................................................................................................... Ecrire vos propres algorithmes ........................................................................................... Les anciennes collections ......................................................................................................... La classe Hashtable ............................................................................................................ Les énumérations ................................................................................................................ Cartes de propriétés ............................................................................................................ Piles .................................................................................................................................... Les ensembles de bits .........................................................................................................
693 694 696 698 699 700 700 701 702 702 703
Chapitre 14. Multithreads .......................................................................................................... Qu’est-ce qu’un thread ? .......................................................................................................... Utiliser des threads pour laisser une chance aux autres tâches ........................................... Interrompre des threads ............................................................................................................ Les états d’un thread ........................................................................................................... Threads terminés ................................................................................................................. Propriétés d’un thread .............................................................................................................. Priorités d’un thread ........................................................................................................... Threads démons .................................................................................................................. Gestionnaire d’exceptions non récupérées ......................................................................... Synchronisation ........................................................................................................................ Exemple de condition de course ......................................................................................... Explication des conditions de course ................................................................................. Verrous d’objet ................................................................................................................... Objets de condition ............................................................................................................. Le mot clé synchronized ..................................................................................................... Blocs synchronisés ............................................................................................................. Le concept des moniteurs ................................................................................................... Champs volatiles ................................................................................................................. Verrous morts ...................................................................................................................... Test de verrous et temporisations ....................................................................................... Lire et écrire des verrous .................................................................................................... Pourquoi les méthodes stop et suspend ne sont plus utilisées .................................................. Queues de blocage .................................................................................................................... Collections compatibles avec les threads ................................................................................. Cartes, jeux et queues efficaces .......................................................................................... CopyOnWriteArray ........................................................................................................... Anciennes collections compatibles avec les threads .......................................................... Callable et Future .....................................................................................................................
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
Livre Java.book Page XIII Mercredi, 13. février 2008 2:34 14
Table des matières
XIII
Executors .................................................................................................................................. Pools de threads .................................................................................................................. Exécution programmée ....................................................................................................... Contrôle de groupes de tâches ............................................................................................ Synchronizers ........................................................................................................................... Sémaphores ......................................................................................................................... Verrous Countdown ............................................................................................................ Barrières ............................................................................................................................. Exchanger ........................................................................................................................... Queues synchrones ............................................................................................................. Exemple : pause et reprise d’une animation ....................................................................... Threads et Swing ................................................................................................................ Exécution de tâches longues ............................................................................................... Utilisation du travailleur Swing .......................................................................................... La règle du thread unique ...................................................................................................
768 769 772 773 774 775 776 776 777 777 777 783 784 788 795
Annexe. Les mots clés de Java ...................................................................................................
797
Index ..............................................................................................................................................
801
Livre Java.book Page XIV Mercredi, 13. février 2008 2:34 14
Livre Java.book Page 1 Mercredi, 13. février 2008 2:34 14
Introduction Avertissement au lecteur Vers la fin de 1995, le langage de programmation Java surgit sur la grande scène d’Internet et obtint immédiatement un énorme succès. La prétention 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 données, de fournisseurs d’informations ou de toute autre source imaginable. Et Java se trouve en bonne position pour relever ce défi. Il s’agit d’un langage de conception très robuste qui a été adopté par la majorité des principaux fournisseurs, à l’exception de Microsoft. Ses caractéristiques intégrées de sécurité offrent un sentiment de confiance aux programmeurs comme aux utilisateurs des applications. De plus, Java intègre des fonctionnalités qui facilitent grandement certaines tâches de programmation avancées, comme la gestion réseaux, la connectivité bases de données ou le développement d’applications multitâches. Depuis le lancement de Java, Sun Microsystems a émis sept révisions majeures du kit de développement Java. Au cours des onze dernières années, l’API (interface de programmation d’application) est passée de 200 à plus de 3 000 classes. Elle traite maintenant des domaines aussi divers que la construction de l’interface utilisateur, la gestion des bases de données, l’internationalisation, la sécurité et le traitement du code XML. L’ouvrage que vous tenez entre les mains est le premier volume de la huitième édition de Au cœur de Java. Chaque édition a suivi la sortie du kit de développement d’aussi près que possible et, chaque fois, nous avons réécrit le livre pour y inclure les toutes dernières fonctionnalités. Cette édition a été mise à jour pour traiter des nouveautés de Java Standard Edition (SE) 6. Ce livre, comme les éditions précédentes, s’adresse essentiellement aux programmeurs professionnels désireux d’utiliser Java pour développer de véritables projets. Nous considérons que le lecteur possède déjà une solide habitude de la programmation autre que Java et qu’il fuit les ouvrages qui fourmillent d’exemples dépourvus d’utilité pratique (comme les grille-pain, les animaux du zoo ou le "texte nerveux"). Vous n’en trouverez pas ici. Notre objectif est de vous permettre d’appréhender le langage et la bibliothèque Java, et non de vous donner l’illusion que vous les comprenez. Vous trouverez de nombreux programmes de démonstration qui abordent la plupart des sujets traités dans cet ouvrage. Ces programmes sont volontairement simples afin que le lecteur puisse se concentrer sur les points importants. Néanmoins, dans la plupart des cas, il s’agit d’applications utiles qui pourront vous servir de base pour le développement de vos propres projets. Nous supposons également que vous souhaitez apprendre les caractéristiques avancées de Java ; c’est pourquoi nous étudierons en détail : m
la programmation orientée objet ;
m
le mécanisme de réflexion de Java et de proxy ;
m
les interfaces et classes internes ;
Livre Java.book Page 2 Mercredi, 13. février 2008 2:34 14
2
Au cœur de Java 2 - Notions fondamentales
m
le modèle d’écouteur d’événement ;
m
la conception d’interfaces graphiques avec la boîte à outils Swing ;
m
la gestion des exceptions ;
m
la programmation générique ;
m
le cadre des collections ;
m
la simultanéité.
Enfin, compte tenu de l’explosion de la bibliothèque de classes de Java, nous avons dû répartir l’étude de toutes les fonctionnalités 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 d’une interface graphique. Le second volume traite plus exhaustivement des fonctionnalités d’entreprise et de la programmation avancée des interfaces utilisateur. Il aborde les sujets suivants : m
les fichiers et les flux ;
m
les objets distribués ;
m
les bases de données ;
m
les composants GUI avancés ;
m
les méthodes natives ;
m
le traitement XML ;
m
la programmation réseau ;
m
le graphisme avancé ;
m
l’internationalisation ;
m
JavaBeans ;
m
les annotations.
Pour cette édition, nous avons réorganisé le contenu des deux volumes. Le multithread, notamment, est traité dans le Volume I, car il est devenu très important, la loi de Moore étant désormais obsolète. Lors de la rédaction d’un ouvrage comme celui-ci, il est inévitable de commettre des erreurs et des inexactitudes. Nous avons donc préparé sur le site Web http://horstmann.com/corejava une liste de questions courantes, de corrections et d’explications. Un formulaire permettant de signaler des bogues et de suggérer des améliorations est placé stratégiquement à la fin de la page des corrections (pour vous encourager à la lire). Ne soyez pas déçu si nous ne répondons pas à chaque requête ou si nous ne vous écrivons pas rapidement. Nous lisons tous les e-mails et apprécions vos commentaires, qui nous permettent d’améliorer les futures versions de cet ouvrage.
A propos de ce livre Le Chapitre 1 présentera les caractéristiques 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 fins. Nous terminerons par un historique de Java et nous préciserons la manière dont il a évolué.
Livre Java.book Page 3 Mercredi, 13. février 2008 2:34 14
Introduction
3
Le Chapitre 2 vous indiquera comment télécharger et installer le JDK et les exemples de programme du livre. Nous vous guiderons ensuite dans la compilation et l’exécution de trois programmes Java typiques : une application console, une application graphique et un applet, grâce à 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 commençant par les éléments 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 problème, car la syntaxe employée 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 orientée objet (POO) est maintenant au cœur des méthodes modernes de programmation et Java est un langage orienté objet. Le Chapitre 4 présentera l’encapsulation — la première des deux notions fondamentales de l’orientation objet — et le mécanisme du langage Java qui permet de l’implémenter, c’est-à-dire les classes et les méthodes. En plus des règles de Java, nous vous proposerons des conseils pour une bonne conception orientée 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 familiarisés avec la programmation orientée objet doivent se donner le temps d’étudier ces concepts avant de poursuivre leur exploration de Java. Les classes et l’encapsulation ne constituent qu’une partie du concept de POO et le Chapitre 5 introduira l’autre élément essentiel : l’héritage. Celui-ci permet de récupérer une classe existante et de la modifier selon vos besoins. Il s’agit là d’une technique fondamentale de la programmation Java. Le mécanisme d’héritage de Java est comparable à celui de C++. Ici encore, les programmeurs C++ pourront se concentrer uniquement sur les différences entre les deux langages. Le Chapitre 6 vous montrera comment utiliser la notion d’interface. Les interfaces permettent de dépasser le modèle d’héritage simple vu au Chapitre 5. En maîtrisant les interfaces, vous pourrez profiter pleinement de l’approche orientée objet de la programmation Java. Nous traiterons également dans ce chapitre une caractéristique technique très utile de Java, appelée classe interne. Les classes internes permettent d’obtenir des programmes plus propres et plus concis. Au Chapitre 7, nous commencerons véritablement la programmation d’applications. Tout programmeur en Java doit connaître un minimum de programmation de GUI, des bases que vous trouverez dans ce volume. Nous vous montrerons comment créer des fenêtres, y dessiner et y tracer des figures géométriques, formater du texte avec différentes polices et afficher des images. Le Chapitre 8 sera consacré à une étude détaillée du modèle d’événement AWT, la boîte à outils de fenêtre abstraite. Nous verrons comment écrire le code permettant de répondre à des événements tels que des clics de la souris ou des frappes de touches. Vous verrez par la même occasion comment gérer des éléments de l’interface utilisateur graphique comme les boutons ou les panneaux. Le Chapitre 9 examinera de manière approfondie l’outil Swing, qui permet de créer des interfaces graphiques multi-plates-formes. Vous apprendrez tout ce qu’il faut savoir sur les différents types de boutons, les composants de saisie, les bordures, les barres de défilement, les zones de listes, les menus et les boîtes de dialogue. Certains de ces composants, plus avancés, seront étudiés dans le Volume II. Le Chapitre 10 indique comment déployer des programmes, sous forme d’applications ou d’applets. Nous expliquons comment emballer des programmes dans les fichiers JAR et livrer des applications
Livre Java.book Page 4 Mercredi, 13. février 2008 2:34 14
4
Au cœur de Java 2 - Notions fondamentales
sur Internet avec Java Web Start et les mécanismes d’applet. Enfin, nous verrons la manière dont les programmes Java peuvent stocker et récupérer des informations de configuration lorsqu’ils ont été déployés. Le Chapitre 11 traitera de la gestion des exceptions, un mécanisme robuste qui s’appuie sur le fait que même les bons programmes peuvent subir des dommages. Les exceptions fournissent un moyen efficace de séparer le code normal de traitement et la gestion d’erreurs. Bien entendu, même si vous avez sécurisé votre programme en gérant toutes les conditions exceptionnelles, il n’est pas certain qu’il fonctionne parfaitement. La seconde partie de ce chapitre vous donnera quelques astuces de débogage. Pour terminer, nous vous accompagnerons dans une session de débogage. Le Chapitre 12 donne une vue d’ensemble de la programmation générique, une avancée majeure de Java SE 5.0. Elle facilite la lecture de vos programmes, tout en les sécurisant. Nous vous montrerons comment utiliser des types forts et comment traiter les complexités liées aux transtypages peu sûrs, mais aussi comment gérer les complexités 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 récupérer plus tard, vous utiliserez une collection adaptée à votre situation, au lieu de simplement faire entrer les éléments dans un tableau. Ce chapitre indique comment profiter des collections standard déjà créées pour vous. Le Chapitre 14 clôturera ce livre avec une discussion sur le multithreading, qui permet de programmer des tâches à réaliser en parallèle (un thread est un flux de contrôle dans un programme). Nous vous indiquerons comment configurer les threads et en gérer la synchronisation. Le multithreading a beaucoup évolué dans Java SE 5.0 et nous vous en montrerons les nouveaux mécanismes. L’Annexe est consacrée aux mots clés du langage Java.
Conventions Comme c’est l’usage dans la plupart des ouvrages d’informatique, nous employons la police Courier pour le code des programmes. INFO ASTUCE Les infos et les astuces sont repérées par une de ces deux icônes.
ATTENTION Une icône "Attention" vous prévient s’il y a du danger à l’horizon.
INFO C++ De nombreuses notes d’info C++ précisent les différences entre Java et C++. Vous pouvez ignorer ces notes si vous n’êtes pas familiarisé avec ce langage.
Livre Java.book Page 5 Mercredi, 13. février 2008 2:34 14
Introduction
5
API Java
Java est accompagné d’une importante bibliothèque de programmation (API, Application Programming Interface). Lorsque nous utilisons pour la première fois un appel à l’API, nous proposons également une brève description dans une note "API" située à la fin de la section. Ces descriptions sont un peu plus informelles que celles de la documentation officielle en ligne, mais nous espérons qu’elles 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 compressée. Ils peuvent être décompressés avec un des outils courants du marché ou avec l’utilitaire jar du kit JDK. Reportez-vous au Chapitre 2 pour en savoir plus sur l’installation du JDK et des listings d’exemple.
Livre Java.book Page 6 Mercredi, 13. février 2008 2:34 14
Livre Java.book Page 7 Mercredi, 13. février 2008 2:34 14
1 Une introduction à Java Au sommaire de ce chapitre
✔ Java, plate-forme de programmation ✔ Les termes clés du livre blanc de Java ✔ Les applets Java et Internet ✔ Bref historique de Java ✔ Les idées fausses les plus répandues concernant Java La première version de Java, sortie en 1996, a fait naître beaucoup de passions, pas seulement au niveau de la presse informatique, mais également dans la presse plus généraliste comme The New York Times, The Washington Post et Business Week. Java présente l’avantage d’être le premier et le seul langage de programmation dont l’histoire est contée à la radio. Il est plutôt amusant de revisiter cette époque pionnière, nous vous présenterons donc un bref historique de Java dans ce chapitre.
Java, plate-forme de programmation Dans la première édition de cet ouvrage, nous avons dû écrire ceci : "La réputation de Java en tant que langage informatique est exagérée : Java est assurément un bon langage de programmation. Il s’agit, sans aucun doute, de l’un des meilleurs disponibles pour un programmeur sérieux. Java aurait, potentiellement, pu être un grand langage de programmation, mais il est probablement trop tard pour cela. Lorsqu’un langage commence à être exploité, se pose le problème de la compatibilité avec le code existant." Un haut responsable de Sun Microsystems, que nous ne nommerons pas, a adressé à notre éditeur de nombreux commentaires sur ce paragraphe. Mais, avec le temps, notre pronostic semble s’avérer. Java présente de très bonnes fonctionnalités (nous les verrons en détail plus loin dans ce chapitre). Il a pourtant sa part d’inconvénients, et les derniers ajouts ne sont pas aussi agréables que les premiers, et ce pour des raisons de compatibilité. Toutefois, comme nous le disions dans la première édition, Java n’a jamais été qu’un langage. Même s’il en existe à foison, peu font beaucoup d’éclats. Java est une plate-forme complète, disposant
Livre Java.book Page 8 Mercredi, 13. février 2008 2:34 14
8
Au cœur de Java 2 - Notions fondamentales
d’une importante bibliothèque, d’une grande quantité de code réutilisable et d’un environnement d’exécution qui propose des services tels que la sécurité, la portabilité sur les systèmes d’exploitation et le ramasse-miettes automatique. En tant que programmeur, vous voulez un langage à la syntaxe agréable et à la sémantique compréhensible (donc, pas de C++). Java répond à ces critères, comme des dizaines d’autres langages. Certains vous proposent la portabilité, le ramasse-miettes et des outils du même genre, mais ils ne disposent pas de vraie bibliothèque, ce qui vous oblige à déployer la vôtre si vous souhaitez utiliser de beaux graphiques, le réseau ou l’accès aux bases de données. Java regroupe tout cela : un langage de qualité, un environnement d’exécution idoine et une grande bibliothèque. C’est cette combinaison qui fait de Java une proposition à laquelle de nombreux programmeurs ne résistent pas.
Les termes clés du livre blanc de Java Les auteurs de Java ont écrit un important livre blanc qui présente les objectifs et les réalisations de leur conception. Ce livre s’articule autour des onze termes clés suivants : Simplicité
Portabilité
Orienté objet
Interprété
Compatible avec les réseaux
Performances élevées
Fiabilité
Multithread
Sécurité
Dynamique
Architecture neutre
Dans la suite de ce chapitre, nous allons : m
résumer, par l’intermédiaire d’extraits du livre blanc, ce que les concepteurs de Java ont voulu traduire avec chacun de ces termes clés ;
m
exprimer ce que nous pensons de chaque terme, à partir de notre expérience de la version actuelle de Java. INFO
A l’heure où nous écrivons ces lignes, le livre blanc est disponible à l’adresse suivante : http://java.sun.com/docs/ white/langenv/. Le résumé des onze mots clés figure à l’adresse http://java.sun.com/docs/overviews/java/javaoverview-1.html.
Simplicité *
Nous avons voulu créer un système qui puisse être programmé simplement, sans nécessiter un apprentissage ésotérique, et qui tire parti de l’expérience standard actuelle. En conséquence, même si nous pensions que C++ ne convenait pas, Java a été conçu de façon relativement proche de ce langage dans le dessein de faciliter la compréhension du système. De nombreuses fonctions compliquées, mal comprises, rarement utilisées, de C++, qui nous semblaient par expérience apporter plus d’inconvénients que d’avantages, ont été supprimées de Java.
Livre Java.book Page 9 Mercredi, 13. février 2008 2:34 14
Chapitre 1
Une introduction à Java
9
La syntaxe de Java représente réellement une version améliorée de C++. Les fichiers d’en-tête, l’arithmétique des pointeurs (ou même une syntaxe de pointeur), les structures, les unions, la surcharge d’opérateur, les classes de base virtuelles, etc., ne sont plus nécessaires (tout au long de cet ouvrage, nous avons inclus des notes Info C++ décrivant plus précisément les différences entre Java et C++). Les concepteurs n’ont cependant pas tenté de modifier certaines fonctions pièges de C++ telles que l’instruction 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 paraîtra plus complexe. Une partie de la syntaxe vous semblera étrange (même si sa maîtrise est rapide). Plus important encore, la programmation en Java nécessitera davantage de travail. L’intérêt de Visual Basic réside dans le fait que son environnement visuel de conception fournit de façon presque automatique une grande partie de l’infrastructure d’une application. En Java, la fonctionnalité équivalente doit être programmée manuellement, généralement à l’aide d’une quantité respectable de code. Il existe cependant des environnements de développement tiers qui permettent l’écriture de programmes à l’aide d’opérations "glisser-déplacer". *
Un autre avantage de sa simplicité est sa petite taille. L’un des buts de Java est de permettre à des logiciels de s’exécuter intégralement sur de modestes machines. Ainsi, la taille cumulée de l’interpréteur de base et du support des classes est d’environ 40 Ko ; pour supporter les classes standard ainsi que la gestion multitraitement (threads), il faut ajouter 175 Ko.
C’est un exploit. Toutefois, sachez que la taille de la bibliothèque a atteint des proportions considérables. Il existe maintenant une Java Micro Edition séparée, avec une bibliothèque plus petite, convenant aux périphériques intégrés.
Orienté objet *
Pour rester simples, disons que la conception orientée objet est une technique de programmation qui se concentre sur les données (les objets) et sur les interfaces avec ces objets. Pour faire une analogie avec la menuiserie, on pourrait dire qu’un menuisier "orienté objet" s’intéresse essentiellement à la chaise (l’objet) qu’il fabrique et ensuite aux outils utilisés pour la fabriquer. Par opposition, le menuisier "non orienté objet" penserait d’abord à ses outils. Les facilités orientées objet de Java sont essentiellement celles de C++...
Au cours des trente dernières années, la programmation orientée objet a prouvé ses avantages, et il est inconcevable qu’un langage de programmation moderne n’en tire pas parti. En fait, les fonctionnalités orientées objet de Java sont comparables à celles de C++. Les différences majeures résident dans l’héritage multiple (que Java a remplacé par le concept plus simple des interfaces) et le modèle objet de Java (métaclasses), que nous verrons au Chapitre 5. INFO Si vous n’avez aucune expérience des langages de programmation orientée objet, prenez soin de lire attentivement les Chapitres 4 à 6. Ils vous expliquent ce qu’est la programmation orientée objet et pour quelles raisons elle est plus utile à la programmation de projets sophistiqués que ne le sont les langages procéduraux comme C ou Basic.
Livre Java.book Page 10 Mercredi, 13. février 2008 2:34 14
10
Au cœur de Java 2 - Notions fondamentales
Compatible avec les réseaux *
Java possède une importante bibliothèque de routines permettant de gérer les protocoles TCP/IP tels que HTTP et FTP. Les applications Java peuvent charger des objets sur Internet et y accéder via des URL avec la même facilité qu’elles accèdent à un fichier local sur le système.
Nous avons trouvé que les fonctionnalités réseau de Java étaient à la fois fiables et d’utilisation aisée. Toute personne ayant essayé de faire de la programmation pour Internet avec un autre langage se réjouira de la simplicité de Java lorsqu’il s’agit de mettre en œuvre des tâches lourdes, comme l’ouverture d’une connexion avec une socket (nous verrons les réseaux dans le Volume II de cet ouvrage). Le mécanisme d’invocation de méthode à distance (RMI) autorise la communication entre objets distribués (voir également le Volume II).
Fiabilité *
Java a été conçu pour que les programmes qui l’utilisent soient fiables sous différents aspects. Sa conception encourage le programmeur à traquer préventivement les éventuels problèmes, à lancer des vérifications dynamiques en cours d’exécution et à éliminer les situations génératrices d’erreurs... La seule et unique grosse différence entre C/C++ et Java réside dans le fait que ce dernier intègre un modèle de pointeur qui écarte les risques d’écrasement de la mémoire et d’endommagement des données.
Voilà encore une caractéristique fort utile. Le compilateur Java détecte de nombreux problèmes qui, dans d’autres langages, ne sont visibles qu’au moment de l’exécution. Tous les programmeurs qui ont passé des heures à récupérer une mémoire corrompue par un bogue de pointeur seront très heureux d’exploiter cette caractéristique de Java. Si vous connaissez déjà des langages comme Visual Basic, qui n’exploitent pas les pointeurs de façon explicite, vous vous demandez sûrement pourquoi ce problème est si important. Les programmeurs en C n’ont pas cette chance. Ils ont besoin des pointeurs pour accéder à des chaînes, à des tableaux, à des objets et même à des fichiers. Dans Visual Basic, vous n’employez de pointeur pour aucune de ces entités, pas plus que vous n’avez besoin de vous soucier de leurs allocations en mémoire. En revanche, certaines structures de données sont difficiles à implémenter sans l’aide de pointeurs. Java vous donne le meilleur des deux mondes. Vous n’avez pas besoin des pointeurs pour les constructions habituelles, comme les chaînes et les tableaux. Vous disposez de la puissance des pointeurs en cas de nécessité, par exemple, pour construire des listes chaînées. Et cela en toute sécurité dans la mesure où vous ne pouvez jamais accéder à un mauvais pointeur ni faire des erreurs d’allocation de mémoire, pas plus qu’il n’est nécessaire de vous protéger des "fuites" de mémoire.
Sécurité *
Java a été conçu pour être exploité dans des environnements serveur et distribués. Dans ce but, la sécurité n’a pas été négligée. Java permet la construction de systèmes inaltérables et sans virus.
Dans la première édition, nous déclarions : "Il ne faut jamais dire fontaine je ne boirai plus de ton eau." Et nous avions raison. Peu après la publication de la première version du JDK, un groupe d’experts de la sécurité de l’université de Princeton a localisé les premiers bogues des caractéristiques de sécurité de Java 1.0. Sun Microsystems a encouragé la recherche sur la sécurité de Java, en
Livre Java.book Page 11 Mercredi, 13. février 2008 2:34 14
Chapitre 1
Une introduction à Java
11
rendant publiques les caractéristiques et l’implémentation de la machine virtuelle et des bibliothèques de sécurité. Il a réparé rapidement tous les bogues connus. Quoi qu’il en soit, Java se charge de rendre particulièrement difficile le contournement des mécanismes de sécurité. Jusqu’à présent, les bogues qui ont été localisés étaient très subtils et relativement peu nombreux. Dès le départ, Java a été conçu pour rendre impossibles certains types d’attaques et, parmi eux : m
la surcharge de la pile d’exécution, une attaque commune des vers et des virus ;
m
l’endommagement de la mémoire située à l’extérieur de son propre espace de traitement ;
m
la lecture ou l’écriture des fichiers sans autorisation.
Avec le temps, un certain nombre de fonctionnalités relatives à la sécurité ont été ajoutées à Java. Depuis la version 1.1, Java intègre la notion de classe signée numériquement (voir Au cœur de Java 2 Volume II, éditions CampusPress), qui vous permet de savoir qui l’a écrite. Votre degré de confiance envers son auteur va déterminer l’ampleur des privilèges que vous allez accorder à la classe sur votre machine. INFO Le mécanisme concurrent de mise à disposition de code, élaboré par Microsoft et fondé sur sa technologie ActiveX, emploie exclusivement les signatures numériques pour assurer la sécurité. Bien évidemment, cela n’est pas suffisant. Tous les utilisateurs des produits Microsoft peuvent confirmer que les programmes proposés par des concepteurs bien connus rencontrent des défaillances qui provoquent des dégâts. Le modèle de sécurité de Java est nettement plus puissant que celui d’ActiveX dans la mesure où il contrôle l’application en cours d’exécution et l’empêche de faire des ravages.
Architecture neutre *
Le compilateur génère un format de fichier objet dont l’architecture est neutre — le code compilé est exécutable sur de nombreux processeurs, à partir du moment où le système d’exécution de Java est présent. Pour ce faire, le compilateur Java génère des instructions en bytecode (ou pseudo-code) qui n’ont de lien avec aucune architecture d’ordinateur particulière. Au contraire, ces instructions ont été conçues pour être à la fois faciles à interpréter, quelle que soit la machine, et faciles à traduire à la volée en code machine natif.
L’idée n’est pas nouvelle. Il y a plus de trente ans, la mise en œuvre originale de Pascal par Niklaus Wirth et le système Pascal UCSD utilisaient tous deux la même approche. Bien entendu, l’interprétation des bytecodes est forcément plus lente que l’exécution des instructions machine à pleine vitesse. On peut donc douter de la pertinence de cette idée. Toutefois, les machines virtuelles ont le choix de traduire les séquences de bytecode fréquemment utilisées en code machine, une procédure appelée compilation en "juste-à-temps" (just-in-time ou JIT). Cette stratégie s’est révélée tellement efficace que la plate-forme .NET de Microsoft va jusqu’à reposer sur une machine virtuelle. La machine virtuelle présente d’autres avantages. Elle accroît la sécurité car elle est en mesure de vérifier le comportement des suites d’instructions. Certains programmes vont jusqu’à produire des bytecodes à la volée, en améliorant dynamiquement les capacités d’un programme en cours d’exécution.
Livre Java.book Page 12 Mercredi, 13. février 2008 2:34 14
12
Au cœur de Java 2 - Notions fondamentales
Portabilité *
A la différence de C et de C++, on ne trouve pas les aspects de dépendance de la mise en œuvre dans la spécification. Les tailles des types de données primaires sont spécifiées, ainsi que le comportement arithmétique qui leur est applicable.
Par exemple, en Java, un int est toujours un entier en 32 bits. Dans C/C++, int peut représenter un entier 16 bits, un entier 32 bits ou toute autre taille décidée par le concepteur du compilateur. La seule restriction est que le type int doit contenir au moins autant d’octets qu’un short int et ne peut pas en contenir plus qu’un long int. Le principe d’une taille fixe pour les types numériques élimine les principaux soucis du portage d’applications. Les données binaires sont stockées et transmises dans un format fixe, ce qui met fin à la confusion sur l’ordre des octets. Les chaînes sont enregistrées au format Unicode standard. *
Les bibliothèques intégrées au système définissent des interfaces portables. Par exemple, il existe une classe Window abstraite, accompagnée de ses mises en œuvre pour UNIX, Windows et Macintosh.
Tous ceux qui ont essayé savent que l’élaboration d’un programme compatible avec Windows, Macintosh et dix versions d’UNIX, représente un effort héroïque. Java 1.0 a accompli cet effort en proposant un kit d’outils simples sachant "coller" aux principaux composants d’interface utilisateur d’un grand nombre de plates-formes. Malheureusement, le résultat était une bibliothèque qui donnait, avec beaucoup de travail, des programmes à peine acceptables sur différents systèmes. En outre, on rencontrait souvent des bogues différents sur les différentes implémentations graphiques. Mais ce n’était qu’un début. Il existe de nombreuses applications pour lesquelles la portabilité est plus importante que l’efficacité de l’interface utilisateur, et ce sont celles qui ont bénéficié des premières versions de Java. Aujourd’hui, le kit d’outils de l’interface utilisateur a été entièrement réécrit de manière à ne plus dépendre de l’interface utilisateur de l’hôte. Le résultat est nettement plus cohérent et nous pensons qu’il est beaucoup plus intéressant que dans les précédentes versions de Java.
Interprété *
L’interpréteur de Java peut exécuter les bytecodes directement sur n’importe quelle machine sur laquelle il a été porté. Dans la mesure où la liaison est un processus plus incrémentiel et léger, le processus de développement peut se révéler plus rapide et exploratoire.
La liaison incrémentielle présente des avantages, mais ils ont été largement exagérés. Quoi qu’il en soit, nous avons trouvé les outils de développement relativement lents. Aujourd’hui, les bytecodes sont traduits en code machine par le compilateur en juste-à-temps.
Performances élevées *
En règle générale, les performances des bytecodes interprétés sont tout à fait suffisantes ; il existe toutefois des situations dans lesquelles des performances plus élevées sont nécessaires. Les bytecodes peuvent être traduits à la volée (en cours d’exécution) en code machine pour l’unité centrale destinée à accueillir l’application.
Aux premiers temps de Java, de nombreux utilisateurs rejetaient la formule "performances élevées". Il existe toutefois aujourd’hui des compilateurs JIT (just-in-time, juste-à-temps) d’une qualité telle
Livre Java.book Page 13 Mercredi, 13. février 2008 2:34 14
Chapitre 1
Une introduction à Java
13
qu’ils concurrencent les compilateurs traditionnels et, dans certains cas, les dépassent du fait qu’ils disposent de davantage d’informations. Par exemple, un compilateur JIT est capable d’identifier du code souvent exécuté et d’optimiser exclusivement la vitesse de celui-ci. Une optimisation plus poussée consiste à éliminer les appels de fonctions. Le compilateur en juste-à-temps connaît les classes qui ont été chargées. Il peut utiliser la suppression lorsque, en fonction de la collection de classes actuellement chargée, une fonction particulière n’est jamais écrasée. Il peut ensuite annuler cette optimisation si nécessaire.
Multithread *
Les avantages du multithread sont une meilleure interréactivité et un meilleur comportement en temps réel.
Si vous avez déjà essayé de programmer le multithread dans un autre langage, vous allez être agréablement surpris de la simplicité de cette tâche dans Java. Avec lui, les threads sont également capables de tirer parti des systèmes multiprocesseurs. Le côté négatif réside dans le fait que les implémentations de threads sur les plates-formes principales sont très différentes, et Java ne fait aucun effort pour être indépendant de la plate-forme à cet égard. La seule partie de code identique entre les différentes machines est celle de l’appel du multithread. Java se décharge de l’implémentation du multithread sur le système d’exploitation sous-jacent ou sur une bibliothèque de threads. Malgré tout, la simplicité du multithread est l’une des raisons principales du succès de Java pour le développement côté serveur.
Java, langage dynamique *
Sur plusieurs points, Java est un langage plus dynamique que C ou C++. Il a été conçu pour s’adapter à un environnement en évolution constante. Les bibliothèques peuvent ajouter librement de nouvelles méthodes et variables sans pour autant affecter leurs clients. La recherche des informations de type exécution dans Java est simple.
Cette fonction est importante lorsque l’on doit ajouter du code à un programme en cours d’exécution. Le premier exemple est celui du code téléchargé depuis Internet pour s’exécuter dans un navigateur. Avec la version 1.0 de Java, la recherche d’informations de type exécution était relativement complexe. A l’heure actuelle, les versions courantes de Java procurent au programmeur un aperçu complet de la structure et du comportement de ses objets. Cela se révèle très utile pour les systèmes nécessitant une analyse des objets au cours de l’exécution, tels que les générateurs graphiques de Java, les débogueurs évolués, les composants enfichables et les bases de données objet. INFO Peu après les premiers succès de Java, Microsoft a sorti un produit intitulé J++ qui présente un lien de parenté avec Java. Pour l’heure, Microsoft ne prend plus en charge J++ mais a introduit un nouveau langage, appelé C#, qui présente aussi de nombreuses similarités avec Java mais utilise une autre machine virtuelle. Il existe même un J# pour faire migrer les applications J++ vers la machine virtuelle utilisée par C#. Sachez que nous ne reviendrons pas sur J++, C# ou J# dans cet ouvrage.
Livre Java.book Page 14 Mercredi, 13. février 2008 2:34 14
14
Au cœur de Java 2 - Notions fondamentales
Les applets Java et Internet L’idée de base est simple : les utilisateurs téléchargent les bytecodes Java depuis Internet et les exécutent sur leurs propres machines. Les programmes Java s’exécutant sur les pages Web sont nommés applets. Pour utiliser un applet, vous devez disposer d’un navigateur Web compatible Java, qui exécutera les bytecodes. Vous n’aurez besoin d’installer aucun logiciel. Sun fournit sous licence le code source de Java et affirme qu’aucun changement n’affectera le langage et la structure de la bibliothèque de base. Vous obtiendrez la dernière version du programme dès que vous visiterez la page Web contenant l’applet. Et surtout, grâce à la sécurité de la machine virtuelle, vous n’aurez plus à vous inquiéter des attaques provenant d’un code hostile. Lorsque l’utilisateur télécharge un applet, il s’agit presque de l’intégration d’une image dans une page Web. L’applet s’insère dans la page et le texte se répartit dans son espace. Le fait est que l’image est vivante. Elle réagit aux commandes utilisateur, change d’apparence et transfère les données entre l’ordinateur qui présente l’applet et l’ordinateur qui la sert. La Figure 1.1 présente un exemple intéressant de page Web dynamique, un applet qui permet d’afficher des molécules et réalise des calculs sophistiqués. A l’aide de la souris, vous pouvez faire pivoter chaque molécule et zoomer dessus, pour mieux en comprendre la structure. Ce type de manipulation directe est impossible avec des pages Web statiques, elle n’est possible qu’avec les applets (vous trouverez cet applet à l’adresse http://jmol.sourceforge.net). A leur apparition, les applets ont suscité une grande excitation. Beaucoup pensent que l’attirance pour les applets a été responsable de la formidable popularité de Java. Or, cette première excitation s’est rapidement transformée en frustration. Différentes versions de Netscape et d’Internet Explorer exécutaient des versions différentes de Java, certaines particulièrement obsolètes. Cette situation malheureuse a compliqué de plus en plus le développement d’applets capables de tirer parti de la version la plus actuelle de Java. Aujourd’hui, la plupart des pages Web utilisent simplement JavaScript ou Flash pour obtenir des effets dynamiques dans le navigateur. Java, quant à lui, est devenu le langage le plus populaire pour développer des applications côté serveur qui produisent des pages Web et réalisent la logique en arrière-plan. Figure 1.1 L’applet Jmol.
Livre Java.book Page 15 Mercredi, 13. février 2008 2:34 14
Chapitre 1
Une introduction à Java
15
Bref historique de Java Cette section présente un bref historique de l’évolution de Java. Elle se réfère à diverses sources publiées (et, plus important encore, à un entretien avec les créateurs de Java paru dans le magazine en ligne SunWorld de juillet 1995). La naissance de Java date de 1991, lorsqu’un groupe d’ingénieurs de Sun, dirigé par Patrick Naughton et James Gosling, voulut concevoir un petit langage informatique adapté à des appareils de consommation comme les décodeurs du câble TV. Ces appareils ne disposant que de très peu de puissance ou de mémoire, le langage devait être concis et générer un code très strict. Des constructeurs différents étant susceptibles de choisir des unités centrales différentes, il était également important que le langage ne soit pas lié à une seule architecture. Le nom de code du projet était "Green". Les exigences d’un code concis et indépendant de la plate-forme conduisirent l’équipe à reprendre le modèle que certaines implémentations de Pascal avaient adopté au début de l’avènement des PC. Niklaus Wirth, l’inventeur de Pascal, avait préconisé la conception d’un langage portable générant du code intermédiaire pour des machines hypothétiques (souvent nommées machines virtuelles — de là, la machine virtuelle Java ou JVM). Ce code pouvait alors être utilisé sur toute machine disposant de l’interpréteur approprié. Les ingénieurs du projet Green utilisaient également une machine virtuelle, ce qui résolvait leur principal problème. Les employés de Sun avaient toutefois une culture UNIX. Ils ont donc basé leur langage sur C++ plutôt que sur Pascal. Au lieu de créer un langage fonctionnel, ils ont mis au point un langage orienté objet. Cependant, comme Gosling l’annonce dans l’interview, "depuis toujours, le langage est un outil et non une fin". Gosling décida de nommer son langage "Oak" (probablement parce qu’il appréciait la vue de sa fenêtre de bureau, qui donnait sur un chêne). Les employés de Sun se sont aperçus plus tard que ce nom avait déjà été attribué à un langage informatique. Ils l’ont donc transformé en Java. Ce choix s’est révélé heureux. Malheureusement, personne ne fut intéressé pour le produire chez Sun. L’équipe de Green dut trouver d’autres ouvertures pour commercialiser sa technologie. Toutefois, aucune des grandes sociétés d’électronique de grande consommation n’a été intéressée. Le groupe soumit alors un nouveau projet. Il proposa la conception d’un décodeur de câble TV capable de gérer de nouveaux services câblés, tels que la vidéo à la demande. Malgré cela, le contrat n’a pu être décroché (pour la petite histoire, la compagnie qui l’a obtenu était dirigée par le même Jim Clark qui avait démarré Netscape — une entreprise qui a beaucoup participé au succès de Java). Le projet Green (sous le nouveau nom de "First Person Inc.") a passé l’année 1993 et la moitié de 1994 à rechercher des acheteurs pour sa technologie — peine perdue (Patrick Naughton, l’un des fondateurs du groupe et principal agent marketing, prétend avoir parcouru plus de 80 000 km en avion pour vendre sa technologie). First Person a été dissoute en 1994. Pendant ce temps, le World Wide Web d’Internet devenait de plus en plus important. L’élément clé du Web est le navigateur qui traduit la page hypertexte à l’écran. En 1994, la plupart des gens utilisaient Mosaic, un navigateur Web non commercialisé et issu du Supercomputing Center de l’université de l’Illinois en 1993 (alors qu’il était encore étudiant, Marc Andreessen avait participé à la création de Mosaic pour 6,85 $ l’heure. Par la suite, il a obtenu la notoriété et la fortune en tant que cofondateur et directeur de technologie de Netscape). Lors d’une interview pour le SunWorld, Gosling a déclaré qu’au milieu de l’année 1994, les développeurs de langage avaient réalisé qu’ils pouvaient créer un navigateur vraiment cool. Il s’agissait
Livre Java.book Page 16 Mercredi, 13. février 2008 2:34 14
16
Au cœur de Java 2 - Notions fondamentales
d’une des rares choses dans le courant client/serveur qui nécessitait certaines des actions bizarres qu’ils avaient réalisées : l’architecture neutre, le temps réel, la fiabilité, la sécurité — 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 à l’esprit la puissance de ce qui est actuellement nommé les applets. Ils ont donc donné la possibilité au navigateur d’exécuter le code au sein des pages Web. Cette "démonstration de technologie" fut présentée au SunWorld le 23 mai 1995 et elle fut à l’origine de l’engouement pour Java, qui ne s’est pas démenti. Sun a diffusé la première version de Java début 1996. On a rapidement réalisé que cette version ne pouvait être utilisée pour un développement d’applications sérieux. Elle permettait, bien sûr, de créer un applet de texte animé qui se déplaçait de façon aléatoire sur un fond. Mais il était impossible d’imprimer... Cette fonctionnalité n’était pas prévue par la version 1.0. Son successeur, la version 1.1, a comblé les fossés les plus évidents, a grandement amélioré la capacité de réflexion et ajouté un nouveau modèle pour la programmation GUI. Elle demeurait pourtant assez limitée. Les grands projets de la conférence JavaOne de 1998 étaient la future version de Java 1.2. Cette dernière était destinée à remplacer les premières boîtes à outils graphiques et GUI d’amateur par des versions sophistiquées et modulaires se rapprochant beaucoup plus que leurs prédécesseurs de la promesse du "Write Once, Run Anywhere"™ (un même programme s’exécute partout). Trois jours après (!) sa sortie en décembre 1998, le service marketing de Sun a transformé le nom, qui est devenu Java 2 Standard Edition Software Development Kit Version 1.2 ! Outre "l’Edition Standard", deux autres éditions ont été introduites : "Micro Edition" pour les services intégrés comme les téléphones portables, et "Entreprise Edition" pour le traitement côté serveur. Cet ouvrage se concentre sur l’Edition Standard. Les versions 1.3 et 1.4 constituent une amélioration incrémentielle par rapport à la version Java 2 initiale, avec une bibliothèque standard en pleine croissance, des performances accrues et, bien entendu, un certain nombre de bogues corrigés. Pendant ce temps, la majeure partie de la passion générée par les applets Java et les applications côté client a diminué, mais Java est devenu la plateforme de choix pour les applications côté serveur. La version 5.0 est la première depuis la version 1.1 qui actualise le langage Java de manière significative (cette version était numérotée, à l’origine, 1.5, mais le numéro est devenu 5.0 lors de la conférence JavaOne de 2004). Après de nombreuses années de recherche, des types génériques ont été ajoutés (à peu près comparables aux modèles C++), le défi étant d’intégrer cette fonctionnalité sans exiger de changements de la machine virtuelle. Plusieurs autres fonctionnalités utiles ont été inspirées par le C# : une boucle for each, l’autoboxing (passage automatique entre type de base et classes encapsulantes) et les métadonnées. Les changements de langage continuent à poser des problèmes de compatibilité, mais plusieurs de ces fonctionnalités sont si séduisantes que les programmeurs devraient les adopter rapidement. La version 6 (dépourvue du suffixe .0) est sortie fin 2006. Une fois de plus, le langage n’a pas connu de changement, mais les performances et la bibliothèque ont été améliorées. Le Tableau 1.1 montre l’évolution du langage Java et de la bibliothèque. Vous le voyez, la taille de l’interface de programmation d’application (API) a considérablement augmenté.
Livre Java.book Page 17 Mercredi, 13. février 2008 2:34 14
Chapitre 1
Une introduction à Java
17
Tableau 1.1 : Evolution du langage Java
Version
Année
Nouvelles fonctionnalités du langage
Nombre de classes et d’interfaces
1.0
1996
Le langage lui-même
211
1.1
1997
Classes internes
477
1.2
1998
Aucune
1 524
1.3
2000
Aucune
1 840
1.4
2004
Assertions
2 723
5.0
2004
Classes génériques, boucle for each, varargs, autoboxing, métadonnées, énumérations, importation static
3 279
6
2006
Aucune
3 777
Les idées fausses les plus répandues concernant Java Nous clôturons ce chapitre par une liste de quelques idées fausses concernant Java. Elles seront accompagnées de leur commentaire. Java est une extension de HTML. Java est un langage de programmation. HTML représente une façon de décrire la structure d’une page Web. Ils n’ont rien en commun, à l’exception du fait qu’il existe des extensions HTML permettant d’insérer des applets Java sur une page Web. J’utilise XML, je n’ai donc pas besoin de Java. Java est un langage de programmation ; XML est une manière de décrire les données. Vous pouvez traiter des données XML avec tout langage de programmation, mais l’API Java en contient une excellente prise en charge. En outre, de nombreux outils XML tiers très importants sont mis en place en Java. Voir le Volume II pour en savoir plus. Java est un langage de programmation facile à apprendre. Aucun langage de programmation aussi puissant que Java n’est facile. Vous devez toujours distinguer la facilité de l’écriture de programmes triviaux et la difficulté que représente un travail sérieux. Considérez également que quatre chapitres seulement de ce livre traitent du langage Java. Les autres chapitres dans les deux volumes traitent de la façon de mettre le langage en application, à l’aide des bibliothèques Java. Celles-ci contiennent des milliers de classes et d’interfaces, et des dizaines de milliers de fonctions. Vous n’avez heureusement pas besoin de connaître chacune d’entre elles, mais vous devez cependant être capable d’en reconnaître un grand nombre pour pouvoir obtenir quelque chose de réaliste.
Livre Java.book Page 18 Mercredi, 13. février 2008 2:34 14
18
Au cœur de Java 2 - Notions fondamentales
Java va devenir un langage de programmation universel pour toutes les plates-formes. En théorie, c’est possible, et il s’agit certainement du souhait de tous les vendeurs, à l’exception de Microsoft. Il existe cependant de nombreuses applications, déjà parfaitement efficaces sur les ordinateurs de bureau, qui ne fonctionneraient pas sur d’autres unités ou à l’intérieur d’un navigateur. Ces applications ont été écrites de façon à tirer parti de la vitesse du processeur et de la bibliothèque de l’interface utilisateur native. Elles ont été portées tant bien que mal sur toutes les plates-formes importantes. Parmi ces types d’applications figurent les traitements de texte, les éditeurs d’images et les navigateurs Web. Ils sont écrits en C ou C++, et nous ne voyons aucun intérêt pour l’utilisateur final à les réécrire en Java. Java est simplement un autre langage de programmation. Java est un bon langage de programmation. De nombreux programmeurs le préfèrent à C, C++ ou C#. Mais des centaines de bons langages de programmation n’ont jamais réussi à percer, alors que des langages avec des défauts évidents, tels que C++ et Visual Basic, ont remporté un large succès. Pourquoi ? Le succès d’un langage de programmation est bien plus déterminé par la qualité du système de support qui l’entoure que par l’élégance de sa syntaxe. Existe-t-il des bibliothèques utiles, pratiques et standard pour les fonctions que vous envisagez de mettre en œuvre ? Des vendeurs d’outils ont-ils créé de bons environnements de programmation et de débogage ? Le langage et l’ensemble des outils s’intègrent-ils avec le reste de l’infrastructure informatique ? Java a du succès parce que ses bibliothèques de classes vous permettent de réaliser facilement ce qui représentait jusqu’alors une tâche complexe. La gestion de réseau et les multithreads en sont des exemples. Le fait que Java réduise les erreurs de pointeur est un bon point. Il semble que les programmeurs soient plus productifs ainsi. Mais il ne s’agit pas de la source de son succès. L’arrivée de C# rend Java obsolète. C# a repris plusieurs bonnes idées de Java, par exemple un langage de programmation propre, une machine virtuelle et un ramasse-miettes. Mais, quelles qu’en soient les raisons, le C# a également manqué certaines bonnes choses, comme la sécurité et l’indépendance de la plate-forme. Si vous appréciez Windows, optez pour le C#. Mais, si l’on en juge par les offres d’emploi, Java reste le langage préféré de la majorité des développeurs. Java est un outil propriétaire, il faut donc l’éviter. Sun Microsystems distribue Java sous licence aux distributeurs et aux utilisateurs finaux. Même si Sun possède un contrôle total sur Java, il a, par le biais de la "Communauté Java", impliqué de nombreuses autres sociétés dans le développement de versions et la conception de nouvelles bibliothèques. Le code source de la machine virtuelle et des bibliothèques est disponible gratuitement, mais pour étude uniquement et non pour modification et redistribution. Jusqu’à présent, Java est en "source fermée, mais fonctionne bien". Cette situation a considérablement 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 générale), la licence open source de Linux. Reste à voir comment Sun gérera Java à l’avenir, mais il ne fait aucun
Livre Java.book Page 19 Mercredi, 13. février 2008 2:34 14
Chapitre 1
Une introduction à Java
19
doute que le côté open source de Java a constitué une évolution très courageuse, qui prolongera la vie de Java de nombreuses années. Java est interprété, il est donc trop lent pour les applications sérieuses. Aux premiers jours de Java, le langage était interprété. Aujourd’hui, sauf sur les plates-formes "Micro" comme les téléphones portables, la machine virtuelle Java utilise un compilateur JIT. Les hot spots de votre code s’exécuteront aussi rapidement en Java qu’en C++ et même plus rapidement dans certains cas. Java est moins rapide que le C++. Le démarrage de la machine virtuelle peut être lent et les GUI Java sont plus lents que leurs homologues natifs car ils sont conçus indépendamment de la plate-forme. Le public se plaint depuis des années de la lenteur des applications Java. Toutefois, les ordinateurs actuels sont plus rapides. Un programme Java lent s’exécutera un peu mieux que ces programmes C++ incroyablement rapides d’il y a quelques années. Pour l’heure, ces plaintes semblent obsolètes et certains détracteurs ont tendance à viser plutôt la laideur des interfaces utilisateur de Java que leur lenteur. Tous les programmes Java s’exécutent dans une page Web. Tous les applets Java s’exécutent dans un navigateur Web. C’est la définition même d’un applet — un programme Java s’exécutant dans un navigateur. Mais la plupart des programmes Java sont des applications autonomes qui s’exécutent indépendamment d’un navigateur Web. De nombreux programmes Java s’exécutent 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 sûr un sujet passionnant. Mais les programmes Java autonomes sont plus importants et plus utiles dans la pratique. Les programmes Java représentent un risque majeur pour la sécurité. Aux premiers temps de Java, son système de sécurité a quelquefois été pris en défaut, et ces incidents ont été largement commentés. La plupart sont dus à l’implémentation de Java dans un navigateur spécifique. Les chercheurs ont considéré ce système de sécurité comme un défi à relever et ont tenté de détecter les failles de l’armure de Java. Les pannes techniques découvertes ont toutes été rapidement corrigées et, à notre connaissance, aucun des systèmes actuels n’a jamais été pris en défaut. Pour replacer ces incidents dans une juste perspective, prenez en compte les millions de virus qui attaquent les fichiers exécutables de Windows ainsi que les macros de Word. De réels dégâts sont alors causés, mais curieusement, peu de critiques sont émises concernant la faiblesse de la plateforme concernée. Le mécanisme ActiveX d’Internet Explorer pourrait également représenter une cible facile à prendre en défaut, mais ce système de sécurité est tellement simple à contourner que très peu de chercheurs ont pensé à publier leur découverte. Certains administrateurs système ont même désactivé Java dans les navigateurs de leur entreprise, alors qu’ils continuent à autoriser les utilisateurs à télécharger des fichiers exécutables, des contrôles ActiveX et des documents Word. Il s’agit d’un comportement tout à fait ridicule — actuellement, le risque de se voir attaqué par un applet Java hostile est à peu près comparable au risque de mourir dans un accident d’avion. Le risque d’infection inhérent à l’ouverture d’un document Word est, au contraire, comparable au risque de mourir en traversant à pied une autoroute surchargée.
Livre Java.book Page 20 Mercredi, 13. février 2008 2:34 14
20
Au cœur de Java 2 - Notions fondamentales
JavaScript est une version simplifiée de Java. JavaScript, un langage de script que l’on peut utiliser dans les pages Web, a été inventé par Netscape et s’appelait à l’origine LiveScript. On trouve dans la syntaxe de JavaScript des réminiscences de Java, mais il n’existe aucune relation (à l’exception du nom, bien sûr) entre ces deux langages. Un sous-ensemble de JavaScript est standardisé sous le nom de ECMA-262. JavaScript est plus étroitement intégré dans les navigateurs que ne le sont les applets Java. En particulier, un programme JavaScript peut modifier le document affiché, tandis qu’un applet ne peut que contrôler l’apparence d’une zone limitée. Avec Java, je peux remplacer mon ordinateur par une "boîte noire Internet" bon marché. Lors de la première sortie de Java, certains pariaient gros là-dessus. Depuis la première édition de cet ouvrage, nous pensons qu’il est absurde d’imaginer que l’on puisse abandonner une machine de bureau puissante et pratique pour une machine limitée sans mémoire locale. Cependant, un ordinateur réseau pourvu de Java est une option plausible pour une "initiative zéro administration". En effet, vous éliminez ainsi le coût des ordinateurs de l’entreprise, mais même cela n’a pas tenu ses promesses. Par ailleurs, Java est devenu largement distribué sur les téléphones portables. Nous devons avouer que nous n’avons pas encore vu d’application Java indispensable fonctionnant sur les téléphones portables, mais les jeux et les économiseurs d’écran usuels semblent bien se vendre sur de nombreux marchés. ASTUCE Pour obtenir des réponses aux questions communes sur Java, consultez les FAQ Java sur le Web : http:// www.apl.jhu.edu/~hall/java/FAQs-and-Tutorials.html.
Livre Java.book Page 21 Mercredi, 13. février 2008 2:34 14
2 L’environnement de programmation de Java Au sommaire de ce chapitre
✔ Installation du kit de développement Java ✔ Choix d’un environnement de développement ✔ Utilisation des outils de ligne de commande ✔ Utilisation d’un environnement de développement intégré ✔ Exécution d’une application graphique ✔ Elaboration et exécution d’applets Ce chapitre traite de l’installation du kit de développement Java (JDK) et de la façon de compiler et d’exécuter différents types de programmes : les programmes consoles, les applications graphiques et les applets. Vous lancez les outils JDK en tapant les commandes dans une fenêtre shell. De nombreux programmeurs préfèrent toutefois le confort d’un environnement de développement intégré. Vous apprendrez à utiliser un environnement disponible gratuitement, pour compiler et exécuter les programmes Java. Faciles à comprendre et à utiliser, les environnements de développement intégrés peuvent nécessiter des ressources importantes et être lourds à utiliser pour de petits programmes. Comme solution intermédiaire, vous disposez des éditeurs de texte appelant le compilateur Java et exécutant les programmes Java. Lorsque vous aurez maîtrisé les techniques présentées dans ce chapitre et choisi vos outils de développement, vous serez prêt à aborder le Chapitre 3, où vous commencerez à explorer le langage de programmation Java.
Installation du kit de développement Java Les versions les plus complètes et les plus récentes du kit de développement Java (JDK) sont disponibles auprès de Sun Microsystems pour Solaris, Linux et Windows. Certaines versions de Java sont disponibles en divers degrés de développement pour Macintosh et de nombreuses autres plates-formes, mais ces versions sont fournies sous licence et distribuées par les fournisseurs de ces plates-formes.
Livre Java.book Page 22 Mercredi, 13. février 2008 2:34 14
22
Au cœur de Java 2 - Notions fondamentales
INFO Certaines distributions de Linux possèdent des versions préemballées 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.
Télécharger le JDK Pour télécharger le JDK, accédez 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 résumé. Vous avez déjà rencontré l’abréviation 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 n’est pas adapté pour les développeurs, il est plutôt destiné aux utilisateurs n’ayant pas besoin du compilateur. Vous rencontrerez aussi très souvent le terme "Java SE". Il signifie "Java Standard Edition", par opposition à Java EE (Entreprise Edition) et à Java ME (Micro Edition). Le terme "Java 2" est apparu en 1998, l’année où les commerciaux de Sun ont considéré qu’augmenter le numéro de version par une décimale ne traduisait pas correctement les nouveautés du JDK 1.2. Or, ils ne s’en sont aperçus qu’après la sortie et ont donc décidé de conserver le numéro 1.2 pour le kit de développement. Les versions consécutives ont été numérotées 1.3, 1.4 et 5.0. La plate-forme a toutefois été renommée 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 désarmant pour les ingénieurs, mais c’est là le côté 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 s’est appelée Java SE 6. Vous trouverez toujours, par moments, une référence aux versions 1.5 et 1.6, ce sont simplement des synonymes des versions 5.0 et 6. Enfin, lorsque Sun apporte un changement mineur pour corriger des problèmes urgents, il nomme cette opération une mise à jour. Ainsi, la première mise à jour du kit de développement pour Java SE 6 s’intitule officiellement JDK 6u1 et porte le numéro de version interne 1.6.0_01. Pour les utilisateurs de Solaris, de Linux ou de Windows, accédez à l’adresse http://java.sun.com/ javase pour télécharger le JDK. Demandez la version 6 ou supérieure, puis choisissez votre plateforme. Ne vous inquiétez pas si le logiciel est qualifié de "mise à jour". Le module contient la version actuelle de l’ensemble du JDK. Sun sort parfois des modules contenant à la fois le Java Development Kit et un environnement de développement intégré. 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 suggérons de n’installer pour l’heure que le Java Development Kit. Si vous décidez par la suite d’utiliser l’environnement de développement intégré de Sun, téléchargez-le simplement à l’adresse http://netbeans.org.
Livre Java.book Page 23 Mercredi, 13. février 2008 2:34 14
Chapitre 2
L’environnement de programmation de Java
23
Tableau 2.1 : Le jargon de Java
Nom
Acronyme
Explication
Java Development Kit
JDK
Le logiciel destiné aux programmeurs qui souhaitent écrire des programmes Java
Java Runtime Environment
JRE
Le logiciel destiné aux clients qui veulent exécuter des programmes Java
Standard Edition
SE
La plate-forme Java à utiliser sur les ordinateurs de bureau et les applications de serveur simples
Enterprise Edition
EE
La plate-forme Java pour les applications de serveur complexes
Micro Edition
ME
La plate-forme Java à utiliser sur les téléphones portables et autres petits appareils
Java 2
J2
Un terme obsolète désignant les versions de Java entre 1998 et 2006
Software Development Kit
SDK
Terme obsolète désignant le JDK entre 1998 et 2006
Mise à jour (Update)
u
Terme employé par Sun pour désigner un correctif de bogue
NetBeans
–
L’environnement de développement intégré de Sun
Une fois le JDK téléchargé, suivez les instructions d’installation, qui sont fonction de la plate-forme. A l’heure où nous écrivons, elles étaient disponibles à l’adresse http://java.sun.com/javase/6/ webnotes/install/index.html. Seules les instructions d’installation et de compilation pour Java dépendent du système. Une fois Java installé et opérationnel, les autres informations fournies dans ce livre s’appliqueront à votre situation. L’indépendance vis-à-vis du système est un avantage important de Java. INFO La procédure d’installation propose un répertoire d’installation par défaut incluant le numéro de version de Java JDK, comme jdk1.6.0. Ceci peut paraître pénible, mais le numéro de version est finalement assez pratique, puisque vous pouvez tester facilement une nouvelle version du JDK. Sous Windows, nous vous recommandons fortement de ne pas accepter l’emplacement par défaut 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 désignons le répertoire d’installation par jdk. Par exemple, lorsque nous faisons référence au répertoire jdk/bin, nous désignons le répertoire ayant le nom /usr/local/jdk1.6.0/bin ou C:\jdk1.6.0\bin.
Livre Java.book Page 24 Mercredi, 13. février 2008 2:34 14
24
Au cœur de Java 2 - Notions fondamentales
Configurer le chemin d’exécution Après avoir installé le JDK, vous devez effectuer une étape supplémentaire : ajouter le répertoire jdk/bin au chemin d’exécution, la liste des répertoires que traverse le système d’exploitation pour localiser les fichiers exécutables. Les directives concernant cette étape varient également en fonction du système d’exploitation. m
Sous UNIX (y compris Solaris ou Linux), la procédure pour modifier le chemin d’exécution dépend du shell que vous utilisez. Si vous utilisez le C shell (qui est le défaut pour Solaris), vous devez ajouter une ligne analogue à celle qui suit à la fin de votre fichier ~/.cshrc : set path=(/usr/local/jdk/bin $path)
Si vous utilisez le Bourne Again shell (défaut pour Linux), ajoutez une ligne analogue à celle qui suit, à la fin de votre fichier ~/.bashrc ou ~/.bash_profile : export PATH=/usr/local/jdk/bin:$PATH m
Sous Windows, connectez-vous en tant qu’administrateur. Ouvrez le Panneau de configuration, passez en affichage classique et sélectionnez Système. Sous Windows NT/2000/XP, la boîte de dialogue des propriétés système s’ouvre immédiatement. Sous Vista, sélectionnez les paramètres système avancés (voir Figure 2.1). Dans la boîte de dialogue des propriétés du système, cliquez sur l’onglet Paramètres système avancés, puis sur le bouton Variables d’environnement. Parcourez la fenêtre Variables système pour rechercher la variable nommée Path (voir Figure 2.2). Ajoutez le répertoire jdk\bin au début du chemin, en ajoutant un point-virgule pour séparer la nouvelle entrée, de la façon suivante : c:\jdk\bin;le reste
Sauvegardez votre configuration. Toute nouvelle fenêtre console que vous lancerez comprendra le chemin correct. Procédez de la façon suivante pour vérifier que vous avez effectué les manipulations appropriées. Démarrez une fenêtre shell. Tapez la ligne : java -version
Appuyez sur la touche Entrée. Vous devez obtenir un affichage 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 specified is not recognized as an internal or external command, operable program or batch file", messages qui signalent une commande ou un fichier erroné, vous devez vérifier votre installation. INFO Selon la version de Windows, une fenêtre shell s’ouvre d’une des manières suivantes. Sous Windows NT/2000/XP, choisissez l’option Exécuter du menu Démarrer et tapez cmd. Sous Vista, cliquez sur Démarrer et tapez cmd. Appuyez sur Entrée pour ouvrir la fenêtre shell. Si vous n’avez jamais utilisé de fenêtre 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 à l’adresse http://www.cs.sjsu.edu/faculty/horstman/CS46A/windows/tutorial.html.
Livre Java.book Page 25 Mercredi, 13. février 2008 2:34 14
Chapitre 2
L’environnement de programmation de Java
Figure 2.1 Lancement de la boîte de dialogue des propriétés du système sous Windows Vista.
Figure 2.2 Paramétrage de la variable d’environnement Path sous Windows Vista.
25
Livre Java.book Page 26 Mercredi, 13. février 2008 2:34 14
26
Au cœur de Java 2 - Notions fondamentales
Installer la bibliothèque et la documentation Les fichiers source de bibliothèque sont fournis dans le JDK sous la forme d’un fichier compressé src.zip et vous devez le décompresser pour avoir accès au code source. La procédure suivante est hautement recommandée. 1. Assurez-vous que le JDK est installé et que le répertoire jdk/bin figure dans le chemin d’exécution. 2. Ouvrez une fenêtre shell. 3. Positionnez-vous dans le répertoire jdk (par ex. /usr/local/jdk1.6.0 ou C:\jdk1.6.0). 4. Créez un sous-répertoire src. mkdir src cd src
5. Exécutez la commande : jar xvf ../src.zip
(ou jar xvf ..\src.zip sous Windows). ASTUCE Le fichier src.zip contient le code source pour toutes les bibliothèques publiques. Vous pouvez obtenir d’autres codes source (pour le compilateur, la machine virtuelle, les méthodes natives et les classes privées helper) à l’adresse http://download.java.net/jdk6.
La documentation est contenue dans un fichier compressé séparé du JDK. Vous pouvez télécharger la documentation à l’adresse http://java.sun.com/javase/downloads. Procédez de la façon suivante : 1. Assurez-vous que le JDK est installé et que le répertoire jdk/bin figure dans le chemin d’exécution. 2. Copiez le fichier zip de documentation dans le répertoire qui contient le répertoire jdk. Le fichier est nommé jdk-version-doc.zip, où version ressemble à 6. 3. Lancez une fenêtre shell. 4. Positionnez-vous dans le répertoire jdk. 5. Exécutez la commande : jar xvf jdk-version-doc.zip
où version est le numéro de version approprié.
Installer les exemples de programmes Il est conseillé d’installer les exemples de programmes que vous pouvez télécharger à l’adresse www.pearsoneducation.fr. Les programmes sont compressés dans un fichier zip corejavavol1.zip. Décompressez-les dans un répertoire séparé que nous vous recommandons d’appeler CoreJavaBook. Procédez de la façon suivante :
Livre Java.book Page 27 Mercredi, 13. février 2008 2:34 14
Chapitre 2
L’environnement de programmation de Java
27
1. Assurez-vous que le JDK est installé et que le répertoire jdk/bin figure dans le chemin d’exécution. 2. Créez un répertoire nommé CoreJavaBook. 3. Copiez le fichier corejavavol1.zip dans ce répertoire. 4. Lancez une fenêtre shell. 5. Positionnez-vous dans le répertoire CoreJavaBook. 6. Exécutez la commande : jar xvf corejavavol1.zip
Explorer les répertoires de Java Au cours de votre étude, vous aurez à examiner des fichiers source Java. Vous devrez également, bien sûr, exploiter au maximum la documentation bibliothèque. Le Tableau 2.2 présente l’arborescence des répertoires du JDK. Tableau 2.2 : Arborescence des répertoires de Java
Structure du répertoire
Description Le nom peut être différent, par exemple jdk5.0
jdk bin
Compilateur et outils
demo
Démos
docs
Documentation de la bibliothèque au format HTML (après décompression de j2sdkversion-doc.zip)
include
Fichiers pour les méthodes natives (voir Volume II)
jre
Fichiers d’environnement d’exécution de Java
lib
Fichiers de bibliothèque
src
Source de bibliothèque (après décompression de src.zip)
Les deux sous-répertoires les plus importants sont docs et src. Le répertoire docs contient la documentation de la bibliothèque Java au format HTML. Vous pouvez la consulter à l’aide de tout navigateur Web tel que Netscape. ASTUCE Définissez un signet dans votre navigateur pour le fichier docs/api/index.html. Vous consulterez fréquemment cette page au cours de votre étude de la plate-forme Java !
Livre Java.book Page 28 Mercredi, 13. février 2008 2:34 14
28
Au cœur de Java 2 - Notions fondamentales
Le répertoire src contient le code source de la partie publique des bibliothèques 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 l’on a toujours la possibilité de se plonger dans le code source pour découvrir ce qui est réellement réalisé par une fonction de bibliothèque. Si vous vous intéressez, par exemple, à la classe System, vous pouvez consulter src/java/lang/System.java.
Choix de l’environnement de développement Si vous avez l’habitude de programmer avec Microsoft Visual Studio, vous êtes accoutumé à un environnement de développement qui dispose d’un éditeur de texte intégré et de menus vous permettant de compiler et d’exécuter un programme avec un débogueur intégré. La version de base du JDK ne contient rien de tel, même approximativement. Tout se fait par l’entrée de commandes dans une fenêtre shell. Même si cela peut paraître lourd, il s’agit d’une compétence essentielle. A la première installation de Java, vous devrez mettre au propre votre installation avant d’installer un environnement de développement. De plus, en réalisant vous-même les étapes de base, vous appréhenderez mieux ce que fait l’environnement de développement dans votre dos. Toutefois, lorsque vous aurez maîtrisé les bases de la compilation et de l’exécution des programmes Java, vous aurez besoin d’un environnement de développement professionnel. Au cours de la dernière décennie, ces environnements sont devenus si puissants et si commodes qu’il n’est tout simplement pas logique de s’en passer. Deux excellents choix sont les programmes gratuits Eclipse et NetBeans. Nous vous présentons dans ce chapitre une mise en route d’Eclipse, qui est un peu plus performant que NetBeans, même si ce dernier rattrape rapidement son retard. Bien entendu, si vous disposez déjà d’un environnement de développement, n’hésitez pas à l’utiliser. Auparavant, nous recommandions d’utiliser un éditeur de texte comme Emacs, JEdit ou TextPad pour des programmes simples. Nous ne le faisons plus car les environnements de développement intégrés sont aujourd’hui rapides et pratiques. En résumé, nous supposons que vous savez utiliser les outils de base du JDK et que vous devriez vous familiariser avec un environnement de développement intégré.
Utilisation des outils de ligne de commande Commençons par le plus difficile : compiler et lancer un programme Java à partir de la ligne de commande. 1. Ouvrez un shell. 2. Positionnez-vous dans le répertoire CoreJavaBook/v1ch02/Welcome (le répertoire CoreJavaBook est celui dans lequel vous avez installé le code source pour les exemples du livre, tel qu’expliqué précédemment). 3. Entrez les commandes suivantes : javac Welcome.java java Welcome
Vous devez voir apparaître le message de la Figure 2.3 à l’écran.
Livre Java.book Page 29 Mercredi, 13. février 2008 2:34 14
Chapitre 2
L’environnement de programmation de Java
29
Figure 2.3 Compilation et exécution de Welcome.java.
Félicitations ! Vous venez de compiler et d’exécuter votre premier programme Java. Que s’est-il passé ? Le programme javac est le compilateur Java. Il compile le fichier Welcome.java en un fichier Welcome.class. Le programme java lance la machine virtuelle Java. Il interprète les bytecodes que le compilateur a placés dans le fichier class. INFO Si vous obtenez un message d’erreur relatif à la ligne for (String g : greeting)
cela signifie 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 profiterons dans cet ouvrage. Si vous utilisez une ancienne version, vous devez réécrire la boucle comme suit : for (int i = 0; i < greeting.length; i++) System.out.println(greeting[i]);
Le programme Welcome est extrêmement simple. Il se contente d’afficher un message sur l’écran. 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 */
Livre Java.book Page 30 Mercredi, 13. février 2008 2:34 14
30
Au cœur de Java 2 - Notions fondamentales
public class Welcome { public static void main(String[] args) { String[] greeting = new String[3]; greeting[0] = "Welcome to Core Java"; greeting[1] = "by Cay Horstmann"; greeting[2] = "and Gary Cornell"; for (String g : greeting) System.out.println(g); } }
Conseils pour la recherche d’erreurs A l’heure des environnements de développement visuels, les programmeurs n’ont pas l’habitude de lancer des programmes dans une fenêtre shell. Tant de choses peuvent mal tourner et mener à des résultats décevants. Surveillez particulièrement les points suivants : m
Si vous tapez le programme manuellement, faites attention aux lettres majuscules et minuscules. En particulier, le nom de classe est Welcome et non welcome ou WELCOME.
m
Le compilateur requiert un nom de fichier Welcome.java. Lorsque vous exécutez le programme, vous spécifiez un nom de classe (Welcome) sans extension .java ni .class.
m
Si vous obtenez un message tel que "Bad command or file name" ou "javac: command not found", signalant une commande erronée, vous devez vérifier votre installation, en particulier la configuration du chemin d’exécution.
m
Si javac signale une erreur "cannot read: Welcome.java", signalant une erreur de lecture du fichier, vérifiez si ce fichier est présent dans le répertoire. Sous UNIX, vérifiez que vous avez respecté la casse des caractères pour Welcome.java. Sous Windows, utilisez la commande shell dir, et non l’outil Explorateur graphique. Certains éditeurs de texte (en particulier le Bloc-notes) ajoutent systématiquement une extension .txt après chaque fichier. Si vous utilisez le Bloc-notes pour modifier Welcome.java, le fichier sera enregistré sous le nom Welcome.java.txt. Dans la configuration par défaut de Windows, l’Explorateur conspire avec le Bloc-notes et masque l’extension .txt, car elle est considérée comme un "type de fichier connu". Dans ce cas, vous devez renommer le fichier à l’aide de la commande shell ren ou le réenregistrer, en plaçant des guillemets autour du nom de fichier : "Welcome.java".
m
Si java affiche un message signalant une erreur "java.lang.NoClassDefFoundError", vérifiez soigneusement le nom de la classe concernée. Si l’interpréteur se plaint que welcome contient un w minuscule, vous devez réémettre la commande java Welcome avec un W majuscule. Comme toujours, la casse doit être respectée dans Java.
Livre Java.book Page 31 Mercredi, 13. février 2008 2:34 14
Chapitre 2
L’environnement de programmation de Java
31
Si l’interpréteur signale un problème concernant Welcome/java, vous avez accidentellement tapé java Welcome.java. Réémettez la commande java Welcome. m
Si vous avez tapé java Welcome et que la machine virtuelle ne trouve pas la classe Welcome, vérifiez si quelqu’un a configuré la variable d’environnement CLASSPATH sur votre système. Il n’est généralement pas conseillé de définir cette variable globalement, même si certains installateurs de logiciels mal écrits sous Windows le font. Pour l’annuler temporairement dans la fenêtre 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 d’erreur sur une nouvelle construction de langage, vérifiez que votre compilateur supporte Java SE 5.0.
m
Si vous avez trop d’erreurs dans votre programme, tous les messages vont défiler très vite. Le compilateur envoie les messages vers la sortie d’erreur standard, ce qui rend leur capture difficile s’ils occupent plus d’un écran. Utilisez l’opérateur shell 2> pour rediriger les erreurs vers un fichier : javac MyProg.java 2> errors.txt
ASTUCE Il existe à l’adresse http://java.sun.com/docs/books/tutorial/getStarted/cupojava/ un excellent didacticiel qui explore en détail les pièges qui peuvent dérouter les débutants.
Utilisation d’un environnement de développement intégré Dans cette section, vous apprendrez à compiler un programme à l’aide d’Eclipse, un environnement de développement intégré gratuit disponible à l’adresse http://eclipse.org. Eclipse est écrit en Java mais, comme il utilise une bibliothèque de fenêtre non standard, il n’est pas aussi portable que Java. Il en existe néanmoins des versions pour Linux, Mac OS X, Solaris et Windows. Il existe d’autres IDE populaires mais Eclipse est actuellement le plus usité. Voici les étapes de démarrage : 1. Après le démarrage d’Eclipse, choisissez File -> New Project. 2. Sélectionnez "Java Project" dans la boîte de dialogue de l’assistant (voir Figure 2.4). Ces captures d’écran proviennent d’Eclipse 3.2. Votre version sera peut-être légèrement différente. 3. Cliquez sur Next. Indiquez le nom du projet, à savoir "Welcome", et tapez le nom de chemin complet jusqu’au répertoire qui contient Welcome.java ; consultez la Figure 2.5. 4. Vérifiez que l’option intitulée "Create project in workspace" est décochée. 5. Cliquez sur Finish. Le projet est maintenant créé.
Livre Java.book Page 32 Mercredi, 13. février 2008 2:34 14
32
Au cœur de Java 2 - Notions fondamentales
Figure 2.4 Boîte de dialogue New Project dans Eclipse.
Figure 2.5 Configuration d’un projet Eclipse.
Livre Java.book Page 33 Mercredi, 13. février 2008 2:34 14
Chapitre 2
L’environnement de programmation de Java
33
6. Cliquez sur le triangle situé dans le volet de gauche près de la fenêtre de projet pour l’ouvrir, puis cliquez sur le triangle près de "Default package". Double-cliquez sur Welcome.java. Une fenêtre s’ouvre avec le code du programme (voir Figure 2.6). Figure 2.6 Modification d’un fichier source avec Eclipse.
7. Cliquez du bouton droit sur le nom du projet (Welcome), dans le volet le plus à gauche. Sélectionnez Run -> Run As -> Java Application. Une fenêtre apparaît au bas de la fenêtre. Le résultat du programme s’y affiche (voir Figure 2.7). Figure 2.7 Exécution d’un programme dans Eclipse.
Livre Java.book Page 34 Mercredi, 13. février 2008 2:34 14
34
Au cœur de Java 2 - Notions fondamentales
Localiser les erreurs de compilation Notre programme ne devrait pas contenir d’erreur de frappe ou de bogue (après tout, il ne comprend que quelques lignes de code). Supposons, pour la démonstration, qu’il contienne une coquille (peutêtre même une erreur de syntaxe). Essayez d’exécuter le fichier en modifiant la casse de String de la façon suivante : public static void main(string[] args)
Compilez à nouveau le programme. Vous obtiendrez des messages d’erreur (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 fenêtre d’édition. Ce comportement vous permet de corriger rapidement vos erreurs. ASTUCE Bien souvent, un rapport d’erreur d’Eclipse est accompagné de l’icône d’une ampoule. Cliquez dessus pour obtenir une liste des correctifs proposés.
Figure 2.8 Des messages d’erreur dans Eclipse.
Ces instructions doivent vous amener à vouloir travailler dans un environnement intégré. Nous étudierons le débogueur Eclipse au Chapitre 11.
Livre Java.book Page 35 Mercredi, 13. février 2008 2:34 14
Chapitre 2
L’environnement de programmation de Java
35
Exécution d’une application graphique Le programme Welcome ne présentait pas beaucoup d’intérêt. Exécutons maintenant une application graphique. Il s’agit d’un afficheur simple de fichiers image. Compilons ce programme et exécutonsle depuis la ligne de commande. 1. Ouvrez une fenêtre shell. 2. Placez-vous sur le répertoire CoreJavaBook/v1ch02/ImageViewer. 3. Tapez : javac ImageViewer.java java ImageViewer
Une nouvelle fenêtre de programme apparaît avec notre visionneuse, ImageViewer (voir Figure 2.9). Sélectionnez maintenant File/Open et recherchez une image à ouvrir (nous avons inclus quelques exemples de fichiers dans le même répertoire). Pour fermer le programme, cliquez sur le bouton de fermeture dans la barre de titre ou déroulez le menu système et choisissez Quitter (pour compiler et exécuter ce programme dans un éditeur de texte ou un environnement de développement intégré, procédez comme précédemment. Par exemple, dans le cas d’Emacs, choisissez JDE -> Compile, puis JDE -> Run App. Figure 2.9 Exécution de l’application ImageViewer.
Nous espérons que vous trouverez ce programme pratique et intéressant. Examinez rapidement le code source. Ce programme est plus long que le précédent, mais il n’est pas très compliqué en comparaison avec la quantité de code qui aurait été nécessaire pour écrire une application analogue en C ou C++. Ce type de programme est bien sûr très facile à écrire avec Visual Basic ou plutôt à copier et coller. Le JDK ne propose pas de générateur d’interface visuel, vous devez donc tout programmer à la main (voir Listing 2.2). Vous apprendrez à créer des programmes graphiques tels que celui-ci aux Chapitres 7 à 9.
Livre Java.book Page 36 Mercredi, 13. février 2008 2:34 14
36
Au cœur de Java 2 - Notions fondamentales
Listing 2.2 : ImageViewer.java import import import import
java.awt.EventQueue; java.awt.event.*; java.io.*; javax.swing.*;
/** * Un programme permettant d’afficher 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 d’afficher 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 sélecteur 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) {
Livre Java.book Page 37 Mercredi, 13. février 2008 2:34 14
Chapitre 2
L’environnement de programmation de Java
37
// montrer la boîte de dialogue du sélecteur int result = chooser.showOpenDialog(null); // en cas de sélection d’un fichier, définir comme icône // de l’étiquette if (result == JFileChooser.APPROVE_OPTION) { String name = chooser.getSelectedFile().getPath(); label.setIcon(new ImageIcon(name)); } } }); JMenuItem exitItem = new JMenuItem("Exit"); menu.add(exitItem); exitItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { System.exit(0); } }); } private private private private
JLabel label; JFileChooser chooser; static final int DEFAULT_WIDTH = 300; static final int DEFAULT_HEIGHT = 400;
}
Elaboration et exécution d’applets Les deux premiers programmes présentés dans cet ouvrage sont des applications Java, des programmes autonomes comme tout programme natif. Comme nous l’avons mentionné dans le dernier chapitre, la réputation de Java est due en grande partie à sa capacité à exécuter des applets dans un navigateur Web. Nous allons vous montrer comment créer et exécuter un applet à partir de la ligne de commande. Puis nous allons charger l’applet dans l’éditeur d’applets fourni avec le JDK et enfin, nous l’afficherons dans un navigateur Web. Ouvrez un shell et placez-vous dans le répertoire CoreJavaBook/v1ch02/WelcomeApplet, puis saisissez les commandes suivantes : javac WelcomeApplet.java appletviewer WelcomeApplet.html
La Figure 2.10 montre ce que vous voyez dans la fenêtre de l’afficheur d’applets. La première commande, qui nous est maintenant familière, est la commande d’appel du compilateur Java. Celui-ci compile le fichier source WelcomeApplet.java et produit le fichier de bytecodes WelcomeApplet.class. Cependant, nous n’exécutons pas cette fois l’interpréteur 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 fichier HTML plutôt que le nom d’un fichier de classe java. Le contenu du fichier WelcomeApplet.html est présenté dans le Listing 2.3 ci-après.
Livre Java.book Page 38 Mercredi, 13. février 2008 2:34 14
38
Au cœur de Java 2 - Notions fondamentales
Figure 2.10 L’applet WelcomeApplet dans l’afficheur d’applets.
Listing 2.3 : WelcomeApplet.html WelcomeApplet
This applet is from the book Core Java by Cay Horstmann and Gary Cornell, published by Sun Microsystems Press.
The source.
Si vous connaissez le code HTML, vous remarquerez quelques instructions standard et la balise applet qui demande à l’afficheur de charger l’applet dont le code est stocké dans WelcomeApplet.class. Cet afficheur d’applets 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, téléchargez simplement la dernière version, rendez-vous sur le site http://java.com et utilisez le système de vérification des versions pour chercher si vous devez installer le plug-in Java.
m
Certaines versions d’Internet Explorer ne prennent pas du tout en charge Java. D’autres ne prennent en charge que la très obsolète machine virtuelle Microsoft Java. Si vous exécutez Internet Explorer, accédez à l’adresse http://java.com et installez le plug-in Java.
m
Safari est intégré dans l’implémentation Macintosh Java de Macintosh sous OS X, qui prend en charge Java SE 5.0 au moment où nous rédigeons.
A condition d’avoir un navigateur compatible avec une version moderne de Java, vous pouvez tenter de charger l’applet dans le navigateur.
Livre Java.book Page 39 Mercredi, 13. février 2008 2:34 14
Chapitre 2
L’environnement de programmation de Java
39
1. Démarrez votre navigateur. 2. Sélectionnez Fichier -> Ouvrir (ou l’équivalent). 3. Placez-vous sur le répertoire CoreJavaBook/v1ch02/WelcomeApplet. Vous devez voir apparaître le fichier WelcomeApplet.html dans la boîte de dialogue. Chargez le fichier. 4. Votre navigateur va maintenant charger l’applet avec le texte qui l’entoure. Votre écran doit ressembler à celui de la Figure 2.11. Vous pouvez constater que cette application est réellement dynamique et adaptée à l’environnement Internet. Cliquez sur le bouton Cay Horstmann, l’applet commande au navigateur d’afficher la page Web de Cay. Cliquez sur le bouton Gary Cornell, l’applet lance l’affichage d’une fenêtre de courrier électronique avec l’adresse e-mail de Gary préenregistrée. Figure 2.11 Exécution de l’applet WelcomeApplet dans un navigateur.
Vous remarquerez qu’aucun de ces deux boutons ne fonctionne dans l’afficheur d’applets. Cet afficheur n’a pas la possibilité d’envoyer du courrier ou d’afficher une page Web, il ignore donc vos demandes. L’afficheur d’applets est uniquement destiné à tester les applets de façon isolée, mais vous devrez placer ces dernières dans un navigateur pour contrôler leur interaction avec le navigateur et Internet. Pour terminer, le code de l’applet est présenté dans le Listing 2.4. A ce niveau de votre étude, contentez-vous de l’examiner rapidement. Nous reviendrons sur l’écriture des applets au Chapitre 10. Listing 2.4 : WelcomeApplet.java import import import import
java.awt.*; java.awt.event.*; java.net.*; javax.swing.*;
Livre Java.book Page 40 Mercredi, 13. février 2008 2:34 14
40
Au cœur de Java 2 - Notions fondamentales
/** * Cet applet affiche un message des auteurs. * @version 1.22 2007-04-08 * @author Cay Horstmann */ public class WelcomeApplet extends JApplet { public void init() { EventQueue.invokeLater(new Runnable() { public void run() { setLayout(new BorderLayout()); JLabel label = new JLabel(getParameter("greeting"), SwingConstants.CENTER); label.setFont(new Font("Serif", Font.BOLD, 18)); add(label, BorderLayout.CENTER); JPanel panel = new JPanel(); JButton cayButton = new JButton("Cay Horstmann"); cayButton.addActionListener(makeAction ("http://www.horstmann.com")); panel.add(cayButton); JButton garyButton = new JButton("Gary Cornell"); garyButton.addActionListener(makeAction ("mailto:
[email protected]")); 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 mécanismes de la compilation et de l’exécution des programmes Java. Vous êtes maintenant prêt à aborder le Chapitre 3, où vous attaquerez l’apprentissage du langage Java.
Livre Java.book Page 41 Mercredi, 13. février 2008 2:34 14
3 Structures fondamentales de la programmation Java Au sommaire de ce chapitre
✔ Un exemple simple de programme Java ✔ Commentaires ✔ Types de données ✔ Variables ✔ Opérateurs ✔ Chaînes ✔ Entrées et sorties ✔ Flux de contrôle ✔ Grands nombres ✔ Tableaux Nous supposons maintenant que vous avez correctement installé le JDK et que vous avez pu exécuter les exemples de programmes proposés au Chapitre 2. Il est temps d’aborder la programmation. Ce chapitre vous montrera comment sont implémentés en Java certains concepts fondamentaux de la programmation, tels que les types de données, les instructions de branchement et les boucles. Malheureusement, Java ne permet pas d’écrire facilement un programme utilisant une interface graphique — il faut connaître de nombreuses fonctionnalités pour construire des fenêtres, ajouter des zones de texte, des boutons et les autres composants d’une interface. La présentation des techniques exigées par une interface graphique nous entraînerait trop loin de notre sujet — les concepts fondamentaux de la programmation — et les exemples de programmes proposés dans ce chapitre ne seront que des programmes conçus pour illustrer un concept. Tous ces exemples utilisent simplement une fenêtre shell pour l’entrée et la sortie d’informations.
Livre Java.book Page 42 Mercredi, 13. février 2008 2:34 14
42
Au cœur de Java 2 - Notions fondamentales
Si vous êtes un programmeur C++ expérimenté, vous pouvez vous contenter de parcourir ce chapitre et de vous concentrer sur les rubriques Info C++. Les programmeurs qui viennent d’un autre environnement, comme Visual Basic, rencontreront à la fois des concepts familiers et une syntaxe très différente : nous leur recommandons de lire ce chapitre attentivement.
Un exemple simple de programme Java Examinons le plus simple des programmes Java ; il se contente d’afficher un message à la console : public class FirstSample { public static void main(String[] args) { System.out.println("We will not use ’Hello, World!’"); } }
Même si cela doit prendre un peu de temps, il est nécessaire de vous familiariser avec la présentation de cet exemple ; les éléments qui le composent se retrouveront dans toutes les applications. Avant tout, précisons que Java est sensible à la casse des caractères (majuscules et minuscules). Si vous commettez la moindre erreur en tapant, par exemple, Main au lieu de main, le programme ne pourra pas s’exécuter ! Etudions maintenant le code source, ligne par ligne. Le mot clé public est appelé un modificateur d’accès (ou encore un spécificateur d’accès ou spécificateur de visibilité) ; les modificateurs déterminent les autres parties du programme qui peuvent être utilisées par notre exemple. Nous reparlerons des modificateurs au Chapitre 5. Le mot clé class est là pour vous rappeler que tout ce que l’on programme en Java se trouve à l’intérieur d’une classe. Nous étudierons en détail les classes dans le prochain chapitre, mais considérez dès à présent qu’une classe est une sorte de conteneur renfermant la logique du programme qui définit le comportement d’une application. Comme nous l’avons 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. Derrière le mot clé class se trouve le nom de la classe. Les règles de Java sont assez souples en ce qui concerne l’attribution des noms de classes. Ceux-ci doivent commencer par une lettre et peuvent ensuite contenir n’importe quelle combinaison de lettres et de chiffres. Leur taille n’est pas limitée. Il ne faut cependant pas attribuer un mot réservé de Java (comme public ou class) à un nom de classe (vous trouverez une liste des mots réservés dans l’Annexe). Comme vous pouvez le constater avec notre classe FirstSample, la convention généralement admise est de former les noms de classes avec des substantifs commençant par une majuscule. Lorsqu’un nom est constitué de plusieurs mots, placez l’initiale de chaque mot en majuscule. Il faut donner au fichier du code source le même nom que celui de la classe publique, avec l’extension .java. Le code sera donc sauvegardé dans un fichier baptisé FirstSample.java (répétons que la casse des caractères est importante, il ne faut pas nommer le fichier firstsample.java). Si vous n’avez pas commis d’erreur de frappe en nommant le fichier et en tapant le code source, la compilation de ce code produira un fichier contenant le pseudo-code de la classe. Le compilateur Java nomme automatiquement le fichier de pseudo-code FirstSample.class et le sauvegarde dans le même répertoire que le fichier source. Lorsque tout cela est terminé, lancez le programme à l’aide de la commande suivante : java FirstSample.
Livre Java.book Page 43 Mercredi, 13. février 2008 2:34 14
Chapitre 3
Structures fondamentales de la programmation Java
43
Ne spécifiez pas l’extension .class. L’exécution du programme affiche simplement la chaîne We will not use ’Hello, World’! à la console. Lorsque vous tapez la commande java NomDeClasse
pour lancer un programme compilé, la machine virtuelle démarre toujours l’exécution par les instructions de la méthode main de la classe spécifiée. Par conséquent, vous devez écrire une méthode main dans le fichier source de la classe pour que le code puisse être exécuté. Bien entendu, il est possible d’ajouter vos propres méthodes à une classe et de les appeler à partir de la méthode main (vous verrez au prochain chapitre comment écrire vos propres méthodes). INFO Selon la spécification du langage Java, la méthode main doit être déclarée public. La spécification est le document officiel qui décrit le langage Java. Vous pouvez le consulter ou le télécharger à l’adresse http://java.sun.com/docs/ books/jls. Toutefois, plusieurs versions du lanceur Java avaient pour intention d’exécuter les programmes Java même lorsque la méthode main n’était pas public. Un programmeur a alors rédigé un rapport de bogue. Pour le voir, consultez le site http://bugs.sun.com/bugdatabase/index.jsp et entrez le numéro d’identification 4252539. Le bogue a toutefois fait l’objet de la mention "clos, ne sera pas résolu". Un ingénieur Sun a ajouté une explication indiquant que la spécification de la machine virtuelle (à l’adresse http://java.sun.com/docs/books/vmspec) n’obligeait pas à ce que main soit public et a précisé que "le résoudre risquait d’entraîner des problèmes". Heureusement, le bon sens a fini par parler. Le lanceur Java de Java SE 1.4 et au-delà oblige à ce que la méthode main soit public. Cette histoire est assez intéressante. D’un côté, il est désagréable de voir que des ingénieurs d’assurance qualité, souvent débordés et pas toujours au fait des aspects pointus de Java, prennent des décisions contestables sur les rapports de bogues. De l’autre, il est remarquable que Sun place les rapports de bogues et leurs résolutions sur le Web, afin que tout le monde puisse les étudier. La "parade des bogues" est une ressource très utile pour les programmeurs. Vous pouvez même "voter" pour votre bogue favori. Ceux qui réuniront le plus de suffrages pourraient bien être résolus dans la prochaine version du JDK.
Remarquez les accolades dans le code source. En Java, comme en C/C++, les accolades sont employées pour délimiter les parties (généralement appelées blocs) de votre programme. En Java, le code de chaque méthode doit débuter par une accolade ouvrante { et se terminer par une accolade fermante }. La manière d’employer des accolades a provoqué une inépuisable controverse. Nous employons dans cet ouvrage un style d’indentation 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 présentation que vous préférez. Nous reparlerons de l’emploi des accolades lorsque nous étudierons les boucles. Pour l’instant, ne vous préoccupez pas des mots clés static void, songez simplement qu’ils sont nécessaires à la compilation du programme. Cette curieuse incantation vous sera familière à la fin du Chapitre 4. Rappelez-vous surtout que chaque application Java doit disposer d’une méthode main déclarée de la manière suivante : public class NomDeClasse { public static void main(String[] args) { Instructions du programme } }
Livre Java.book Page 44 Mercredi, 13. février 2008 2:34 14
44
Au cœur de Java 2 - Notions fondamentales
INFO C++ Les programmeurs C++ savent ce qu’est une classe. Les classes Java sont comparables aux classes C++, mais certaines différences risquent de vous induire en erreur. En Java, par exemple, toutes les fonctions sont des méthodes d’une classe quelconque (la terminologie standard les appelle des méthodes et non des fonctions membres). Ainsi, Java requiert que la méthode main se trouve dans une classe. Sans doute êtes-vous également familiarisé avec la notion de fonction membre statique en C++. Il s’agit de fonctions membres définies à l’intérieur d’une classe et qui n’opèrent pas sur des objets. En Java, la méthode main est toujours statique. Précisons enfin que, comme en C/C++, le mot clé void indique que la méthode ne renvoie aucune valeur. Contrairement à C/C++, la méthode main ne renvoie pas un "code de sortie" au système d’exploitation. Si la méthode main se termine normalement, le programme Java a le code de sortie 0 qui l’indique. Pour terminer le programme avec un code de sortie différent, utilisez la méthode System.exit.
Portez maintenant votre attention sur ce fragment de code : { System.out.println("We will not use ’Hello, World!’"); }
Les accolades délimitent le début et la fin du corps de la méthode. Celle-ci ne contient qu’une seule instruction. Comme dans la plupart des langages de programmation, vous pouvez considérer 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 délimitent pas la fin d’une instruction, et une même instruction peut donc occuper plusieurs lignes en cas de besoin. Le corps de la méthode main contient une instruction qui envoie une ligne de texte vers la console. Nous employons ici l’objet System.out et appelons sa méthode println. Remarquez que le point sert à invoquer la méthode. Java utilise toujours la syntaxe objet.méthode(paramètres)
pour ce qui équivaut à un appel de fonction. Dans ce cas précis, nous appelons la méthode println et lui passons une chaîne en paramètre. La méthode affiche la chaîne sur la console. Elle passe ensuite à la ligne afin que chaque appel à println affiche la chaîne spécifiée sur une nouvelle ligne. Notez que Java, comme C/C++, utilise les guillemets pour délimiter les chaînes (pour plus d’informations, voir la section de ce chapitre consacrée aux chaînes). Comme les fonctions de n’importe quel langage de programmation, les méthodes de Java peuvent utiliser zéro, un ou plusieurs paramètres (certains langages les appellent arguments). Même si une méthode ne prend aucun paramètre, il faut néanmoins employer des parenthèses vides). Il existe par exemple une variante sans paramètres de la méthode println qui imprime une ligne vide. Elle est invoquée de la façon suivante : System.out.println();
INFO
System.out dispose également d’une méthode print qui n’ajoute pas de retour à la ligne en sortie. Par exemple, System.out.print("Bonjour") imprime "Bonjour" sans retourner à la ligne. La sortie suivante apparaîtra immédiatement derrière le "r" de "Bonjour".
Livre Java.book Page 45 Mercredi, 13. février 2008 2:34 14
Chapitre 3
Structures fondamentales de la programmation Java
45
Commentaires Les commentaires de Java, comme ceux de la plupart des langages de programmation, n’apparaissent pas dans le programme exécutable. Vous pouvez donc ajouter autant de commentaires que vous le souhaitez sans craindre de gonfler la taille du code compilé. Java propose trois types de commentaires. Le plus courant est //, qui débute un commentaire allant jusqu’à la fin de ligne : System.out.println("We will not use ’Hello, World!’"); // Ce gag est-il trop connu ?
Lorsque des commentaires plus longs sont nécessaires, il est possible de placer // au début de chaque ligne, ou vous pouvez utiliser /* et */ pour délimiter le début et la fin d’un long commentaire. Nous voyons ce type de délimiteur au Listing 3.1. Listing 3.1 : FirstSample.java /** * Voici le premier exemple de programme de Au coeur de Java, Chapitre 3 * @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 troisième type de commentaire utilisable pour la génération automatique de documentation. Ce type de commentaire commence par /** et se termine par */. Pour en savoir plus sur la génération automatique de documentation, consultez le Chapitre 4. ATTENTION Les commentaires /* */ ne peuvent pas être imbriqués en Java. Autrement dit, pour désactiver un bloc de code, il ne suffit pas de l’enfermer entre un /* et un */, car ce bloc peut lui-même contenir un délimiteur */.
Types de données Java est un langage fortement typé. Cela signifie que le type de chaque variable doit être déclaré. Il existe huit types primitifs en Java. Quatre d’entre eux sont des types entiers (integer), deux sont des types réels à virgule flottante, un est le type caractère char utilisé pour le codage Unicode (voir la section consacrée au type char) et le type boolean, pour les valeurs booléennes (vrai/faux). INFO Java dispose d’un package arithmétique de précision 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.
Livre Java.book Page 46 Mercredi, 13. février 2008 2:34 14
46
Au cœur de Java 2 - Notions fondamentales
Entiers Les types entiers représentent les nombres sans partie décimale. Les valeurs négatives sont autorisées. Java dispose des quatre types présentés au Tableau 3.1. Tableau 3.1 : Les types entiers de Java
Type
Occupation en mémoire
Intervalle (limites incluses)
int
4 octets
– 2 147 483 648 à 2 147 483 647 (un peu plus de 2 milliards)
short
2 octets
– 32768 à 32767
long
8 octets
– 9 223 372 036 854 775 808 à 9 223 372 036 854 775 807
byte
1 octet
– 128 à 127
Le type int se révèle le plus pratique dans la majorité des cas. Bien entendu, si vous désirez exprimer le nombre des habitants de la planète, vous devrez employer le type long. Les types byte et short sont essentiellement destinés à des applications spécialisées, telles que la gestion bas niveau des fichiers ou la manipulation de tableaux volumineux, lorsque l’occupation mémoire doit être réduite au minimum. En Java, la plage valide des types de nombres entiers ne dépend pas de la machine sur laquelle s’exécute le code. Cela épargne bien des efforts au programmeur souhaitant porter un logiciel d’une plate-forme vers une autre, ou même entre différents systèmes d’exploitation sur une même plate-forme. En revanche, les programmes C et C++ utilisent le type d’entier le plus efficace pour chaque processeur. Par conséquent, un programme C qui fonctionne bien sur un processeur 32 bits peut provoquer un dépassement de capacité sur un système 16 bits. Comme les programmes Java doivent s’exécuter de la même manière sur toutes les machines, les plages de valeur des différents types sont fixes. Le suffixe des entiers longs est L (par exemple, 4000000000L). Le préfixe des entiers hexadécimaux est 0x (par exemple, 0xCAFE). Les valeurs octales ont le préfixe 0. Par exemple, 010 vaut 8. Cela peut prêter à confusion, il est donc déconseillé d’avoir recours aux constantes octales.
INFO C++ En C et C++, int représente le type entier qui dépend de l’ordinateur cible. Sur un processeur 16 bits, comme le 8086, les entiers sont codés sur 2 octets. Sur un processeur 32 bits, comme le SPARC de Sun, ils sont codés sur 4 octets. Sur un Pentium Intel, le codage du type entier C et C++ dépend du système d’exploitation : 2 octets sous DOS et Windows 3.1, 4 octets sous Windows en mode 32 bits. En Java, la taille de tous les types numériques est indépendante de la plate-forme utilisée. Remarquez que Java ne possède pas de type non signé.
Livre Java.book Page 47 Mercredi, 13. février 2008 2:34 14
Chapitre 3
Structures fondamentales de la programmation Java
47
Types à virgule flottante Les types à virgule flottante expriment les nombres réels disposant d’une partie décimale. Il existe deux types à virgule flottante, présentés au Tableau 3.2. Tableau 3.2 : Les types à virgule flottante
Type
Occupation en mémoire
Intervalle
float
4 octets
Environ ± 3.40282347E + 38F (6 ou 7 décimales significatives)
double
8 octets
Environ ± 1.79769313486231570E + 308 (15 chiffres significatifs)
Le terme double indique que les nombres de ce type ont une précision deux fois supérieure à ceux du type float (on les appelle parfois nombres à double précision). On choisira de préférence le type double dans la plupart des applications. La précision limitée de float se révèle insuffisante dans de nombreuses situations. On ne l’emploiera que dans les rares cas où la vitesse de calcul (plus élevée pour les nombres à précision simple) est importante pour l’exécution, ou lorsqu’une grande quantité de nombres doit être stockée (afin d’économiser la mémoire). Les nombres de type float ont pour suffixe F, par exemple 3.402F. Les nombres à décimales exprimés sans ce suffixe F, par exemple 3.402, sont toujours considérés comme étant du type double. Pour ces derniers, il est également possible de spécifier un suffixe D, par exemple 3.402D. INFO Depuis Java SE 5.0, vous pouvez spécifier des nombres à virgule flottante en hexadécimal. Par exemple, 0.125 = 2–3 équivaut à 0x1.0p-3. Dans la notation hexadécimale, vous utilisez p, et non e, pour indiquer un exposant.
Tous les calculs en virgule flottante respectent la spécification IEEE 754. En particulier, il existe trois valeurs spéciales en virgule flottante, qui servent à indiquer les dépassements et les erreurs : m
infinité positive ;
m
infinité négative ;
m
NaN (Not a Number — pas un nombre).
Par exemple, le résultat de la division d’un nombre positif par 0 est "infinité positive". Le calcul 0/0 ou la racine carrée d’un nombre négatif donne "NaN". INFO Il existe des constantes Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY et Double.NaN (ainsi que les constantes correspondantes Float) permettant de représenter ces valeurs spéciales. Elles sont cependant rarement utilisées dans la pratique. En particulier, vous ne pouvez tester if (x == Double.NaN) // n’est jamais vrai
pour vérifier si un résultat particulier est égal à Double.NaN. Toutes les valeurs "pas un nombre" sont considérées comme distinctes. Vous pouvez cependant employer la méthode Double.isNaN : if (Double.isNaN(x)) // vérifier si x est "pas un nombre"
Livre Java.book Page 48 Mercredi, 13. février 2008 2:34 14
48
Au cœur de Java 2 - Notions fondamentales
ATTENTION Les nombres à virgule flottante ne conviennent pas aux calculs financiers, dans lesquels les erreurs d’arrondi 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 d’arrondi proviennent du fait que les nombres à virgule flottante sont représentés dans un système de nombres binaires. Il n’existe pas de représentation binaire précise de la fraction 1/10, tout comme il n’existe pas de représentation précise de la fraction 1/3 dans le système décimal. Si vous voulez obtenir des calculs numériques précis sans erreurs d’arrondi, utilisez la classe BigDecimal, présentée plus loin dans ce chapitre.
Le type char Le type char sert à décrire des caractères individuels. Le plus souvent, il s’agira de constantes de caractères. Par exemple, ’A’ est une constante de caractère de valeur 65. Elle diffère de "A", une chaîne contenant un seul caractère. Les unités de code Unicode peuvent s’exprimer sous forme de valeurs hexadécimales allant de \u0000 à \uFFFF. Ainsi, \u2122 correspond au symbole de marque commerciale (™) et \u03C0 correspond à la lettre grecque Pi (π). Outre les séquences d’échappement \u, indiquant le codage en Unicode, il existe plusieurs séquences d’échappement pour les caractères spéciaux, comme cela est indiqué au Tableau 3.3. Vous pouvez les utiliser dans des constantes et des chaînes de caractères, comme ’\u2122’ ou "Hello\ n". La séquence d’échappement \u (mais aucune des autres séquences d’échappement) peut même être utilisée en dehors des constantes et des chaînes de caractères entre apostrophes ou entre guillemets. L’exemple : public static void main(String\u005B\u005D args)
est tout à fait autorisé, \u005B et \u005D sont les codages pour [ et ]. Tableau 3.3 : Séquences d’échappement pour les caractères spéciaux
Séquence d’échappement
Nom
Valeur Unicode
\b
Retour arrière
\u0008
\t
Tabulation
\u0009
\n
Séparation de ligne
\u000a
\r
Retour chariot
\u000d
\"
Guillemet
\u0022
\’
Apostrophe
\u0027
\\
Barre oblique inverse
\u005c
Pour bien comprendre le type char, vous devez connaître le schéma de codage Unicode. Unicode a été inventé pour surmonter les limitations des schémas de codage traditionnels. Avant lui, il existait de nombreuses normes différentes : ASCII aux Etats-Unis, ISO 8859-1 pour les langues d’Europe de l’Est, KOI-8 pour le russe, GB18030 et BIG-5 pour le chinois, etc. Deux problèmes se posaient. Une valeur de code particulière correspondait à des lettres différentes selon le schéma de codage.
Livre Java.book Page 49 Mercredi, 13. février 2008 2:34 14
Chapitre 3
Structures fondamentales de la programmation Java
49
De plus, le codage des langues ayant de grands jeux de caractères avait des longueurs variables : certains caractères communs étaient codés sous la forme d’octets simples, d’autres nécessitaient deux octets ou plus. Unicode a été conçu pour résoudre ces problèmes. Aux débuts des efforts d’unification, dans les années 1980, un code de largeur fixe sur 2 octets était plus que suffisant pour coder tous les caractères utilisés dans toutes les langues du monde, et il restait encore de la place pour une future extension (ou, du moins, c’est ce que l’on pensait). En 1991 est sorti Unicode 1.0, qui utilisait légèrement moins de la moitié des 65 536 valeurs de code disponibles. Java a été conçu dès le départ pour utiliser les caractères Unicode à 16 bits, ce qui constituait une avancée majeure sur les autres langages de programmation, qui utilisaient des caractères sur 8 bits. Malheureusement, au fil du temps, ce qui devait arriver arriva. Unicode a grossi au-delà des 65 536 caractères, principalement du fait de l’ajout d’un très grand jeu d’idéogrammes utilisés pour le chinois, le japonais et le coréen. Le type char à 16 bits est désormais insuffisant pour décrire tous les caractères Unicode. Nous ferons appel à la terminologie pour expliquer comment ce problème a été résolu en Java, et ce depuis Java SE 5.0. Un point de code est une valeur de code associée à un caractère dans un schéma de codage. Dans la norme Unicode, les points de code sont écrits en hexadécimal et préfixés par U+, par exemple U+0041 pour le point de code de la lettre A. Unicode possède des points de code regroupés en 17 plans de codes. Le premier, appelé plan multilingue de base, est constitué des caractères 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 caractères complémentaires. Le codage UTF-16 permet de représenter tous les points de code Unicode dans un code de longueur variable. Les caractères du plan multilingue de base sont représentés sous forme de valeurs de 16 bits, appelées unités de code. Les caractères complémentaires sont englobés sous forme de paires consécutives d’unités de code. Chacune des valeurs d’une paire de codage se situe dans une plage inutilisée de 2 048 octets du plan multilingue de base, appelé zone de remplacement (de U+D800 à U+DBFF pour la première unité de code, de U+DC00 à U+DFFF pour la deuxième unité de code). Ceci est assez intéressant, car vous pouvez immédiatement dire si une unité de code procède à un codage sur un seul caractère ou s’il s’agit de la première ou de la deuxième partie d’un caractère complémentaire. Par exemple, le symbole mathématique pour le jeu d’entiers a le point de code U+1D56B et est codé par les deux unités de code U+D835 et U+DD6B (voir http://en.wikipedia.org/wiki/UTF-16 pour obtenir une description de l’algorithme de codage). En Java, le type char décrit une unité de code en codage UTF-16. Nous recommandons fortement de ne pas utiliser le type char dans vos programmes, à moins d’être à l’aise dans la manipulation des unités de code UTF-16. Il vaut presque mieux traiter les chaînes (ce que nous verrons plus loin) sous forme de types de données abstraits.
Type booléen Le type boolean peut avoir deux valeurs, false (faux) ou true (vrai). Il est employé pour l’évaluation de conditions logiques. Les conversions entre valeurs entières et booléennes sont impossibles.
Livre Java.book Page 50 Mercredi, 13. février 2008 2:34 14
50
Au cœur de Java 2 - Notions fondamentales
INFO C++ En C++, des nombres et même des pointeurs peuvent être employés à la place des valeurs booléennes. La valeur 0 équivaut à la valeur booléenne false, et une valeur non zéro équivaut à true. Ce n’est pas le cas avec Java. Les programmeurs Java sont donc mis en garde contre ce type d’accidents : if (x = 0) // pardon... je voulais dire x == 0
En C++, ce test est compilé et exécuté, il donne toujours le résultat false. En Java, la compilation du test échoue, car l’expression entière x = 0 ne peut pas être convertie en une valeur booléenne.
Variables En Java, toute variable a un type. Vous déclarez une variable en spécifiant d’abord son type, puis son nom. Voici quelques exemples de déclarations : double salary; int vacationDays; long earthPopulation; boolean done;
Remarquez que chaque déclaration se termine par un point-virgule. Il est nécessaire, car une déclaration est une instruction Java complète. Un nom de variable doit débuter par une lettre et doit être une séquence de lettres ou de chiffres. Notez que le sens des termes "lettres" et "chiffres" est plus large en Java que dans beaucoup d’autres langages. Une lettre est définie comme l’un des caractères ’A’...’Z’, ’a’...’z’, ’_’, ou n’importe quel caractère Unicode représentant une lettre dans une langue quelconque. Par exemple, un utilisateur français ou allemand peut employer des caractères accentués, tels que ä ou ê, dans un nom de variable. De même, les lecteurs grecs peuvent utiliser π. D’une manière comparable, un chiffre est un des caractères ’0’ à ’9’, ou n’importe quel caractère Unicode représentant un chiffre dans une langue. Des espaces ou des symboles tels que ’+’ ou © ne peuvent pas être employés. Tous les caractères d’un nom de variable sont significatifs, ainsi que la casse de chaque caractère (minuscule ou majuscule). La longueur possible d’un nom est théoriquement illimitée. ASTUCE Si vous vous demandez lesquels des caractères Unicode sont des "lettres" pour Java, vous pouvez le savoir en appelant les méthodes isJavaIdentifierStart et isJavaIdentifierPart de la classe Character.
Il est évidemment interdit de donner à une variable le même nom qu’un mot réservé de Java (voir, dans l’Annexe, la liste des mots réservés). Il est possible de déclarer plusieurs variables sur une seule ligne, comme suit : int i, j; // ce sont deux entiers en Java
Ce style de déclaration n’est pas recommandé. Si vous définissez chaque variable séparément, vos programmes seront plus faciles à lire.
Livre Java.book Page 51 Mercredi, 13. février 2008 2:34 14
Chapitre 3
Structures fondamentales de la programmation Java
51
INFO Vous avez vu que les noms étaient sensibles à la casse. Par exemple, hireday et hireDay sont deux noms différents. En règle générale, n’employez pas deux noms différant seulement par la casse des caractères. Il est toutefois difficile parfois de définir un nom correct pour une variable. Les programmeurs attribuent alors fréquemment à la variable le même nom que le type, par exemple : Box box; // OK--Box est le type et box le nom de la variable
Cependant, une meilleure solution consiste à attribuer un préfixe "a" à la variable Box aBox;
Initialisation des variables Après avoir déclaré une variable, vous devez explicitement l’initialiser à l’aide d’une instruction d’affectation. Vous ne devez pas utiliser une variable non initialisée. Le compilateur Java signale la suite d’instructions suivantes comme une erreur : int vacationDays; System.out.println(vacationDays); //ERREUR--variable non-initialisée
L’affectation d’une variable déclarée se fait à l’aide du symbole d’égalité (=), précédé à gauche du nom de la variable et suivi à droite par une expression Java représentant la valeur appropriée : int vacationDays; vacationDays = 12;
Une fonctionnalité de Java permet de déclarer et d’initialiser simultanément une variable en une seule instruction, de la façon suivante : int vacationDays = 12;
Précisons enfin que Java permet de déclarer des variables n’importe 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 déclarer la variable ici
En Java, il est recommandé de déclarer les variables aussi près que possible du point de leur première utilisation. INFO C++ C et C++ font une distinction entre une déclaration et une définition de variables. Par exemple, int i = 10;
est une définition, alors que extern int i;
est une déclaration. En Java, il n’y a pas de déclaration séparée de la définition.
Livre Java.book Page 52 Mercredi, 13. février 2008 2:34 14
52
Au cœur de Java 2 - Notions fondamentales
Constantes En Java, le mot clé final sert à désigner 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 signifie que vous affectez une valeur à la variable une seule fois, et une fois pour toutes. Par convention, les noms des constantes sont entièrement en majuscules. Il est plus courant de créer une constante qui est accessible à plusieurs méthodes de la même classe. Les méthodes de ce genre sont appelées généralement constantes de classe. On définit une constante de classe à l’aide des mots clés 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 définition de la constante de classe apparaît en dehors de la méthode main. La constante peut donc aussi être employée dans d’autres méthodes de la même classe. De plus, si (comme dans notre exemple), la constante est déclarée public, les méthodes 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 réservé de Java, il n’est pas utilisé actuellement. C’est le mot clé final qui permet de définir une constante.
Opérateurs Les habituels opérateurs arithmétiques + – * / sont respectivement utilisés en Java pour les opérations d’addition, de soustraction, de multiplication et de division. L’opérateur de division / indique une division entière si les deux arguments sont des entiers et une division en virgule flottante dans les
Livre Java.book Page 53 Mercredi, 13. février 2008 2:34 14
Chapitre 3
Structures fondamentales de la programmation Java
53
autres cas. Le reste d’une division entière est représenté par le symbole %. Par exemple, 15/2 donne 7 ; 15 % 2 donne 1 et 15.0/2 donne 7.5. Notez que la division d’un entier par 0 déclenche une exception, alors que la division d’une valeur en virgule flottante par 0 donne un résultat infini ou NaN. Les opérateurs arithmétiques peuvent être utilisés en combinaison avec l’opérateur d’affectation pour simplifier l’écriture d’une affectation. Par exemple, l’instruction x += 4;
équivaut à l’instruction x = x + 4;
En règle générale, placez l’opérateur arithmétique à gauche du signe =, par exemple *= ou %=. INFO L’un des intérêts évidents de la programmation en langage Java est la portabilité. Un calcul doit aboutir au même résultat quelle que soit la machine virtuelle sur laquelle il est exécuté. Dans le cas de calculs avec des nombres à virgule flottante, il est étonnamment difficile d’obtenir cette portabilité. Le type double utilise 64 bits pour le stockage d’une valeur numérique, mais certains processeurs emploient des registres à virgule flottante de 80 bits. Ces registres ajoutent une précision lors des étapes intermédiaires d’un calcul. Examinez, par exemple, le calcul suivant : double w = x * y / z;
De nombreux processeurs Intel vont calculer x * y et placer le résultat dans un registre 80 bits, puis diviser par z pour finalement tronquer le résultat à 64 bits. Cela peut donner un résultat plus précis et éviter un dépassement d’exposant. Cependant le résultat peut être différent de celui d’un calcul effectué constamment sur 64 bits. Pour cette raison, la spécification initiale de la machine virtuelle Java prévoyait la troncation de tous les calculs intermédiaires, au grand dam de la communauté numérique. Non seulement les calculs tronqués peuvent provoquer un dépassement de capacité, mais ils sont aussi plus lents, du fait du temps nécessaire aux opérations de troncation. C’est pourquoi le langage Java a été actualisé pour tenir compte des besoins conflictuels de performance optimale et de reproductibilité parfaite. Par défaut, les concepteurs de machine virtuelle peuvent maintenant utiliser une précision étendue pour les calculs intermédiaires. Toutefois, les méthodes balisées avec le mot clé strictfp doivent utiliser des opérations virgule flottante strictes menant à des résultats reproductibles. Vous pouvez, par exemple, baliser la méthode main de la façon suivante : public static strictfp void main(String[] args)
Toutes les instructions au sein de main auront alors recours à des calculs virgule flottante stricts. Si vous balisez une classe à l’aide de strictfp, toutes ses méthodes utiliseront les calculs virgule flottante stricts. Les détails sordides dépendent essentiellement du comportement des processeurs Intel. En mode par défaut, les résultats intermédiaires sont autorisés à utiliser un exposant étendu, mais pas une mantisse étendue (les puces Intel gèrent la troncation de la mantisse sans perte de performance). Par conséquent, la seule différence entre les modes par défaut et strict est que les calculs stricts peuvent engendrer des dépassements de capacité, à la différence des calculs par défaut. Si vos yeux s’arrondissent à la lecture de cette Info, ne vous inquiétez pas. Le dépassement de capacité en virgule flottante ne se pose pas pour la majorité des programmes courants. Nous n’utiliserons pas le mot clé strictfp dans cet ouvrage.
Livre Java.book Page 54 Mercredi, 13. février 2008 2:34 14
54
Au cœur de Java 2 - Notions fondamentales
Opérateurs d’incrémentation et de décrémentation Les programmeurs savent évidemment qu’une des opérations les plus courantes effectuées sur une variable numérique consiste à lui ajouter ou à lui retrancher 1. Suivant les traces de C et de C++, Java offre des opérateurs d’incrémentation et de décrémentation : n++ ajoute 1 à la valeur courante n et n-- retranche 1 à cette valeur. Ainsi, cet exemple : int n = 12; n++;
donne à n la valeur 13. Comme ces opérateurs modifient la valeur d’une variable, ils ne peuvent pas être appliqués à des nombres. Par exemple, 4++ n’est pas une instruction valide. Ces opérateurs peuvent en réalité prendre deux formes ; vous avez vu la forme "suffixe", où l’opérateur est situé après l’opérande : n++. Il peut également être placé en préfixe : ++n. Dans les deux cas, la variable est incrémentée de 1. La différence entre ces deux formes n’apparaît que lorsqu’elles sont employées dans des expressions. Lorsqu’il est placé en préfixe, l’opérateur effectue d’abord l’addition ; en suffixe, il fournit l’ancienne 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 déconseillons vivement l’emploi de l’opérateur ++ dans d’autres expressions, car cela entraîne souvent un code confus et des bogues difficiles à détecter. L’opérateur ++ a bien évidemment donné son nom au langage C++, mais il a également provoqué une des premières plaisanteries à propos de ce langage. Les anti-C++ ont fait remarquer que même le nom du langage contenait un bogue : "En fait, il devrait s’appeler ++C, car on ne désire employer le langage qu’après son amélioration."
Opérateurs relationnels et booléens Java offre le jeu complet d’opérateurs relationnels. Un double signe égal, ==, permet de tester l’égalité de deux opérandes. Par exemple, l’évaluation de 3 == 7
donne un résultat faux (false). Utilisez != pour pratiquer un test d’inégalité. Par exemple, le résultat de 3 != 7
est vrai (true). De plus, nous disposons des habituels opérateurs < (inférieur à), > (supérieur à), = (supérieur ou égal à). Suivant l’exemple de C++, Java utilise && comme opérateur "et" logique et || comme opérateur "ou" logique. Le point d’exclamation ! représente l’opérateur de négation. Les opérateurs && et || sont évalués de manière optimisée (en court-circuit). Le deuxième argument n’est pas évalué si le premier détermine déjà la valeur. Si vous combinez deux expressions avec l’opérateur &&, expression 1 && expression 2
Livre Java.book Page 55 Mercredi, 13. février 2008 2:34 14
Chapitre 3
Structures fondamentales de la programmation Java
55
la valeur de la deuxième expression n’est pas calculée si la première a pu être évaluée à false (puisque le résultat final serait false de toute façon). Ce comportement peut éviter des erreurs. Par exemple, dans l’expression x != 0 && 1 / x > x + y // pas de division par 0
la seconde partie n’est jamais évaluée si x égale zéro. Par conséquent, 1/x n’est pas calculé si x vaut zéro, et une erreur "division par zéro" ne peut pas se produire. De même, si la première expression est évaluée à true, la valeur de expression1 || expression2 est automatiquement true, sans que la deuxième expression doive être évaluée. Enfin, Java gère l’opérateur ternaire ? qui se révèle utile à l’occasion. L’expression condition ? expression 1 : expression 2
est évaluée à expression1 si la condition est true, à expression2 sinon. Par exemple, x < y ? x : y
donne le plus petit entre x et y.
Opérateurs binaires Lorsqu’on manipule des types entiers, il est possible d’employer des opérateurs travaillant directement sur les bits qui composent ces entiers. Certaines techniques de masquage permettent de récupérer un bit particulier dans un nombre. Les opérateurs binaires sont les suivants : &("et")
|("ou")
^("ou exclusif")
~("non")
Ces opérateurs travaillent sur des groupes de bits. Par exemple, si n est une variable entière, alors la déclaration int quatrièmeBitPartantDeDroite = (n & 8) / 8;
renvoie 1 si le quatrième bit à partir de la droite est à 1 dans la représentation binaire de n, et zéro dans le cas contraire. L’emploi de & avec la puissance de deux appropriée permet de masquer tous les bits sauf un. INFO Lorsqu’ils sont appliqués à des valeurs boolean, les opérateurs & et | donnent une valeur boolean. Ces opérateurs sont comparables aux opérateurs && et ||, excepté que & et | ne sont pas évalués de façon optimisée "court-circuit". C’est-à-dire que les deux arguments sont évalués en premier, avant le calcul du résultat.
Il existe également des opérateurs >> et 3;
Signalons enfin qu’il existe également un opérateur >>> permettant de remplir les bits de poids fort avec des zéros, alors que >> étend le bit de signature dans les bits de poids fort. Il n’existe pas d’opérateur >>>
De la gauche vers la droite
< >= instanceof
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 énumérés Une variable ne doit quelquefois contenir qu’un jeu de valeurs limité. Vous pouvez par exemple vendre des vêtements ou des pizzas en quatre formats : petit, moyen, grand, très grand. Bien sûr, ces formats pourraient être codés sous forme d’entiers 1, 2, 3, 4 ou de caractères S, M, L et X. Mais cette configuration est sujette à erreur. Une variable peut trop facilement contenir une valeur erronée (comme 0 ou m). Depuis Java SE 5.0, vous pouvez définir votre propre type énuméré dès qu’une situation se présente. Un type énuméré possède un nombre fini de valeurs nommées. Par exemple, enum Size { SMALL, MEDIUM, LARGE, EXTRA_LARGE };
Livre Java.book Page 60 Mercredi, 13. février 2008 2:34 14
60
Au cœur de Java 2 - Notions fondamentales
Vous pouvez maintenant déclarer des variables de ce type : Size s = Size.MEDIUM;
Une variable de type Size ne peut contenir que l’une des valeurs listées dans la déclaration de type ou la valeur spéciale null qui indique que la variable n’est définie sur aucune valeur. Nous verrons les types énumérés plus en détail au Chapitre 5.
Chaînes Les chaînes sont des suites de caractères Unicode. Par exemple, la chaîne "Java\u2122" est constituée de cinq caractères Unicode J, a, v, a et ™. Java ne possède pas de type chaîne intégré. En revanche, la bibliothèque standard Java contient une classe prédéfinie appelée, assez naturellement, String. Chaque chaîne entre guillemets est une instance de la classe String : String e = ""; // une chaîne vide String greeting = "Hello";
Sous-chaînes Pour extraire une sous-chaîne à partir d’une chaîne plus grande, utilisez la méthode substring de la classe String. Par exemple, String greeting = "Hello"; String s = greeting.substring(0, 3);
crée une chaîne constituée des caractères "Hel". Le deuxième paramètre de substring est la première unité de code que vous ne souhaitez pas copier. Ici, nous voulons copier les unités 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 reconnaît un avantage au fonctionnement de substring : il facilite le calcul du nombre d’unités de code présentes dans la sous-chaîne. La chaîne s.substring(a, b) a toujours les unités de code b - a. Par exemple, la sous-chaîne "Hel" a 3 – 0 = 3 unités de code.
Concaténation Java, comme la plupart des langages de programmation, autorise l’emploi du signe + pour joindre (concaténer) deux chaînes : String expletive = "Expletive"; String PG13 = "deleted"; String message = expletive + PG13;
Le code qui précède donne à la variable message le contenu "Expletivedeleted" (remarquez qu’il n’insère pas d’espace entre les mots : le signe + accole les deux chaînes exactement telles qu’elles sont fournies). Lorsque vous concaténez une chaîne et une valeur qui n’est pas une chaîne, cette valeur est convertie en chaîne (comme vous le verrez au Chapitre 5, tout objet Java peut être converti en chaîne).
Livre Java.book Page 61 Mercredi, 13. février 2008 2:34 14
Chapitre 3
Structures fondamentales de la programmation Java
61
Voici un exemple : int age = 13; String rating = "PG" + age;
qui donne à la chaîne rating la valeur "PG13". Cette caractéristique est utilisée couramment pour l’affichage. Par exemple, l’instruction System.out.println("The answer is " + answer);
est parfaitement valide et affichera la réponse voulue avec un espacement correct (notez l’espace derrière le mot is).
Les chaînes sont inaltérables La classe String ne fournit pas de méthode permettant de modifier un caractère dans une chaîne existante. Au cas où vous voudriez transformer le contenu de la variable greeting en "Help!", vous ne pouvez pas remplacer directement la dernière position de greeting par ’p’ et par ’!’. Si vous êtes un programmeur C, vous devez vous sentir frustré. Et, pourtant, la solution est très simple en Java : il suffit de récupérer la sous-chaîne à conserver et de la concaténer avec les caractères à remplacer : greeting = greeting.substring(0, 3) + "p!";
Cette instruction donne à la variable greeting la valeur "Help!". Comme il n’est pas possible de modifier individuellement des caractères dans une chaîne Java, la documentation indique que les objets de la classe String sont inaltérables. Tout comme le nombre 3 vaut toujours 3, la chaîne "Hello" contient toujours la suite de caractères ’H’, ’e’, ’l’, ’l’, ’o’. Il est impossible de changer cette valeur. En revanche, comme nous venons de le voir, il est possible de modifier le contenu de la variable chaîne greeting et de lui faire référencer une chaîne différente (de même qu’une variable numérique contenant la valeur 3 peut recevoir la valeur 4). On pourrait penser que tout cela n’est pas très performant. Ne serait-il pas plus simple de changer les unités de code au lieu de construire une nouvelle chaîne ? Oui et non. En vérité, il n’est pas très intéressant de générer une nouvelle chaîne contenant la concaténation de "Hel" et de "p!". Mais les chaînes inaltérables possèdent pourtant un énorme avantage : le compilateur peut les partager. Pour comprendre cette technique, imaginez que les diverses chaînes résident dans un pool commun. Les variables chaînes, quant à elles, pointent sur des positions dans le pool. Si vous copiez une variable chaîne, la chaîne d’origine et la copie partagent les mêmes caractères. Tout bien considéré, les concepteurs de Java ont estimé que l’efficacité du partage des chaînes surpassait l’inconvénient de la modification des chaînes par extraction de sous-chaînes et concaténation. Examinez vos propres programmes ; la plupart du temps, vous ne modifiez sans doute pas les chaînes — vous vous contentez de les comparer. Bien entendu, il existe des situations où une manipulation directe se révèle plus efficace (par exemple lorsqu’on assemble des chaînes à partir de caractères individuels tapés au clavier ou récupérés dans un fichier). Pour ces cas-là, Java fournit une classe séparée, que nous décrivons plus loin dans ce chapitre.
Livre Java.book Page 62 Mercredi, 13. février 2008 2:34 14
62
Au cœur de Java 2 - Notions fondamentales
INFO C++ Les programmeurs C sont souvent stupéfaits lorsqu’ils rencontrent des chaînes Java pour la première fois, car ils considèrent les chaînes comme des tableaux de caractères : char greeting[] = "Hello";
La comparaison n’est pas bonne ; une chaîne Java ressemble davantage à un pointeur char* : char* greeting = "Hello";
Lorsque vous remplacez la valeur de greeting par une autre chaîne, le code Java exécute à peu près ce qui suit : char* temp = malloc(6); strncpy(temp, greeting, 3); strcpy(temp + 3, "p!", 3); greeting = temp;
greeting pointe maintenant vers la chaîne "Help!". Et même le plus intégriste des programmeurs C doit reconnaître que la syntaxe de Java est plus agréable qu’une série d’appels à strncpy. Mais que se passe-t-il si nous affectons une nouvelle valeur à greeting ? greeting = "Howdy";
La chaîne d’origine ne va-t-elle pas occuper inutilement la mémoire, puisqu’elle a été allouée dans le pool ? Heureusement, Java récupère automatiquement la mémoire libérée. Si un bloc de mémoire n’est plus utilisé, il finira par être recyclé. Si vous programmez en C++ et utilisez la classe string définie par ANSI C++, vous vous sentirez à l’aise avec le type String de Java. Les objets string de C++ se chargent automatiquement de l’allocation et de la récupération de la mémoire. La gestion de la mémoire est accomplie explicitement par les constructeurs, les opérateurs d’affectations et les destructeurs. Cependant, les chaînes C++ sont altérables — il est possible de modifier individuellement un caractère dans une chaîne.
Test d’égalité des chaînes Pour savoir si deux chaînes sont égales, utilisez la méthode equals ; l’expression s.equals(t)
donne true si les chaînes s et t sont égales, et false dans le cas contraire. Notez que s et t peuvent être des variables chaînes ou des constantes chaînes. Par exemple, "Hello".equals(greeting)
est parfaitement valide. Pour tester si deux chaînes sont identiques à l’exception de la casse des caractères, utilisez la méthode equalsIgnoreCase : "Hello".equalsIgnoreCase("hello")
Attention, n’employez pas l’opérateur == pour tester l’égalité de deux chaînes ! Cet opérateur détermine seulement si les chaînes sont stockées au même emplacement. Il est évident que si deux chaînes se trouvent à la même adresse, elles doivent être égales. Mais des copies de chaînes identiques peuvent être stockées à des emplacements différents dans la mémoire : String greeting = "Hello"; //initialise la chaîne greeting if (greeting == "Hello") . . . // probablement vrai
Livre Java.book Page 63 Mercredi, 13. février 2008 2:34 14
Chapitre 3
Structures fondamentales de la programmation Java
63
if (greeting.substring(0, 3) == "Hel") . . . // probablement faux
Si la machine virtuelle faisait en sorte de toujours partager les chaînes identiques, l’opérateur == pourrait être utilisé pour un test d’égalité. Mais seules les constantes chaînes sont partagées, et non les chaînes créées à l’aide de l’opérateur + ou de la méthode substring. Par conséquent, n’employez jamais == pour comparer des chaînes, car cela pourrait générer le pire des bogues : un bogue intermittent qui semble se produire aléatoirement. INFO C++ Si vous êtes familiarisé avec la classe string de C++, vous devez être particulièrement attentif aux tests d’égalité, car la classe string surcharge l’opérateur == pour tester l’égalité du contenu de deux chaînes. Il est peut-être dommage que les chaînes Java aient un "aspect général" comparable à celui des valeurs numériques, mais qu’elles se comportent comme des pointeurs lors des tests d’égalité. Les concepteurs auraient pu redéfinir == pour l’adapter aux chaînes, comme ils l’ont fait pour l’opérateur +, mais aucun langage n’est parfait. Pour comparer des chaînes, les programmeurs C n’emploient pas l’opérateur ==, mais la fonction strcmp. La méthode analogue en langage Java est compareTo. Vous pouvez écrire if (greeting.compareTo("Hello") == 0) . . .
mais il est plus pratique d’appeler la méthode equals.
Points de code et unités de code Les chaînes Java sont implémentées sous forme de suites de valeurs char. Comme nous l’avons vu, le type char est une unité de code permettant de représenter des points de code Unicode en codage UTF-16. Les caractères Unicode les plus souvent utilisés peuvent être représentés par une seule unité de code. Les caractères complémentaires exigent, quant à eux, une paire d’unités de code. La méthode length produit le nombre d’unités de code exigé pour une chaîne donnée dans le codage UTF-16. Par exemple : String greeting = "Hello"; int n = greeting.length(); // vaut 5
Pour obtenir la bonne longueur, c’est-à-dire le nombre de points de code, appelez int cpCount = greeting.codePointCount(0, greeting.length());
L’appel s.chartAt(n) renvoie l’unité de code présente à la position n, où n est compris entre 0 et s.length() -1. Par exemple : char first = greeting.charAt(0); // le premier est ’H’ char last = greeting.charAt(4); // le dernier est ’o’
Pour accéder au ième point de code, utilisez les instructions int index = greeting.offsetByCodePoints(0, i); int cp = greeting.codePointAt(index);
Livre Java.book Page 64 Mercredi, 13. février 2008 2:34 14
64
Au cœur de Java 2 - Notions fondamentales
INFO Java compte les unités de code dans les chaînes d’une manière particulière : la première unité de code d’une chaîne occupe la position 0. Cette convention est née dans le C, à une époque où il existait une raison technique à démarrer les positions à 0. Cette raison a disparu depuis longtemps, et seule la nuisance demeure. Toutefois, les programmeurs sont tellement habitués à cette convention que les concepteurs Java ont décidé de la garder.
Pourquoi nous préoccuper tant des unités de code ? Etudiez la phrase is the set of integers
Le caractère exige deux unités de code en codage UTF-16. Appeler char ch = sentence.charAt(1)
ne renvoie pas un espace mais la deuxième unité de code de . Pour éviter ce problème, il vaut mieux ne pas utiliser le type char, qui est de trop bas niveau. Si votre code traverse une chaîne 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 méthode codePointAt peut dire si une unité de code est la première ou la seconde moitié d’un caractère complémentaire, elle renvoie le bon résultat quel que soit le cas. Ainsi, vous pouvez revenir en arrière avec les instructions suivantes : i--; int cp = sentence.codePointAt(i); if (Character.isSupplementaryCodePoint(cp)) i--;
L’API String La classe String de Java contient plus de 50 méthodes. Beaucoup d’entre elles sont assez utiles pour être employées couramment. La note API qui suit présente les méthodes qui nous semblent les plus intéressantes pour le programmeur. INFO Vous rencontrerez ces notes API dans tout l’ouvrage. Elles vous aideront à comprendre l’interface de programmation Java, ou API (Application Programming Interface). Chaque note API commence par le nom d’une classe, tel que java.lang.String — la signification du nom de package java.lang sera expliquée au Chapitre 4. Le nom de la classe est suivi des noms, explications et descriptions des paramètres d’une ou plusieurs méthodes de cette classe. Nous ne donnons pas la liste de toutes les méthodes d’une classe donnée, mais seulement celles qui sont utilisées le plus fréquemment, décrites sous une forme concise. Consultez la documentation en ligne si vous désirez obtenir une liste complète des méthodes d’une classe. Nous indiquons également le numéro de version dans laquelle une classe particulière a été introduite. Si une méthode a été ajoutée par la suite, elle présente un numéro de version distinct.
Livre Java.book Page 65 Mercredi, 13. février 2008 2:34 14
Chapitre 3
Structures fondamentales de la programmation Java
65
java.lang.String 1.0
•
char charAt(int index)
Renvoie l’unité de code située à la position spécifiée. Vous ne voudrez probablement pas appeler cette méthode à moins d’être intéressé par les unités de code de bas niveau. •
int codePointAt(int index) 5.0
Renvoie le point de code qui démarre ou se termine à l’emplacement spécifié. •
int offsetByCodePoints(int startIndex, int cpCount) 5.0
Renvoie l’indice du point de code d’où pointe cpCount, depuis le point de code jusqu’à startIndex. •
int compareTo(String other)
Renvoie une valeur négative si la chaîne se trouve avant other (dans l’ordre alphabétique), une valeur positive si la chaîne se trouve après other ou un 0 si les deux chaînes sont identiques. •
boolean endsWith(String suffix)
Renvoie true si la chaîne se termine par suffix. •
boolean equals(Object other)
Renvoie true si la chaîne est identique à other. •
boolean equalsIgnoreCase(String other)
Renvoie true si la chaîne 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 départ de la première sous-chaîne égale à str ou au point de code cp, en commençant par la position 0 ou par fromIndex ou –1 si str n’apparaît pas dans cette chaîne. • • • •
int lastIndexOf(String str) int lastIndexOf(String str, int fromIndex) int lastIndexOf(int cp) int lastIndexOf(int cp, int fromIndex)
Renvoient la position de départ de la dernière sous-chaîne égale à str ou au point de code cp, en commençant à la fin de la chaîne ou par fromIndex. •
int length()
Renvoie la taille (ou longueur) de la chaîne. •
int codePointCount(int startIndex, int endIndex) 5.0
Renvoie le nombre de points de code entre startIndex et endIndex - 1. Les substitutions sans paires sont considérées comme des points de code. •
String replace(CharSequence oldString, CharSequence newString)
Renvoie une nouvelle chaîne, obtenue en remplaçant tous les caractères oldString de la chaîne par les caractères newString. Vous pouvez fournir des objets String ou StringBuilder pour les paramètres CharSequence.
Livre Java.book Page 66 Mercredi, 13. février 2008 2:34 14
66
•
Au cœur de Java 2 - Notions fondamentales
boolean startsWith(String prefix)
Renvoie true si la chaîne commence par prefix. • •
String substring(int beginIndex) String substring(int beginIndex, int endIndex)
Renvoient une nouvelle chaîne composée de toutes les unités de code situées entre beginIndex et, soit la fin de la chaîne, soit endIndex - 1. •
String toLowerCase()
Renvoie une nouvelle chaîne composée de tous les caractères de la chaîne d’origine, mais dont les majuscules ont été converties en minuscules. •
String toUpperCase()
Renvoie une nouvelle chaîne composée de tous les caractères de la chaîne d’origine, mais dont les minuscules ont été converties en majuscules. •
String trim()
Renvoie une nouvelle chaîne en éliminant tous les espaces qui auraient pu se trouver devant ou derrière la chaîne d’origine.
Lire la documentation API en ligne Vous avez vu que la classe String comprend quantité de méthodes. Il existe de plus des milliers de classes dans les bibliothèques standard, avec bien d’autres méthodes encore. Il est impossible de mémoriser toutes les classes et méthodes utiles. Il est donc essentiel que vous puissiez facilement consulter la documentation API en ligne concernant les classes et méthodes de la bibliothèque standard. La documentation API fait partie du JDK. Elle est au format HTML. Pointez votre navigateur Web sur le sous-répertoire docs/api/index.html de votre installation JDK. Vous verrez apparaître un écran comme celui de la Figure 3.2. Figure 3.2 Les trois panneaux de la documentation API.
Livre Java.book Page 67 Mercredi, 13. février 2008 2:34 14
Chapitre 3
Structures fondamentales de la programmation Java
67
L’écran est divisé en trois fenêtres. Une petite fenêtre en haut à gauche affiche tous les packages disponibles. Au-dessous, une fenêtre plus grande énumère toutes les classes. Cliquez sur un nom de classe pour faire apparaître la documentation API pour cette classe dans la fenêtre de droite (voir Figure 3.3). Par exemple, pour obtenir des informations sur les méthodes de la classe String, faites défiler la deuxième fenêtre jusqu’à ce que le lien String soit visible, puis cliquez dessus.
Figure 3.3 Description de la classe String.
Faites défiler la fenêtre de droite jusqu’à atteindre le résumé de toutes les méthodes, triées par ordre alphabétique (voir Figure 3.4). Cliquez sur le nom d’une méthode pour afficher sa description détaillée (voir Figure 3.5). Par exemple, si vous cliquez sur le lien compareToIgnoreCase, vous obtiendrez la description de la méthode compareToIgnoreCase. ASTUCE Affectez dès maintenant un signet à la page docs/api/index.html dans votre navigateur.
Livre Java.book Page 68 Mercredi, 13. février 2008 2:34 14
68
Au cœur de Java 2 - Notions fondamentales
Figure 3.4 Liste des méthodes de la classe String.
Figure 3.5 Description détaillée d’une méthode de la classe String.
Livre Java.book Page 69 Mercredi, 13. février 2008 2:34 14
Chapitre 3
Structures fondamentales de la programmation Java
69
Construction de chaînes Pour le traitement d’une saisie, vous devez parfois construire des chaînes à partir de caractères individuels ou de mots d’un fichier. La concaténation serait totalement inefficace dans ce cas. A chaque concaténation, un nouvel objet String est construit. L’opération prend du temps et consomme de la mémoire mais la classe StringBuilder permet d’éviter ce problème. Procédez comme suit pour construire une chaîne à partir de petits éléments. Construisez d’abord un constructeur de chaîne vide : StringBuilder builder = new StringBuilder();
Nous verrons les constructeurs et l’opérateur new en détail au Chapitre 4. Chaque fois que vous devez ajouter une partie, appelez la méthode append. builder.append(ch); // annexe un seul caractère builder.append(str); // annexe une chaîne
Lorsque vous avez terminé de construire la chaîne, appelez la méthode toString. Vous obtiendrez un objet String avec la suite de caractères contenue dans le constructeur. String completedString = builder.toString();
INFO La classe StringBuilder a été introduite dans le JDK 5.0. Son prédécesseur, StringBuffer, est légèrement moins efficace mais permet à plusieurs threads d’ajouter ou de supprimer des caractères. Si toute la modification des chaînes survient dans un seul thread, ce qui est généralement le cas, utilisez plutôt StringBuilder. Les API des deux classes sont identiques.
Les notes d’API suivantes contiennent les méthodes les plus importantes pour la classe StringBuilder. java.lang.StringBuilder 5.0
•
StringBuilder()
Construit un constructeur de chaîne vide. •
int length
Renvoie le nombre d’unités de code du constructeur ou du tampon. •
StringBuilder append(String str)
Annexe une chaîne et renvoie this. •
StringBuilder append(char c)
Annexe une unité de code et renvoie this. •
StringBuilder appendCodePoint(int cp)
Annexe un point de code, en le transformant en une ou deux unités de code et renvoie this. •
void setCharAt(int i, char c)
Définit la ième unité de code sur c. •
StringBuilder insert(int offset, String str)
Insère une chaîne à la position offset et renvoie this.
Livre Java.book Page 70 Mercredi, 13. février 2008 2:34 14
70
•
Au cœur de Java 2 - Notions fondamentales
StringBuilder insert(int offset, char c)
Insère une unité de code à la position offset et renvoie this. •
StringBuilder delete(int startIndex, int endIndex)
Supprime les unités de code avec les décalages startIndex à endIndex - 1 et renvoie this. •
String toString()
Renvoie une chaîne contenant les mêmes données que le constructeur ou le tampon.
Entrées et sorties Pour rendre nos programmes d’exemple plus intéressants, 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 d’outils et de techniques que ce que nous avons à disposition pour le moment. L’intérêt pour l’instant étant de se familiariser avec le langage de programmation Java, nous allons nous contenter de notre humble console pour l’entrée et la sortie. La programmation des interfaces graphiques est traitée aux Chapitres 7 à 9.
Lire les caractères entrés Vous avez pu constater combien il était simple d’afficher une sortie sur l’unité de "sortie standard" (c’est-à-dire la fenêtre de la console) en appelant System.out.println. La lecture d’une "entrée standard" System.in n’est pas aussi simple. La lecture d’une entrée au clavier se fait en construisant un Scanner attaché à System.in. Scanner in = new Scanner(System.in);
Les diverses méthodes de la classe Scanner permettent ensuite de lire les entrées. Par exemple, la méthode nextLine lit une ligne saisie : System.out.print("What is your name? "); String name = in.nextLine();
Ici, nous utilisons la méthode nextLine car la saisie pourrait contenir des espaces. Pour lire un seul mot (délimité par des espaces), appelez String firstName = in.next();
Pour lire un entier, utilisez la méthode nextInt : System.out.print("How old are you? "); int age = in.nextInt();
De même, la méthode nextDouble lit le prochain chiffre à virgule flottante. Le programme du Listing 3.2 demande le nom de l’utilisateur et son âge, puis affiche un message du style Hello, Cay. Next year, you’ll be 46
Enfin, ajoutez la ligne import java.util.*;
Livre Java.book Page 71 Mercredi, 13. février 2008 2:34 14
Chapitre 3
Structures fondamentales de la programmation Java
71
au début du programme. La classe Scanner est définie dans le package java.util. Dès que vous utilisez une classe qui n’est pas définie dans le package de base java.lang, vous devez utiliser une directive import. Nous étudierons les packages et les directives import plus en détail au Chapitre 4. Listing 3.2 : InputTest.java import java.util.*; /** * Ce programme montre une entrée 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); // récupérer la première entrée System.out.print("What is your name? "); String name = in.nextLine(); // récupérer la seconde entrée System.out.print("How old are you? "); int age = in nextInt(); // afficher la sortie à la console System.out.println("Hello, " + name + ". Next year, you’ll be " + (age + 1)); } }
INFO La classe Scanner ne convient pas pour la lecture d’un mot de passe à partir d’une console puisque l’entrée est tout à fait visible pour tous. Java SE 6 introduit une classe Console réservée à 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 sécurité, le mot de passe est renvoyé dans un tableau de caractères plutôt que dans une chaîne. Une fois que vous avez traité le mot de passe, écrasez immédiatement les éléments du tableau par une valeur de remplissage (le traitement des tableaux est traité plus loin dans ce chapitre). Le traitement des entrées avec un objet Console n’est pas aussi pratique qu’avec un Scanner. Vous ne pouvez lire qu’une ligne d’entrée à la fois. Il n’existe pas de méthodes pour lire des mots ou des nombres individuels.
java.util.Scanner 5.0 • Scanner(InputStream in)
Construit un objet Scanner à partir du flux de saisie donné. •
String nextLine()
Lit la prochaine ligne saisie.
Livre Java.book Page 72 Mercredi, 13. février 2008 2:34 14
72
•
Au cœur de Java 2 - Notions fondamentales
String next()
Lit le prochain mot saisi (délimité par un espace). • •
int nextInt() double nextDouble()
Lisent et transforment la prochaine suite de caractères qui représente un entier ou un nombre à virgule flottante. •
boolean hasNext()
Teste s’il y a un autre mot dans la saisie. • •
boolean hasNextInt() boolean hasNextDouble()
Testent si la prochaine suite de caractères représente un entier ou un nombre à virgule flottante. java.lang.System 1.0 • static Console console() 6
Renvoie un objet Console pour interagir avec l’utilisateur via une fenêtre console si cette interaction est possible, null dans les autres cas. Un objet Console est disponible pour tout programme lancé dans une fenêtre console. Dans le cas contraire, la disponibilité dépend du système. java.io.Console 6 • static char[] readPassword(String prompt, Object... args) • static String readLine(String prompt, Object... args)
Affichent l’invite et lisent la saisie utilisateur jusqu’à la fin de la ligne d’entrée. Les paramètres args peuvent servir à fournir des arguments de mise en forme, comme c’est indiqué à la section suivante.
Mise en forme de l’affichage L’instruction System.out.print(x) permet d’afficher un nombre x à la console. Cette instruction affichera x avec le maximum de chiffres différents de zéro (pour le type donné). Par exemple, double x = 10000.0 / 3.0; System.out.print(x);
affiche 3333.3333333333335
Cela pose un problème si vous désirez afficher, par exemple, des euros et des centimes. Dans les précédentes versions de Java, l’affichage des nombres posait quelques problèmes. Heureusement, Java SE 5.0 a rapatrié la vénérable méthode printf de la bibliothèque C. Par exemple, l’appel System.out.printf("%8.2f", x);
affiche x avec une largeur de champ de 8 caractères et une précision de 2 caractères. En fait, l’affichage contient un espace préalable et les sept caractères 3333.33
Vous pouvez fournir plusieurs paramètres à printf, et notamment System.out.printf("Hello, %s. Next year, you’ll be %d", name, age);
Livre Java.book Page 73 Mercredi, 13. février 2008 2:34 14
Chapitre 3
Structures fondamentales de la programmation Java
73
Chacun des spécificateurs de format qui commencent par le caractère % est remplacé par l’argument correspondant. Le caractère de conversion qui termine un spécificateur de format indique le type de la valeur à mettre en forme : f est un nombre à virgule flottante, s une chaîne et d une valeur décimale. Le Tableau 3.5 montre tous les caractères de conversion. Tableau 3.5 : Conversions pour printf
Caractère de conversion
Type
Exemple
d
Entier décimal
159
x
Entier hexadécimal
9f
o
Entier octal
237
f
Virgule fixe, virgule flottante
15.9
e
Virgule flottante exponentielle
1.59e+01
g
Virgule flottante générale (le plus court entre e et f)
a
Virgule flottante hexadécimale
0x1.fccdp3
s
Chaîne
Hello
c
Caractère
H
b
Valeur booléenne
true
h
Code de hachage
42628b2
tx
Date et heure
Voir Tableau 3.7
%
Symbole du pourcentage
%
n
Séparateur de ligne fonction de la plate-forme
Vous pouvez également spécifier des drapeaux qui contrôleront l’apparence du résultat mis en forme. Le Tableau 3.6 énumère les drapeaux. Par exemple, le drapeau virgule ajoute des séparateurs de groupe. Ainsi, System.out.printf("%,.2f", 1000.0 / 3.0);
affiche 3,333.33
Vous pouvez afficher plusieurs drapeaux, par exemple "%,(.2f", pour utiliser des séparateurs de groupe et inclure les nombres négatifs entre parenthèses. INFO Vous pouvez utiliser la conversion s pour mettre en forme des objets arbitraires. Lorsqu’un objet arbitraire implémente l’interface Formattable, la méthode formatTo de l’objet est appelée. Dans le cas contraire, c’est la méthode toString qui est appelée pour transformer l’objet en chaîne. Nous traiterons de la méthode toString au Chapitre 5 et des interfaces au Chapitre 6.
Livre Java.book Page 74 Mercredi, 13. février 2008 2:34 14
74
Au cœur de Java 2 - Notions fondamentales
Tableau 3.6 : Drapeaux pour printf
Drapeau
Rôle
Exemple
+
Affiche le signe des nombres positifs et négatifs.
+3333.33
espace
Ajoute un espace avant les nombres positifs.
| 3333.33|
0
Ajoute des zéros préalables.
003333.33
-
Justifie le champ à gauche.
|3333.33|
(
Entoure le nombre négatif de parenthèses.
(3333.33)
,
Ajoute des séparateurs de groupe.
3,333.33
# (pour format f)
Inclut toujours une décimale.
3,333
# (pour format x ou o)
Ajoute le préfixe 0x ou 0.
0xcafe
$
Indique l’indice de l’argument à mettre en forme ; par exemple, %1$d %1$x affiche le premier argument en décimal et hexadécimal.
159 9F
<
Met en forme la même valeur que la spécification précédente ; par exemple, %d %= target) { performance = "Satisfactory"; bonus = 100; }
Dans cet extrait de code, toutes les instructions qui se trouvent à l’intérieur des accolades seront exécutées lorsque la valeur de yourSales sera supérieure à la valeur de target (voir Figure 3.7).
Livre Java.book Page 80 Mercredi, 13. février 2008 2:34 14
80
Au cœur de Java 2 - Notions fondamentales
Figure 3.7 Organigramme de l’instruction if. NON yourSales target
OUI
performance =“Satisfactory”
bonus=100
INFO Un bloc (parfois appelé instruction composée) permet de regrouper plus d’une instruction (simple) dans une structure de programmation Java qui, sans ce regroupement, ne pourrait contenir qu’une seule instruction (simple).
L’instruction conditionnelle de Java a l’aspect suivant (voir Figure 3.8) : if (condition)
instruction1
else
instruction2
Par exemple : if (yourSales >= target) { performance = "Satisfactory"; bonus = 100 + 0.01 * (yourSales - target); } else { performance = "Unsatisfactory"; bonus = 0; }
La partie else est toujours facultative. Une directive else est toujours associée à l’instruction if la plus proche. Par conséquent, dans l’instruction if (x = 1.5 * target) { performance = "Fine"; bonus = 500; } else if (yourSales >= target) { performance = "Satisfactory"; bonus = 100; } else { System.out.println("You’re fired"); }
Livre Java.book Page 82 Mercredi, 13. février 2008 2:34 14
82
Au cœur de Java 2 - Notions fondamentales
yourSales 2*target
OUI
performance =“Excellent”
bonus=1000
performance =“Fine”
bonus=500
performance =“Satisfactory”
bonus=100
NON
yourSales 1.5*target
OUI
NON
OUI yourSales target
NON
Print “You’re fired”
Figure 3.9 Organigramme de l’instruction if/else if (branchement multiple).
Boucles La boucle while exécute une instruction (qui peut être une instruction de bloc) tant qu’une condition est vraie. Sa forme générale est la suivante : while (condition) instruction
La boucle while ne s’exécute jamais si la condition est fausse dès le départ (voir Figure 3.10).
Livre Java.book Page 83 Mercredi, 13. février 2008 2:34 14
Chapitre 3
Structures fondamentales de la programmation Java
83
Figure 3.10 Organigramme de l’instruction while. NON balance < goal
OUI
update balance
years++
Print years
Dans le Listing 3.3, nous écrivons un programme permettant de déterminer combien de temps sera nécessaire pour économiser une certaine somme vous permettant de prendre une retraite bien méritée, en supposant que vous déposiez chaque année une même somme d’argent à un taux d’intérêt spécifié. Dans notre exemple, nous incrémentons un compteur et nous mettons à jour le total cumulé dans le corps de la boucle jusqu’à ce que le total excède le montant souhaité : while (balance < goal) { balance += payment; double interest = balance * interestRate / 100; balance += interest; years++; } System.out.println(years + " years.");
Livre Java.book Page 84 Mercredi, 13. février 2008 2:34 14
84
Au cœur de Java 2 - Notions fondamentales
Ne vous fiez pas à ce programme pour prévoir votre retraite. Nous avons laissé de côté quelques détails comme l’inflation et votre espérance de vie. Le test d’une boucle while est effectué avant l’exécution du corps de la boucle. Par conséquent, ce bloc peut ne jamais être exécuté. Si vous voulez être certain que le bloc soit exécuté au moins une fois, vous devez placer la condition de test en fin de boucle. Pour cela, employez une boucle do/while, dont voici la syntaxe : do instruction while (condition);
Cette boucle exécute l’instruction (généralement un bloc) avant de tester la condition. Le programme réexécute le bloc avant d’effectuer 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 prêt à partir à la retraite : do { balance += payment; double interest = balance * interestRate / 100; balance += interest; year++; // afficher le solde actuel . . . // demander si prêt à prendre la retraite // et récupérer la réponse . . . } while (input.equals("N"));
Tant que la réponse de l’utilisateur est "N", la boucle est répétée (voir Figure 3.11). Ce programme est un bon exemple d’une boucle devant être exécutée au moins une fois, car l’utilisateur doit pouvoir vérifier le solde avant de décider s’il est suffisant pour assurer sa retraite. Listing 3.3 : Retirement.java import java.util.*; /** * Ce programme présente une boucle while. * @version 1.20 2004-02-10 * @author Cay Horstmann */ public class Retirement { public static void main(String[] args) { // lire les infos entrées 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();
Livre Java.book Page 85 Mercredi, 13. février 2008 2:34 14
Chapitre 3
Structures fondamentales de la programmation Java
double balance = 0; int years = 0; // mettre à jour le solde du compte tant que cible non atteinte while (balance < goal) { // ajouter versements et intérêts de cette année balance += payment; double interest = balance * interestRate / 100; balance += interest; years++; } System.out.println ("You can retire in " + years + " years."); } }
Figure 3.11 Organigramme de l’instruction do/while. actualiser le solde
imprimer le solde demander "Ready to retire? (Y/N)"
lire l'entrée
OUI input="N"
NON
85
Livre Java.book Page 86 Mercredi, 13. février 2008 2:34 14
86
Au cœur de Java 2 - Notions fondamentales
Listing 3.4 : Retirement2.java import java.util.*; /** * Ce programme présente une boucle do/while. * @version 1.20 2004-02-10 * @author Cay Horstmann */ public class Retirement2 { public static void main(String[] args) { Scanner in = new Scanner(System.in); System.out.print("How much money will you contribute every year?"); double payment = in.nextDouble(); System.out.print("Interest rate in %: "); double interestRate = in.nextDouble(); double balance = 0; int year = 0; String input; // mettre à jour le solde du compte tant que l’utilisateur // n’est pas prêt à prendre sa retraite do { // ajouter versements et intérêts de cette année balance += payment; double interest = balance * interestRate / 100; balance += interest; year++; // afficher le solde actuel System.out.println("After year %d, your balance is %,.2f%n", year, balance); // demander si prêt pour la retraite System.out.print("Ready to retire? (Y/N)"); input = in.next(); } while (input.equals("N")); } }
Boucles déterminées La boucle for est une construction très générale pour gérer une itération contrôlée par un compteur ou une variable similaire, mis à jour après chaque itération. Comme le montre la Figure 3.12, le code suivant affiche les nombres 1 à 10 sur l’écran : for (int i = 1; i 0; i--) System.out.println("Counting down . . . " + i); System.out.println("Blastoff!");
Livre Java.book Page 88 Mercredi, 13. février 2008 2:34 14
88
Au cœur de Java 2 - Notions fondamentales
ATTENTION Soyez prudent lorsque vous testez l’égalité de deux nombres réels. 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 d’arrondi, la valeur finale risque de n’être jamais atteinte. Par exemple, dans la boucle ci-dessus, x saute de 9.99999999999998 à 10.09999999999998, puisqu’il n’existe pas de représentation binaire exacte pour 0.1.
Lorsque vous déclarez une variable dans le premier élément d’une instruction for, la portée de cette variable s’étend jusqu’à la fin du corps de la boucle : for (int i = 1; i largest) largest = v; return largest; }
Appelez simplement la fonction comme ceci : double m = max(3.1, 40.4, -5);
Le compilateur transfère un nouveau double[] { 3.1, 40.4, -5 } à la fonction max. INFO Le transfert d’un tableau comme dernier paramètre d’une méthode avec des paramètres variables est autorisé, par exemple : System.out.printf("%d %s", new Object[] { new Integer(1), "widgets" } );
Vous pouvez donc redéfinir une fonction existante dont le dernier paramètre est un tableau par une méthode disposant de paramètres variables, sans casser un code existant. MessageFormat.format, par exemple, a été amélioré dans ce sens dans Java SE 5.0. Si vous le souhaitez, vous pouvez même déclarer la méthode main sous la forme : public static void main(String... args)
Classes d’énumération Vous avez vu au Chapitre 3 comment définir les types énumérés dans Java SE 5.0 et versions ultérieures. Voici un exemple typique : public enum Size { SMALL, MEDIUM, LARGE, EXTRA_LARGE };
Le type défini par cette déclaration est, en fait, une classe. La classe possède exactement quatre instances ; il n’est pas possible de construire de nouveaux objets. Vous n’avez donc jamais besoin d’utiliser equals pour les valeurs de types énumérés. Utilisez simplement == pour les comparer. Vous pouvez, si vous le souhaitez, ajouter des constructeurs, des méthodes et des champs à un type énuméré. Bien entendu, les constructeurs ne sont invoqués que lorsque les constantes énumérées 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; }
Livre Java.book Page 222 Mercredi, 13. février 2008 2:34 14
222
Au cœur de Java 2 - Notions fondamentales
private String abbreviation; }
Tous les types énumérés sont des sous-classes de la classe Enum, dont ils héritent de plusieurs méthodes. La méthode la plus utile est toString, qui renvoie le nom de la constante énumérée. Ainsi, Size.SMALL.toString() renvoie la chaîne "SMALL". L’inverse de toString est la méthode statique valueOf. Par exemple, l’instruction Size s = (Size) Enum.valueOf(Size.class, "SMALL");
définit s sur Size.SMALL. Chaque type énuméré possède une méthode de valeurs statique qui renvoie un tableau de toutes les valeurs de l’énumération. Par exemple, l’appel : Size[] values = Size.values();
renvoie le tableau avec les éléments Size.SMALL, Size.MEDIUM, Size.LARGE et Size.EXTRA_LARGE. La méthode ordinal donne la position d’une constante énumérée dans la déclaration enum, en partant de zéro. Ainsi, Size.MEDIUM.ordinal() renvoie 1. Le petit programme du Listing 5.5 montre comment fonctionnent les types énumérés. INFO La classe Enum dispose d’un paramètre de type que nous avons ignoré pour des raisons de simplicité. Par exemple, le type énuméré Size étend en fait Enum. Le paramètre de type est utilisé dans la méthode compareTo. Nous verrons cette méthode au Chapitre 6 et les paramètres de type au Chapitre 12.
Listing 5.5 : EnumTest.java import java.util.*; /** * Ce programme présente les types énumérés. * @version 1.0 2004-05-24 * @author Cay Horstmann */ public class EnumTest { public static void main(String[] args) { Scanner in = new Scanner(System.in); System.out.print("Enter a size: (SMALL, MEDIUM, LARGE, EXTRA_LARGE) "); String input = in.next().toUpperCase(); Size size = Enum.valueOf(Size.class, input); System.out.println("size=" + size); System.out.println("abbreviation=" + size.getAbbreviation()); if (size == Size.EXTRA_LARGE) System.out.println("Good job--you paid attention to the _."); } }
Livre Java.book Page 223 Mercredi, 13. février 2008 2:34 14
Chapitre 5
L’héritage
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 5.0
•
static Enum valueOf(Class enumClass, String name)
Renvoie la constante énumérée de la classe donnée, avec le nom donné. •
String toString()
Renvoie le nom de cette constante énumérée. •
int ordinal()
Renvoie la position basée sur zéro de cette constante énumérée dans la déclaration enum. •
int compareTo(E other)
Renvoie un entier négatif si cette constante énumérée arrive avant, zéro si this == other, et un entier positif le reste du temps. L’ordre des constantes est donné par la déclaration enum.
Réflexion La bibliothèque de réflexion constitue une boîte à outils riche et élaborée pour écrire des programmes qui manipulent dynamiquement du code Java. Cette fonctionnalité est très largement utilisée dans JavaBeans, l’architecture des composants Java (voir le Volume II pour en savoir plus sur JavaBeans). Au moyen de la réflexion, Java est capable de supporter des outils comme ceux auxquels les utilisateurs de Visual Basic sont habitués. Précisément, lorsque de nouvelles classes sont ajoutées au moment de la conception ou de l’exécution, des outils de développement d’application rapide peuvent se renseigner dynamiquement sur les capacités des classes qui ont été ajoutées. Un programme qui peut analyser les capacités des classes est appelé réflecteur. Le mécanisme de réflexion est extrêmement puissant. Comme vous le montrent les sections suivantes, il peut être employé pour : m
analyser les capacités des classes au moment de l’exécution ;
m
étudier les objets au moment de l’exécution, par exemple pour écrire une seule méthode toString qui fonctionne pour toutes les classes ;
m
implémenter un code générique pour la manipulation des tableaux ;
m
profiter des objets Method qui se comportent comme les pointeurs de fonction des langages tels que C++.
La réflexion est un mécanisme puissant et complexe ; il intéresse toutefois principalement les concepteurs d’outils, et non les programmeurs d’applications. Si vous vous intéressez à la programmation des applications plutôt qu’aux outils pour les programmeurs Java, vous pouvez ignorer sans problème le reste de ce chapitre et y revenir par la suite.
Livre Java.book Page 224 Mercredi, 13. février 2008 2:34 14
224
Au cœur de Java 2 - Notions fondamentales
La classe Class Lorsque le programme est lancé, le système d’exécution de Java gère, pour tous les objets, ce que l’on appelle "l’identification de type à l’exécution". Cette information mémorise la classe à laquelle chaque objet appartient. L’information de type au moment de l’exécution est employée par la machine virtuelle pour sélectionner les méthodes correctes à exécuter. Vous pouvez accéder à cette information en travaillant avec une classe Java particulière, baptisée singulièrement Class. La méthode getClass() de la classe Object renvoie une instance de type Class : Employee e; . . . Class cl = e.getClass();
Tout comme un objet Employee décrit les propriétés d’un employé particulier, un objet Class décrit les propriétés d’une classe particulière. La méthode la plus utilisée de Class est sans conteste getName, qui renvoie le nom de la classe. Par exemple, l’instruction System.out.println(e.getClass().getName() + " " + e.getName());
affiche 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 réglé sur "java.util.Date"
Vous pouvez aussi obtenir un objet Class correspondant à un nom de classe, à l’aide de la méthode statique forName : String className = "java.util.Date"; Class cl = Class.forName(className);
Cette technique est utilisée si le nom de classe est stocké dans une chaîne qui peut changer à l’exécution. Cela fonctionne lorsque className est bien le nom d’une classe ou d’une interface. Dans le cas contraire, la méthode forName lance une exception vérifiée. Consultez la section "Introduction à l’interception des exceptions" plus loin pour voir comment fournir un gestionnaire d’exception lorsque vous utilisez cette méthode. ASTUCE Au démarrage, la classe contenant votre méthode main est chargée. Elle charge toutes les classes dont elle a besoin. Chacune de ces classes charge les classes qui lui sont nécessaires, et ainsi de suite. Cela peut demander un certain temps pour une application importante, ce qui est frustrant pour l’utilisateur. Vous pouvez donner aux utilisateurs de votre programme l’illusion d’un démarrage plus rapide, à l’aide de l’astuce suivante. Assurez-vous que la classe contenant la méthode main ne fait pas explicitement référence aux autres classes. Affichez d’abord un écran splash, puis forcez manuellement le chargement des autres classes en appelant Class.forName.
Livre Java.book Page 225 Mercredi, 13. février 2008 2:34 14
Chapitre 5
L’héritage
225
Une troisième technique emploie un raccourci pratique pour obtenir un objet de type Class. En effet, si T est d’un type Java quelconque, alors T.class représente l’objet classe correspondant. Voici quelques exemples : Class cl1 = Date.class; // si vous importez java.util.*; Class cl2 = int.class; Class cl3 = Double[].class;
Remarquez qu’un objet Class désigne en réalité un type, qui n’est pas nécessairement une classe. Par exemple, int n’est pas une classe, mais int.class est pourtant un objet du type Class. INFO Depuis Java SE 5.0, la classe Class possède des paramètres. Par exemple, Employee.class est de type Class. Nous ne traiterons pas de ce problème car il compliquerait encore un concept déjà abstrait. Dans un but pratique, vous pouvez ignorer le paramètre de type et travailler avec le type Class brut. Voyez le Chapitre 13 pour en savoir plus sur ce problème.
ATTENTION Pour des raisons historiques, la méthode getName renvoie des noms étranges pour les types tableaux : • Double[].class.getName() renvoie "[Ljava.lang.Double;".
• int[].class.getName() renvoie "[I".
La machine virtuelle gère un unique objet Class pour chaque type. Vous pouvez donc utiliser l’opérateur == pour comparer les objets class, par exemple : if (e.getClass() == Employee.class) . . .
Il existe une autre méthode utile qui permet de créer une instance de classe à la volée. Cette méthode se nomme, assez naturellement, newInstance(). Par exemple, e.getClass().newInstance();
crée une nouvelle instance du même type de classe que e. La méthode newInstance appelle le constructeur par défaut (celui qui ne reçoit aucun paramètre) pour initialiser l’objet nouvellement créé. Une exception est déclenchée si la classe ne dispose pas de constructeur par défaut. Une combinaison de forName et de newInstance permet de créer un objet à partir d’un nom de classe stocké dans une chaîne : String s = "java.util.Date"; Object m = Class.forName(s).newInstance();
INFO Vous ne pourrez pas employer cette technique si vous devez fournir des paramètres au constructeur d’une classe que vous souhaitez instancier à la volée. Vous devrez recourir à la méthode newInstance de la classe Constructor.
Livre Java.book Page 226 Mercredi, 13. février 2008 2:34 14
226
Au cœur de Java 2 - Notions fondamentales
INFO C++ La méthode newInstance correspond à un constructeur virtuel en C++. Cependant, la construction virtuelle en C++ n’est pas une caractéristique du langage, mais une simple tournure idiomatique qui doit être prise en charge par une bibliothèque spécialisée. Class est comparable à la classe type_info de C++ et la méthode getClass équivaut à l’opérateur typeid. Néanmoins, la classe Class de Java est plus souple que type_info qui peut seulement fournir le nom d’un type, et non créer de nouveaux objets de ce type.
Introduction à l’interception d’exceptions La gestion des exceptions est traitée en détail au Chapitre 11 mais, en attendant, vous pouvez rencontrer des cas où les méthodes menacent de lancer des exceptions. Lorsqu’une erreur se produit au moment de l’exécution, un programme peut "lancer une exception". Le lancement d’une exception est plus souple que la terminaison du programme, car vous pouvez fournir un gestionnaire qui "intercepte" l’exception et la traite. Si vous ne fournissez pas de gestionnaire, le programme se termine pourtant et affiche à la console un message donnant le type de l’exception. Vous avez peut-être déjà vu un compte-rendu d’exception si vous employez à tort une référence null ou que vous dépassiez les limites d’un tableau. Il existe deux sortes d’exceptions : vérifiées et non vérifiées (checked/unchecked). Dans le cas d’exceptions vérifiées, le compilateur vérifie que vous avez fourni un gestionnaire. Cependant, de nombreuses exceptions communes, telle qu’une tentative d’accéder à une référence null, ne sont pas vérifiées. Le compilateur ne vérifie pas si vous fournissez un gestionnaire pour ces erreurs — après tout, vous êtes censé mobiliser votre énergie pour éviter ces erreurs plutôt que de coder des gestionnaires pour les traiter. Mais toutes les erreurs ne sont pas évitables. Si une exception peut se produire en dépit de vos efforts, le compilateur s’attendra à ce que vous fournissiez un gestionnaire. Class.forName est un exemple de méthode qui déclenche une exception vérifiée. Vous étudierez, au Chapitre 11, plusieurs stratégies de gestion d’exception. Pour l’instant, nous nous contenterons de voir l’implémentation du gestionnaire le plus simple. Placez une ou plusieurs instructions pouvant lancer des exceptions vérifiées, dans un bloc d’instructions try, puis fournissez le code du gestionnaire dans la clause catch. try { instructions pouvant déclencher des exceptions } catch(Exception e) { action du gestionnaire }
En voici un exemple : try { String name = . . .; // extraire le nom de classe Class cl = Class.forName(name); // peut lancer une exception . . . // faire quelque chose avec cl }
Livre Java.book Page 227 Mercredi, 13. février 2008 2:34 14
Chapitre 5
L’héritage
227
catch(Exception e) { e.printStackTrace(); }
Si le nom de classe n’existe 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 à l’aide de la méthode printStackTrace de la classe Throwable qui est la superclasse de la classe Exception). Si aucune des méthodes dans le bloc try ne déclenche d’exception, le code du gestionnaire dans la clause catch est sauté. Vous n’avez besoin de fournir de gestionnaire d’exception que pour les exceptions vérifiées. Il est facile de déterminer les méthodes qui lancent celles-ci — le compilateur protestera quand vous appellerez une méthode menaçant de lancer une exception vérifiée et que vous ne fournirez pas de gestionnaire. java.lang.Class 1.0 • static Class forName(String className)
Renvoie l’objet Class qui représente la classe ayant pour nom className. •
Object newInstance()
Renvoie une nouvelle instance de cette classe. java.lang.reflect.Constructor 1.1 • Object newInstance(Object[] args)
Construit une nouvelle instance de la classe déclarante du constructeur. Paramètres :
args
Les paramètres fournis au constructeur. Voir la section relative à la réflexion pour plus d’informations sur la façon de fournir les paramètres.
java.lang.Throwable 1.0 • void printStackTrace()
Affiche l’objet Throwable et la trace de la pile dans l’unité d’erreur standard.
La réflexion pour analyser les caractéristiques d’une classe Nous vous proposons un bref aperçu des éléments les plus importants du mécanisme de réflexion, car il permet d’examiner la structure d’une classe. Les trois classes Field, Method et Constructor, qui se trouvent dans le package java.lang.reflect, décrivent respectivement les champs, les méthodes et les constructeurs d’une classe. Ces trois classes disposent d’une méthode getName qui renvoie le nom de l’élément. La classe Field possède une méthode getType renvoyant un objet, de type Class, qui décrit le type du champ. Les classes Method et Constructor possèdent des méthodes permettant d’obtenir les types des paramètres et la classe Method signale aussi le type de retour. Les trois classes possèdent également une méthode appelée getModifiers : elle renvoie un entier dont les bits sont utilisés comme sémaphores pour décrire les modificateurs spécifiés, tels que public ou static. Vous pouvez alors utiliser les méthodes statiques de la classe Modifier du package java.lang.reflect pour analyser les entiers renvoyés par getModifiers. Par exemple, il existe des méthodes telles que isPublic, isPrivate ou isFinal pour déterminer si un constructeur ou une méthode a été déclarée public, private ou final.
Livre Java.book Page 228 Mercredi, 13. février 2008 2:34 14
228
Au cœur de Java 2 - Notions fondamentales
Il vous suffit d’appeler la méthode appropriée de Modifier et de l’utiliser sur l’entier renvoyé par getModifiers. Il est également possible d’employer Modifier.toString pour afficher les modificateurs. Les méthodes getFields, getMethods et getConstructors de la classe Class renvoient dans des tableaux les éléments publics, les méthodes et les constructeurs gérés par la classe. Ces éléments sont des objets de la classe correspondante de java.lang.reflect. Cela inclut les membres publics des superclasses. Les méthodes getDeclaredFields, getDeclaredMethods et getDeclaredConstructors de Class renvoient des tableaux constitués de tous les champs, méthodes et constructeurs déclarés dans la classe, y compris les membres privés et protégés, mais pas les membres des superclasses. Le Listing 5.6 explique comment afficher toutes les informations relatives à une classe. Le programme vous demande le nom d’une classe, puis affiche la signature de chaque méthode et de chaque constructeur ainsi que le nom de chaque champ de données de la classe en question. Par exemple, si vous tapez : java.lang.Double
Le programme affiche : 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; }
Livre Java.book Page 229 Mercredi, 13. février 2008 2:34 14
Chapitre 5
L’héritage
229
Ce qui est remarquable dans ce programme, c’est qu’il peut analyser toute classe apte à être chargée par l’interpréteur, et pas seulement les classes disponibles au moment où le programme est compilé. Nous le réutiliserons au chapitre suivant pour examiner les classes internes générées automatiquement par le compilateur Java. Listing 5.6 : ReflectionTest.java import java.util.*; import java.lang.reflect.*; /** * Ce programme utilise la réflexion pour afficher toutes les * caractéristiques d’une 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 l’entrée de l’utilisateur 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); }
Livre Java.book Page 230 Mercredi, 13. février 2008 2:34 14
230
Au cœur de Java 2 - Notions fondamentales
/** * affiche tous les constructeurs d’une 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 paramètres 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 méthodes d’une 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 méthode String modifiers = Modifier.toString(m.getModifiers()); if (modifiers.length() > 0) System.out.print( modifiers + " "); System.out.print(retType.getName() + " " + name + "("); // imprime les types des paramètres 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(");"); } }
Livre Java.book Page 231 Mercredi, 13. février 2008 2:34 14
Chapitre 5
L’héritage
231
/** * Affiche tous les champs d’une classe * @param cl Une classe */ public static void printFields(Class cl) { Field[] fields = cl.getDeclaredFields(); for (Field f : fields) { Class type = f.getType(); String name = f.getName(); System.out.print(" "); String modifiers = Modifier.toString(f.getModifiers()); if (modifiers.length() > 0) System.out.println( modifiers + " "); System.out.println(type.getName() + " " + name + ";"); } } } java.lang.Class 1.0
• •
Field[] getFields() 1.1 Field[] getDeclaredFields() 1.1
getFields renvoie un tableau contenant les objets Field représentant les champs publics de cette classe ou de ses superclasses. getDeclaredFields renvoie un tableau d’objets Field pour tous les champs de cette classe. Ces méthodes renvoient un tableau de longueur 0 s’il n’y a pas de champ correspondant ou si l’objet Class représente 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 méthodes publiques et inclut des méthodes héritées ; getDeclaredMethods renvoie toutes les méthodes de cette classe ou interface mais n’inclut pas les méthodes héritées. • •
Constructor[] getConstructors() 1.1 Constructor[] getDeclaredConstructors() 1.1
Renvoient un tableau d’objets Constructor représentant tous les constructeurs publics (avec getConstructors) ou tous les constructeurs (avec getDeclaredConstructors) de la classe désignée 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 l’objet Class de la classe qui définit ce constructeur, cette méthode ou ce champ. •
Class[] getExceptionTypes()
Dans les classes Constructor et Method, renvoie un tableau d’objets Class qui représentent les types d’exceptions déclenchées par la méthode.
Livre Java.book Page 232 Mercredi, 13. février 2008 2:34 14
232
•
Au cœur de Java 2 - Notions fondamentales
int getModifiers()
Renvoie un entier décrivant les modificateurs du constructeur, de la méthode ou du champ. Utilisez les méthodes de la classe Modifier pour analyser le résultat. •
String getName()
Renvoie une chaîne qui donne le nom du constructeur, de la méthode ou du champ. •
Class[] getParameterTypes()
Dans les classes Constructor et Method, renvoie un tableau d’objets Class qui représentent les types des paramètres. •
Class getReturnType()
Dans les classes Method, renvoie un objet Class qui représente le type de retour. java.lang.reflect.Modifier 1.1
•
static String toString(int modifiers)
Renvoie une chaîne avec les modificateurs correspondant aux bits définis 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 méthodes testent le bit dans la valeur modifiers qui correspond à l’élément modificateur du nom de la méthode.
La réflexion pour l’analyse des objets à l’exécution Dans la section précédente, nous avons vu comment trouver le nom et le type des champs de n’importe quel objet : m
obtenir l’objet Class correspondant ;
m
appeler getDeclaredFields sur l’objet Class.
Dans cette section, nous allons franchir une étape supplémentaire et étudier le contenu des champs. Bien entendu, il est facile de lire le contenu d’un champ spécifique d’un objet dont le nom et le type sont connus lors de l’écriture du programme. Mais la réflexion nous permet de lire les champs des objets qui n’étaient pas connus au moment de la compilation. A cet égard, la méthode essentielle est la méthode get de la classe Field. Si f est un objet de type Field (obtenu par exemple grâce à 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 paraître un peu abstrait ; nous allons donc prendre un exemple :
Livre Java.book Page 233 Mercredi, 13. février 2008 2:34 14
Chapitre 5
L’héritage
233
Employee harry = new Employee("Harry Hacker", 35000, 10, 1, 1989); Class cl = harry.getClass(); // l’objet class représentant Employee Field f = cl.getDeclaredField("name"); // le champ name de la classe Employee Object v = f.get(harry); // la valeur du champ name de l’objet harry // c’est-à-dire l’objet String "Harry Hacker"
En fait, ce code pose un problème. Comme le champ name est un champ privé, la méthode get déclenche une exception IllegalAccessException (Exception pour accès interdit). Cette méthode ne peut être employée que pour obtenir les valeurs des champs accessibles. Le mécanisme de sécurité de Java vous permet de connaître les champs d’un objet, mais pas de lire la valeur de ces champs si vous n’avez pas une autorisation d’accès. Par défaut, le mécanisme de réflexion respecte le contrôle des accès. Néanmoins, si un programme Java n’est pas contrôlé par un gestionnaire de sécurité qui le lui interdit, il peut outrepasser son droit d’accès. Pour cela, il faut invoquer la méthode setAccessible d’un objet Field, Method ou Constructor : f.setAccessible(true); // les appels f.get(harry) sont maintenant autorisés
La méthode setAccessible se trouve dans la classe AccessibleObject, qui est la superclasse commune des classes Field, Method et Constructor. Cette fonctionnalité est destinée au débogage, au stockage permanent et à des mécanismes similaires. Nous l’emploierons pour étudier une méthode toString générique. La méthode get pose un second problème. Le champ name est de type String, il est donc possible de récupérer la valeur en tant que Object. Mais supposons que nous désirions é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 première consiste à utiliser la méthode getDouble de la classe Field ; la seconde est un appel à get, car le mécanisme de réflexion enveloppe automatiquement la valeur du champ dans la classe enveloppe appropriée, en l’occurrence, Double. Bien entendu, il est possible de modifier les valeurs obtenues. L’appel f.set(obj, value) affecte une nouvelle valeur au champ de l’objet obj représenté par f. Le Listing 5.7 montre comment écrire une méthode toString générique capable de fonctionner avec n’importe quelle classe. Elle appelle d’abord getDeclaredField pour obtenir tous les champs de données. Elle utilise ensuite la méthode setAccessible pour rendre tous ces champs accessibles, puis elle récupère le nom et la valeur de chaque champ. Le Listing 5.7 convertit chaque valeur en chaîne par un appel à sa propre méthode 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();
Livre Java.book Page 234 Mercredi, 13. février 2008 2:34 14
234
Au cœur de Java 2 - Notions fondamentales
AccessibleObject.setAccessible(fields, true); // extraire les noms et valeurs de tous les champs for (Field f : fields) { if (!Modifier.isStatic(f.getModifiers())) { if (!r.endsWith("[")) r += "," r += f.getName() + "="; try { Object val = f.get(obj); r += val.toString(val); } catch (Exception e) { e.printStackTrace(); } } } r += "]"; cl = cl.getSuperclass(); } while (cl != null); return r; } . . . }
La totalité du code du Listing 5.7 doit traiter deux complexités. Les cycles de référence pourraient entraîner une récursion infinie. ObjectAnalyzer suit donc la trace des objets qui ont déjà été visités. De même, pour regarder dans les tableaux, vous devez adopter une approche différente. Vous en saurez plus dans la section suivante. Cette méthode toString peut être employée pour disséquer n’importe quel objet. Par exemple, l’appel ArrayList squares = new ArrayList(); for (int i = 1; i 0) { // réorganiser a[i] et a[j] . . . }
Le compilateur doit savoir que a[i] possède réellement une méthode compareTo. Si a est un tableau d’objets Comparable, l’existence de la méthode est confirmée, car chaque classe qui implémente l’interface Comparable doit fournir la méthode. INFO Il serait logique que la méthode sort dans la classe Arrays soit définie pour accepter un tableau Comparable[], afin que le compilateur puisse protester si quelqu’un appelle sort avec un tableau dont le type d’élément n’implémente pas l’interface Comparable. Malheureusement, il n’en est rien. La méthode sort accepte un tableau Object[] et a recours à un transtypage bizarre : // dans la bibliothèque standard -- Non recommandé if (((Comparable) a[i]).compareTo(a[j]) > 0) { // réorganiser a[i] et a[j] . . . }
Si a[i] n’appartient pas à une classe qui implémente l’interface Comparable, la machine virtuelle déclenche une exception.
Le Listing 6.1 décrit la totalité du code nécessaire pour le tri d’un tableau d’employés. Listing 6.1 : EmployeeSortTest.java import java.util.*; /** * Ce programme présente l’utilisation de l’interface Comparable. * @version 1.30 2004-02-27 * @author Cay Horstmann */
Livre Java.book Page 251 Mercredi, 13. février 2008 2:34 14
Chapitre 6
Interfaces et classes internes
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 { 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 employés * @param other Un autre objet Employee * @return une valeur négative si cet employé * a un salaire inférieur à 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; }
251
Livre Java.book Page 252 Mercredi, 13. février 2008 2:34 14
252
Au cœur de Java 2 - Notions fondamentales
private String name; private double salary; } java.lang.Comparable 1.0 int compareTo(T other)
•
Compare cet objet à d’autres et renvoie un entier négatif si cet objet est inférieur à l’autre, 0 s’ils sont égaux et un entier positif dans les autres cas. java.util.Arrays 1.2 • static void sort(Object[] a)
Trie les éléments du tableau a, à l’aide d’un algorithme mergesort personnalisé. Tous les éléments du tableau doivent appartenir à des classes qui implémentent l’interface Comparable ; ils doivent aussi tous être comparables entre eux. INFO Selon les standards du langage : "L’implémenteur doit s’assurer 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 déclencher une exception si y.compareTo(x) déclenche une exception)." Ici, "sgn" est le signe d’un nombre : sgn(n) vaut –1 si n est négatif, 0 si n égale 0 et 1 si n est positif. En bon français, si vous inversez les paramètres de compareTo, le signe (mais pas nécessairement la valeur réelle) du résultat doit aussi être inversé. Comme avec la méthode equals, des problèmes peuvent survenir lorsque l’on fait appel à l’héritage. Etant donné que Manager étend Employee, il implémente Comparable et non pas Comparable . Si Manager choisit de remplacer compareTo, il doit être préparé à comparer les directeurs aux employés. Il ne peut pas simplement transtyper l’employé en directeur : class Manager extends Employee { public int compareTo(Employee other) { Manager otherManager = (Manager) other; // NON . . . } . . . }
La règle d’"antisymétrie" est violée. Si x est un Employee et y un Manager, l’appel x.compareTo(y) ne déclenche pas d’exception — il compare simplement x et y en tant qu’employés. Mais l’inverse, y.compareTo(x) déclenche une exception ClassCastException. C’est la même situation qu’avec la méthode equals vue au Chapitre 5 ; la solution est la même. Il existe deux scénarios distincts. Si les sous-classes ont des notions différentes de la comparaison, vous devez interdire la comparaison des objets appartenant à des classes différentes. Chaque méthode compareTo devrait commencer par le test if (getClass() != other.getClass()) throw new ClassCastException();
S’il existe un algorithme commun pour comparer les objets de sous-classe, fournissez simplement une seule méthode compareTo dans la superclasse et déclarez-la sous la forme final. Supposons par exemple que vous vouliez que les directeurs aient une meilleure situation que les employés ordinaires, en dehors du salaire. Qu’adviendra-t-il des autres sous-classes comme Executive et Secretary ? Si vous devez établir un ordre hiérarchique, fournissez une méthode dans la classe Employee (par exemple rank). Amenez
Livre Java.book Page 253 Mercredi, 13. février 2008 2:34 14
Chapitre 6
Interfaces et classes internes
253
chaque sous-classe à remplacer rank et implémentez une seule méthode compareTo qui prendra en compte les valeurs du rang.
Propriétés des interfaces Les interfaces ne sont pas des classes. En particulier, vous ne devez jamais utiliser l’opérateur new pour instancier une interface : x = new Comparable(. . .); // ERREUR
Cependant, bien que vous ne puissiez pas construire des objets interface, vous pouvez toujours déclarer des variables interface : Comparable x; // OK
Une variable interface doit faire référence à un objet d’une classe qui implémente l’interface : x = new Employee(. . .); // OK du moment que Employee implémente Comparable
Ensuite, tout comme vous utilisez instanceof pour vérifier si un objet appartient à une classe spécifique, vous pouvez utiliser instanceof pour vérifier si un objet implémente une interface : if (unObjet instanceof Comparable) { . . . }
Tout comme vous pouvez construire des hiérarchies de classes, vous pouvez étendre des interfaces. Cela autorise plusieurs chaînes d’interfaces allant d’un plus large degré de généralité à un plus petit degré de spécialisation. Supposez, par exemple, que vous ayez une interface appelée Moveable : public interface Moveable { void move(double x, double y); }
Vous pourriez alors imaginer une interface appelée Powered qui l’étendrait : public interface Powered extends Moveable { double milesPerGallon(); }
Bien que vous ne puissiez pas mettre des champs d’instance ou des méthodes statiques dans une interface, vous pouvez fournir des constantes à l’intérieur. Par exemple : public interface Powered extends Moveable { double milesPerGallon(); double SPEED_LIMIT = 95; // une constante public static final }
Tout comme les méthodes dans une interface sont automatiquement public, les champs sont toujours public static final. INFO Il est légal de qualifier les méthodes d’interface de public, et les champs de public static final. Certains programmeurs le font, par habitude ou pour une plus grande clarté. Cependant, les spécifications du langage Java recommandent de ne pas fournir de mots clés redondants et nous respectons cette recommandation.
Livre Java.book Page 254 Mercredi, 13. février 2008 2:34 14
254
Au cœur de Java 2 - Notions fondamentales
Certaines interfaces définissent simplement les constantes et pas de méthodes. Par exemple, la bibliothèque standard contient une interface SwingConstants qui définit les constantes NORTH, SOUTH, HORIZONTAL, etc. Toute classe qui choisit d’implémenter l’interface SwingConstants hérite automatiquement de ces constantes. Ses méthodes peuvent faire simplement référence à NORTH au lieu du SwingConstants.NORTH, plus encombrant. Toutefois, cette utilisation des interfaces semble plutôt dégénérée, et nous ne la recommandons pas. Alors que chaque classe ne peut avoir qu’une superclasse, les classes peuvent implémenter plusieurs interfaces. Cela vous donne un maximum de flexibilité pour la définition du comportement d’une classe. Par exemple, le langage Java a une importante interface intégrée, appelée Cloneable (nous reviendrons en détail sur cette interface dans la section suivante). Si votre classe implémente Cloneable, la méthode clone dans la classe Object réalisera une copie fidèle des objets de votre classe. Supposez, par conséquent, que vous vouliez disposer des fonctions de clonage et de comparaison ; il vous suffira d’implémenter les deux interfaces : class Employee implements Cloneable, Comparable
Utilisez des virgules pour séparer les interfaces qui décrivent les caractéristiques que vous voulez fournir.
Interfaces et classes abstraites Si vous avez lu la section concernant les classes abstraites au Chapitre 5, vous vous demandez peutêtre pourquoi les concepteurs du langage Java se sont compliqué la tâche en introduisant le concept d’interface. Pourquoi ne pas faire de Comparable une classe abstraite ? abstract class Comparable // Pourquoi pas ? { public abstract int compareTo(Object other); }
La classe Employee pourrait simplement étendre cette classe abstraite et fournir la méthode compareTo : class Employee extends Comparable // Pourquoi pas ? { public int compareTo(Object other) { . . . } }
Il y a, malheureusement, un problème majeur en ce qui concerne l’utilisation d’une classe de base abstraite pour exprimer une propriété générique. Une classe ne peut étendre qu’une seule classe. Supposons qu’une autre classe dérive déjà de la classe Employee, disons Person. La classe Employee ne peut alors étendre de seconde classe : class Employee extends Person, Comparable // ERREUR
En revanche, chaque classe peut implémenter autant d’interfaces qu’elle le souhaite : class Employee extends Person implements Comparable // OK
Les autres langages de programmation, en particulier C++, autorisent une classe à avoir plus d’une superclasse. Cette fonctionnalité est appelée héritage multiple. Les concepteurs de Java ont choisi de ne pas la prendre en charge, car elle rend le langage soit très complexe (comme dans C++), soit moins efficace (comme dans Eiffel).
Livre Java.book Page 255 Mercredi, 13. février 2008 2:34 14
Chapitre 6
Interfaces et classes internes
255
Les interfaces, elles, procurent la plupart des avantages de l’héritage multiple tout en évitant les risques de complexité et d’inefficacité. INFO C++ Le langage C++ prend en charge l’héritage multiple, avec tous les tracas qui l’accompagnent, comme les classes de base virtuelles, les règles de prédominance et les transtypages de pointeurs transversaux. Peu de programmeurs C++ exploitent réellement l’héritage multiple et certains affirment qu’il ne devrait jamais être utilisé. D’autres conseillent de n’employer l’héritage multiple que pour l’héritage de style "mix-in". Dans ce style, une classe de base primaire décrit l’objet parent et des classes de base additionnelles (les mix-in) peuvent fournir des caractéristiques complémentaires. Ce style est comparable à une classe Java ayant une seule classe de base et des interfaces additionnelles. Néanmoins, en C++, les mix-in peuvent ajouter un comportement par défaut, ce que ne peuvent pas faire les interfaces Java.
Clonage d’objets Lorsque vous faites une copie d’une variable, l’original et la copie sont des références au même objet (voir Figure 6.1). Cela signifie que tout changement apporté à l’une des variables affecte aussi l’autre. Employee original = new Employee("John Public", 50000); Employee copy = original; copy.raiseSalary(10); // aïe--a aussi changé l’original
Si vous voulez que copy soit un nouvel objet qui, au départ, est identique à original, mais dont l’état peut diverger avec le temps, appelez la méthode clone. Employee copy = original.clone(); copy.raiseSalary(10); // OK—-original inchangé
En réalité, les choses ne sont pas si simples. La méthode clone est une méthode protected de Object, ce qui signifie que votre code ne peut pas simplement l’appeler directement. Seule la classe Employee peut cloner des objets Employee. Et il y a une bonne raison à cela. Réfléchissons à la manière dont la classe Object peut implémenter clone. Elle ne sait rien de l’objet et ne peut effectuer qu’une copie champ à champ. Si tous les champs de données de l’objet sont des nombres ou appartiennent à un autre type de base, la copie des champs ne posera pas de problème. Mais si l’objet contient des références à des sous-objets, la copie des champs vous donnera une autre référence au sous-objet. En conséquence, l’original et les objets clonés continueront à partager certaines informations. Pour visualiser ce phénomène, considérons la classe Employee, présentée au Chapitre 4. La Figure 6.2 montre ce qui se passe lorsque vous appelez la méthode clone de la classe Object pour cloner un tel objet Employee. Vous pouvez constater que l’opération de clonage par défaut est "superficielle" (shallow) — elle ne clone pas les objets qui sont référencés à l’intérieur d’autres objets. Le fait que cette copie soit superficielle est-il important ? Cela dépend. Si le sous-objet qui est partagé entre l’original et le clone superficiel est inaltérable, le partage est sûr. Cela se produit certainement si le sous-objet appartient à une classe inaltérable, telle que String. Le sous-objet peut aussi tout simplement rester constant pendant toute la durée de vie de l’objet, sans que des méthodes d’altération ne l’affectent, ni qu’aucune méthode ne produise de référence vers lui.
Livre Java.book Page 256 Mercredi, 13. février 2008 2:34 14
256
Au cœur de Java 2 - Notions fondamentales
Figure 6.1
Copie
Copie et clonage.
original =
Employee
copy =
Clonage
Figure 6.2
original =
Une copie "shallow".
original =
Employee
copy =
Employee
Employee
String
name = salary =
50000.0
hireDay =
copy =
Employee name = salary = hireDay =
50000.0
Date
Livre Java.book Page 257 Mercredi, 13. février 2008 2:34 14
Chapitre 6
Interfaces et classes internes
257
Cependant, assez fréquemment, les sous-objets sont altérables et vous devez redéfinir la méthode clone pour réaliser une copie "intégrale" (deep copy) qui clone aussi les sous-objets. Dans notre exemple, le champ hireDay est un objet Date, qui lui est altérable. Pour chaque classe, vous devez décider si oui ou non : 1. La méthode clone par défaut convient. 2. La méthode clone par défaut peut être corrigée par un appel de clone sur les sous-objets altérables. 3. La méthode clone ne doit pas être tentée. La troisième option est celle par défaut. Pour pouvoir choisir la première ou la deuxième option, une classe doit : 1. Implémenter l’interface Cloneable. 2. Redéfinir la méthode clone avec le modificateur d’accès public. INFO La méthode clone est déclarée protected dans la classe Object afin que votre code ne puisse pas simplement appeler unObjet.clone(). Mais les méthodes protégées ne sont-elles pas accessibles à partir de n’importe quelle sous-classe et toute classe n’est-elle pas une sous-classe de Object ? Heureusement, les règles concernant les accès protégés sont plus subtiles (voir Chapitre 5). Une sous-classe peut appeler une méthode clone protégée, seulement pour cloner ses propres objets. Vous devez redéfinir clone comme publique pour permettre aux objets d’être clonés par n’importe quelle méthode.
Dans ce cas, l’apparence de l’interface Cloneable n’a rien à voir avec l’utilisation normale des interfaces. En particulier, elle ne spécifie pas la méthode clone — qui est héritée de la classe Object. L’interface sert purement de balise, indiquant que le concepteur de la classe comprend le processus de clonage. Les objets sont tellement paranoïaques en ce qui concerne le clonage, qu’ils génèrent une exception vérifiée (checked exception), si un objet requiert le clonage, mais n’implémentent pas cette interface. INFO L’interface Cloneable fait partie des quelques interfaces de balisage que fournit Java (certains programmeurs les appellent interfaces marqueur). Souvenez-vous que l’intérêt habituel d’une interface telle que Comparable est d’assurer qu’une classe implémente une méthode ou un jeu de méthodes spécifique. Une interface de balisage n’a pas de méthodes ; son seul objectif est de permettre l’utilisation de instanceof dans une requête de type : if (obj instanceof Cloneable) . . .
Nous vous recommandons de ne pas utiliser les interfaces de balisage dans vos programmes.
Même si l’implémentation par défaut (copie superficielle) de clone est adéquate, vous devez toujours implémenter l’interface Cloneable, redéfinir clone comme public, appeler super.clone(). Voici un exemple : class Employee implements Cloneable {
Livre Java.book Page 258 Mercredi, 13. février 2008 2:34 14
258
Au cœur de Java 2 - Notions fondamentales
// élever le niveau de visibilité à public, // changer le type de retour public Employee clone() throws CloneNotSupportedException { Return (Employee) super.clone(); } . . . }
INFO Avant Java SE 5.0, la méthode clone avait toujours un type de retour Object. Les types de retours covariants de Java SE 5.0 vous permettent de spécifier le type de retour correct pour vos méthodes clone.
La méthode clone que vous venez de voir n’ajoute aucune fonctionnalité à la copie "shallow" fournie par Object.clone. Elle se contente de rendre la méthode public. Pour réaliser une copie intégrale, vous devrez poursuivre l’exercice et cloner les champs d’instance altérables. Voici un exemple de méthode clone qui crée une copie intégrale : class Employee implements Cloneable { . . . public Employee clone() throws CloneNotSupportedException { // appeler Object.clone() Employee cloned = (Employee) super.clone(); // cloner les champs altérables cloned.hireDay = (Date) hireDay.clone() return cloned; } }
La méthode clone de la classe Object menace de lancer une exception CloneNotSupportedException, et ce dès que clone est appelée sur un objet dont la classe n’implémente pas l’interface Cloneable. Bien entendu, les classes Employee et Date implémentent l’interface Cloneable, l’exception ne sera donc pas déclenchée. Toutefois, le compilateur ne le sait pas. Nous avons donc déclaré l’exception : public Employee clone() throws CloneNotSupportedException
Vaudrait-il mieux l’intercepter ? public Employee clone() { try { return super.clone(); } catch (CloneNotSupportedException e) { return null; } // ceci n’arriverait pas, puisque nous utilisons Cloneable }
Livre Java.book Page 259 Mercredi, 13. février 2008 2:34 14
Chapitre 6
Interfaces et classes internes
259
Ceci convient bien pour les classes final. Dans les autres cas, il vaut mieux laisser le spécificateur throws à sa place. Les sous-classes ont alors l’option 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 défini la méthode clone pour la classe Employee, n’importe qui peut aussi cloner les objets Manager. La méthode clone Employee peut-elle assurer cette tâche ? Cela dépend des champs de la classe Manager. Dans notre cas, cela ne pose pas de problème, car le champ bonus est du type primitif. Mais Manager pourrait avoir acquis des champs qui exigent une copie intégrale ou qui ne peuvent pas être clonés. Il n’est absolument pas garanti que l’implémenteur de la sous-classe dispose d’un clone fixe pour réaliser l’action adéquate. Vous devez donc vous assurer que la méthode clone est déclarée comme protégée (protected) dans la classe Object. Mais vous n’aurez pas ce luxe si vous souhaitez que les utilisateurs de vos classes puissent appeler clone. Devez-vous alors implémenter clone dans vos propres classes ? Si vos clients doivent réaliser des copies intégrales, probablement oui. Certains auteurs considèrent toutefois qu’il vaut mieux éviter totalement clone et implémenter à sa place une autre méthode dans ce but. Nous convenons que clone est plutôt étrange, mais vous rencontrerez les mêmes problèmes en utilisant une autre méthode. De toute façon, le clonage est moins commun que vous pourriez le penser. Moins de 5 % des classes de la bibliothèque standard implémentent clone. Le programme du Listing 6.2 clone un objet Employee, puis invoque deux méthodes d’altération. La méthode raiseSalary change la valeur du champ salary, alors que la méthode setHireDay change l’état du champ hireDay. Aucune de ces altérations n’affecte l’objet original, car clone a été définie pour faire une copie intégrale. INFO Tous les types de tableaux possèdent une méthode clone qui est public et non protected. Vous pouvez l’utiliser pour créer un nouveau tableau qui contiendra des copies de tous les éléments. 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 mécanisme pour cloner les objets à l’aide de la fonction de sérialisation d’objet de Java. Ce mécanisme est facile à implémenter et sûr, mais il n’est pas très efficace.
Listing 6.2 : CloneTest.java import java.util.*; /** * Ce programme présente le clonage. * @version 1.10 2002-07-01 * @author Cay Horstmann */ public class CloneTest {
Livre Java.book Page 260 Mercredi, 13. février 2008 2:34 14
260
Au cœur de Java 2 - Notions fondamentales
public static void main(String[] args) { try { Employee original = new Employee("John Q. Public", 50000); original.setHireDay(2000, 1, 1); Employee copy = original.clone(); copy.raiseSalary(10); copy.setHireDay(2002, 12, 31); System.out.println("original=" + original); System.out.println("copy=" + copy); } catch (CloneNotSupportedException e) { e.printStackTrace(); } } } class Employee implements Cloneable { public Employee(String n, double s) { name = n; salary = s; hireDay = new Date(); } public Employee clone() throws CloneNotSupportedException { // appeler Object.clone() Employee cloned = (Employee) super.clone(); // cloner les champs altérables cloned.hireDay = (Date) hireDay.clone(); return cloned; } /** * Affecte une date donnée au jour d’embauche (hireday) * @param year L’année du jour d’embauche * @param month Le mois du jour d’embauche * @param day Le jour d’embauche */ public void setHireDay(int year, int month, int day) { Date newHireDay = new GregorianCalendar(year, month - 1, day).getTime(); // Exemple de mutation de champ d’instance hireDay.setTime(newHireDay.getTime()); } public void raiseSalary(double byPercent) { double raise = salary * byPercent / 100; salary += raise; }
Livre Java.book Page 261 Mercredi, 13. février 2008 2:34 14
Chapitre 6
Interfaces et classes internes
261
public String toString() { return "Employee[name=" + name + ",salary=" + salary + ",hireDay=" + hireDay() + "]"; } private String name; private double salary; private Date hireDay; }
Interfaces et callbacks Un pattern habituel de la programmation est celui des callbacks. Dans ce pattern, vous indiquez l’action à réaliser en cas de survenue d’un événement particulier. Vous pourriez vouloir, par exemple, qu’une action particulière survienne lorsque l’on clique sur un bouton ou que l’on sélectionne un élément de menu. Toutefois, comme vous n’avez pas encore vu comment implémenter les interfaces utilisateur, nous envisagerons une situation similaire, mais plus simple. Le package javax.swing contient une classe Timer, utile pour être averti de l’expiration d’un délai imparti. Par exemple, si une partie de votre programme contient une horloge, vous pouvez demander à être averti à chaque seconde, de manière à pouvoir mettre à jour l’affichage de l’horloge. Lorsque vous construisez un minuteur, vous définissez l’intervalle de temps et lui indiquez ce qu’il doit faire lorsque le délai est écoulé. Comment indiquer au minuteur ce qu’il doit faire ? Dans de nombreux langages de programmation, vous fournissez le nom d’une fonction que le minuteur doit appeler périodiquement. Toutefois, les classes de la bibliothèque Java adoptent une approche orientée objet. Vous transférez un objet d’une classe quelconque. Le minuteur appelle alors l’une des méthodes de cet objet. Transférer un objet est une opération plus flexible que transférer une fonction car l’objet peut transporter des informations complémentaires. Bien entendu, le minuteur doit savoir quelle méthode appeler. Vous devez spécifier un objet d’une classe qui implémente l’interface ActionListener du package java.awt.event. Voici cette interface : public interface ActionListener { void actionPerformed(ActionEvent event); }
Le minuteur appelle la méthode actionPerformed lorsque le délai a expiré. INFO C++ Comme vous l’avez vu au Chapitre 5, Java possède l’équivalent des pointeurs de fonction, à savoir les objets Method. Ils sont toutefois difficiles à utiliser, plus lents et la sécurité des types ne peut pas être vérifiée au moment de la compilation. Dès que vous utilisez un pointeur de fonction en C++, envisagez d’utiliser une interface en Java.
Livre Java.book Page 262 Mercredi, 13. février 2008 2:34 14
262
Au cœur de Java 2 - Notions fondamentales
Supposons que vous souhaitiez afficher le message "At the tone, the time is …" (à la tonalité, il sera…), suivi d’un bip, et ce toutes les 10 secondes. Définissez une classe qui implémente l’interface ActionListener. Vous placerez alors toutes les instructions que vous voulez voir exécuter dans la méthode 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 paramètre ActionEvent de la méthode actionPerformed. Il apporte des informations sur l’événement, comme l’objet source qui l’a généré (voir le Chapitre 8 pour en savoir plus). Toutefois, il n’est pas important d’obtenir des informations détaillées sur ce programme et vous pouvez ignorer le paramètre en toute sécurité. 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 paramètre du constructeur Timer correspond au délai qui doit s’écouler entre les notifications, mesuré en millièmes de seconde. Nous voulons être avertis toutes les 10 secondes. Le deuxième paramètre est l’objet écouteur. Enfin, vous démarrez le minuteur : t.start();
Toutes les 10 secondes, un message du type At the tone, the time is Thu Apr 13 23:29:08 PDT 2000
s’affiche, suivi d’un bip. Le Listing 6.3 fait fonctionner le minuteur et son écouteur. Une fois le minuteur démarré, le programme affiche un message et attend que l’utilisateur clique sur OK pour s’arrêter. Entre-temps, l’heure actuelle s’affiche par intervalles de 10 secondes. Soyez patient lorsque vous exécutez le programme. La boîte de dialogue "Quit program?" (fermer le programme ?) s’affiche immédiatement, mais le premier message du minuteur n’apparaît qu’après 10 secondes. Sachez que le programme importe la classe javax.swing.Timer par nom, en plus d’importer javax.swing.* et java.util.*. Ceci annule l’ambiguïté qui existait entre javax.swing.Timer et java.util.Timer, une classe non liée pour programmer les tâches d’arrière-plan. Listing 6.3 : TimerTest.java /** * @version 1.00 2000-04-13 * @author Cay Horstmann */ import java.awt.*;
Livre Java.book Page 263 Mercredi, 13. février 2008 2:34 14
Chapitre 6
Interfaces et classes internes
263
import java.awt.event.*; import java.util.*; import javax.swing.*; import javax.swing.Timer; // résoudre le conflit avec java.util.Timer public class TimerTest { public static void main(String[] args) { ActionListener listener = new TimePrinter(); // construit un minuteur qui appelle l’écouteur // toutes les 10 secondes Timer t = new Timer(10000, listener); t.start(); JOptionPane.showMessageDialog(null, "Quit program?"); System.exit(0); } } class TimePrinter implements ActionListener { public void actionPerformed(ActionEvent event) { Date now = new Date(); System.out.println("At the tone, the time is " + now); Toolkit.getDefaultToolkit().beep(); } } javax.swing.JOptionPane 1.2
•
static void showMessageDialog(Component parent, Object message)
Affiche une boîte de dialogue avec une invite et un bouton OK. La boîte de dialogue est centrée sur le composant parent. Si parent est null, la boîte de dialogue est centrée à l’écran. javax.swing.Timer 1.2 • Timer(int interval, ActionListener listener)
Construit un minuteur qui avertit l’écouteur lorsque les millièmes de seconde de l’intervalle se sont écoulés. •
void start()
Démarre le minuteur. Une fois lancé, il appelle actionPerformed sur ses écouteurs. •
void stop()
Arrête le minuteur. Une fois arrêté, il n’appelle plus actionPerformed sur ses écouteurs. javax.awt.Toolkit 1.0 • static Toolkit getDefaultToolkit()
Récupère la boîte à outils par défaut. Une boîte à outils contient des informations sur l’environnement de l’interface graphique utilisateur. •
void beep()
Emet un bip.
Livre Java.book Page 264 Mercredi, 13. février 2008 2:34 14
264
Au cœur de Java 2 - Notions fondamentales
Classes internes Une classe interne est une classe qui est définie à l’intérieur d’une autre classe. Trois raisons justifient l’emploi de classes internes : m
Les méthodes de classe internes peuvent accéder aux données, à partir de la portée où elles sont définies, y compris les données qui pourraient être des données privées.
m
Les classes internes peuvent être cachées aux autres classes du même package.
Les classes internes anonymes sont utiles pour définir des callbacks sans écrire beaucoup de code. Nous allons diviser ce sujet assez complexe en plusieurs étapes : m
m
A partir de la section suivante, vous verrez une classe interne simple qui accède à un champ d’instance de sa classe externe.
m
A la section "Règles particulières de syntaxe pour les classes internes", nous verrons les règles de syntaxe spéciales pour les classes internes.
m
A la section "Utilité, nécessité et sécurité des classes internes", nous étudierons les classes internes pour voir comment les traduire en classes ordinaires. Les lecteurs délicats pourront sauter cette section.
m
A la section "Classes internes locales", nous discuterons des classes internes locales qui peuvent accéder aux variables locales dans la portée qui les englobe.
m
A la section "Classes internes anonymes", nous présenterons les classes internes anonymes et montrerons comment les utiliser habituellement pour implémenter les callbacks.
m
Enfin, à partir de la section "Classes internes statiques", vous verrez comment utiliser les classes internes statiques pour les classes d’aide imbriquées. INFO C++
Le langage C++ permet l’emploi de classes imbriquées. Une classe imbriquée se situe dans la portée de la classe qui l’englobe. Voici un exemple typique ; une classe de liste liée définit une classe permettant de stocker les liens et une classe qui détermine une position d’itération : class LinkedList { public: class Iterator // une classe imbriquée { public: void insert(int x); int erase(); . . . }; . . . private: class Link // une classe imbriquée { public: Link* next; int data; }; . . . };
Livre Java.book Page 265 Mercredi, 13. février 2008 2:34 14
Chapitre 6
Interfaces et classes internes
265
L’imbrication est une relation entre les classes, non entre les objets. Un objet LinkedList n’a pas de sous-objets de types Iterator ou Link. Cela procure deux avantages : le contrôle de nom et le contrôle d’accès. Puisque le nom Iterator est imbriqué dans la classe LinkedList, il est connu à l’extérieur sous la forme LinkedList::Iterator et ne peut pas entrer en conflit avec une autre classe baptisée Iterator. En Java, cet avantage est moins important, car les packages Java procurent un contrôle 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 n’y a aucun risque à déclarer ses champs public. Ils ne sont accessibles qu’aux méthodes de la classe LinkedList (qui a le besoin légitime d’y accéder). Ils sont invisibles à l’extérieur de LinkedList. En Java, ce genre de contrôle n’était pas possible avant l’arrivée des classes internes. Les classes internes de Java possèdent cependant une caractéristique supplémentaire qui les rend plus riches, et donc plus utiles que les classes imbriquées de C++. Un objet d’une classe interne possède une référence implicite à l’objet de la classe externe qui l’a instancié. Grâce à ce pointeur, il peut accéder à tous les champs de l’objet externe. Nous verrons dans ce chapitre les détails de ce mécanisme. En Java, les classes internes static ne sont pas dotées de ce pointeur. Elles sont exactement équivalentes aux classes imbriquées de C++.
Accéder à l’état d’un objet à l’aide d’une classe interne La syntaxe des classes internes est assez complexe. C’est pourquoi nous utiliserons un exemple simple, bien que peu réaliste, pour démontrer l’usage des classes internes. Nous allons refactoriser l’exemple TimerTest et extraire une classe TalkingClock. Une horloge parlante se construit avec deux paramètres : l’intervalle entre les annonces et une balise pour activer ou désactiver le bip : public class TalkingClock { public TalkingClock(int interval, boolean beep) public void start() { . . . }
{ . . . }
private int interval; private boolean beep; public class TimePrinter implements ActionListener // une classe interne { . . . } }
Remarquez la classe TimePrinter, qui se trouve à l’intérieur de la classe TalkingClock. Cela ne signifie pas que chaque TalkingClock possède un champ d’instance TimePrinter. Comme vous le verrez, les objets TimePrinter sont construits par les méthodes de la classe TalkingClock. Voici la classe TimePrinter plus en détail. Sachez que la méthode actionPerformed vérifie la balise du bip avant d’émettre 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(); } }
Livre Java.book Page 266 Mercredi, 13. février 2008 2:34 14
266
Au cœur de Java 2 - Notions fondamentales
Une chose surprenante se passe. La classe TimePrinter n’a pas de champ d’instance ni de variable nommée beep. En fait, beep fait référence au champ de l’objet TalkingClock qui a créé TimePrinter. C’est une innovation. Traditionnellement, une méthode pouvait référencer les champs de l’objet qui l’invoquait. Une méthode de la classe interne a accès à la fois à ses propres champs et à ceux de l’objet externe créateur. Pour que tout cela fonctionne, un objet d’une classe interne obtient toujours une référence implicite à l’objet qui l’a créé (voir Figure 6.3). Cette référence est invisible dans la définition de la classe interne. Pour faire la lumière sur ce concept, nous allons appeler la référence à l’objet externe outer. La méthode 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 référence de classe externe est définie dans le constructeur. Le compilateur modifie tous les constructeurs de classe interne, en ajoutant un paramètre pour la référence de classe externe. Etant donné que TimePrinter ne définit aucun constructeur, le compilateur synthétise un constructeur par défaut, générant un code comme celui-ci : public TimePrinter(TalkingClock clock) // Code généré automatiquement { outer = clock; }
Figure 6.3 Un objet d’une classe interne possède une référence à un objet d’une classe externe.
TimePrinter outer =
TalkingClock interval = beep =
1000 true
Notez bien que outer n’est pas un mot clé du langage Java. Nous l’avons uniquement utilisé pour illustrer le mécanisme mis en œuvre dans une classe interne. Lorsqu’un objet TimePrinter est construit dans la méthode start, le compilateur passe la référence this au constructeur dans l’horloge parlante courante : ActionListener listener = new TimePrinter( this); // paramètre ajouté automatiquement
Livre Java.book Page 267 Mercredi, 13. février 2008 2:34 14
Chapitre 6
Interfaces et classes internes
267
Le Listing 6.4 décrit le programme complet qui teste la classe interne. Examinez de nouveau le contrôle d’accès. Si la classe TimePrinter avait été une classe régulière, il aurait fallu accéder à la balise beep par l’intermédiaire d’une méthode publique de la classe TalkingClock. L’emploi d’une classe interne constitue une amélioration. Il n’est pas nécessaire de fournir des méthodes d’accès qui n’intéressent qu’une seule autre classe. INFO Nous aurions pu déclarer la classe TimePrinter comme private. Dans ce cas, seules les méthodes TalkingClock auraient pu construire les objets TimePrinter. Seules les classes internes peuvent être privées. Les classes ordinaires ont toujours une visibilité soit publique, soit sur le package.
Listing 6.4 : InnerClassTest.java import import import import import
java.awt.*; java.awt.event.*; java.util.*; javax.swing.*; javax.swing.Timer;
/** * Ce programme présente l’utilisation 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 l’utilisateur // clique sur "OK" JOptionPane.showMessageDialog(null, "Quit program?"); System.exit(0); } } /** * Une horloge qui affiche l’heure à intervalles réguliers. */ class TalkingClock { /** * Construit une horloge parlante * @param interval L’intervalle entre les messages * (en millièmes de seconde) * @param beep true si l’horloge doit sonner */ public TalkingClock(int interval, boolean beep) { this.interval = interval; this.beep = beep; }
Livre Java.book Page 268 Mercredi, 13. février 2008 2:34 14
268
Au cœur de Java 2 - Notions fondamentales
/** * Lance l’horloge. */ 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(); } } }
Règles particulières de syntaxe pour les classes internes Dans la section précédente, nous avons explicité la référence de classe externe d’une classe interne en la baptisant outer. En réalité, la syntaxe correcte pour la référence externe est un peu plus complexe. L’expression ClasseExterne.this
indique la référence de classe externe. Vous pouvez par exemple, écrire la méthode actionPerformed de la classe interne TimePrinter de la façon suivante : public void actionPerformed(ActionEvent event) { ... if (TalkingClock.this.beep) Toolkit.getDefaultToolkit().beep(); }
Inversement, vous pouvez écrire le constructeur de l’objet interne plus explicitement, à l’aide de la syntaxe : objetExterne.new ClasseInterne(paramètres de construction)
Par exemple, ActionListener listener = this.new TimePrinter();
Ici, la référence à la classe externe de l’objet TimePrinter nouvellement construit est définie par la référence this de la méthode qui crée l’objet de la classe interne. C’est le cas le plus courant. Comme toujours, l’indication this. est redondante. Néanmoins, il est également possible de donner à la référence 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 n’importe quelle horloge parlante : TalkingClock jabberer = new TalkingClock(1000, true); TalkingClock.TimePrinter listener = jabberer.new TimePrinter();
Livre Java.book Page 269 Mercredi, 13. février 2008 2:34 14
Chapitre 6
Interfaces et classes internes
269
Notez que vous faites référence à une classe interne de la façon suivante ClasseExterne.ClasseInterne
lorsqu’elle se trouve être hors de la portée de la classe externe.
Utilité, nécessité et sécurité des classes internes Lorsque les classes internes ont été ajoutées au langage Java dans Java 1.1, de nombreux programmeurs les ont considérées comme une nouvelle fonctionnalité majeure qui était hors de propos dans la philosophie Java. La syntaxe est complexe (nous le verrons en examinant les classes internes anonymes dans la suite de ce chapitre). Il n’est pas évident de saisir l’interaction des classes internes avec d’autres caractéristiques du langage comme le contrôle d’accès et la sécurité. En ajoutant une fonctionnalité élégante et intéressante plutôt que nécessaire, Java a-t-il commencé à suivre la voie funeste de tant d’autres langages, en se dotant d’une caractéristique élégante et intéressante, mais pas nécessaire ? Nous ne donnerons pas une réponse définitive, mais notez que les classes internes constituent un phénomène qui est lié au compilateur et non à la machine virtuelle. Les classes internes sont traduites en fichiers de classe réguliers, avec des signes $ délimitant les noms des classes externes et internes et la machine virtuelle Java ne les reconnaît pas comme une particularité. Par exemple, la classe TimePrinter à l’intérieur de la classe TalkingClock est traduite en un fichier de classe TalkingClock$TimePrinter.class. Pour le constater, faites cette expérience : exécutez le programme ReflectionTest du Chapitre 5 et donnez-lui comme classe de réflexion la classe TalkingClock$TimePrinter. Vous pouvez aussi simplement employer l’utilitaire javap : javap –private nomClasse
INFO Si vous utilisez UNIX, n’oubliez pas d’échapper le caractère $ si vous fournissez le nom de classe sur la ligne de commande. En fait, exécutez le programme ReflectionTest ou javap sous la forme : java ReflectionTest TalkingClock\$TimePrinter
ou javap –private TalkingClock\$TimePrinter
Vous obtenez donc l’affichage suivant : public class TalkingClock$TimePrinter { public TalkingClock$TimePrinter(TalkingClock); public void actionPerformed(java.awt.event.ActionEvent); final TalkingClock this$0; }
Vous pouvez voir que le compilateur a généré un champ d’instance supplémentaire, this$0, pour la référence à la classe externe (le nom this$0 est synthétisé par le compilateur — vous ne pouvez pas y faire référence dans le code source). Vous pouvez aussi voir le paramètre TalkingClock ajouté pour le constructeur.
Livre Java.book Page 270 Mercredi, 13. février 2008 2:34 14
270
Au cœur de Java 2 - Notions fondamentales
Si le compilateur peut effectuer automatiquement cette transformation, ne pourrait-on pas simplement programmer manuellement le même mécanisme ? Essayons. Nous allons faire de TimePrinter une classe régulière, extérieure à la classe TalkingClock. Lors de la construction d’un objet TimePrinter, nous lui passerons la référence this de l’objet qui le crée : class TalkingClock { . . . public void start() { ActionListener listener = new TimePrinter(this); Timer t = new Timer(interval, listener); t.start(); } } class TimePrinter implements ActionListener { public TimePrinter(TalkingClock clock) { outer = clock; } . . . private TalkingClock outer; }
Examinons maintenant la méthode actionPerformed. Elle doit pouvoir accéder à outer.beep. if (outer.beep) . . . // ERREUR
Nous venons de rencontrer un problème. La classe interne peut accéder aux données privées de la classe externe, mais notre classe externe TimePrinter ne le peut pas. Nous voyons bien que les classes internes sont intrinsèquement plus puissantes que les classes régulières, puisqu’elles ont un privilège d’accès supérieur. Il est légitime de se demander comment les classes internes peuvent acquérir ce privilège d’accès supérieur, alors qu’elles sont traduites en classes régulières simplement dotées de noms particuliers — la machine virtuelle ne les distingue pas. Pour résoudre ce mystère, utilisons de nouveau le programme ReflectionTest afin d’espionner 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 méthode statique access$0 ajoutée par le compilateur à la classe externe. Elle renvoie le champ beep de l’objet transféré en tant que paramètre. Elle est appelée par les méthodes de la classe interne. L’instruction if (beep)
Livre Java.book Page 271 Mercredi, 13. février 2008 2:34 14
Chapitre 6
Interfaces et classes internes
271
dans la méthode actionPerformed de la classe TimePrinter réalise, en fait, l’appel suivant : if (access$0(outer));
Y a-t-il un risque pour la sécurité ? Bien sûr. Il est facile à un tiers d’invoquer la méthode access$0 pour lire le champ privé beep. Bien entendu, access$0 n’est pas un nom autorisé pour une méthode Java. Néanmoins, pour des pirates familiers de la structure des fichiers de classe, il est facile de produire un fichier de classe avec des instructions (machine virtuelle) qui appellent cette méthode, par exemple en utilisant un éditeur hexadécimal. Les méthodes d’accès secrètes ayant une visibilité au niveau du package, le code d’attaque devrait être placé dans le même package que la classe attaquée. En résumé, si une classe interne accède à un champ privé, il est possible d’accéder à ce champ par le biais de classes ajoutées au package de la classe externe, mais une telle opération exige du talent et de la détermination. Un programmeur ne peut pas obtenir accidentellement un tel privilège d’accès ; pour y parvenir, il doit intentionnellement créer ou modifier un fichier de classe. INFO Les constructeurs et méthodes synthétisés peuvent être assez complexes (passez cette info si vous n’avez pas le courage d’aborder ce sujet). Supposons que nous transformions TimePrinter en classe interne privée. Il n’existe pas de classe privée 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 méthode start de la classe TalkingClock en new TalkingClock$TimePrinter(this, null)
Classes internes locales Si vous examinez attentivement le code de l’exemple TalkingClock, vous constaterez que vous n’avez besoin qu’une seule fois du nom du type TimePrinter : lorsque vous créez un objet de ce type dans la méthode start. Dans une telle situation, vous pouvez définir les classes localement à l’intérieur d’une seule méthode : public void start() { class TimePrinter implements ActionListener { public void actionPerformed(ActionEvent event) { Date now = new Date(); System.out.println("At the tone, the time is " + now); if (beep) Toolkit.getDefaultToolkit().beep(); } }
Livre Java.book Page 272 Mercredi, 13. février 2008 2:34 14
272
Au cœur de Java 2 - Notions fondamentales
ActionListener listener = new TimePrinter(); Timer t = new Timer(interval, listener); t.start(); }
Les classes locales ne sont jamais déclarées avec un spécificateur d’accès (c’est-à-dire public ou private). Leur portée est toujours restreinte au bloc dans lequel elles sont déclarées. Les classes locales présentent l’immense avantage d’être complètement cachées au monde extérieur, et même au reste du code de la classe TalkingClock. A part start, aucune méthode ne connaît l’existence de la classe TimePrinter.
Accès aux variables final à partir de méthodes externes Les classes locales ont un autre avantage sur les autres classes internes. Elles peuvent non seulement accéder aux champs de leurs classes externes, mais également aux variables locales ! Ces variables locales doivent être déclarées final. Voici un exemple typique. Déplaçons les paramètres interval et beep du constructeur TalkingClock à la méthode start : public void start(int interval, final boolean beep) { class TimePrinter implements ActionListener { public void actionPerformed(ActionEvent event) { Date now = new Date(); System.out.println("At the tone, the time is " + now); if (beep) Toolkit.getDefaultToolkit().beep(); } } ActionListener listener = new TimePrinter(); Timer t = new Timer(interval, listener); t.start(); }
Notez que la classe TalkingClock n’a plus besoin de stocker une variable d’instance beep. Elle fait simplement référence à la variable paramètre beep de la méthode start. Cela n’est peut-être pas si surprenant. La ligne if (beep) . . .
est située au plus profond de la méthode start, alors pourquoi ne pourrait-elle pas avoir accès à la variable beep ? Afin de comprendre ce problème délicat, examinons de plus près le flux d’exécution : 1. La méthode start est appelée. 2. La variable objet listener est initialisée par un appel au constructeur de la classe interne TimePrinter. 3. La référence à listener est passée au constructeur de Timer, le temporisateur est démarré et la méthode start se termine. A ce moment, la variable paramètre beep de la méthode start n’existe plus. 4. Une seconde plus tard, la méthode actionPerformed exécute if (beep) . . .
Livre Java.book Page 273 Mercredi, 13. février 2008 2:34 14
Chapitre 6
Interfaces et classes internes
273
Pour que le code de la méthode actionPerformed fonctionne, la classe TimePrinter doit avoir fait une copie de la valeur de paramètre beep avant qu’il ne disparaisse en tant que variable locale de la méthode start. Et c’est exactement ce qui se passe. Dans notre exemple, le compilateur synthétise 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 paramètre boolean du constructeur et la variable d’instance val$beep. Lorsqu’un objet est créé, la valeur beep est passée au constructeur et stockée dans le champ val$beep. Le compilateur détecte l’accès des variables locales, crée des champs d’instance pour chacune d’elles et copie les variables locales dans le constructeur afin que les champs d’instance soient initialisés. Du point de vue du programmeur, l’accès aux variables locales est assez plaisant. Il simplifie vos classes internes en réduisant le nombre des champs d’instance que vous devez programmer explicitement. Comme nous l’avons déjà signalé, les méthodes d’une classe locale peuvent uniquement faire référence à des variables locales déclarées final. C’est la raison pour laquelle le paramètre beep a été déclaré final dans notre exemple. Une variable locale déclarée final ne peut pas être modifiée après avoir été initialisée. Ainsi, nous avons la garantie que la variable locale et sa copie dans la classe locale auront bien la même valeur. INFO Vous avez déjà vu des variables final utilisées en tant que constantes, à l’image de ce qui suit : public static final double SPEED_LIMIT = 55;
Le mot clé final peut être appliqué aux variables locales, aux variables d’instance et aux variables statiques. Dans tous les cas, cela signifie la même chose : cette variable ne peut être affectée qu’une seule fois après sa création. Il n’est pas possible d’en modifier ultérieurement la valeur — elle est définitive. Cela dit, il n’est pas obligatoire d’initialiser une variable final lors de sa définition. Par exemple, la variable paramètre final beep est initialisée une fois après sa création, lorsque la méthode start est appelée (si elle est appelée plusieurs fois, chaque appel crée son propre paramètre beep). La variable d’instance val$beep, que nous avons vue dans la classe interne TalkingClock$1TimePrinter, est définie une seule fois, dans le constructeur de la classe interne. Une variable final qui n’est pas initialisée lors de sa définition est souvent appelée variable finale vide.
La restriction final est assez peu pratique. Supposons, par exemple, que vous souhaitiez actualiser un compteur dans la portée. Ici, nous voulons compter la fréquence d’appel de la méthode compareTo pendant le tri. int counter = 0; Date[] dates = new Date[100];
Livre Java.book Page 274 Mercredi, 13. février 2008 2:34 14
274
Au cœur de Java 2 - Notions fondamentales
for (int i = 0; i < dates.length; i++) dates[i] = new Date() { public int compareTo(Date other) { counter++; // ERREUR return super.compareTo(other); } }; Arrays.sort(dates); System.out.println(counter + " comparisons.");
Vous ne pouvez pas déclarer counter comme final car il est évident que vous devez l’actualiser. Vous ne pouvez pas non plus le remplacer par un Integer car les objets Integer sont inaltérables. 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 déclarée final, mais cela signifie simplement que vous ne pouvez pas lui faire référencer un autre tableau. Libre à vous d’altérer les éléments du tableau. Lorsque les classes internes ont été inventées, le compilateur de prototype a automatiquement effectué cette transformation pour toutes les variables locales modifiées dans la classe interne. Certains programmeurs craignaient malgré tout que le compilateur produise des tas d’objets dans leur dos ; la restriction final a donc été adoptée à la place. Une prochaine version du langage Java pourrait d’ailleurs bien inverser cette décision.
Classes internes anonymes Lors de l’utilisation de classes internes locales, vous pouvez souvent aller plus loin. Si vous ne souhaitez créer qu’un seul objet de cette classe, il n’est même pas nécessaire de donner un nom à la classe. Une telle classe est appelée classe interne anonyme : public void start(int interval, final boolean beep) { ActionListener listener = new ActionListener() { public void actionPerformed(ActionEvent event) { Date now = new Date(); System.out.println("At the tone, the time is " + now); if (beep) Toolkit.getDefaultToolkit().beep(); } }; Timer t = new Timer(interval, listener); t.start(); }
Livre Java.book Page 275 Mercredi, 13. février 2008 2:34 14
Chapitre 6
Interfaces et classes internes
275
Il s’agit là d’une syntaxe assez obscure qui signifie : créer un nouvel objet d’une classe qui implémente l’interface ActionListener, où la méthode actionPerformed requise est celle définie entre les accolades { }. En général, la syntaxe est : new SuperType(paramètres de construction) { méthodes et données de la classe interne }
SuperType peut être ici une interface telle que ActionListener ; la classe interne implémente 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 d’un constructeur doit être identique à celui de la classe (et celle-ci n’a pas de nom). Au lieu de cela, les paramètres de construction sont donnés au constructeur de la superclasse. En particulier, chaque fois qu’une classe interne implémente une interface, elle ne peut pas avoir de paramètres de construction. Il faut néanmoins toujours fournir les parenthèses, comme dans cet exemple : new TypeInterface() { méthodes et données }
Il faut y regarder à deux fois pour faire la différence entre la construction d’un nouvel objet d’une classe régulière et la construction d’un objet d’une classe interne anonyme qui étend cette classe. Person queen = // un objet Person count = // un objet
new Person("Mary"); Person new Person("Dracula") { ... }; d’une classe interne étendant Person
Si la parenthèse fermante de la liste de paramètres du constructeur est suivie d’une accolade ouvrante, cela définit une classe interne anonyme. Les classes internes anonymes sont-elles une brillante idée, ou plutôt un excellent moyen d’écrire du code hermétique ? Sans doute les deux. Lorsque le code d’une classe interne est très court — quelques lignes de code simple — cela peut faire gagner un peu de temps. Mais c’est exactement le genre d’économie qui vous amène à concourir pour le prix du code Java le plus obscur. Le Listing 6.5 contient le code source complet du programme d’horloge 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 présente les classes internes anonymes. * @version 1.10 2004-02-27 * @author Cay Horstmann */
Livre Java.book Page 276 Mercredi, 13. février 2008 2:34 14
276
Au cœur de Java 2 - Notions fondamentales
public class AnonymousInnerClassTest { public static void main(String[] args) { TalkingClock clock = new TalkingClock(); clock.start(1000, true); // laisse le programme fonctionner jusqu’à ce que l’utilisateur // clique sur "OK" JOptionPane.showMessageDialog(null, "Quit program?"); System.exit(0); } } /** * Une horloge qui affiche l’heure à intervalles réguliers. */ class TalkingClock { /** * Démarre l’horloge. * @param interval L’intervalle entre les messages * (en millièmes de seconde) * @param beep true si l’horloge doit émettre un bip */ public void start(int interval, final boolean beep) { ActionListener listener = new ActionListener() { public void actionPerformed(ActionEvent event) { Date now = new Date(); System.out.println("At the tone, the time is " + now); if (beep) Toolkit.getDefaultToolkit().beep(); } }; Timer t = new Timer(interval, listener); t.start(); } }
Classes internes statiques On désire parfois utiliser une classe interne pour cacher simplement une classe dans une autre, sans avoir besoin de fournir à la classe interne une référence à un objet de la classe externe. A cette fin, la classe interne est déclarée static. Voici un exemple typique qui montre les raisons d’un tel choix. Prenons le calcul des valeurs minimale et maximale d’un tableau. Bien entendu, on écrit normalement une méthode pour calculer le minimum et une autre pour calculer le maximum. Lorsque ces deux méthodes sont appelées, le tableau est parcouru deux fois. Il serait plus efficace de parcourir le tableau une seule fois et de calculer simultanément les deux valeurs : double min = Double.MAX_VALUE; double max = Double.MIN_VALUE; for (double v : values)
Livre Java.book Page 277 Mercredi, 13. février 2008 2:34 14
Chapitre 6
Interfaces et classes internes
277
{ if (min > v) min = v; if (max < v) max = v; }
Cependant, la méthode doit renvoyer deux nombres. Pour ce faire, nous déclarons une classe Pair qui stocke deux valeurs numériques : class Pair { public Pair(double f, double s) { first = f; second = s; } public double getFirst() { return first; } public double getSecond() { return second; } private double first; private double second; }
La fonction minmax peut alors renvoyer un objet de type Pair : class ArrayAlg { public static Pair minmax(double[] values) { . . . return new Pair(min, max); } }
L’appelant de la fonction utilise ensuite les méthodes getFirst et getSecond pour récupérer les réponses : Pair p = ArrayAlg.minmax(d); System.out.println("min = " + p.getFirst()); System.out.println("max = " + p.getSecond());
Bien sûr, le nom Pair est un terme très courant et, dans un grand projet, il se peut qu’un autre programmeur ait eu la même idée brillante, mais sa classe Pair contient une paire de chaînes de caractères. Ce conflit 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 qu’il en soit, et contrairement aux autres classes internes employées dans les exemples précédents, nous ne voulons pas avoir une référence à un autre objet au sein d’un objet Pair. Cette référence peut être supprimée en déclarant la classe interne comme static : class ArrayAlg { public static class Pair { . . . } . . . }
Livre Java.book Page 278 Mercredi, 13. février 2008 2:34 14
278
Au cœur de Java 2 - Notions fondamentales
Seules les classes internes peuvent évidemment être déclarées static. Une classe interne static ressemble exactement à n’importe quelle autre classe interne, avec cette différence, cependant, qu’un objet d’une classe interne statique ne possède pas de référence à l’objet externe qui l’a créé. Dans notre exemple, nous devons utiliser une classe interne statique, car l’objet de la classe interne est construit dans une méthode statique : public static Pair minmax(double[] d) { . . . return new Pair(min, max); }
Si Pair n’avait pas été déclaré static, le compilateur aurait indiqué qu’aucun objet implicite de type ArrayAlg n’était disponible pour initialiser l’objet de la classe interne. INFO On utilise une classe interne statique lorsque la classe interne n’a pas besoin d’accéder à un objet de la classe externe. On emploie aussi le terme classe imbriquée pour désigner les classes internes statiques.
INFO Les classes internes qui sont déclarées 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 imbriquée Pair. Listing 6.6 : StaticInnerClassTest.java /** * Ce programme présente l’utilisation 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 {
Livre Java.book Page 279 Mercredi, 13. février 2008 2:34 14
Chapitre 6
Interfaces et classes internes
/** * 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 d’un tableau * @param values Un tableau de nombres à virgule flottante * @return une paire dont le premier élément est le minimum et * dont le second élément 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); } }
279
Livre Java.book Page 280 Mercredi, 13. février 2008 2:34 14
280
Au cœur de Java 2 - Notions fondamentales
Proxies Dans cette dernière 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 créer, au moment de l’exécution, de nouvelles classes qui implémentent un jeu donné d’interfaces. Les proxies ne sont nécessaires que si vous ne savez pas, au moment de la compilation, les interfaces que vous devez implémenter. Cette situation se produit rarement pour les programmeurs d’application, mais n’hésitez pas à passer cette section si les difficultés ne vous intéressent pas. Cependant, pour certaines applications système, la souplesse que procurent les proxies peut avoir une importance capitale. Supposons que vous vouliez construire un objet d’une classe qui implémente une ou plusieurs interfaces dont vous ne connaissez peut-être pas la nature exacte au moment de la compilation. C’est là un problème ardu. Pour construire une classe réelle, vous pouvez simplement utiliser la méthode newInstance ou la réflexion pour trouver un constructeur. Mais vous ne pouvez pas instancier une interface. Vous devez définir une nouvelle classe dans un programme qui s’exécute. Pour résoudre ce problème, certains programmes génèrent du code, le placent dans un fichier, invoquent le compilateur puis chargent le fichier de classe résultant. Cette procédure est lente, bien entendu, et demande aussi le déploiement du compilateur avec le programme. Le mécanisme de proxy est une meilleure solution. La classe proxy peut créer des classes entièrement nouvelles au moment de l’exécution. Une telle classe proxy implémente les interfaces que vous spécifiez. En particulier, elle possède les méthodes suivantes : m
toutes les méthodes requises par les interfaces spécifiées ;
m
toutes les méthodes définies dans la classe Object (toString, equals, etc.).
Néanmoins, vous ne pouvez pas définir de nouveau code pour ces méthodes au moment de l’exécution. A la place, vous devez fournir un gestionnaire d’invocation. Ce gestionnaire est un objet de toute classe implémentant l’interface InvocationHandler. Cette interface possède une seule méthode : Object invoke(Object proxy, Method method, Object[] args)
Chaque fois qu’une méthode est appelée sur l’objet proxy, la méthode invoke du gestionnaire d’invocation est appelée, avec l’objet Method et les paramètres de l’appel original. Le gestionnaire d’invocation doit alors trouver comment gérer l’appel. Pour créer un objet proxy, vous appelez la méthode newProxyInstance de la classe Proxy. Cette méthode a trois paramètres : m
Un chargeur de classe. Dans le cadre du modèle de sécurité Java, il est possible d’utiliser différents chargeurs de classe pour les classes système, les classes qui sont téléchargées à partir d’Internet, etc. Nous verrons les chargeurs de classes au Chapitre 9 du Volume II. Pour l’instant, nous spécifierons null pour utiliser le chargeur de classe par défaut.
m
Un tableau d’objets Class, un pour chaque interface à implémenter.
m
Un gestionnaire d’invocation.
Deux questions restent en suspens. Comment définissons-nous le gestionnaire ? Et que pouvonsnous faire de l’objet proxy résultant ? Les réponses dépendent évidemment du problème que nous voulons résoudre avec le mécanisme de proxy. Les proxies peuvent être employés dans bien des cas, par exemple : m
le routage d’appels de méthode vers des serveurs distants ;
Livre Java.book Page 281 Mercredi, 13. février 2008 2:34 14
Chapitre 6
Interfaces et classes internes
281
m
l’association d’événements de l’interface utilisateur avec des actions, dans un programme qui s’exécute ;
m
la trace des appels de méthode à des fins de débogage.
Dans notre exemple de programme, nous allons utiliser les proxies et les gestionnaires d’invocation pour tracer les appels de méthode. Nous définissons une classe enveloppe TraceHandler qui stocke un objet enveloppé (wrapped). Sa méthode invoke affiche simplement le nom et les paramètres de la méthode à appeler, puis appelle la méthode avec l’objet enveloppé en tant que paramètre 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 paramètres de la méthode . . . // invoquer la méthode return m.invoke(target, args); } private Object target; }
Voici comment vous construisez un objet proxy qui déclenche l’activité de trace chaque fois que l’une de ses méthodes est appelée : Object value = . . .; // construire l’enveloppe (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 qu’une méthode de l’une des interfaces sera appelée sur le proxy, le nom de la méthode et ses paramètres seront affichés, puis la méthode sera invoquée sur value. Dans le programme du Listing 6.7, les objets proxy sont employés pour réaliser la trace d’une recherche binaire. Un tableau est rempli avec les valeurs entières de proxies de 1 à 1 000. Puis la méthode binarySearch de la classe Arrays est invoquée pour rechercher un entier aléatoire dans le tableau. Enfin, l’élément correspondant s’affiche : Object[] elements = new Object[1000]; // remplir les éléments 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 aléatoire Integer key = new Random().nextInt(elements.length) + 1;
Livre Java.book Page 282 Mercredi, 13. février 2008 2:34 14
282
Au cœur de Java 2 - Notions fondamentales
// rechercher la clé (key) int result = Arrays.binarySearch(elements, key); // afficher la correspondance si trouvée if (result >= 0) System.out.println(elements[result]);
La classe Integer implémente l’interface Comparable. Les objets proxy appartiennent à une classe qui est définie au moment de l’exécution (son nom est du style $Proxy0). Cette classe implémente également l’interface Comparable. Sa méthode compareTo appelle la méthode invoke du gestionnaire de l’objet proxy. INFO Comme vous l’avez vu plus tôt dans ce chapitre, depuis Java SE 5.0 la classe Integer implémente en fait Comparable. Toutefois, au moment de l’exécution, tous les types génériques sont effacés et le proxy est construit avec l’objet de classe, pour la classe brute Comparable.
La méthode binarySearch réalise des appels du style : if (elements[i].compareTo(key) < 0) . . .
Puisque nous avons complété le tableau avec des objets proxy, les appels à compareTo appellent la méthode invoke de la classe TraceHandler. Cette méthode affiche le nom de la méthode et ses paramètres, puis invoque compareTo sur l’objet Integer enveloppé. Enfin, à la fin du programme, nous appelons : System.out.println(elements[result]);
La méthode println appelle toString sur l’objet proxy, et cet appel est aussi redirigé vers le gestionnaire d’invocation. Voici la trace complète d’une exécution 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 l’algorithme de recherche dichotomique qui coupe en deux l’intervalle de recherche à chaque étape. Vous voyez que la méthode toString est mise en proxy, même si elle n’appartient pas à l’interface Comparable. Vous le découvrirez à la prochaine section, certaines méthodes Object sont toujours mises en proxy. Listing 6.7 : ProxyTest.java import java.lang.reflect.*; import java.util.*; /** * Ce programme présente l’utilisation des proxies.
Livre Java.book Page 283 Mercredi, 13. février 2008 2:34 14
Chapitre 6
Interfaces et classes internes
* @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 éléments 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 aléatoire Integer key = new Random().nextInt(elements.length) + 1; // recherche la clé int result = Arrays.binarySearch(elements, key); // affiche la correspondance le cas échéant if (result >= 0) System.out.println(elements[result]); } } /** * Un gestionnaire d’invocation qui affiche le nom et les paramètres * de la méthode, puis appelle la méthode initiale */ class TraceHandler implements InvocationHandler { /** * Construit un TraceHandler * @param t le paramètre implicite de l’appel de méthode */ public TraceHandler(Object t) { target = t; } public Object invoke(Object proxy, Method m, Object[] args) throws Throwable { // affiche l’argument implicite System.out.print(target); // affiche le nom de la méthode 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]);
283
Livre Java.book Page 284 Mercredi, 13. février 2008 2:34 14
284
Au cœur de Java 2 - Notions fondamentales
if (i < args.length - 1) System.out.print(", "); } } System.out.println(")"); // appelle la méthode réelle return m.invoke(target, args); } private Object target; }
Propriétés des classes proxy Maintenant que vous avez vu les classes proxy à l’œuvre, nous allons étudier certaines de leurs propriétés. Souvenez-vous que les classes proxy sont créées à la volée, dans un programme en cours d’exécution. Cependant, une fois créées, ce sont des classes régulières, comme n’importe quelle autre classe dans la machine virtuelle. Toutes les classes proxy étendent la classe Proxy. Une classe proxy n’a qu’une variable d’instance — le gestionnaire d’invocation qui est défini dans la superclasse Proxy. Toutes les données supplémentaires nécessaires pour exécuter les tâches des objets proxy doivent être stockées dans le gestionnaire d’invocation. Par exemple, lorsque les objets Comparable ont été transformés en proxies dans le programme du Listing 6.7, la classe TraceHandler a enveloppé les objets réels. Toutes les classes proxy remplacent les méthodes toString, equals et hashCode de la classe Object. Comme toutes les méthodes proxy, ces méthodes appellent simplement invoke sur le gestionnaire d’invocation. Les autres méthodes de la classe Object (comme clone et getClass) ne sont pas redéfinies. Les noms des classes proxy ne sont pas définis. La classe Proxy dans la machine virtuelle de Sun génère des noms de classes commençant par la chaîne $Proxy. Il n’y a qu’une classe proxy pour un chargeur de classe et un jeu ordonné d’interfaces particuliers. C’est-à-dire que si vous appelez deux fois la méthode newProxyInstance avec le même chargeur de classe et le même tableau d’interface, vous obtenez deux objets de la même classe. Vous pouvez aussi obtenir cette classe à l’aide de la méthode getProxyClass : Class proxyClass = Proxy.getProxyClass(null, interfaces);
Une classe proxy est toujours public et final. Si toutes les interfaces qu’implémente la classe proxy sont public, alors la classe proxy n’appartient pas à un package particulier. Sinon, toutes les interfaces non publiques doivent appartenir au même package, et la classe proxy appartient alors aussi à ce package. Vous pouvez déterminer si un objet Class particulier représente une classe proxy en appelant la méthode isProxyClass de la classe Proxy. java.lang.reflect.InvocationHandler 1.3
•
Object invoke(Object proxy, Method method, Object[] args)
Définit cette méthode pour qu’elle contienne l’action que vous voulez exécuter chaque fois qu’une méthode a été invoquée sur l’objet proxy.
Livre Java.book Page 285 Mercredi, 13. février 2008 2:34 14
Chapitre 6
Interfaces et classes internes
285
java.lang.reflect.Proxy 1.3 • static Class getProxyClass(ClassLoader loader, Class[] interfaces)
Renvoie la classe proxy qui implémente les interfaces données. •
static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler handler)
Construit une nouvelle instance de la classe proxy qui implémente les interfaces données. Toutes les méthodes appellent la méthode invoke de l’objet gestionnaire donné. •
static boolean isProxyClass(Class c)
Renvoie true si c est une classe proxy. Cela clôt notre dernier chapitre sur les bases du langage de programmation Java. Les interfaces et les classes internes sont des concepts que vous rencontrerez fréquemment. Comme nous l’avons mentionné toutefois, les proxies constituent une technique pointue principalement destinée aux concepteurs d’outils, et non aux programmeurs d’application. Vous êtes maintenant prêt à aborder l’apprentissage des techniques de programmation graphique et des interfaces utilisateur, qui commence au Chapitre 7.
Livre Java.book Page 286 Mercredi, 13. février 2008 2:34 14
Livre Java.book Page 287 Mercredi, 13. février 2008 2:34 14
7 Programmation graphique Au sommaire de ce chapitre
✔ Introduction à Swing ✔ Création d’un cadre ✔ Positionnement d’un cadre ✔ Affichage des informations dans un composant ✔ Formes 2D ✔ Couleurs ✔ Texte et polices ✔ Affichage d’images Vous avez étudié jusqu’à présent l’écriture de programmes qui n’acceptaient que des caractères tapés au clavier, les traitaient et affichaient le résultat dans une console. De nos jours, ce n’est pas ce que désirent la plupart des utilisateurs. Les programmes modernes et les pages Web ne fonctionnent pas de cette manière. Ce chapitre vous ouvrira la voie qui permet d’écrire des programmes utilisant une interface utilisateur graphique (GUI, Graphic User Interface). Vous apprendrez en particulier à écrire des programmes qui permettent de spécifier la taille et la position des fenêtres, d’y afficher du texte avec diverses polices de caractères, de dessiner des images, et ainsi de suite. Vous obtiendrez ainsi un éventail de compétences que nous mettrons en œuvre dans les prochains chapitres. Les deux chapitres suivants vous montreront comment gérer des événements — tels que les frappes au clavier et les clics de la souris — et comment ajouter des éléments d’interfaces : menus, boutons, etc. Après avoir lu ces trois chapitres, vous saurez écrire des programmes graphiques autonomes. Des techniques plus sophistiquées de programmation graphique sont étudiées au Volume II. Si, au contraire, vous souhaitez utiliser Java pour la programmation côté serveur uniquement et que la GUI ne vous intéresse pas, vous pouvez aisément sauter ces chapitres.
Livre Java.book Page 288 Mercredi, 13. février 2008 2:34 14
288
Au cœur de Java 2 - Notions fondamentales
Introduction à Swing Lors de l’introduction de Java 1.0, le programme contenait une bibliothèque de classe que Sun appelait Abstract Window Toolkit (AWT) pour la programmation de base de l’interface graphique utilisateur (GUI). La manière dont la bibliothèque AWT de base gère les éléments de l’interface utilisateur se fait par la délégation de leur création et de leur comportement à la boîte à outils native du GUI sur chaque plate-forme cible (Windows, Solaris, Macintosh, etc.). Si vous avez par exemple utilisé la version originale d’AWT pour placer une boîte de texte sur une fenêtre Java, une case de texte "pair" sous-jacente a géré la saisie du texte. Le programme qui en résulte pourrait alors, en théorie, s’exécuter sur l’une de ces plates-formes, avec "l’aspect" de la plate-forme cible, d’où le slogan de Sun, "Ecrire une fois, exécuter partout". L’approche fondée sur les pairs fonctionnait bien pour les applications simples, mais il est rapidement devenu évident qu’il était très difficile d’écrire une bibliothèque graphique portable de haute qualité qui dépendait des éléments d’une interface utilisateur native. L’interface utilisateur comme les menus, les barres de défilement et les champs de texte peuvent avoir des différences subtiles de comportement sur différentes plates-formes. Il était donc difficile d’offrir aux utilisateurs une expérience cohérente et prévisible. De plus, certains environnements graphiques (comme X11/Motif) ne disposent pas d’une large collection de composants d’interface utilisateur comme l’ont Windows ou Macintosh. Ceci restreint ensuite une bibliothèque portable fondée sur des pairs à adopter l’approche du "plus petit dénominateur commun". En conséquence, les applications GUI élaborées avec AWT n’étaient pas aussi jolies que les applications Windows ou Macintosh natives et n’avaient pas non plus le type de fonctionnalité que les utilisateurs de ces plates-formes attendaient. Plus triste encore, il existait différents bogues dans la bibliothèque de l’interface utilisateur AWT sur les différentes plates-formes. Les développeurs se sont plaints qu’ils devaient tester leurs applications sur chaque plate-forme, une pratique qui s’est appelée, avec un peu de dérision, "Ecrire une fois, déboguer partout". En 1996, Netscape a créé une bibliothèque de GUI dénommée IFC (Internet Foundation Classes) qui utilisait une approche totalement différente. Les éléments de l’interface utilisateur, comme les boutons, les menus, etc., étaient dessinés sur des fenêtres vierges. La seule fonctionnalité requise par le système de fenêtres sous-jacent nécessitait une méthode pour faire apparaître les fenêtres et dessiner dedans. Ainsi, les éléments IFC de Netscape avaient le même aspect et se comportaient de la même manière, quelle que soit la plate-forme d’exécution du programme. Sun a collaboré avec Netscape pour parfaire cette approche, avec pour résultat une bibliothèque d’interfaces utilisateur portant le nom de code "Swing". Swing constituait une extension de Java 1.1 et s’est finalement intégré dans la bibliothèque standard de Java SE 1.2. Comme l’a dit Duke Ellington, "Tout ça n’est rien si je n’ai pas le swing." Et Swing est maintenant le nom officiel du kit de développement d’interface graphique. Swing fait partie des classes JFC (Java Foundation Classes). L’ensemble des JFC est vaste et ne se limite pas à Swing. Elles incluent non seulement les composants Swing, mais également des API d’accessibilité, de dessin 2D et de fonctionnalités de glisser-déplacer (ou drag and drop).
Livre Java.book Page 289 Mercredi, 13. février 2008 2:34 14
Chapitre 7
Programmation graphique
289
INFO Swing ne remplace pas complètement AWT, il est fondé sur l’architecture AWT. Swing fournit simplement des composants d’interface plus performants. Vous utilisez l’architecture de base d’AWT, en particulier la gestion des événements, lorsque vous écrivez un programme Swing. A partir de maintenant, nous emploierons le terme "Swing" pour désigner les classes allégées de l’interface utilisateur "dessinée" et "AWT" pour désigner les mécanismes sousjacents du kit de fenêtrage (tels que la gestion d’événements).
Bien entendu, les éléments d’interface Swing seront un peu plus lents à s’afficher sur l’écran que les composants lourds employés par AWT. Précisons que cette différence de vitesse ne pose pas de problème sur les machines récentes. En revanche, il existe d’excellentes raisons d’opter pour Swing : m
Swing propose un ensemble d’éléments d’interface plus étendu et plus pratique.
m
Swing dépend peu de la plate-forme d’exécution ; en conséquence, il est moins sensible aux bogues spécifiques d’une plate-forme.
m
Swing procure une bonne expérience à l’utilisateur qui travaille sur plusieurs plates-formes.
Cependant, le troisième avantage cité représente également un recul : si les éléments de l’interface utilisateur se ressemblent sur toutes les plates-formes, leur aspect ("look and feel") est quand même différent de celui des contrôles natifs et les utilisateurs vont devoir s’adapter à cette nouvelle présentation. Swing résout ce problème d’une manière très élégante. Les programmeurs d’une application Swing peuvent donner à leur interface un look and feel spécifique. Les Figures 7.1 et 7.2 montrent le même programme en cours d’exécution, l’un sous Windows, l’autre sous GTK (pour des raisons de copyright, le look and feel Windows n’est disponible que pour les programmes Java exécutés sur des plates-formes Windows). Figure 7.1 Application Swing avec le look and feel de Windows.
Sun a développé un look and feel indépendant de la plate-forme, baptisé "Metal", jusqu’à ce que les spécialistes du marketing le renomment "Java look and feel". Or la plupart des programmeurs continuent à utiliser le terme "Metal", et c’est ce que nous ferons dans ce livre.
Livre Java.book Page 290 Mercredi, 13. février 2008 2:34 14
290
Au cœur de Java 2 - Notions fondamentales
Figure 7.2 Application Swing avec le look and feel de GTK.
Certains ont fait la critique que Metal était un peu indigeste et son aspect a été modernisé pour la version 5.0 (voir Figure 7.3). Sachez que l’aspect Metal prend en charge plusieurs thèmes, des variations mineures des couleurs et des polices. Le thème par défaut s’appelle "Ocean". Figure 7.3 Le look and feel "Metal" de Swing, avec le thème Ocean.
Dans Java SE 6, Sun a amélioré la prise en charge du look and feel natif pour Windows et GTK. Une application Swing choisit désormais des personnalisations des schémas de couleurs et rend fidèlement les boutons et barres d’outils modernes. Certains préfèrent que les applications Java emploient le look and feel de leurs plates-formes, d’autres optent pour Metal ou pour un look and feel indépendant. Vous le verrez au Chapitre 8, il est très facile de laisser les utilisateurs choisir leur look and feel préféré.
Livre Java.book Page 291 Mercredi, 13. février 2008 2:34 14
Chapitre 7
Programmation graphique
291
INFO Bien que cela dépasse le cadre de cet ouvrage, sachez que le programmeur Java peut améliorer un look and feel existant ou en concevoir un qui soit entièrement nouveau. C’est un travail fastidieux qui exige de spécifier la méthode de dessin des divers composants Swing. Certains développeurs ont déjà accompli cette tâche 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 découvrir toute une série d’implémentations look and feel intéressantes. Java SE 5.0 a introduit un nouveau look and feel, appelé Synth, qui facilite ce processus. Synth vous permet de définir un nouveau look and feel en fournissant des fichiers image et des descripteurs XML, sans effectuer aucune programmation.
INFO La plus grande de la programmation de l’interface utilisateur Java s’effectue aujourd’hui en Swing, à une notable exception près. L’environnement de développement intégré Eclipse utilise une boîte à outils graphique appelée SWT qui est identique à AWT, faisant concorder des composants natifs sur diverses plates-formes. Vous trouverez des articles décrivant SWT à l’adresse http://www.eclipse.org/articles/.
Si vous avez déjà programmé des applications pour Windows avec Visual Basic ou C#, vous connaissez la simplicité d’emploi des outils graphiques et des éditeurs de ressources de ces produits. Ces logiciels permettent de concevoir l’aspect d’une application et génèrent une bonne partie (ou la totalité) du code de l’interface. Il existe des outils de développement d’interface pour Java, mais, pour les utiliser efficacement, vous devez savoir construire l’interface manuellement. Le reste du chapitre donne les bases pour afficher une fenêtre et dessiner son contenu.
Création d’un cadre En Java, une fenêtre de haut niveau — c’est-à-dire une fenêtre qui n’est pas contenue dans une autre fenêtre — est appelée frame (cadre ou fenêtre d’encadrement). Pour représenter ce niveau supérieur, la bibliothèque AWT possède une classe nommée Frame. La version Swing de cette classe est baptisée JFrame, qui étend la classe Frame et désigne l’un des rares composants Swing qui ne soient pas dessinés sur un canevas (grille). Les éléments de décoration (boutons, barre de titre, icônes, etc.) ne sont pas dessinés par Swing, mais par le système de fenêtrage de l’utilisateur. ATTENTION La plupart des classes de composant Swing commencent par la lettre "J" : JButton, JFrame, etc. Ce sont des classes comme Button et Frame, mais il s’agit de composants AWT. Si vous omettez par inadvertance la lettre "J", votre programme peut toujours se compiler et s’exécuter, mais le mélange de Swing et de composants AWT peut amener à des incohérences visuelles et de comportement.
Nous allons maintenant étudier les méthodes employées le plus fréquemment lorsque nous travaillons avec un composant JFrame. Le Listing 7.1 présente un programme simple qui affiche une fenêtre vide sur l’écran, comme le montre la Figure 7.4.
Livre Java.book Page 292 Mercredi, 13. février 2008 2:34 14
292
Au cœur de Java 2 - Notions fondamentales
Figure 7.4 Le plus simple des cadres visibles.
Listing 7.1 : SimpleFrameTest.java import javax.swing.*; /** * @version 1.32 2007-06-12 * @author Cay Horstmann */ public class SimpleFrameTest { public static void main(String[] args) { SimpleFrame frame = new SimpleFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } } class SimpleFrame extends JFrame { public SimpleFrame() { setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); } public static final int DEFAULT_WIDTH = 300; public static final int DEFAULT_HEIGHT = 200; }
Examinons ce programme ligne par ligne. Les classes Swing se trouvent dans le package javax.swing. Le terme javax désigne un package d’extension (pour le distinguer d’un package standard). Pour des raisons historiques, les classes Swing constituent une extension mais elles font partie de chaque implémentation de Java SE depuis la version 1.2. Par défaut, un cadre a une taille assez peu utile de 0 × 0 pixel. Nous définissons une sous-classe SimpleFrame dont le constructeur définit la taille à 300 × 200 pixels. C’est la seule différence entre un SimpleFrame et un JFrame. Dans la méthode main de la classe SimpleFrameTest, nous commençons par construire un objet SimpleFrame et nous l’affichons. Nous devons traiter deux problèmes techniques dans chaque programme Swing. Tout d’abord, tous les composants Swing doivent être configurés à partir du thread de répartition des événements, le thread de contrôle qui transfère des événements comme des clics de souris et des
Livre Java.book Page 293 Mercredi, 13. février 2008 2:34 14
Chapitre 7
Programmation graphique
293
frappes de touche sur les composants de l’interface utilisateur. Le fragment de code suivant sert à exécuter des instructions dans le thread de répartition des événements : EventQueue.invokeLater(new Runnable() { public void run() { Instructions } });
Nous verrons les détails au Chapitre 14. Pour l’heure, considérez simplement qu’il s’agit d’une incantation magique servant à lancer un programme Swing. INFO Vous verrez que de nombreux programmes Swing n’initialisent pas l’interface utilisateur dans le thread de répartition des événements. Cette situation était parfaitement acceptable pour réaliser l’initialisation dans le thread principal. Malheureusement, les composants Swing devenant plus complexes, les programmeurs de Sun ne pouvaient plus garantir la sécurité de cette méthode. La probabilité d’erreur est extrêmement faible et vous ne voudrez sûrement pas faire partie des malchanceux qui rencontrent un problème intermittent. Mieux vaut procéder dans les règles, même si le code semble plutôt mystérieux.
Nous indiquons ensuite ce qui doit se passer lorsque l’utilisateur ferme ce cadre. Dans ce cas précis, le programme doit sortir. Utilisez pour cela l’instruction : frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Dans d’autres programmes comprenant plusieurs cadres, vous ne souhaiterez pas la sortie du programme si l’utilisateur ferme seulement l’un des cadres. Par défaut, un cadre est masqué lorsque l’utilisateur le ferme, mais le programme ne se termine pas pour autant. Il aurait pu être intéressant que le programme se termine après que le dernier cadre est devenu invisible, mais il n’en est pas ainsi. Le simple fait de construire un cadre ne l’affiche pas automatiquement. Les cadres sont au départ invisibles. Cela permet au programmeur d’y ajouter des composants avant l’affichage. Pour afficher le cadre, la méthode main appelle la méthode setVisible du cadre. INFO Avant Java SE 5.0, il était possible d’utiliser la méthode show, dont la classe JFrame héritait dans la superclasse Window. Cette dernière avait elle-même une superclasse Component qui disposait également d’une méthode show. La méthode Component.show était une méthode dépréciée dans Java SE 1.2. Vous êtes censé appeler setVisible(true) pour afficher un composant. Néanmoins, jusqu’à Java SE 1.4, la méthode Window.show n’était pas dépréciée. En fait, elle était assez utile, pour afficher la fenêtre et la faire apparaître au premier plan. Malheureusement, cet avantage a disparu avec la politique de dépréciation et Java SE 5.0 dépréciait la méthode show également pour les fenêtres.
Après avoir planifié les instructions d’initialisation, la méthode main se termine. Sachez qu’elle ne met pas fin au programme, mais simplement au thread principal. Le thread de répartition des événements maintient le programme en activité jusqu’à son achèvement, soit par fermeture du cadre, soit par appel à la méthode System.exit.
Livre Java.book Page 294 Mercredi, 13. février 2008 2:34 14
294
Au cœur de Java 2 - Notions fondamentales
Le résultat du programme est présenté à la Figure 7.4 : il s’agit d’un cadre parfaitement vide et sans intérêt. La barre de titre et les éléments, tels que les boutons à droite, sont dessinés par le système d’exploitation et non par la bibliothèque Swing. Si vous exécutez le même programme sous Windows, GTK ou Mac, les fioritures du cadre seront différentes. La bibliothèque Swing dessine tout à l’intérieur du cadre. Dans ce programme, elle emplit simplement le cadre avec une couleur d’arrière-plan par défaut. INFO Depuis Java SE 1.4, vous pouvez désactiver toutes les décorations de cadre en appelant frame.setUndecorated(true).
Positionnement d’un cadre La classe JFrame ne fournit que peu de méthodes capables de modifier l’aspect d’un cadre. Cependant, grâce à l’héritage, les diverses superclasses de JFrame proposent la plupart des méthodes permettant d’agir sur la taille et la position d’un cadre. Les plus importantes sont les suivantes : m
Les méthodes setLocation et setBounds définissent la position du cadre.
m
La méthode setIconImage indique au système de fenêtres l’icône à afficher dans la barre de titre, la fenêtre de commutation des tâches, etc.
m
La méthode setTitle spécifie le texte affiché dans la barre de titre.
m
La méthode setResizable reçoit un paramètre booléen pour déterminer si la taille d’un cadre peut être modifiée par l’utilisateur.
La Figure 7.5 illustre la chaîne d’héritage de la classe JFrame. ASTUCE Les notes API de cette section proposent les méthodes les plus importantes, selon nous, pour donner un aspect correct aux cadres. Certaines sont définies dans la classe JFrame. D’autres proviennent des diverses superclasses de JFrame. A un certain point, vous devrez peut-être faire des recherches dans la documentation API pour retrouver des méthodes à usage spécifique. Malheureusement, cela est un peu fastidieux avec les méthodes héritées. Ainsi, par exemple, la méthode toFront s’applique aux objets du type JFrame, mais puisqu’elle est simplement héritée de la classe Window, la documentation de JFrame ne l’explique pas. S’il vous semble qu’une méthode serait nécessaire et qu’elle ne soit pas expliquée dans la documentation de la classe pour laquelle vous travaillez, essayez de parcourir la documentation API des méthodes des superclasses de cette classe. Le haut de chaque page API possède des liens hypertexte vers les superclasses et les méthodes héritées sont répertoriées sous le résumé des méthodes nouvelles et remplacées.
Comme l’indiquent les notes API, c’est généralement dans la classe Component (ancêtre de tous les objets d’interface utilisateur graphique) ou dans la classe Window (superclasse du parent de la classe Frame) que l’on recherche les méthodes permettant de modifier la taille et la position des cadres. Par exemple, la méthode setLocation de la classe Component permet de repositionner un composant. Si vous appelez : setLocation(x, y)
Livre Java.book Page 295 Mercredi, 13. février 2008 2:34 14
Chapitre 7
Programmation graphique
295
le coin supérieur gauche du cadre est placé à x pixels du bord gauche et y pixels du sommet de l’écran — (0, 0) représente le coin supérieur gauche de l’écran. De même, la méthode setBounds de Component permet de modifier simultanément la taille et la position d’un composant (en particulier JFrame), de la façon suivante : setBounds(x, y, width, height)
Figure 7.5 La hiérarchie d’héritage des classes JFrame et JPanel dans AWT et Swing.
Objet
Composant
Conteneur
JComponent
Fenêtre
JPanel
Cadre
JFrame
Vous pouvez aussi donner le contrôle du placement au système de fenêtres. Si vous appelez setLocationByPlatform(true);
avant d’afficher le cadre, le système de fenêtres choisit l’emplacement (mais non la taille), généralement avec un léger décalage par rapport à la dernière fenêtre.
Livre Java.book Page 296 Mercredi, 13. février 2008 2:34 14
296
Au cœur de Java 2 - Notions fondamentales
INFO Pour un cadre, les coordonnées de setLocation et setBounds sont relatives à l’écran. Pour d’autres composants, placés dans un conteneur, les coordonnées sont relatives au conteneur, comme vous le verrez au Chapitre 9.
Propriétés des cadres De nombreuses méthodes de classes de composants sont fournies par les paires get/set, notamment les méthodes de la classe Frame : public String getTitle() public void setTitle(String title)
Cette paire get/set est appelée une propriété, elle possède un nom et un type. Le nom s’obtient en modifiant la première lettre venant après get ou set. Par exemple, la classe Frame possède une propriété de nom title et de type String. Conceptuellement, title est une propriété du cadre. Lorsque nous la définissons, nous nous attendons à ce que le titre change à l’écran. Lorsque nous obtenons la propriété, nous nous attendons à recevoir la valeur définie. Peu importe comment la classe Frame implémente cette propriété. Utilise-t-elle simplement un cadre pair pour stocker le titre ? Ou dispose-t-elle d’un champ d’instance private String title; // non requis pour la propriété
Si la classe ne possède pas de champ d’instance correspondant, nous ne savons pas comment sont implémentées les méthodes get et set (et cela nous est égal). Elles se contentent peut-être de lire et d’écrire le champ d’instance ? Ou font-elles plus, par exemple en avertissant le système de fenêtres que le titre a changé. Il existe toutefois une exception à la convention get/set : pour les propriétés de type boolean, la méthode get démarre par is. Ainsi, par exemple, les deux méthodes suivantes définissent la propriété locationByPlatform : public boolean isLocationByPlatform() public void setLocationByPlatform(boolean b)
Nous verrons les propriétés plus en détail au Chapitre 8 du Volume II. INFO De nombreux langages de programmation, notamment Visual Basic et C#, disposent d’une prise en charge intégrée pour les propriétés. Il est possible qu’une prochaine version de Java possède également une construction de langage allant dans ce sens.
Déterminer une taille de cadre adéquate N’oubliez pas que si vous ne spécifiez pas explicitement la taille d’un cadre, celui-ci aura par défaut une largeur et une hauteur de 0 pixel. Pour simplifier notre programme, nous avons donné au cadre une taille qui devrait être acceptée par la plupart des systèmes d’affichage. Cependant, dans une
Livre Java.book Page 297 Mercredi, 13. février 2008 2:34 14
Chapitre 7
Programmation graphique
297
application professionnelle, vous devez d’abord déterminer la résolution de l’écran afin d’adapter la taille du cadre : une fenêtre qui peut sembler raisonnablement grande sur l’écran d’un portable aura la taille d’un timbre-poste sur un écran en haute résolution. Pour connaître la taille de l’écran, procédez aux étapes suivantes. Appelez la méthode statique getDefaultToolkit de la classe Toolkit pour récupérer l’objet Toolkit (la classe Toolkit est un dépotoir pour diverses méthodes qui interfacent avec le système de fenêtrage natif). Appelez ensuite la méthode getScreenSize, qui renvoie la taille de l’écran sous la forme d’un objet Dimension (un objet Dimension stocke simultanément une largeur et une hauteur dans des variables d’instance publiques appelées 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 système de fenêtrage de placer le cadre : setSize(screenWidth / 2, screenHeight / 2); setLocationByPlatform(true);
Nous fournissons également une icône à notre cadre. Comme la représentation des images dépend aussi du système, nous employons la boîte à outils pour charger une image. Ensuite, nous affectons l’image à l’icône du cadre : Image img = kit.getImage("icon.gif"); setIconImage(img);
La position de l’icône dépendra du système d’exploitation. Sous Windows, par exemple, l’icône s’affiche dans le coin supérieur gauche de la fenêtre, et elle apparaît également dans la liste des tâches lorsque vous appuyez sur la combinaison de touches Alt+Tab. Le Listing 7.2 présente le programme complet. Lorsque vous exécuterez le programme, remarquez l’icône "Core Java". Voici quelques conseils supplémentaires pour gérer les cadres : m
Si votre cadre ne contient que des composants standard comme des boutons et des champs de texte, il vous suffit d’appeler la méthode pack pour régler sa taille. Le cadre sera réglé sur la plus petite taille qui permet de loger tous les composants. Il est assez fréquent de définir le cadre principal d’un programme sur la taille maximale. Depuis Java SE 1.4, vous pouvez maximiser un cadre en appelant : frame.setExtendedState(Frame.MAXIMIZED_BOTH);
m
Vous pouvez aussi retenir la manière dont l’utilisateur place et dimensionne le cadre de votre application et restaurer ces limites lorsque vous relancez l’application. Vous verrez au Chapitre 10 comment utiliser l’API Preferences dans ce but.
m
Si vous écrivez une application qui profite de l’affichage multiple, utilisez les classes GraphicsEnvironment et GraphicsDevice pour obtenir les dimensions des écrans.
m
La classe GraphicsDevice vous permet aussi d’exécuter votre application en mode plein écran.
Livre Java.book Page 298 Mercredi, 13. février 2008 2:34 14
298
Au cœur de Java 2 - Notions fondamentales
Listing 7.2 : SizedFrameTest.java import java.awt.*; import javax.swing.*; /** * @version 1.32 2007-04-14 * @author Cay Horstmann */ public class SizedFrameTest { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { SizedFrame frame = new SizedFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); } } class SizedFrame extends JFrame { public SizedFrame() { // extraire les dimensions de l’écran Toolkit kit = Toolkit.getDefaultToolkit(); Dimension screenSize = kit.getScreenSize(); int screenHeight = screenSize.height; int screenWidth = screenSize.width; // définir la largeur et la hauteur du cadre et // laisser la plate-forme choisir l’emplacement à l’écran setSize(screenWidth / 2, screenHeight / 2); setLocationByPlatform(true); // définir l’icône et le titre du cadre Image img = kit.getImage("icon.gif"); setIconImage(img); setTitle("SizedFrame"); } } java.awt.Component 1.0
• •
boolean isVisible() void setVisible(boolean b)
Récupèrent ou définissent la propriété visible. A l’origine, les composants sont visibles, à l’exception des composants de haut niveau, comme JFrame.
Livre Java.book Page 299 Mercredi, 13. février 2008 2:34 14
Chapitre 7
•
Programmation graphique
299
void setSize(int width, int height) 1.1
Redimensionne le composant sur la largeur et la hauteur spécifiées. •
void setLocation(int x, int y) 1.1
Déplace le composant vers un nouvel emplacement. Les coordonnées x et y utilisent les coordonnées du conteneur si le composant n’est pas de haut niveau ou les coordonnées de l’écran si le composant est de haut niveau (par exemple, a JFrame). •
void setBounds(int x, int y, int width, int height) 1.1
Déplace et redimensionne le composant. • •
Dimension getSize() 1.1 void setSize(Dimension d) 1.1
Récupèrent ou définissent la propriété size de ce composant. java.awt.Window 1.0
•
void toFront()
Affiche cette fenêtre par-dessus toutes les autres. •
void toBack()
Place cette fenêtre au-dessous de la pile des fenêtres du bureau et réorganise les autres fenêtres visibles. • •
boolean isLocationByPlatform() 5.0 void setLocationByPlatform(boolean b) 5.0
Récupèrent ou définissent la propriété locationByPlatform. Lorsque la propriété est définie avant que cette fenêtre ne soit affichée, la plate-forme choisit un emplacement adéquat. java.awt.Frame 1.0
• •
boolean isResizable() void setResizable(boolean b)
Récupèrent ou définissent la propriété resizable. Lorsque la propriété est définie, l’utilisateur peut modifier la taille du cadre. • •
String getTitle() void setTitle(String s)
Récupèrent ou définissent la propriété title qui détermine le texte de la barre de titre du cadre. • •
Image getIconImage() void setIconImage(Image image)
Récupèrent ou définissent la propriété iconImage qui détermine l’icône du cadre. Le système de fenêtrage peut afficher l’icône dans le cadre de la décoration ou à d’autres endroits. • •
boolean isUndecorated() 1.4 void setUndecorated(boolean b) 1.4
Récupèrent ou définissent la propriété undecorated. Lorsque la propriété est définie, le cadre est affiché sans décorations, comme une barre de titre ou un bouton de fermeture. Cette méthode doit être appelée avant que le cadre soit affiché. •
int getExtendedState() 1.4
Livre Java.book Page 300 Mercredi, 13. février 2008 2:34 14
300
•
Au cœur de Java 2 - Notions fondamentales
void setExtendedState(int state) 1.4
Récupèrent ou définissent l’état étendu de la fenêtre. L’état est l’un de ceux-ci : Frame.NORMAL Frame.ICONIFIED Frame.MAXIMIZED_HORIZ Frame.MAXIMIZED_VERT Frame.MAXIMIZED_BOTH java.awt.Toolkit 1.0 static Toolkit getDefaultToolkit()
•
Renvoie la boîte à outils par défaut. •
Dimension getScreenSize()
Récupère la taille de l’écran. •
Image getImage(String filename)
Charge une image à partir du fichier spécifié par filename.
Affichage des informations dans un composant Nous allons voir maintenant comment afficher des informations à l’intérieur d’un cadre. Par exemple, au lieu d’afficher "Not a Hello, World program" en mode texte dans une fenêtre de console, comme nous l’avons fait au Chapitre 3, nous afficherons le message dans un cadre (voir Figure 7.6). Figure 7.6 Un cadre qui affiche des informations.
Il est possible de dessiner directement le message dans le cadre, mais ce n’est pas considéré comme une bonne technique de programmation. En Java, les cadres sont conçus pour être les conteneurs d’autres composants (barre de menus et autres éléments d’interface). On dessine normalement sur un autre composant préalablement 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 possède quatre couches superposées. Nous n’avons pas à nous préoccuper ici de la racine (JRoot), de la couche superposée (JLayeredPane) et de la vitre ; elles sont nécessaires pour l’organisation de la barre de menus et du contenu, ainsi que pour implémenter l’aspect (look and feel) du cadre. La partie qui intéresse les programmeurs Swing est la couche contenu. Lorsque nous concevons un cadre, nous ajoutons les composants au contenu à l’aide d’instructions comme celles-ci : Container contentPane = frame.getContentPane(); Component c = . . .; contentPane.add(c);
Livre Java.book Page 301 Mercredi, 13. février 2008 2:34 14
Chapitre 7
Programmation graphique
301
Jusqu’à Java SE 1.4, la méthode add de la classe JFrame était définie de manière à déclencher une exception avec le message "Do not use JFrame.add(). Use JFrame.getContentPane().add() instead". Depuis Java SE 5.0, la méthode JFrame.add n’essaie plus de rééduquer les programmeurs et appelle simplement add sur le volet de contenu. Ainsi, depuis Java SE 5.0, vous pouvez simplement utiliser l’appel : frame.add(c);
Figure 7.7 La structure interne d’un cadre JFrame.
Titre Cadre Volet racine Volet en couche Barre de menu (en option) Volet de contenu Vitrine
Dans notre cas, nous désirons uniquement ajouter au contenu un composant sur lequel nous écrirons le message. Pour dessiner sur un composant, vous définissez une classe qui étend JComponent et remplace la méthode paintComponent dans cette classe. La méthode paintComponent reçoit un paramètre, de type Graphics. Un objet Graphics mémorise une série de paramètres pour le dessin d’images et de texte, tels que la police définie ou la couleur. Un dessin en Java doit passer par un objet Graphics. Il possède des méthodes pour dessiner des motifs, des images et du texte. INFO Le paramètre Graphics est comparable à un contexte d’affichage de Windows ou à un contexte graphique en programmation X11.
Livre Java.book Page 302 Mercredi, 13. février 2008 2:34 14
302
Au cœur de Java 2 - Notions fondamentales
Voici comment créer un panneau, sur lequel vous pourrez dessiner : class MyComponent extends JComponent { public void paintComponent(Graphics g) { code de dessin } }
Chaque fois qu’une fenêtre doit être redessinée, quelle qu’en soit la raison, le gestionnaire d’événement envoie une notification au composant. Les méthodes paintComponent de tous les composants sont alors exécutées. N’appelez jamais directement la méthode paintComponent. Elle est appelée automatiquement lorsqu’une portion de votre application doit être redessinée ; vous ne devez pas créer d’interférence. Quelles sortes d’actions sont déclenchées par cette réponse automatique ? Par exemple, la fenêtre est redessinée parce que l’utilisateur a modifié sa taille ou parce qu’il l’ouvre à nouveau après l’avoir réduite dans la barre des tâches. De même, si l’utilisateur a ouvert, puis refermé une autre fenêtre au-dessus d’une fenêtre existante, cette dernière doit être redessinée, car son affichage a été perturbé (le système graphique n’effectue pas de sauvegarde des pixels cachés). Bien entendu, lors de sa première ouverture, une fenêtre doit exécuter le code qui indique comment, et où, les éléments qu’elle contient sont affichés. ASTUCE Si vous devez forcer une fenêtre à se redessiner, appelez la méthode repaint plutôt que paintComponent. La méthode repaint appelle paintComponent pour tous les composants de la fenêtre, en fournissant chaque fois un objet Graphics approprié.
Comme vous avez pu le constater dans le fragment de code précédent, la méthode paintComponent prend un seul paramètre de type Graphics. Les coordonnées et les dimensions appliquées à cet objet sont exprimées en pixels. Les coordonnées (0, 0) représentent le coin supérieur gauche du composant sur lequel vous dessinez. L’affichage de texte est considéré comme une forme particulière de dessin. La classe Graphics dispose d’une méthode drawString dont voici la syntaxe : g.drawString(text, x, y)
Nous souhaitons dessiner la chaîne "Not a Hello, World program" à peu près au centre de la fenêtre originale. Bien que nous ne sachions pas encore comment mesurer la taille de la chaîne, nous la ferons commencer à la position (75, 100). Cela signifie que le premier caractère sera situé à 75 pixels du bord gauche et 100 pixels du bord supérieur de la fenêtre (en fait, c’est la ligne de base du texte qui se situe à 100 pixels — nous verrons bientôt comment mesurer le texte). Notre méthode paintComponent ressemble donc à ceci : class NotHelloWorldComponent extends JComponent { public void paintComponent(Graphics g) {
Livre Java.book Page 303 Mercredi, 13. février 2008 2:34 14
Chapitre 7
Programmation graphique
303
g.drawString("Not a Hello, World program", MESSAGE_X, MESSAGE_Y); } public static final int MESSAGE_X = 75; public static final int MESSAGE_Y = 100; }
Le Listing 7.3 montre le code complet. INFO Au lieu d’étendre JComponent, certains programmeurs préfèrent étendre la classe JPanel. Un JPanel a pour but d’être un conteneur d’autres composants, mais il est aussi possible de dessiner dessus. Il y a une seule différence. Un panneau est opaque, ce qui signifie qu’il est chargé de dessiner tous les pixels dans les limites. La manière la plus simple d’y parvenir consiste à remplir le panneau avec la couleur d’arrière-plan, en appelant super.paintComponent dans la méthode paintComponent de chaque sous-classe de panneau : class NotHelloWorldPanel extends JPanel { public void paintComponent(Graphics g) { super.paintComponent(g); . . . // code de dessin } }
Listing 7.3 : NotHelloWorld.java import javax.swing.*; import java.awt.*; /** * @version 1.32 2007-06-12 * @author Cay Horstmann */ public class NotHelloWorld { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { NotHelloWorldFrame frame = new NotHelloWorldFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); } } /** * Un cadre qui contient un panneau de message */
Livre Java.book Page 304 Mercredi, 13. février 2008 2:34 14
304
Au cœur de Java 2 - Notions fondamentales
class NotHelloWorldFrame extends JFrame { public NotHelloWorldFrame() { setTitle("NotHelloWorld"); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); // ajouter le panneau au cadre NotHelloWorldPanel panel = new NotHelloWorldPanel(); add(panel); } public static final int DEFAULT_WIDTH = 300; public static final int DEFAULT_HEIGHT = 200; } /** * Un panneau qui affiche un message. */ class NotHelloWorldPanel extends JPanel { public void paintComponent(Graphics g) { g.drawString("Not a Hello, World program", MESSAGE_X, MESSAGE_Y); } public static final int MESSAGE_X = 75; public static final int MESSAGE_Y = 100; } javax.swing.JFrame 1.2
•
Container getContentPane()
Renvoie l’objet du panneau de contenu pour JFrame. •
Component add(Component c)
Ajoute et renvoie le composant donné au volet de contenu de ce cadre (avant Java SE 5.0, cette méthode déclenchait une exception). java.awt.Component 1.0
•
void repaint()
Provoque un nouveau dessin du composant "dès que possible". •
public void repaint(int x, int y, int width, int height)
Provoque un nouveau dessin d’une partie du composant "dès que possible". javax.swing.JComponent 1.2
•
void paintComponent(Graphics g)
Surcharge cette méthode pour décrire la manière dont votre composant doit être dessiné.
Livre Java.book Page 305 Mercredi, 13. février 2008 2:34 14
Chapitre 7
Programmation graphique
305
Formes 2D Depuis la version 1.0 de Java, la classe Graphics disposait de méthodes pour dessiner des lignes, des rectangles, des ellipses, etc. Mais ces opérations de dessin sont très limitées. Par exemple, vous ne pouvez pas tracer des traits d’épaisseurs différentes ni faire pivoter les formes. Java SE 1.2 a introduit la bibliothèque Java 2D qui implémente un jeu d’opérations graphiques très puissantes. Dans ce chapitre, nous allons seulement examiner les fonctions de base de la bibliothèque Java 2D. Consultez le chapitre du Volume II traitant des fonctions AWT avancées pour plus d’informations sur les techniques sophistiquées. Pour dessiner des formes dans la bibliothèque Java 2D, vous devez obtenir un objet de la classe Graphics2D. Il s’agit d’une sous-classe de la classe Graphics. Depuis Java SE 2, les méthodes telles que paintComponent reçoivent automatiquement un objet de la classe Graphics2D. Il suffit d’avoir recours à un transtypage, de la façon suivante : public void paintComponent(Graphics g) { Graphics2D g2 = (Graphics2D)g; . . . }
La bibliothèque Java 2D organise les formes géométriques d’une façon orientée objet. En particulier, il existe des classes pour représenter des lignes, des rectangles et des ellipses : Line2D Rectangle2D Ellipse2D
Ces classes implémentent toutes l’interface Shape. INFO La bibliothèque Java 2D gère des formes plus complexes — en particulier, les arcs, les courbes quadratiques et cubiques et les objets "general path". Voir le Chapitre 7 du Volume II pour plus d’informations.
Pour dessiner une forme, vous devez d’abord créer un objet d’une classe qui implémente l’interface Shape puis appeler la méthode draw de la classe Graphics2D. Par exemple : Rectangle2D rect = . . .; g2.draw(rect);
INFO Avant l’apparition de la bibliothèque Java 2D, les programmeurs utilisaient les méthodes de la classe Graphics telles que drawRectangle pour dessiner des formes. En apparence, les appels de méthode de l’ancien style paraissent plus simples. Cependant, avec la bibliothèque Java 2D, vos options restent ouvertes — vous pouvez ultérieurement améliorer vos dessins à l’aide des nombreux outils que fournit la bibliothèque.
L’utilisation des classes de formes Java 2D amène une certaine complexité. Contrairement aux méthodes de la version 1.0, qui utilisaient des entiers pour les coordonnées de pixels, Java 2D emploie des valeurs de coordonnées en virgule flottante. Cela est souvent pratique, car vous pouvez spécifier
Livre Java.book Page 306 Mercredi, 13. février 2008 2:34 14
306
Au cœur de Java 2 - Notions fondamentales
pour vos formes des coordonnées qui sont significatives pour vous (comme des millimètres par exemple), puis les traduire en pixels. La bibliothèque Java 2D utilise des valeurs float simple précision pour nombre de ses calculs internes en virgule flottante. La simple précision est suffisante — après tout, le but ultime des calculs géométriques est de définir des pixels à l’écran ou sur l’imprimante. Tant qu’une erreur d’arrondi reste cantonnée à un pixel, l’aspect visuel n’est pas affecté. De plus, les calculs en virgule flottante sont plus rapides sur certaines plates-formes et les valeurs float requièrent un volume de stockage réduit 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 inflexible en ce qui concerne les transtypages nécessaires pour la conversion de valeurs double en valeurs float. Par exemple, examinez l’instruction suivante : float f = 1.2; // Erreur
Cette instruction échoue à la compilation, car la constante 1.2 est du type double, et le compilateur est très susceptible en ce qui concerne la perte de précision. Le remède consiste à ajouter un suffixe F à la constante à virgule flottante : float f = 1.2F; // Ok
Considérez maintenant l’instruction Rectangle2D r = . . . float f = r.getWidth(); // Erreur
Cette instruction échouera également à la compilation, pour la même raison. La méthode getWidth renvoie un double. Cette fois, le remède consiste à prévoir un transtypage : float f = (float)r.getWidth(); // Ok
Les suffixes et les transtypages étant un inconvénient évident, les concepteurs de la bibliothèque 2D ont décidé de fournir deux versions de chaque classe de forme : une avec des coordonnées float pour les programmeurs économes, et une avec des coordonnées double pour les paresseux (dans cet ouvrage, nous nous rangerons du côté des seconds, et nous utiliserons des coordonnées double dans la mesure du possible). Les concepteurs de la bibliothèque ont choisi une méthode curieuse, et assez déroutante au premier abord, pour le packaging de ces choix. Examinez la classe Rectangle2D. C’est une classe abstraite, avec deux sous-classes concrètes, qui sont aussi des classes internes statiques : Rectangle2D.Float Rectangle2D.Double
La Figure 7.8 montre le schéma d’héritage. Figure 7.8 Les classes Rectangle2D.
Rectangle2D
Rectangle2D .Float
Rectangle2D .Double
Livre Java.book Page 307 Mercredi, 13. février 2008 2:34 14
Chapitre 7
Programmation graphique
307
Il est préférable de tenter d’ignorer le fait que les deux classes concrètes sont internes statiques — c’est juste une astuce pour éviter d’avoir à fournir des noms tels que FloatRectangle2D et DoubleRectangle2D (pour plus d’informations au sujet des classes internes statiques, reportez-vous au Chapitre 6). Lorsque vous construisez un objet Rectangle2D.Float, vous fournissez les coordonnées 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 réalité, puisque à la fois Rectangle2D.Float et Rectangle2D.Double étendent la classe commune Rectangle2D, et que les méthodes dans les sous-classes surchargent simplement les méthodes dans la superclasse Rectangle2D, il n’y a aucun intérêt à mémoriser le type exact de forme. Vous pouvez simplement employer des variables Rectangle2D pour stocker les références 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);
C’est-à-dire que vous n’avez besoin d’utiliser les empoisonnantes classes internes que lorsque vous construisez les objets forme. Les paramètres de construction indiquent le coin supérieur gauche, la largeur et la hauteur du rectangle. INFO En réalité, la classe Rectangle2D.Float possède une méthode supplémentaire qui n’est pas héritée de Rectangle2D, il s’agit de setRect(float x, float y, float h, float w). Vous perdez cette méthode si vous stockez la référence Rectangle2D.Float dans une variable Rectangle2D. Mais ce n’est pas une grosse perte — la classe Rectangle2D dispose d’une méthode setRect avec des paramètres double.
Les méthodes de Rectangle2D utilisent des paramètres et des valeurs renvoyées de type double. Par exemple, la méthode getWidth renvoie une valeur double, même si la largeur est stockée sous la forme de float dans un objet Rectangle2D.Float. ASTUCE Utilisez simplement les classes de forme Double pour éviter d’avoir à manipuler des valeurs float. Cependant, si vous construisez des milliers d’objets forme, envisagez d’employer les classes Float pour économiser la mémoire.
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 l’utiliser : Point2D p = new Point2D.Double(10, 20);
Livre Java.book Page 308 Mercredi, 13. février 2008 2:34 14
308
Au cœur de Java 2 - Notions fondamentales
ASTUCE La classe Point2D est très utile — elle est plus orientée objet pour fonctionner avec les objets Point2D qu’avec des valeurs x et y séparées. De nombreux constructeurs et méthodes acceptent des paramètres Point2D. Nous vous suggérons d’utiliser des objets Point2D autant que possible — ils facilitent généralement la compréhension des calculs géométriques.
Les classes Rectangle2D et Ellipse2D héritent toutes deux d’une superclasse commune RectangularShape. Bien sûr, les ellipses ne sont pas rectangulaires, mais elles sont incluses dans un rectangle englobant (voir Figure 7.9). Figure 7.9 Le rectangle englobant d’une ellipse.
La classe RectangularShape définit plus de vingt méthodes qui sont communes à ces formes, parmi lesquelles les incontournables getWidth, getHeight, getCenterX et getCenterY (mais malheureusement, au moment où nous écrivons ces lignes, il n’existe pas de méthode getCenter qui renverrait le centre sous la forme d’un objet Point2D). On trouve enfin deux classes héritées de Java 1.0 qui ont été insérées dans la hiérarchie de la classe Shape. Les classes Rectangle et Point, qui stockent un rectangle et un point avec des coordonnées entières, é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 héritées sont grisées. Les objets Rectangle2D et Ellipse2D sont simples à construire. Vous devez spécifier : m
les coordonnées x et y du coin supérieur gauche ;
m
la largeur et la hauteur.
Pour les ellipses, ces valeurs font référence au rectangle englobant. Par exemple, Ellipse2D e = new Ellipse2D.Double(150, 200, 100, 50);
construit une ellipse englobée dans un rectangle dont le coin supérieur gauche a les coordonnées (150, 200), avec une largeur de 100 et une hauteur de 50. Il arrive que vous ne disposiez pas directement des valeurs du coin supérieur gauche. Il est assez fréquent d’avoir les deux coins opposés de la diagonale d’un rectangle, mais peut-être qu’il ne s’agit pas des coins supérieur gauche et inférieur droit. Vous ne pouvez pas construire simplement un rectangle ainsi : Rectangle2D rect = new Rectangle2D.Double(px, py, qx - px, qy - py); // Erreur
Livre Java.book Page 309 Mercredi, 13. février 2008 2:34 14
Chapitre 7
Programmation graphique
309
Figure 7.10 Relations entre les classes Shape.
Forme
Point2D
Point
Forme rectangulaire
Line2D
Ellipse2D
Rectangle2D
Rectangle
Si p n’est pas le coin supérieur gauche, l’une des différences de coordonnées, ou les deux, sera négative et le rectangle sera vide. Dans ce cas, créez d’abord un rectangle vide et utilisez la méthode setFrameFromDiagonal : Rectangle2D rect = new Rectangle2D.Double(); rect.setFrameFromDiagonal(px, py, qx, qy);
Ou, mieux encore, si vous connaissez les points d’angle en tant qu’objets Point2D, p et q : rect.setFrameFromDiagonal(p, q);
Lors de la construction d’une ellipse, vous connaissez généralement le centre, la largeur et la hauteur, mais pas les points des angles du rectangle englobant (qui ne reposent pas sur l’ellipse). Il existe une méthode setFrameFromCenter qui utilise le point central, mais qui requiert toujours l’un des quatre points d’angle. Vous construisez donc généralement une ellipse de la façon suivante : Ellipse2D ellipse = new Ellipse2D.Double(centerX - width / 2, centerY - height / 2, width, height);
Pour construire une ligne, vous fournissez les points de départ et d’arrivée, sous la forme d’objets 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, l’ellipse incluse dans le rectangle, une diagonale du rectangle et un cercle ayant le même centre que le rectangle. La Figure 7.11 montre le résultat.
Livre Java.book Page 310 Mercredi, 13. février 2008 2:34 14
310
Au cœur de Java 2 - Notions fondamentales
Figure 7.11 Rectangles et ellipses.
Listing 7.4 : DrawTest.java import java.awt.*; import java.awt.geom.*; import javax.swing.*; /** * @version 1.32 2007-04-14 * @author Cay Horstmann */ public class DrawTest { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { DrawFrame frame = new DrawFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); } } /** * Un cadre contenant un panneau avec des dessins */ class DrawFrame extends JFrame { public DrawFrame() { setTitle("DrawTest"); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
Livre Java.book Page 311 Mercredi, 13. février 2008 2:34 14
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 l’ellipse à l’intérieur 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 même 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()
• • • •
Livre Java.book Page 312 Mercredi, 13. février 2008 2:34 14
312
• •
Au cœur de Java 2 - Notions fondamentales
double getMaxX() double getMaxY()
Renvoient le centre, les valeurs x ou y minimum ou maximum du rectangle englobant. • •
double getWidth() double getHeight()
Renvoient la largeur ou la hauteur du rectangle englobant. • •
double getX() double getY()
Renvoient les coordonnées x ou y du coin supérieur gauche du rectangle englobant. java.awt.geom.Rectangle2D.Double 1.2 • Rectangle2D.Double(double x, double y, double w, double h)
Construit un rectangle avec les valeurs données pour le coin supérieur 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 données pour le coin supérieur 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 données pour le coin supérieur gauche, la largeur et la hauteur. java.awt.geom.Point2D.Double 1.2 • Point2D.Double(double x, double y)
Construit un point avec les coordonnées indiquées. java.awt.geom.Line2D.Double 1.2 • Line2D.Double(Point2D start, Point2D end) • Line2D.Double(double startX, double startY, double endX, double endY)
Construisent une ligne avec les points de départ et d’arrivée indiqués.
Couleurs La méthode setPaint de la classe Graphics2D permet de sélectionner une couleur qui sera employée par toutes les opérations de dessin ultérieures pour le contexte graphique. Par exemple : g2.setPaint(Color.red); g2.drawString("Warning!", 100, 100);
Vous pouvez remplir de couleur l’intérieur de formes fermées (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
Livre Java.book Page 313 Mercredi, 13. février 2008 2:34 14
Chapitre 7
Programmation graphique
313
Pour dessiner avec plusieurs couleurs, utilisez draw ou fill, puis choisissez une autre couleur et répétez l’opération. Les couleurs sont définies à l’aide de la classe Color. La classe java.awt.Color propose des constantes prédéfinies 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 antérieures, en minuscules.
Vous pouvez spécifier une couleur personnalisée en créant un objet Color à l’aide de ses composantes rouge, verte et bleue. En utilisant une échelle de 0 à 255 (c’est-à-dire un octet) pour les proportions de rouge, de vert et de bleu, appelez le constructeur de Color de la façon suivante : Color(int redness, int greenness, int blueness)
Voici un exemple de définition de couleur personnalisée : g2.setPaint(new Color(0, 128, 128)); // un bleu-vert foncé g2.drawString("Welcome!", 75, 125);
INFO En plus des couleurs franches, vous pouvez sélectionner des définitions de "peinture" plus complexes, comme des images ou des teintes nuancées. Consultez le chapitre du Volume II traitant de AWT pour plus de détails. Si vous utilisez un objet Graphics au lieu de Graphics2D, vous devrez avoir recours à la méthode setColor pour définir les couleurs.
Pour spécifier la couleur d’arrière-plan (ou de fond), utilisez la méthode setBackground de la classe Component, qui est un ancêtre de JComponent : MyComponent p = new MyComponent(); p.setBackground(Color.PINK);
Il existe aussi une méthode setForeground. Elle spécifie la couleur par défaut utilisée pour le dessin. ASTUCE Les méthodes brighter() et darker() de la classe Color produisent des versions plus vives ou plus foncées de la couleur actuelle. La méthode brighter permet de mettre un élément en surbrillance, mais avive en réalité à peine la couleur. Pour obtenir une couleur nettement plus visible, appelez trois fois la méthode : c.brighter().brighter().brighter().
Livre Java.book Page 314 Mercredi, 13. février 2008 2:34 14
314
Au cœur de Java 2 - Notions fondamentales
Java fournit des noms prédéfinis pour de nombreuses couleurs dans sa classe SystemColor. Les constantes de cette classe encapsulent les couleurs employées pour divers éléments du système de l’utilisateur. Par exemple, frame.setBackground(SystemColor.window)
affecte à l’arrière-plan du panneau la couleur utilisée par défaut par toutes les fenêtres du bureau (l’arrière-plan est rempli avec cette couleur chaque fois que la fenêtre est repeinte). L’emploi des couleurs de la classe SystemColor est particulièrement utile si vous voulez dessiner des éléments d’interface dont les couleurs doivent s’accorder avec celles de l’environnement. Le Tableau 7.1 décrit les couleurs système. Tableau 7.1 : Couleurs du système
Nom
Objet
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
Arrière-plan du bureau Arrière-plan des titres Texte des titres Bordure des titres Arrière-plan des titres inactifs Texte des titres inactifs Bordure des titres inactifs Arrière-plan des fenêtres Bordure des fenêtres Texte à l’intérieur d’une fenêtre Arrière-plan des menus Texte des menus Arrière-plan du texte Couleur du texte Texte des contrôles inactifs Arrière-plan du texte en surbrillance Couleur du texte en surbrillance Arrière-plan des contrôles Texte des contrôles Couleur de surbrillance claire des contrôles Couleur de surbrillance des contrôles Ombre des contrôles Ombre foncée des contrôles Arrière-plan des barres de défilement Arrière-plan du texte des bulles d’aide Couleur du texte des bulles d’aide
Livre Java.book Page 315 Mercredi, 13. février 2008 2:34 14
Chapitre 7
Programmation graphique
315
java.awt.Color 1.0
•
Color(int r, int g, int b)
Crée un objet couleur. Paramètres :
r
Valeur de la composante rouge (0-255).
g
Valeur de la composante verte (0-255).
b
Valeur de la composante bleue (0-255).
java.awt.Graphics 1.0
• •
Color getColor() void setColor(Color c)
Récupèrent ou définissent la couleur courante. Toutes les opérations graphiques ultérieures utiliseront la nouvelle couleur. Paramètres :
c
Nouvelle couleur.
java.awt.Graphics2D 1.2
• •
Paint getPaint() void setPaint(Paint p)
Récupèrent ou définissent les paramètres de peinture de ce contexte graphique. La classe Color implémente l’interface Paint. Vous pouvez donc utiliser cette méthode pour affecter les attributs à une couleur. •
void fill(Shape s)
Remplit la forme de la peinture actuelle. java.awt.Component 1.0
• •
Color getBackground() void setBackground (Color c)
Récupèrent ou définissent la couleur d’arrière-plan. Paramètres : • •
c
Nouvelle couleur d’arrière-plan.
Color getForeground() void setForeground(Color c)
Récupèrent ou définissent la couleur d’avant-plan. Paramètres :
c
Nouvelle couleur d’avant-plan.
Texte et polices Le programme NotHelloWorld au début de ce chapitre affichait une chaîne avec la fonte par défaut. Il est possible d’écrire un texte avec une fonte différente. Une fonte est spécifiée grâce à son nom de police. Un nom de police se compose d’un nom de famille de polices, comme "Helvetica" et d’un suffixe 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 connaître les fontes disponibles sur un ordinateur, appelez la méthode getAvailableFontFamilyNames de la classe GraphicsEnvironment. Cette méthode renvoie un tableau de chaînes
Livre Java.book Page 316 Mercredi, 13. février 2008 2:34 14
316
Au cœur de Java 2 - Notions fondamentales
contenant les noms de toutes les fontes disponibles. La méthode statique getLocalGraphicsEnvironment permet d’obtenir une instance de la classe GraphicsEnvironment qui décrit l’environnement graphique du système utilisateur. Le programme suivant donne en sortie une liste de tous les noms de fonte de votre système : import java.awt.*; public class ListFonts { public static void main(String[] args) { String[] fontNames = GraphicsEnvironment .getLocalGraphicsEnvironment() .getAvailableFontFamilyNames(); for (String fontName : fontNames) System.out.println(fontName); } }
Pour un système donné, la liste commencera ainsi : Abadi MT Condensed Light Arial Arial Black Arial Narrow Arioso Baskerville Binner Gothic . . .
et se poursuivra avec quelque soixante-dix fontes supplémentaires. Les noms de fontes peuvent être déposés, et faire l’objet de copyright sous certaines juridictions. La distribution des fontes implique donc souvent le versement de droits d’auteur. Bien entendu, comme dans le cas des parfums célèbres, on trouve des imitations peu coûteuses de certaines fontes réputées. A titre d’exemple, l’imitation d’Helvetica distribuée avec Windows se nomme Arial. Pour établir une ligne de base commune, AWT définit cinq noms de fontes logiques : SansSerif
Serif Espacement fixe Dialog DialogInput
Ces noms sont toujours transcrits en fontes existant dans l’ordinateur client. Par exemple, dans un système Windows, SansSerif est transcrit en Arial. En outre, le JDK de Sun comprend toujours trois familles de polices nommées "Lucida Sans", "Lucida Bright" et "Lucida Sans Typewriter". Pour écrire des caractères dans une fonte, vous devez d’abord créer un objet de la classe Font. Vous spécifiez le nom de fonte, son style et la taille en points. Voici un exemple de construction d’un objet Font : Font sansbold14 = new Font("SansSerif", Font.BOLD, 14);
Livre Java.book Page 317 Mercredi, 13. février 2008 2:34 14
Chapitre 7
Programmation graphique
317
Le troisième paramètre est la taille en points. Le point est souvent utilisé en typographie pour indiquer la taille d’une police. Un pouce comprend 72 points. Le nom de fonte logique peut être employé dans le constructeur de Font. Il faut ensuite spécifier le style (plain, bold, italic ou bold italic) dans le second paramètre 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 définie dans le fichier fontconfig.properties du sous-répertoire jre/lib de l’installation Java. Pour plus d’informations sur ce fichier, voir http://java.sun.com/javase/ 6/docs/technotes/guides/intl/fontconfig.html.
Vous pouvez lire les fontes TrueType ou PostScript Type 1. Un flux d’entrée est nécessaire pour la fonte — généralement à partir d’un fichier disque ou d’une adresse URL (voir le Chapitre 1 du Volume II pour plus d’informations sur les flux). Appelez ensuite la méthode 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 méthode deriveFont pour obtenir une fonte de la taille désirée : Font df = f1.deriveFont(14.0F);
ATTENTION Il existe deux versions surchargées de la méthode deriveFont. L’une d’elles (ayant un paramètre float) définit le corps de la police, l’autre (avec un paramètre int), son style. Dès lors, f1.deriveFont(14) définit le style et non le corps (le résultat est une police italique car la représentation binaire de 14 définit le type ITALIC mais non le type BOLD).
Les fontes Java contiennent les habituels caractères ASCII et des symboles. Par exemple, si vous affichez le caractère ’\u2297’ de la fonte Dialog, vous obtenez un caractère ⊗ représentant une croix dans un cercle. Seuls sont disponibles les symboles définis dans le jeu de caractères Unicode. Voici maintenant le code affichant la chaîne "Hello, World!" avec la fonte sans sérif standard de votre système, 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);
Livre Java.book Page 318 Mercredi, 13. février 2008 2:34 14
318
Au cœur de Java 2 - Notions fondamentales
Nous allons maintenant centrer la chaîne dans son composant au lieu de l’écrire à une position arbitraire. Nous devons connaître la largeur et la hauteur de la chaîne en pixels. Ces dimensions dépendent de trois facteurs : m
La fonte utilisée (ici, sans sérif, bold, 14 points).
m
La chaîne (ici, "Hello, World!").
m
L’unité sur laquelle la chaîne est écrite (ici, l’écran de l’utilisateur).
Pour obtenir un objet qui représente les caractéristiques de fonte de l’écran, appelez la méthode getFontRenderContext de la classe Graphics2D. Elle renvoie un objet de la classe FontRenderContext. Vous passez simplement cet objet à la méthode getStringBounds de la classe Font : FontRenderContext context = g2.getFontRenderContext(); Rectangle2D bounds = f.getStringBounds(message, context);
La méthode getStringBounds renvoie un rectangle qui englobe la chaîne. Pour interpréter les dimensions de ce rectangle, il est utile de connaître certains des termes de typographie (voir Figure 7.12). La ligne de base (baseline) est la ligne imaginaire sur laquelle s’aligne la base des caractères comme "e". Le jambage ascendant (ascent) représente la distance entre la ligne de base et la partie supérieure d’une lettre "longue du haut", comme "b" ou "k" ou un caractère majuscule. Le jambage descendant (descent) représente la distance entre la ligne de base et la partie inférieure d’une lettre "longue du bas", comme "p" ou "g". Figure 7.12 Illustration des termes de typographie.
ligne de base
e b k p g
hauteur
jambage ascendant jambage descendant interligne
ligne de base
L’interligne (leading) est l’intervalle entre la partie inférieure d’une ligne et la partie supérieure 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 méthode getStringBounds est l’étendue horizontale de la chaîne. La hauteur du rectangle est la somme des jambages ascendant, descendant et de l’interligne. L’origine du rectangle se trouve à la ligne de base de la chaîne. La coordonnée y supérieure du rectangle est négative. Vous pouvez obtenir les valeurs de largeur, de hauteur et du jambage ascendant d’une chaîne, de la façon suivante : double stringWidth = bounds.getWidth(); double stringHeight = bounds.getHeight(); double ascent = -bounds.getY();
Si vous devez connaître le jambage descendant ou l’interligne, appelez la méthode getLineMetrics de la classe Font. Elle renvoie un objet de la classe LineMetrics, possédant des méthodes permettant d’obtenir le jambage descendant et l’interligne : LineMetrics metrics = f.getLineMetrics(message, context); float descent = metrics.getDescent(); float leading = metrics.getLeading();
Livre Java.book Page 319 Mercredi, 13. février 2008 2:34 14
Chapitre 7
Programmation graphique
319
Le code suivant utilise toutes ces informations pour centrer une chaîne à l’intérieur de son composant contenant : FontRenderContext context = g2.getFontRenderContext(); Rectangle2D bounds = f.getStringBounds(message, context); // (x,y) = coin supérieur 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, considérez que getWidth() renvoie la largeur du composant. Une portion de cette largeur, bounds.getWidth(), est occupée par la chaîne de message. Le reste doit être équitablement réparti des deux côtés. Par conséquent, l’espace vide de chaque côté représente la moitié de la différence. Le même raisonnement s’applique à la hauteur. INFO Pour calculer des dimensions de mise en page en dehors de la méthode paintComponent, sachez que le contexte de rendu de la fonte ne peut pas être obtenu de l’objet Graphics2D. Appelez plutôt la méthode getFontMetrics de la classe JComponent, puis appelez getFontRenderContext. FontRenderContext context = getFontMetrics(f).getFontRenderContext();
Pour montrer que le positionnement est précis, l’exemple dessine la ligne de base et le rectangle englobant. La Figure 7.13 montre l’affichage à l’écran, le Listing 7.5 le listing du programme. Figure 7.13 Dessin de la ligne de base et des limites de la chaîne.
Listing 7.5 : FontTest.java import import import import
java.awt.*; java.awt.font.*; java.awt.geom.*; javax.swing.*;
/** * @version 1.33 2007-04-14 * @author Cay Horstmann */ public class FontTest
Livre Java.book Page 320 Mercredi, 13. février 2008 2:34 14
320
Au cœur de Java 2 - Notions fondamentales
{ public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { FontFrame frame = new FontFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); } } /** * Un cadre avec un composant pour un message texte */ class FontFrame extends JFrame { public FontFrame() { setTitle("FontTest"); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); // ajouter un composant au cadre FontComponent component = new FontComponent(); add(component); } public static final int DEFAULT_WIDTH = 300; public static final int DEFAULT_HEIGHT = 200; } /** * Un composant affichant un message centré dans une boîte. */ 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); // définir (x,y) = coin supérieur gauche du texte double x = (getWidth() - bounds.getWidth()) / 2; double y = (getHeight() - bounds.getHeight()) / 2;
Livre Java.book Page 321 Mercredi, 13. février 2008 2:34 14
Chapitre 7
Programmation graphique
321
// ajouter jambage ascendant à y pour atteindre la ligne de base double ascent = -bounds.getY(); double baseY = y + ascent; // écrire le message g2.drawString(message, (int)x, (int)(baseY); g2.setPaint(Color.LIGHT_GRAY); // dessiner la ligne de base g2.draw(new Line2D.Double(x, baseY, x + bounds.getWidth(), baseY)); // dessiner le rectangle englobant Rectangle2D rect = new Rectangle2D.Double(x, y, bounds.getWidth(), bounds.getHeight()); g2.draw(rect); } } java.awt.Font 1.0
•
Font(String name, int style, int size)
Crée un nouvel objet fonte. Paramètres :
•
name
Le nom de la fonte. Il s’agit soit d’un nom de police (comme "Helvetica Bold"), soit d’un nom de fonte logique (comme "Serif" ou "SansSerif").
style
Le style (Font.PLAIN, Font.BOLD, Font.ITALIC ou Font.BOLD + Font.ITALIC).
size
La taille en points (par exemple, 12).
String getFontName()
Renvoie le nom de police (par exemple, "Helvetica Bold"). •
String getFamily()
Renvoie le nom de la famille de polices (comme "Helvetica"). •
String getName()
Renvoie le nom logique (par exemple, "SansSerif") si la fonte a été créée avec un nom de police logique ; sinon la méthode renvoie le "nom de police" de la fonte. •
Rectangle2D getStringBounds(String s, FontRenderContext context) 1.2
Renvoie un rectangle qui englobe la chaîne. L’origine du rectangle se trouve sur la ligne de base. La coordonnée y supérieure du rectangle est égale à la valeur négative du jambage ascendant. La hauteur du rectangle égale la somme des jambages ascendant et descendant et de l’interligne. La largeur est celle de la chaîne. •
LineMetrics getLineMetrics(String s, FontRenderContext context) 1.2
Renvoie un objet Line pour déterminer l’étendue de la chaîne.
Livre Java.book Page 322 Mercredi, 13. février 2008 2:34 14
322
• • •
Au cœur de Java 2 - Notions fondamentales
Font deriveFont(int style) 1.2 Font deriveFont(float size) 1.2 Font deriveFont(int style, float size) 1.2
Renvoient une nouvelle fonte équivalant à cette fonte, avec la taille et le style demandés. java.awt.font.LineMetrics 1.2
•
float getAscent()
Renvoie la taille du jambage ascendant — distance entre la ligne de base et le sommet des caractères 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 l’interligne — l’espace entre la partie inférieure d’une ligne de texte et la partie supérieure de la ligne suivante. •
float getHeight()
Renvoie la hauteur totale de la fonte — distance entre deux lignes de base (jambage descendant + interligne + jambage ascendant). java.awt.Graphics 1.0
• •
Font getFont() void setFont(Font font)
Renvoient ou définissent la fonte actuelle. Cette fonte sera employée dans les opérations ultérieures d’affichage de texte. Paramètres : •
font
Une fonte.
void drawString(String str, int x, int y)
Dessine une chaîne avec la fonte et la couleur courantes. Paramètres :
str
La chaîne à dessiner.
x
Coordonnée x du début de la chaîne.
y
Coordonnée y de la ligne de base de la chaîne.
java.awt.Graphics2D 1.2
•
FontRenderContext getFontRenderContext ()
Extrait un contexte de rendu de fonte qui spécifie les caractéristiques de la fonte dans ce contexte graphique. •
void drawString(String str, float x, float y)
Dessine une chaîne avec la fonte et la couleur courantes. Paramètres :
str
La chaîne à dessiner.
x
Coordonnée x du début de la chaîne.
y
Coordonnée y de la ligne de base de la chaîne.
Livre Java.book Page 323 Mercredi, 13. février 2008 2:34 14
Chapitre 7
Programmation graphique
323
javax.swing.JComponent 1.2
•
FontMetrics getFontMetrics(Font f) 5.0
Renvoie la mesure de la fonte donnée. La classe FontMetrics est un précurseur de la classe LineMetrics. java.awt.FontMetrics 1.0
•
FontRenderContext getFontRenderContext() 1.2
Renvoie un contexte de rendu de fonte pour la fonte donnée.
Affichage d’images Vous avez vu comment créer des images simples en traçant des lignes et des formes. Des images complexes, telles que des photographies, sont habituellement générées en externe, par exemple avec un scanner ou un logiciel dédié à la manipulation d’images (comme vous le verrez au Volume II, il est également possible de produire une image pixel par pixel, et de stocker le résultat dans un tableau. Cette procédure est courante pour les images fractales, par exemple). Lorsque des images sont stockées dans des fichiers locaux ou sur Internet, vous pouvez les lire dans une application Java et les afficher sur des objets de type Graphics. Depuis Java SE 1.4, la lecture d’une image est très simple. Si l’image est stockée dans un fichier local, appelez String filename = "..."; Image image = ImageIO.read(new File(filename));
Sinon, vous pouvez fournir une URL : String urlname = "..."; Image image = ImageIO.read(new URL(urlname));
La méthode de lecture déclenche une exception IOException si l’image n’est pas disponible. Nous traiterons du sujet général de la gestion des exceptions au Chapitre 11. Pour l’instant, notre exemple de programme se contente d’intercepter cette exception et affiche un stack trace le cas échéant. La variable image contient alors une référence à un objet qui encapsule les données image. Vous pouvez afficher l’image grâce à la méthode 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 affiche l’image en mosaïque dans la fenêtre. Le résultat ressemble à celui de la Figure 7.14. L’affichage en mosaïque est effectué dans la méthode paintComponent. Nous dessinons d’abord une copie de l’image dans le coin supérieur gauche, puis nous appelons copyArea pour la copier dans toute la fenêtre : for (int i = 0; i * imageWidth =0" est la condition défaillante. En Java, la condition ne fait pas automatiquement partie du rapport d’erreur. Pour la voir, il faut la transférer sous forme de chaîne dans l’objet AssertionError : assert x >=0 : "x >= 0".
Livre Java.book Page 568 Mercredi, 13. février 2008 2:34 14
568
Au cœur de Java 2 - Notions fondamentales
Activation et désactivation des assertions Les assertions sont désactivées par défaut. Pour les activer, exécutez le programme avec l’option -enableassert ou -ea : java -enableassertions MyApp
Sachez qu’il est inutile de recompiler votre programme pour activer ou désactiver les assertions. L’activation ou la désactivation des assertions est une fonction du chargeur de classe. Lorsque les assertions sont désactivées, le chargeur de classe retire le code de l’assertion de sorte qu’il ne ralentisse pas l’exécution. Vous pouvez même activer les assertions dans des classes spécifiques 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. L’option -ea… active les assertions dans toutes les classes du package par défaut. Vous pouvez aussi désactiver les assertions dans certains packages et classes avec l’option -disableassertions ou -da : java -ea:... -da:MyClass MyApp
Certaines classes sont chargées non pas par un chargeur de classes mais directement par la machine virtuelle. Vous pouvez utiliser ces commutateurs pour activer ou désactiver au choix les assertions dans ces classes. Toutefois, les arguments -ea et -da qui activent ou désactivent toutes les assertions ne s’appliquent pas aux "classes système" en l’absence de chargeurs de classes. Utilisez l’argument -enablesystemassertions/-esa pour activer les assertions dans les classes système. Il est aussi possible de contrôler, par le biais de la programmation, le statut des assertions des chargeurs de classes. Reportez-vous aux notes API de cette section.
Vérification des paramètres avec des assertions Le langage Java propose trois mécanismes pour gérer les pannes système : m
déclenchement d’une exception ;
m
consignation ;
m
utilisation des assertions.
A quel moment choisir ces assertions ? Il faut se souvenir de ceci : m
Les pannes d’assertions sont supposées être des erreurs définitives et irrécupérables.
m
Les vérifications d’assertions ne sont activées que lors du développement et du test (ceci est quelquefois comparé à la situation suivante : porter un gilet de sauvetage lorsque l’on se trouve sur la rive et s’en débarrasser au milieu de l’océan).
Il est donc déconseillé d’utiliser les assertions pour signaler des conditions récupérables à une autre partie du programme ou pour communiquer des problèmes à l’utilisateur. Les assertions ne doivent être utilisées que pour retrouver des erreurs internes au programme lors du test.
Livre Java.book Page 569 Mercredi, 13. février 2008 2:34 14
Chapitre 11
Exceptions, consignation, assertion et mise au point
569
Etudions un scénario habituel : la vérification des paramètres de méthode. Faut-il utiliser les assertions pour rechercher des valeurs d’indice interdites ou des références null ? Pour répondre à cette question, étudiez la documentation de la méthode. Supposons que vous implémentiez une méthode de tri : /** * Trie la gamme spécifiée du tableau indiqué * dans l’ordre numérique croissant. La plage à trier * s’étend de fromIndex compris à toIndex, exclu. * @param a Le tableau à trier. * @param fromIndex L’indice du premier élément (inclus) à trier. * @param toIndex L’indice du dernier élément (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 méthode lance une exception si les valeurs d’indice sont fausses. Ce comportement fait partie du contrat que la méthode passe avec ses appelants. Si vous implémentez la méthode, vous devez respecter le contrat et lancer les exceptions indiquées. Il ne conviendrait pas d’utiliser les assertions ici. Faut-il déclarer que a n’est pas null ? Voilà qui ne convient pas non plus. La documentation de la méthode ne mentionne rien sur son comportement lorsque a vaut null. Les appelants ont le droit de supposer que la méthode sera réussie dans ce cas et qu’elle ne lancera pas d’erreur d’assertion. Supposons toutefois que le contrat de la méthode ait été légèrement différent : @param a Le tableau à trier (ne doit pas être null)
Les appelants de la méthode ont été avertis qu’il est interdit d’appeler la méthode avec un tableau null. La méthode peut toutefois commencer avec l’assertion assert a != null;
Les informaticiens désignent ce type de contrat sous le terme de précondition. La méthode initiale ne disposait pas de précondition sur ses paramètres, elle promettait un comportement bien défini dans tous les cas. La méthode révisée possède une seule précondition : que a ne soit pas null. Si l’appelant ne respecte pas la précondition, les paris ne tiennent plus et la méthode peut faire ce qu’elle veut. En fait, une fois l’assertion en place, la méthode aura un comportement assez imprévisible en cas d’appel interdit. Parfois, elle lancera une erreur d’assertion, parfois une exception de pointeur nul, selon la configuration de son chargeur de classe.
Utiliser des assertions pour documenter des hypothèses De nombreux programmeurs utilisent des commentaires pour documenter leurs hypothèses. Vous trouverez un bon exemple à l’adresse http://java.sun.com/javase/6/docs/technotes/guides/ language/assert.html : if (i % 3 == 0) . . . else if (i % 3 == 1) . . . else // (i % 3 == 2) . . .
Livre Java.book Page 570 Mercredi, 13. février 2008 2:34 14
570
Au cœur de Java 2 - Notions fondamentales
L’utilisation d’une assertion prend tout son sens dans ce cas : if (i % 3 == 0) . . . else if (i % 3 == 1) . . . else { assert i % 3 == 2; . . . }
Il serait bien entendu encore plus logique de réfléchir à ce problème de manière 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 négatif, les restes peuvent être –1 ou –2. Par conséquent, la véritable hypothèse est que i n’est pas négatif. Une meilleure assertion serait d’écrire assert i >= 0;
avant l’instruction if. Dans tous les cas, cet exemple fait bon usage des assertions comme contrôle automatique pour le programmeur. Comme vous pouvez le voir, les assertions constituent un outil tactique permettant de tester et de déboguer un programme, tandis que la consignation est un outil stratégique conçu pour servir tout au long du cycle de vie d’un programme. Nous verrons la consignation à la section suivante. java.lang.ClassLoader 1.0
•
void setDefaultAssertionStatus(boolean b) 1.4
Active ou désactive les assertions pour toutes les classes chargées par ce chargeur de classe qui n’ont pas un statut explicite d’assertion de classe ou de package. •
void setClassAssertionStatus(String className, boolean b) 1.4
Active ou désactive les assertions pour la classe donnée ou ses classes internes. •
void setPackageAssertionStatus(String packageName, boolean b) 1.4
Active ou désactive les assertions pour toutes les classes du package donné et ses sous-packages. •
void clearAssertionStatus() 1.4
Supprime tous les paramètres explicites sur le statut d’assertion de classe et de package et désactive les assertions pour toutes les classes chargées par ce chargeur de classe.
La consignation Tous les programmeurs Java connaissent bien le processus d’insertion des appels à System.out .println dans un code à problèmes, pour se faire une idée du comportement du programme. Bien sûr, lorsque vous aurez découvert la cause du problème, les instructions d’affichage s’effaceront ; malgré tout, elles réapparaîtront au prochain problème. L’API 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à d’un certain niveau, et il est aussi simple de les retrouver.
Livre Java.book Page 571 Mercredi, 13. février 2008 2:34 14
Chapitre 11
Exceptions, consignation, assertion et mise au point
571
m
Les journaux supprimés sont très légers, la pénalité est donc mince à les conserver dans le code de consignation de votre application.
m
Les enregistrements des journaux peuvent être dirigés vers différents gestionnaires, afin d’être affichés sur la console, stockés dans un fichier, etc.
m
Les systèmes de consignation et de gestion sont en mesure de filtrer les enregistrements. Selon les critères de leurs créateurs, les filtres rejettent les entrées de journal inintéressantes.
m
Les enregistrements de journaux peuvent être mis en forme de diverses manières, par exemple en texte brut ou en XML.
m
Les applications peuvent faire appel à divers systèmes de consignation, utilisant des noms hiérarchiques comme com.mycompany.myapp, identiques aux noms de packages.
m
Par défaut, la configuration de la consignation se contrôle grâce à un fichier de configuration. Les applications sont en mesure de remplacer ce mécanisme si on le souhaite.
Consignation de base Commençons par le cas le plus simple qui soit. Le système de consignation gère un enregistreur par défaut, nommé Logger.global, que vous pouvez utiliser à la place de System.out. Sa méthode info permet de consigner un message informatif : Logger.global.info("File->Open menu item selected");
Par défaut, l’enregistrement s’affiche comme suit : May 10, 2004 10:12:15 PM LoggingImageViewer fileOpen INFO: File->Open menu item selected
Vous aurez remarqué que l’heure et les noms de la classe et de la méthode d’appel apparaissent automatiquement. Toutefois, si vous appelez Logger.global.setLevel(Level.OFF);
à un endroit qui convient (par exemple au début de main), l’ensemble de la consignation est supprimé.
Consignation avancée Après cette introduction plus que sommaire, voyons à quoi ressemble la consignation pour les professionnels. Dans une application professionnelle, il est inutile d’enregistrer toutes les entrées d’un enregistreur global. Il vaut mieux définir quelques critères. Lorsque vous appelez pour la première fois un enregistreur ayant un nom donné, celui-ci se crée : Logger myLogger = Logger.getLogger("com.mycompany.myapp");
Les appels ultérieurs à ce nom produiront le même objet logger. A l’instar des noms de packages, les noms d’enregistreurs affichent une structure hiérarchique. Ils sont en fait plus hiérarchiques que les packages. En effet, il n’existe aucune relation sémantique entre un package et son parent ; or, les enfants et les parents des enregistreurs (logger) partagent certaines propriétés. Par exemple, si vous définissez le niveau de consignation sur l’enregistreur "com.mycompany", ses enfants en hériteront.
Livre Java.book Page 572 Mercredi, 13. février 2008 2:34 14
572
Au cœur de Java 2 - Notions fondamentales
Il existe sept niveaux de consignation : m
SEVERE
m
WARNING
m
INFO
m
CONFIG
m
FINE
m
FINER
m
FINEST
Par défaut, les trois niveaux supérieurs sont consignés, mais vous pouvez modifier cela : logger.setLevel(Level.FINE);
Désormais, 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 méthodes de consignation pour tous les niveaux, comme : logger.warning(message); logger.fine(message);
etc. Mais vous pouvez aussi utiliser la méthode log, en indiquant le niveau, comme suit : logger.log(Level.FINE, message);
ASTUCE La configuration par défaut permet de consigner tous les enregistrements ayant le niveau INFO ou supérieur. Vous devez donc utiliser les niveaux CONFIG, FINE, FINER et FINEST pour déboguer les messages insignifiants pour l’utilisateur du programme et pourtant utiles au diagnostic.
ATTENTION Si vous réglez le niveau de consignation sur une valeur plus précise que INFO, modifiez également la configuration du gestionnaire de l’enregistreur. Ce gestionnaire par défaut supprime les messages inférieurs au niveau INFO. Consultez la section suivante pour en savoir plus.
L’enregistrement par défaut précise le nom de la classe et de la méthode qui contient l’appel de consignation, comme cela est suggéré par la pile d’appel. Malgré tout, si la machine virtuelle optimise l’exécution, aucune information précise ne sera disponible. Vous pouvez alors utiliser la méthode logp pour indiquer l’emplacement précis de la classe et de la méthode d’appel. La signature de méthode est la suivante : void logp(Level l, String className, String methodName, String message)
Il existe certaines méthodes pratiques pour retracer le flux de l’exécution : void entering(String className, String methodName) void entering(String className, String methodName, Object param)
Livre Java.book Page 573 Mercredi, 13. février 2008 2:34 14
Chapitre 11
Exceptions, consignation, assertion et mise au point
573
void entering(String className, String methodName, Object[] params) void exiting(String className, String methodName) void exiting(String className, String methodName, Object result)
Par exemple : int read(String file, String pattern) { logger.entering("com.mycompany.mylib.Reader", "read", new Object[] { file, pattern }); . . . logger.exiting("com.mycompany.mylib.Reader", "read", count); return count; }
Ces appels génèrent des enregistrements de journaux du niveau FINER, qui commencent par les chaînes ENTRY et RETURN. INFO A l’avenir, les méthodes de consignation seront réécrites pour supporter les listes de paramètres variables ("varargs"). Il sera alors possible de réaliser des appels tels que logger.entering("com.mycompany.mylib .Reader", "read", file, pattern).
Les journaux sont souvent utilisés pour enregistrer les exceptions inattendues. Deux méthodes incluent une description de l’exception dans l’enregistrement : void throwing(String className, String methodName, Throwable t) void log(Level 1, String message, Throwable t)
Les usages courants sont les suivants : if (. . .) { IOException exception = new IOException(". . ."); logger.throwing("com.mycompany.mylib.Reader", "read", exception); throw exception; }
et try { . . . } catch (IOException e) { Logger.getLogger("com.mycompany.myapp").log( Level.WARNING, "Reading image", e); }
L’appel throwing consigne un enregistrement du niveau FINER et un message commençant par THROW.
Livre Java.book Page 574 Mercredi, 13. février 2008 2:34 14
574
Au cœur de Java 2 - Notions fondamentales
Modifier la configuration du gestionnaire de journaux Diverses propriétés du système de consignation peuvent être modifiées par un changement apporté au fichier de configuration. Le fichier par défaut est situé à jre/lib/logging.properties
Pour utiliser un autre fichier, faites passer la propriété java.util.logging.config.file sur l’emplacement du fichier, en lançant votre application avec java -Djava.util.logging.config.file= FichierConfig ClasseMain
ATTENTION Appeler System.setProperty("java.util.logging.config.file", file) dans main n’est d’aucun effet car le gestionnaire de journaux est initialisé au démarrage de la machine virtuelle, avant même l’exécution de main.
Pour changer le niveau de consignation par défaut, modifiez la ligne suivante dans le fichier de configuration : .level=INFO
Vous pouvez spécifier les niveaux de consignation de vos propres enregistreurs en ajoutant des lignes telles que com.mycompany.myapp.level=FINE
Il s’agit ici d’ajouter le suffixe .level au nom de l’enregistreur. Comme vous le verrez plus loin au fil de cette section, ce ne sont pas réellement les enregistreurs qui envoient un message à la console, c’est plutôt le rôle des gestionnaires. Ceux-ci possèdent aussi des niveaux. Pour afficher les messages FINE sur la console, vous devez également définir java.util.logging.ConsoleHandler.level=FINE
ATTENTION Les paramètres de configuration du gestionnaire de journaux ne sont pas des propriétés système. Lancer un programme par -Dcom.mycompany.myapp.level=FINE n’a aucune influence sur l’enregistreur.
ATTENTION Au moins jusqu’à Java SE 6, la documentation API de la classe LogManager indique que vous pouvez paramétrer les propriétés java.util.logging.config.class et java.util.logging.config.file par l’API Preferences. Or, c’est faux, voir le site http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4691587.
INFO Le fichier des propriétés de consignation est traité par la classe java.util.logging.LogManager. Vous pouvez choisir un autre gestionnaire de journal en définissant la propriété système java.util.logging.manager sur le nom d’une sous-classe. Vous pouvez aussi conserver le gestionnaire de journaux standard et pourtant contourner l’initialisation à partir du fichier de propriété de consignation. Définissez la propriété système java.util. logging.config.class sur le nom d’une classe qui définit autrement les propriétés du gestionnaire de journal. Consultez la documentation API pour en savoir plus sur la classe LogManager.
Livre Java.book Page 575 Mercredi, 13. février 2008 2:34 14
Chapitre 11
Exceptions, consignation, assertion et mise au point
575
Il est également possible de modifier les niveaux de consignation dans un programme en cours d’exécution à l’aide du programme jconsole. Voir le site http://java.sun.com/developer/technicalArticles/ J2SE/jconsole.html#LoggingControl.
La localisation Il est quelquefois nécessaire de localiser les messages de consignation de sorte qu’ils puissent être lus par des utilisateurs parlant d’autres langues. L’internationalisation des applications sera traitée au Chapitre 5 du Volume II. Voici toutefois quelques points à garder en tête. Les applications localisées contiennent des informations qui sont fonction des paramètres régionaux, contenus dans des groupes de ressources. Un groupe de ressources est constitué d’un jeu de correspondances (par exemple Etats-Unis ou Allemagne). A titre d’exemple, un groupe de ressources peut mettre en correspondance la chaîne "readingFile" avec les chaînes "Reading file" 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 possède un nom (par exemple "com.mycompany.logmessages"). Pour ajouter des correspondances à un groupe de ressources, fournissez un fichier à chaque paramètre régional. Les correspondances des messages anglais se trouvent dans le fichier com/mycompany/logmessages_en.properties, et les correspondances de messages allemands, dans com/mycompany/logmessages_de.properties (les codes de, en, désignent les langues). Regroupez ces fichiers dans les fichiers de classe de votre application, de sorte que la classe ResourceBundle les retrouve immédiatement. Ces fichiers sont en texte brut et contiennent des entrées telles que readingFile=Achtung! Datei wird eingelesen renamingFile=Datei wird umbenannt ...
Lorsque vous demandez un enregistreur, vous pouvez spécifier un groupe de ressources : Logger logger = Logger.getLogger(loggerName, "com.mycompany.logmessages");
Vous spécifiez ensuite la clé du groupe de ressources, et non la chaîne du message : logger.info("readingFile");
Il faut souvent inclure des arguments dans les messages localisés. Dans ce cas, le message doit contenir des caractères d’emplacement {0}, {1}, etc. Par exemple, pour inclure le nom du fichier avec un message journal, insérez le caractère d’emplacement comme ceci : Reading file {0}. Achtung! Datei {0} wird eingelesen.
Vous transférez ensuite les valeurs au caractère d’emplacement en appelant l’une des méthodes suivantes : logger.log(Level.INFO, "readingFile", fileName); logger.log(Level.INFO, "renamingFile", new Object[] { oldName, newName });
Livre Java.book Page 576 Mercredi, 13. février 2008 2:34 14
576
Au cœur de Java 2 - Notions fondamentales
Les gestionnaires Par défaut, les enregistreurs envoient leurs enregistrements à un ConsoleHandler qui les affiche dans le flux System.err. Plus précisément, l’enregistreur envoie l’enregistrement au gestionnaire parent, et le dernier ancêtre (avec pour nom "") possède un ConsoleHandler. De même, les gestionnaires disposent d’un niveau de consignation. Pour qu’un enregistrement soit consigné, son niveau de consignation doit être supérieur au seuil de l’enregistreur et à celui du gestionnaire. Le fichier de configuration du gestionnaire de consignation définit le niveau de consignation du gestionnaire de console par défaut comme suit : java.util.logging.ConsoleHandler.level=INFO
Les enregistrements du journal ayant le niveau FINE modifient à la fois le niveau de l’enregistreur par défaut et celui du gestionnaire dans la configuration. Vous pouvez aussi contourner le fichier de configuration 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 défaut, un enregistreur enregistre dans ses propres gestionnaires et dans ceux du parent. Ici, c’est un enfant du principal enregistreur (avec le nom ""), qui envoie à la console tous les enregistrements ayant le niveau INFO ou supérieur. Mais ces enregistrements ne doivent pas apparaître deux fois. Nous définissons donc la propriété useParentHandlers sur false. Pour envoyer des enregistrements de journal partout ailleurs, ajoutez un autre gestionnaire. L’API de consignation fournit deux gestionnaires qui se montreront fort utiles à cet effet, un FileHandler et un SocketHandler. SocketHandler envoie les enregistrements vers un hôte et un port spécifiés. FileHandler est bien plus intéressant puisqu’il collecte des enregistrements dans un fichier. Vous pouvez envoyer les enregistrements vers un gestionnaire de fichiers par défaut, comme ceci : FileHandler handler = new FileHandler(); logger.addHandler(handler);
Les enregistrements sont envoyés vers un fichier javan.log dans le répertoire de base de l’utilisateur, où n est remplacé par un nombre pour rendre le fichier unique. Lorsqu’un système ne connaît pas le répertoire de base de l’utilisateur (par exemple sous Windows 95/98/Me), le fichier est stocké dans un emplacement par défaut comme C:\Windows. Par défaut, les enregistrements sont mis en forme au format XML. Un enregistrement de journal ordinaire a la forme suivante : 2002-02-04T07:45:15 1012837515710 1 com.mycompany.myapp INFO com.mycompany.mylib.Reader read 10 Reading file corejava.gif
Livre Java.book Page 577 Mercredi, 13. février 2008 2:34 14
Chapitre 11
Exceptions, consignation, assertion et mise au point
577
Vous pouvez modifier le comportement par défaut du gestionnaire de fichiers en changeant ses paramètres de configuration (voir Tableau 11.2) ou en utilisant un autre constructeur (voir les notes API en fin de section). Il est souvent préférable de changer également le nom par défaut du fichier journal. Vous pourrez utiliser un modèle différent, par exemple %h/myapp.log (voir Tableau 11.3 pour obtenir une explication des variables des modèles). Lorsque plusieurs applications (ou plusieurs copies d’une même application) utilisent le même fichier journal, il est recommandé d’activer la balise "append". Vous pouvez également insérer %u dans le modèle du nom de fichier, pour que chaque application crée sa propre copie du journal. Il est également conseillé d’activer la rotation des fichiers. Les fichiers journaux sont conservés suivant un modèle séquentiel tel que myapp.log.0, myapp.log.1, myapp.log.2, etc. Dès qu’un fichier dépasse le plafond déterminé, le plus vieux est supprimé, les autres sont renommés et un nouveau fichier est créé avec le numéro 0. Tableau 11.2 : Paramètres de configuration du gestionnaire de fichiers
Propriété de configuration
Description
Paramètre par défaut
java.util.logging. FileHandler.level
Le niveau du gestionnaire.
Level.ALL
java.util.logging. FileHandler.append
Contrôle si le gestionnaire doit continuer un fichier existant ou ouvrir un nouveau fichier pour chaque programme.
false
java.util.logging. FileHandler.limit
Le nombre maximal approximatif d’octets à écrire dans un fichier avant d’en ouvrir un autre (0 = pas de limite).
0 (pas de limite) dans la classe FileHandler, 50 000 dans la configuration par défaut du gestionnaire de journaux
java.util.logging. FileHandler.pattern
Le modèle de nom pour le fichier journal. Voir Tableau 11.3 pour connaître ses variables.
%h/java%u.log
java.util.logging. FileHandler.count
Le nombre de journaux dans une suite.
1 (pas de rotation)
java.util.logging. FileHandler.filter
La classe du filtre à utiliser.
Pas de filtrage
java.util.logging. FileHandler.encoding
Le codage de caractères à utiliser.
Le codage de la plate-forme
java.util.logging. FileHandler.formatter
Le formateur des enregistrements.
java.util.logging. XMLFormatter
Livre Java.book Page 578 Mercredi, 13. février 2008 2:34 14
578
Au cœur de Java 2 - Notions fondamentales
ASTUCE De nombreux programmeurs utilisent la consignation pour aider le personnel du support technique. En cas de dérèglement d’un programme au cours d’une session, l’utilisateur peut renvoyer les fichiers journaux pour analyse. Dans ce cas, il convient d’activer la balise "append", d’utiliser la rotation des fichiers, voire de faire les deux.
Tableau 11.3 : Variables du modèle de fichier journal
Variable
Description
%h
La valeur de la propriété système user.home
%t
Le répertoire temporaire du système
%u
Un numéro unique pour éviter les conflits
%g
Le numéro pour les journaux par rotation (le suffixe .%g indique que la rotation est spécifiée, le modèle ne contient pas %g)
%%
Le caractère %
Vous pouvez également définir vos propres gestionnaires en prolongeant la classe Handler ou StreamHandler. Nous définissons ce gestionnaire dans le programme d’exemple à la fin de cette section. Il affiche les enregistrements dans une fenêtre (voir Figure 11.2). Le gestionnaire étend la classe StreamHandler et installe un flux dont les méthodes write affichent 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 affichant des enregistrements dans une fenêtre.
Livre Java.book Page 579 Mercredi, 13. février 2008 2:34 14
Chapitre 11
Exceptions, consignation, assertion et mise au point
579
Cette méthode présente tout de même un problème : le gestionnaire met les enregistrements en mémoire tampon et ne les écrit sur le flux que lorsque le tampon est plein. Nous allons donc écraser la méthode publish pour vider le tampon après 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 définissez les méthodes publish, flush et close.
Les filtres Par défaut, les enregistrements sont filtrés en fonction de leurs niveaux de consignation. Chaque enregistreur et chaque gestionnaire peuvent avoir un filtre optionnel qui réalisera un filtrage complémentaire. Pour définir un filtre, implémentez l’interface Filter et définissez la méthode boolean isLoggable(LogRecord record)
Analysez l’enregistrement du journal à l’aide des critères souhaités et renvoyez true pour ceux que vous voulez inclure dans le journal. Par exemple, un filtre particulier peut n’être intéressé que par les messages générés par les méthodes entering et exiting. Le filtre doit alors appeler record.getMessage() et vérifier s’il commence par ENTRY ou RETURN. Pour installer un filtre dans un enregistreur ou un gestionnaire, appelez simplement la méthode setFilter. Sachez que vous pouvez installer plusieurs filtres à la fois.
Les formateurs Les classes ConsoleHandler et FileHandler délivrent les enregistrements des journaux aux formats texte et XML. Mais vous pouvez aussi définir vos propres formats. Pour cela, prolongez la classe Formatter et surchargez la méthode String format(LogRecord record)
Vous pouvez mettre en forme les informations contenues dans l’enregistrement selon vos préférences et renvoyer la chaîne de résultat. Dans votre méthode format, vous pouvez appeler la méthode String formatMessage(LogRecord record)
Celle-ci met en forme le message qui fait partie de l’enregistrement, en remplaçant les paramètres et en appliquant la localisation. De nombreux formats de fichiers (comme XML) exigent un bloc de début et un bloc de fin pour entourer les enregistrements mis en forme. Dans ce cas, surchargez les méthodes String getHead(Handler h) String getTail(Handler h)
Enfin, appelez la méthode setFormatter pour installer le formateur dans le gestionnaire.
Livre Java.book Page 580 Mercredi, 13. février 2008 2:34 14
580
Au cœur de Java 2 - Notions fondamentales
Une astuce de consignation Convenons qu’avec autant d’options, il est facile de se perdre quand il s’agit de la consignation. L’astuce suivante résume les opérations les plus communes. 1. Pour une application simple, optez pour un enregistreur unique. Donnez-lui le nom de votre package d’application principale, comme com.mycompany.myprog. Vous pouvez toujours récupérer l’enregistreur en appelant Logger logger = Logger.getLogger("com.mycompany.myprog");
Pour plus de commodité, vous pourrez ajouter les champs statiques private static final Logger logger = Logger.getLogger("com.mycompany.myprog");
aux classes ayant une grande activité de consignation. 2. La configuration par défaut enregistre dans la console tous les messages du niveau INFO ou supérieur. Les utilisateurs peuvent surcharger la configuration par défaut mais, comme vous l’avez vu, le processus est un peu plus long. Choisissez donc un paramètre plus raisonnable pour votre application. Le code suivant vérifie que tous les messages sont consignés dans un fichier spécifique à l’application. Placez le code dans la méthode 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, "Can’t create log file handler", e); } }
3. Vous êtes maintenant prêt à consigner le contenu qui vous intéresse. N’oubliez pas que tous les messages ayant le niveau INFO, WARNING et SEVERE s’afficheront dans la console. Réservez donc ces niveaux aux messages signifiants pour les utilisateurs du programme. Le niveau FINE convient bien aux messages destinés aux programmeurs. Dès que vous êtes tenté d’appeler System.out.println, émettez plutôt un message journal : logger.fine("File open dialog cancelled");
Vous devriez aussi consigner les exceptions inattendues, par exemple : try { . . . } catch (SomeException e) { logger.log(Level.FINE, "explanation", e); }
Livre Java.book Page 581 Mercredi, 13. février 2008 2:34 14
Chapitre 11
Exceptions, consignation, assertion et mise au point
581
Le Listing 11.2 présente une astuce pour ajouter un aspect supplémentaire : la consignation des messages s’affiche aussi dans une fenêtre 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 d’images qui consigne * divers événements. * @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, "Can’t 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); } }); } }
Livre Java.book Page 582 Mercredi, 13. février 2008 2:34 14
582
Au cœur de Java 2 - Notions fondamentales
/** * Le cadre qui affiche l’image. */ class ImageViewerFrame extends JFrame { public ImageViewerFrame() { logger.entering("ImageViewerFrame", ""); 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", ""); } private class FileOpenListener implements ActionListener { public void actionPerformed(ActionEvent event) { logger.entering("ImageViewerFrame.FileOpenListener", "actionPerformed", event); // configurer le sélecteur 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(); }
Livre Java.book Page 583 Mercredi, 13. février 2008 2:34 14
Chapitre 11
Exceptions, consignation, assertion et mise au point
public String getDescription() { return "GIF Images"; } }); // afficher la boîte de dialogue du sélecteur de fichiers int r = chooser.showOpenDialog(ImageViewerFrame.this); // si le fichier image est accepté, le définir comme // icône de l’intitulé 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 fenêtre. */ 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)); } }); }
583
Livre Java.book Page 584 Mercredi, 13. février 2008 2:34 14
584
Au cœur de Java 2 - Notions fondamentales
public void publish(LogRecord record) { if (!frame.isVisible()) return; super.publish(record); flush(); } private JFrame frame; } java.util.logging.Logger 1.4
• •
Logger getLogger(String loggerName) Logger getLogger(String loggerName, String bundleName)
Récupèrent l’enregistreur ayant le nom donné. S’il n’existe pas, il est créé. Paramètres :
• • • • • • •
loggerName
Le nom hiérarchique de l’enregistreur, comme com.mycompany.myapp.
bundleName
Le nom du groupe de ressources permettant de rechercher les messages localisés.
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)
Consignent un enregistrement avec le niveau indiqué par le nom de la méthode 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 décrivant l’entrée ou la sortie d’une méthode avec le ou les paramètres donnés ou la valeur de retour. •
void throwing(String className, String methodName, Throwable t)
Consigne un enregistrement décrivant le lancement de l’objet exception donné. • • • •
void log(Level level, String message) void log(Level level, String message, Object obj) void log(Level level, String message, Object[] objs) void log(Level level, String message, Throwable t)
Consignent un enregistrement avec le niveau donné et le message, en incluant en option des objets ou un throwable. Pour inclure des objets, le message doit contenir des emplacements de mise en forme tels {0}, {1}, etc. •
void logp(Level level, String className, String methodName, String message)
Livre Java.book Page 585 Mercredi, 13. février 2008 2:34 14
Chapitre 11
Exceptions, consignation, assertion et mise au point
•
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)
585
Consignent un enregistrement avec le niveau donné, des informations précises sur l’appelant 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 précises sur l’appelant, le nom du groupe de ressources et le message. Consignent, en option, des objets ou un throwable. • •
Level getLevel() void setLevel(Level l)
Récupèrent ou définissent le niveau de cet enregistreur. • •
Logger getParent() void setParent(Logger l)
Récupèrent ou définissent l’enregistreur parent de cet enregistreur. •
Handler[] getHandlers()
Récupère tous les gestionnaires de cet enregistreur. • •
void addHandler(Handler h) void removeHandler(Handler h)
Ajoutent ou suppriment un gestionnaire pour cet enregistreur. • •
boolean getUseParentHandlers() void setUseParentHandlers(boolean b)
Récupèrent et définissent la propriété "use parent handler" (utiliser le gestionnaire parent). Si cette propriété vaut true, l’enregistreur transmet tous les enregistrements consignés aux gestionnaires de son parent. • •
Filter getFilter() void setFilter(Filter f)
Récupèrent et définissent le filtre de cet enregistreur. java.util.logging.Handler 1.4
•
abstract void publish(LogRecord record)
Envoie l’enregistrement à la destination prévue. •
abstract void flush()
Efface toute donnée mise en tampon.
Livre Java.book Page 586 Mercredi, 13. février 2008 2:34 14
586
•
Au cœur de Java 2 - Notions fondamentales
abstract void close()
Efface toute donnée mise en tampon et libère les ressources associées. • •
Filter getFilter() void setFilter(Filter f)
Récupèrent et définissent le filtre de ce gestionnaire. • •
Formatter getFormatter() void setFormatter(Formatter f)
Récupèrent et définissent le formateur de ce gestionnaire. • •
Level getLevel() void setLevel(Level l)
Récupèrent et définissent le niveau de ce gestionnaire. java.util.logging.ConsoleHandler 1.4
•
ConsoleHandler()
Construit un nouveau gestionnaire de console. java.util.logging.FileHandler 1.4
• • • •
FileHandler(String pattern) FileHandler(String pattern, boolean append) FileHandler(String pattern, int limit, int count) FileHandler(String pattern, int limit, int count, boolean append)
Construisent un gestionnaire de fichiers. Paramètres :
pattern
Le motif pour construire le nom du fichier journal. Voir le Tableau 11.3 pour connaître les variables du modèle.
limit
Le nombre maximal approximatif d’octets avant qu’un nouveau fichier journal ne soit ouvert.
count
Le nombre de fichiers dans une rotation.
append
Vaut true lorsqu’un gestionnaire de fichiers nouvellement construit doit continuer un fichier journal existant.
java.util.logging.LogRecord 1.4
•
Level getLevel()
Récupère le niveau de consignation de cet enregistrement. •
String getLoggerName()
Récupère le nom de l’enregistreur qui consigne cet enregistrement. • •
ResourceBundle getResourceBundle() String getResourceBundleName()
Récupèrent le groupe de ressources ou son nom pour l’utiliser pour localiser le message ou null si aucun n’est fourni. •
String getMessage()
Récupère le message "brut" avant la localisation ou la mise en forme.
Livre Java.book Page 587 Mercredi, 13. février 2008 2:34 14
Chapitre 11
•
Exceptions, consignation, assertion et mise au point
587
Object[] getParameters()
Récupère les objets paramètre ou null si aucun n’est fourni. •
Throwable getThrown()
Récupère l’objet lancé ou null si aucun n’est fourni. • •
String getSourceClassName() String getSourceMethodName()
Récupèrent l’emplacement du code qui a consigné cet enregistrement. Ces informations peuvent être fournies par le code de consignation ou automatiquement tirées de la pile d’exécution. Cela peut être inexact si le code de consignation a fourni la mauvaise valeur ou si le code d’exécution était optimisé et que l’emplacement exact ne puisse pas être extrait. •
long getMillis()
Récupère l’heure de la création, en millièmes de seconde, depuis 1970. •
long getSequenceNumber()
Récupère le numéro unique (dans la suite) de cet enregistrement. •
int getThreadID()
Récupère l’ID unique pour le thread dans lequel cet enregistrement a été créé. Ces ID sont attribués par la classe LogRecord et n’ont pas de relation avec les autres ID de thread. java.util.logging.Filter 1.4
•
boolean isLoggable(LogRecord record)
Renvoie true si l’enregistrement de journal donné doit être consigné. java.util.logging.Formatter 1.4
•
abstract String format(LogRecord record)
Renvoie la chaîne née de la mise en forme de l’enregistrement du journal. • •
String getHead(Handler h) String getTail(Handler h)
Renvoient les chaînes qui doivent apparaître au début et à la fin du document contenant les enregistrements du journal. La superclasse Formatter définit ces méthodes de sorte qu’elles renvoient la chaîne vide ; surchargez-les si nécessaire. •
String formatMessage(LogRecord record)
Renvoie la partie du message qui a été localisée et mise en forme.
Les techniques de mise au point Vous avez terminé votre programme et l’avez protégé contre toute éventualité en capturant et en traitant effectivement toutes les exceptions. Vous lancez l’exécution, et cela ne marche pas. Que faire ? (Si vous n’avez jamais rencontré ce problème, passez sans remords au chapitre suivant.) Il est préférable de posséder un système de débogage pratique et puissant. Ces outils de mise au point font en général partie d’environnements professionnels de développement comme Eclipse et NetBeans. Nous verrons le débogueur plus loin dans ce chapitre. Pour l’heure, voici quelques astuces pour un débogage efficace, si vous vous trouvez sans aucun environnement de mise au point :
Livre Java.book Page 588 Mercredi, 13. février 2008 2:34 14
588
Au cœur de Java 2 - Notions fondamentales
1. On peut imprimer ou consigner la valeur d’une variable par System.out.println("x=" + x);
ou Logger.global.info("x=" + x);
Si x est un nombre, il sera converti en une chaîne de caractères. Si c’est un objet, Java appellera sa méthode toString. Pour obtenir l’état de l’objet paramètre implicite, affichez l’état de cet objet : Logger.global.info("this=" + this);
La plupart des classes de la bibliothèque de classes Java s’efforcent de surcharger, s’il y a lieu, la méthode toString afin d’informer efficacement sur la classe. C’est parfait pour le débogage. Vous devez poursuivre le même effort dans vos propres classes. 2. Une petite astuce apparemment peu connue consiste à introduire une méthode main séparée à l’intérieur de chaque classe. Dans celle-ci, vous pouvez inclure un stub de test afin de tester la classe de façon isolée : public class MyClass { méthodes et champs . . . public static void main(String[] args) { code de test } }
Créez quelques objets, appelez toutes les méthodes et vérifiez que chacune fonctionne. Vous pouvez laisser toutes ces méthodes main en place et appeler la machine virtuelle Java de façon séparée sur chacun des fichiers afin d’exécuter les tests. Lorsque vous lancez un applet, aucune de ces méthodes main n’est jamais appelée. Lorsque vous lancez une application, la machine virtuelle Java appelle uniquement la méthode main de la classe de démarrage. 3. Si vous avez aimé l’astuce précédente, consultez JUnit à l’adresse http://junit.org. JUnit est un encadrement de test très populaire qui facilite l’organisation des séquences de test. Exécutez les tests dès que vous modifiez une classe et ajoutez un test cadre dès que vous retrouvez un bogue. 4. Un proxy de consignation est un objet d’une sous-classe qui intercepte les appels de méthode, les consigne et appelle la superclasse. Si, par exemple, vous rencontrez des problèmes avec la méthode setBackground d’un panneau, vous pouvez créer un objet proxy comme instance d’une sous-classe anonyme : JPanel panel = new JPanel() { public void setBackground(Color c) { Logger.global.info("setBackground: c=" + c); super.setBackground(c); } };
Dès que la méthode setBackground est appelée, un message de journal est généré. Pour savoir qui a appelé la méthode, générez une trace de pile.
Livre Java.book Page 589 Mercredi, 13. février 2008 2:34 14
Chapitre 11
Exceptions, consignation, assertion et mise au point
589
5. On peut sortir le contenu de la pile pour un objet exception grâce à la méthode printStackTrace de la classe Throwable. Le morceau de code suivant capture toutes les exceptions, affiche l’objet exception et le contenu de la pile, puis propage l’exception à l’intention de son gestionnaire potentiel : try { . . . } catch (Throwable t) { t.printStackTrace(); throw t; }
Vous n’avez même pas besoin d’intercepter une exception pour générer le traçage d’une pile. Il vous suffit d’insérer l’instruction Thread.dumpStack();
à n’importe quel endroit de votre code pour l’obtenir. 6. En principe, le contenu de la pile s’affiche par System.err. On peut l’envoyer dans un fichier avec la méthode void printStackTrace(PrintWriter). Ou encore, pour afficher le contenu de la pile dans une fenêtre, voici la façon de le placer dans une chaîne : 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 d’intercepter les erreurs d’un programme en les écrivant dans un fichier. Cependant, les erreurs sont envoyées à System.err et non pas à System.out. Par conséquent, on ne peut pas se contenter de les capturer en lançant l’instruction java MyProgram > errors.txt
Capturez plutôt le flux d’erreurs par java MyProgram 2> errors.txt
Pour capturer à la fois et dans le même fichier System.err et System.out, utilisez java MyProgram 2>& errors.txt
Pour capturer System.err et System.out dans le même fichier, utilisez java MyProgram >& errors.txt
Cela fonctionne dans bash et dans le shell Windows. 8. Faire apparaître les traces de pile des exceptions non interceptées dans System.err n’est vraiment pas l’idéal. Ces messages sont déroutants pour l’utilisateur et ils ne sont pas disponibles aux fins de diagnostic lorsque vous en avez besoin. Une meilleure solution consiste à les consigner dans un fichier. Depuis Java SE 5.0, vous pouvez modifier le gestionnaire pour les exceptions non interceptées avec la méthode statique Thread.setDefaultUncaughtExceptionHandler : Thread.setDefaultUncaughtExceptionHandler( new Thread.UncaughtExceptionHandler()
Livre Java.book Page 590 Mercredi, 13. février 2008 2:34 14
590
Au cœur de Java 2 - Notions fondamentales
{ public void uncaughtException(Thread t, Throwable e) { enregistrer les informations dans le fichier journal }; });
9. Pour observer le chargement des classes, exécutez la machine virtuelle Java avec la balise verbose. Vous obtenez une première 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, à l’occasion, pour diagnostiquer des problèmes de chemin de classe. 10. Pour espionner le contenu d’une fenêtre Swing, dont les composants sont si agréablement alignés, il suffit de faire Ctrl+Maj+F1 pour obtenir l’affichage de tous les composants de la hiérarchie : 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 spécial et si ce composant ne s’affiche pas correctement, vous devriez apprécier le Swing graphics debugger. Même si vous n’écrivez pas de composant, il est utile et même amusant de visualiser la façon exacte dont le contenu d’un composant est dessiné. Pour activer le débogage d’un composant Swing, utilisez la méthode setDebugGraphicsOptions de la classe JComponent, avec les options suivantes : DebugGraphics.FLASH_OPTION
"Flashe" en rouge chaque ligne, rectangle ou texte avant de le dessiner.
DebugGraphics.LOG_OPTION
Affiche un message à chaque opération de dessin.
Livre Java.book Page 591 Mercredi, 13. février 2008 2:34 14
Chapitre 11
Exceptions, consignation, assertion et mise au point
591
DebugGraphics.BUFFERED_OPTION
Affiche les opérations effectuées dans le tampon hors écran.
DebugGraphics.NONE_OPTION
Désactive le débogage graphique.
Il semble que, pour faire fonctionner correctement cette option, il soit nécessaire de désactiver la stratégie de "double bufférisation" utilisée par Swing pour réduire le scintillement au moment de la mise à jour d’une fenêtre. Voici la recette pour activer cette option de "flash" : RepaintManager.currentManager(getRootPane()) .setDoubleBufferingEnabled(false); ((JComponent) getContentPane()) .setDebugGraphicsOptions(DebugGraphics.FLASH_OPTION);
Ajoutez ces lignes à la fin de votre constructeur de cadre. A l’exécution, vous verrez le contenu du panneau apparaître lentement. Pour un débogage plus précis, on peut appeler setDebugGraphicsOptions pour un composant particulier. Les puristes peuvent ajuster la durée, le nombre et la couleur des flashes — consultez l’aide en ligne de la classe DebugGraphics pour plus de détails. 12. Java SE 5.0 ajoute l’option -Xlint au compilateur pour retrouver les problèmes de code communs. Par exemple, si vous compilez avec la commande javac -Xlint:fallthrough
le compilateur signale l’absence d’instructions break dans les instructions switch (le terme "lint" décrivait à l’origine un outil permettant de retrouver des problèmes potentiels dans les programmes C ; il s’applique maintenant de manière générique aux outils qui retrouvent les constructions douteuses, sans être interdites). Les options suivantes sont disponibles : Xlint ou -Xlint:all
Réalise toutes les vérifications.
Xlint:deprecation
Identique à -deprecation, recherche les méthodes dépréciées.
Xlint:fallthrough
Recherche les instructions break manquantes dans les instructions switch.
Xlint:finally
Signale les clauses finally qui ne peuvent pas se terminer normalement.
Xlint:none
Ne réalise aucune vérification.
Xlint:path
Vérifie l’existence de tous les répertoires sur le chemin de classe et celle du chemin source.
Xlint:serial
Signale les classes sérialisables ne contenant pas serialVersionUID (voir Chapitre 1 du Volume II).
Xlint:unchecked
Signale les conversions peu sûres entre des types génériques 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 d’installer des agents dans la machine virtuelle pour suivre la consommation de mémoire, l’utilisation des threads, le chargement de classes, etc. Cette fonctionnalité est particulièrement importante pour de gros programmes Java, au fonctionnement prolongé, par exemple
Livre Java.book Page 592 Mercredi, 13. février 2008 2:34 14
592
Au cœur de Java 2 - Notions fondamentales
les serveurs d’applications. En guise de démonstration, le JDK est livré avec un outil graphique appelé jconsole qui propose des statistiques sur les performances d’une machine virtuelle (voir Figure 11.3). Retrouvez ensuite l’ID du process du système d’exploitation qui exécute la machine virtuelle. Sous UNIX/Linux, exécutez l’utilitaire ps ; sous Windows, utilisez le Gestionnaire de tâches. 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 l’option -Dcom.sun.management.jmxremote. java -Dcom.sun.management.jmxremote MyProgram.java jconsole IDprocess
Figure 11.3 Le programme jconsole.
Livre Java.book Page 593 Mercredi, 13. février 2008 2:34 14
Chapitre 11
Exceptions, consignation, assertion et mise au point
593
14. Vous pouvez utiliser l’utilitaire jmap pour récupérer la mise au rebut du tas vous montrant chaque objet qu’il 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 détailler le contenu sur le tas au moment de la mise au rebut. 15. Si vous lancez la machine virtuelle avec le drapeau -Xprof, il exécute un profiler rudimentaire qui suit les méthodes de votre code ayant été le plus souvent exécutées. Les informations de profilage sont envoyées à System.out. La sortie vous indique aussi les méthodes compilées par le compilateur en juste-à-temps (just-in-time). ATTENTION Les options -X du compilateur ne sont pas officiellement prises en charge et risquent de ne pas apparaître dans toutes les versions du JDK. Exécutez java -X pour obtenir une liste de toutes les options non standard.
Utiliser une fenêtre de console Lorsque vous déboguez un applet, les messages d’erreur s’affichent dans une fenêtre. Dans le panneau de configuration du plug-in Java, cochez la case Show Java Console (voir Chapitre 10). La fenêtre de console Java dispose d’un ensemble de barres de défilement de façon à vous permettre de récupérer les messages qui ont défilé en dehors de la fenêtre. Les utilisateurs de la fenêtre de la console Java bénéficient ainsi d’un réel avantage par rapport à la fenêtre de shell DOS dans laquelle les sorties System.out et System.err s’affichent normalement. Nous vous fournissons une classe de fenêtre identique, afin que vous puissiez profiter du même avantage, en visualisant vos messages de débogage à l’intérieur d’une fenêtre lorsque vous mettez au point un programme. La Figure 11.4 présente la classe ConsoleWindow en action. Figure 11.4 La fenêtre de console.
Cette classe est d’un usage facile. Il suffit d’appeler ConsoleWindow.init()
Ensuite, imprimez normalement vers System.out ou System.err.
Livre Java.book Page 594 Mercredi, 13. février 2008 2:34 14
594
Au cœur de Java 2 - Notions fondamentales
Le Listing 11.3 liste le code de la classe ConsoleWindow. Comme vous pouvez le voir, la classe est très simple. Les messages sont affichés dans une JTextArea à l’intérieur d’un panneau JScrollPane. Nous appelons les méthodes System.setOut et System.setErr pour diriger les sorties et flux d’erreurs vers un flux spécial qui ajoute chaque message dans la zone de texte. Listing 11.3 : ConsoleWindow.java import javax.swing.*; import java.io.*; /** * Une fenêtre qui affiche les mots (bytes) envoyés à 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); // définir 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)); } }); // définir 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;
Livre Java.book Page 595 Mercredi, 13. février 2008 2:34 14
Chapitre 11
Exceptions, consignation, assertion et mise au point
595
Tracer les événements AWT Si vous devez écrire une interface utilisateur élaborée en Java, il vous faut savoir quels événements envoient AWT à quels composants. Malheureusement, la documentation AWT est assez indigente sur ce point. Par exemple, nous voulons faire apparaître des phrases d’aide dans la ligne d’état lorsque l’utilisateur déplace la souris sur les diverses parties de l’écran. AWT génère des événements de souris et de focus qu’il vous faut intercepter. Nous vous fournissons une classe EventTrace très utile pour espionner ces événements. Elle affiche toutes les méthodes de gestion des événements avec leurs paramètres. La Figure 11.5 affiche les événements tracés. Pour espionner les messages, il suffit d’ajouter les composants dont les événements doivent être suivis, à l’intérieur d’un traceur d’événement : EventTracer tracer = new EventTracer(); tracer.add(frame);
Cela affiche un texte décrivant tous les événements, comme ceci : public abstract void java.awt.event.MouseListener.mouseExited( java.awt.event.MouseEvent): java.awt.event.MouseEvent[MOUSE_EXITED,(408,14),button=0,clickCount=0] on javax.swing.JButton[,0,345,400x25,...] public abstract void java.awt.event.FocusListener.focusLost( java.awt.event.FocusEvent): java.awt.event.FocusEvent[FOCUS_LOST,temporary,opposite=null] on javax.swing.JButton[,0,345,400x25,...]
Peut-être désirez-vous capturer ces sorties dans un fichier ou une fenêtre de console, comme nous l’avons vu dans les sections précédentes ?
Figure 11.5 Exécution de la classe EventTracer.
Livre Java.book Page 596 Mercredi, 13. février 2008 2:34 14
596
Au cœur de Java 2 - Notions fondamentales
Le Listing 11.4 correspond à la classe EventTracer. L’idée qui s’y cache est simple, même si son implémentation semble mystérieuse. Voici quelques étapes réalisées en coulisse : 1. Lorsque vous ajoutez un composant au traceur d’événement dans la méthode add, la classe introspection des Java Beans analyse le composant à la recherche des méthodes de la forme void addXxxListener(XxxListener) (voir le Chapitre 8 du Volume II). Pour chaque méthode correspondante un EventSetDescriptor est généré. Nous passons chaque descripteur à la méthode addListener. 2. Si le composant est un conteneur, nous énumérons ses composants et pouvons appeler chacun d’entre eux de façon récursive. 3. addListener est appelée avec deux paramètres : le composant dont nous voulons espionner les événements et le descripteur d’événement. La méthode getListenerType de la classe EventSetDescriptor renvoie un objet Class qui décrit l’interface d’écouteur d’événement telle que ActionListener ou ChangeListener. Nous créons un objet proxy pour cette interface. Le gestionnaire de proxy se contente d’imprimer le nom et le paramètre d’événement de la méthode event invoquée. La méthode getAddListenerMethod de la classe EventSetDescriptor renvoie un objet Method que nous utilisons pour ajouter au composant l’objet proxy en tant qu’écouteur d’événement. Ce programme est un bon exemple de la puissance du mécanisme de réflexion. Nous n’avons pas besoin de coder le fait que la classe JButton possède une méthode addActionListener, puisqu’un JSlider possède une méthode addChangeListener. Le mécanisme de réflexion découvre ces faits pour nous. Le Listing 11.5 teste le traceur d’événement. Le programme affiche un cadre avec un bouton et une tirette et trace les événements générés 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 d’événement handler = new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) { System.out.println(method + ":" + args[0]); return null; } }; }
Livre Java.book Page 597 Mercredi, 13. février 2008 2:34 14
Chapitre 11
Exceptions, consignation, assertion et mise au point
/** * Ajoute des traceurs d’événements pour tous les événements * que ce composant et ses enfants peuvent écouter * @param c Un composant */ public void add(Component c) { try { // récupérer tous les événements 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 d’écouteurs si l’exception est lancée if (c instanceof Container) { // récupérer tous les enfants et appeler add // de manière récurrente for (Component comp : ((Container) c).getComponents()) add(comp); } } /** * Ajouter un écouteur au jeu d’événements donné * @param c Un composant * @param eventSet Un descripteur d’une interface d’écouteur */ public void addListener(Component c, EventSetDescriptor eventSet) { // créer l’objet proxy pour ce type d’écouteur // et acheminer tous les appels au gestionnaire Object proxy = Proxy.newProxyInstance(null, new Class[] { eventSet.getListenerType() }, handler); // ajouter le proxy au composant sous forme d’écouteur Method addListenerMethod = eventSet.getAddListenerMethod(); try { addListenerMethod.invoke(c, proxy); } catch(InvocationTargetException e) { } catch(IllegalAccessException e) { } // ok pour ne pas ajouter d’écouteur si l’exception est lancée } private InvocationHandler handler; }
597
Livre Java.book Page 598 Mercredi, 13. février 2008 2:34 14
598
Au cœur de Java 2 - Notions fondamentales
Listing 11.5 : EventTracerTest.java import java.awt.*; import javax.swing.*; /** * @version 1.13 2007-06-12 * @author Cay Horstmann */ public class EventTracerTest { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { JFrame frame = new EventTracerFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); } } class EventTracerFrame extends JFrame { public EventTracerFrame() { setTitle("EventTracerTest"); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); // ajouter un curseur et un bouton add(new JSlider(), BorderLayout.NORTH); add(new JButton("Test"), BorderLayout.SOUTH); // intercepter tous les événements des composants dans le cadre EventTracer tracer = new EventTracer(); tracer.add(this); } public static final int DEFAULT_WIDTH = 400; public static final int DEFAULT_HEIGHT = 400; }
Le robot AWT Java SE 1.3 ajoute une classe Robot que vous pouvez utiliser pour envoyer des frappes de clavier et des clics de souris vers n’importe quel programme AWT. Cette classe a pour vocation de permettre l’automatisation du test des interfaces utilisateur. Pour obtenir un robot, il vous faut d’abord obtenir un objet GraphicsDevice. On obtient l’écran par défaut par une suite d’appels : GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment(); GraphicsDevice screen = environment.getDefaultScreenDevice();
Livre Java.book Page 599 Mercredi, 13. février 2008 2:34 14
Chapitre 11
Exceptions, consignation, assertion et mise au point
599
Puis vous construisez un robot : Robot robot = new Robot(screen);
Pour envoyer une frappe de clavier, demandez au robot de simuler une frappe de touche suivie d’une sortie de touche : robot.keyPress(KeyEvent.VK_TAB); robot.keyRelease(KeyEvent.VK_TAB);
Pour un clic de souris, il vous faut déplacer la souris, puis appuyer et relâcher le bouton : robot.mouseMove(x, y); // x et y sont les coordonnées absolues des pixels à l’écran robot.mousePress(InputEvent.BUTTON1_MASK); robot.mouseRelease(InputEvent.BUTTON1_MASK);
L’idée consiste à simuler le clavier et la souris, puis de prendre un cliché de l’écran pour voir si l’application s’est comportée comme prévu. Vous capturez l’écran avec la méthode createScreenCapture : Rectangle rect = new Rectangle(x, y, width, height); BufferedImage image = robot.createScreenCapture(rect);
Les coordonnées du rectangle se réfèrent également à des pixels en absolu. Pour finir, vous souhaitez en général ajouter un petit délai entre les instructions du robot de telle façon que l’application puisse être en rythme. Utilisez la méthode delay et donnez-lui un temps d’attente en millisecondes. Par exemple : robot.delay(1000); // délai 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 d’espace. Cela active le bouton gauche. Puis le robot attend deux secondes afin que vous puissiez voir ce qui se passe. Après ce délai, le robot simule la touche de tabulation et une autre frappe sur la barre d’espace pour cliquer sur le bouton suivant. Pour finir, on simule un clic du troisième bouton de la souris (peut-être faudra-t-il ajuster les coordonnées X et Y du programme afin d’appuyer réellement sur ce bouton). Le programme se termine en réalisant une capture d’écran et en l’affichant dans un autre cadre (voir Figure 11.6). Figure 11.6 Capture d’écran du robot AWT.
Livre Java.book Page 600 Mercredi, 13. février 2008 2:34 14
600
Au cœur de Java 2 - Notions fondamentales
Comme on le voit dans l’exemple, la classe Robot n’est pas en elle-même adaptée au test de l’interface utilisateur. Au lieu de cela, elle constitue un bloc de base qui peut devenir l’une des pierres de touche d’un outil de test. Un outil de test professionnel peut capturer, stocker et réafficher des scénarios d’interaction d’utilisateurs, et trouver les emplacements sur l’écran de tous les composants. De cette façon, les clics de la souris ne tombent pas au hasard. Nous espérons voir des outils de test sophistiqués apparaître à 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() { // créer un cadre avec un panneau de bouton ButtonFrame frame = new ButtonFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); // joindre un robot au périphérique écran GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment(); GraphicsDevice screen = environment.getDefaultScreenDevice(); try { Robot robot = new Robot(screen); runTest(robot); } catch (AWTException e) { e.printStackTrace(); } } }); } /** * Exécute un exemple de procédure de test * @param robot Le robot attaché au périphérique écran */ public static void runTest(Robot robot) { // simuler une pression sur la barre espace
Livre Java.book Page 601 Mercredi, 13. février 2008 2:34 14
Chapitre 11
Exceptions, consignation, assertion et mise au point
601
robot.keyPress(’ ’); robot.keyRelease(’ ’); // simuler une touche de tabulation suivie d’un 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 l’écran et afficher l’image 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 d’écran */ class ImageFrame extends JFrame { /** * @param image L’image à afficher */ public ImageFrame(Image image) { setTitle("Capture"); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); JLabel label = new JLabel(new ImageIcon(image)); add(label); } public static final int DEFAULT_WIDTH = 450; public static final int DEFAULT_HEIGHT = 350; } java.awt.GraphicsEnvironment 1.2
•
static GraphicsEnvironment getLocalGraphicsEnvironment()
Renvoie l’environnement graphique local.
•
GraphicsDevice getDefaultScreenDevice()
Renvoie l’écran par défaut. Notez que les ordinateurs avec plusieurs moniteurs n’ont qu’un écran virtuel par moniteur. Utilisez la méthode getScreenDevices pour obtenir la liste de tous les écrans.
Livre Java.book Page 602 Mercredi, 13. février 2008 2:34 14
602
Au cœur de Java 2 - Notions fondamentales
java.awt.Robot 1.3
•
Robot(GraphicsDevice device)
Construit un robot d’interaction avec le périphérique.
• •
void keyPress(int key) void keyRelease(int key)
Simulent l’enfoncement ou le relâchement d’une touche. Paramètres :
•
key
Le code de touche. Voir la classe KeyStroke pour plus d’informations sur les codes de touche.
void mouseMove(int x, int y)
Simule un mouvement de la souris. Paramètres :
• •
x,y
La position de la souris en coordonnées absolues et en pixels.
void mousePress(int eventMask) void mouseRelease(int eventMask)
Simulent une pression sur un bouton de la souris ou son relâchement. Paramètres :
•
eventMask
Le masque d’événement décrivant les boutons de la souris. Voir la classe InputEvent pour plus d’informations sur les masques d’événement.
void delay(int milliseconds)
Met le robot en attente pour le nombre de millisecondes donné en paramètre.
•
bufferedImage createScreenCapture(Rectangle rect)
Capture une partie de l’écran. Paramètres :
rect
Le rectangle qui doit être capturé en coordonnées absolues et en pixels.
Utiliser un débogueur Déboguer en utilisant des instructions print est l’une des plus agréables expériences qui soient. Vous vous trouvez constamment à ajouter et enlever des instructions et à recompiler le programme. L’usage d’un débogueur est plus efficace parce que celui-ci fait tourner votre programme jusqu’à ce qu’il atteigne un point d’arrêt prédéterminé. A ce moment-là, vous pouvez observer tout ce qui vous intéresse. Le Listing 11.7 utilise une version délibérément altérée du programme ButtonTest du Chapitre 8. Lorsque vous cliquez sur l’un quelconque des boutons, rien ne se produit. Regardez le code source. Les boutons sont censés définir 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.*;
Livre Java.book Page 603 Mercredi, 13. février 2008 2:34 14
Chapitre 11
Exceptions, consignation, assertion et mise au point
/** * @version 1.22 2007-05-14 * @author Cay Horstmann */ public class BuggyButtonTest { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { BuggyButtonFrame frame = new BuggyButtonFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); } } class BuggyButtonFrame extends JFrame { public BuggyButtonFrame() { setTitle("BuggyButtonTest"); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); // ajouter un panneau au cadre BuggyButtonPanel panel = new BuggyButtonPanel(); add(panel); } public static final int DEFAULT_WIDTH = 300; public static final int DEFAULT_HEIGHT = 200; } class BuggyButtonPanel extends JPanel { public BuggyButtonPanel() { ActionListener listener = new ButtonListener(); JButton yellowButton = new JButton("Yellow"); add(yellowButton); yellowButton.addActionListener(listener); JButton blueButton = new JButton("Blue"); add(blueButton); blueButton.addActionListener(listener); JButton redButton = new JButton("Red"); add(redButton); redButton.addActionListener(listener); }
private class ButtonListener implements ActionListener { public void actionPerformed(ActionEvent event) { String arg = event.getActionCommand(); if (arg.equals("yellow")) setBackground(Color.yellow);
603
Livre Java.book Page 604 Mercredi, 13. février 2008 2:34 14
604
Au cœur de Java 2 - Notions fondamentales
else if (arg.equals("blue")) setBackground(Color.blue); else if (arg.equals("red")) setBackground(Color.red); } } }
Dans un programme aussi court, vous pouvez trouver le bogue simplement en lisant le code source. Mais nous allons imaginer qu’il s’agit d’un programme très complexe. Nous décrivons ci-après comment lancer le débogueur Eclipse pour trouver l’erreur. INFO Si vous utilisez un débogueur indépendant comme JSwat (http://www.bluemarsh.com/java/jswat/) ou le très agréable jdb, vous commencez par compiler votre programme avec l’option -g, comme dans cet exemple : javac -g BuggyButtonTest.java
Dans un environnement intégré, cela s’effectue automatiquement.
Dans Eclipse, lancez le débogueur avec l’option de menu Run -> Debug As -> Java Application. Le programme se lance. Définissez un point d’arrêt à la première ligne de la méthode actionPerformed : faites un clic droit dans la marge gauche, près de la ligne de code et choisissez Toggle Breakpoint (voir Figure 11.7). Figure 11.7 Arrêt à un point d’arrêt.
Livre Java.book Page 605 Mercredi, 13. février 2008 2:34 14
Chapitre 11
Exceptions, consignation, assertion et mise au point
605
Il y a deux commandes de base pour faire du pas à pas dans un programme. La commande Step Into avance en pas à pas en suivant complètement les appels de méthode. Il est plus sûr d’utiliser la commande Step Over qui passe à la ligne suivante du code appelant sans entrer dans le détail d’autres appels de méthode. 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 n’est pas ce qui était prévu. Normalement, le programme doit appeler setColor(Color.yellow), puis quitter la méthode. Etudiez les variables locales et vérifiez la valeur de la variable arg :
Nous comprenons l’erreur. L’argument arg est en réalité "Yellow" avec un Y majuscule, mais le test est écrit ainsi : if (arg.equals("yellow"))
avec un y minuscule. Mystère éclairci ! Pour terminer la session de mise au point, sélectionnez Run -> Terminate dans le menu.
Livre Java.book Page 606 Mercredi, 13. février 2008 2:34 14
606
Au cœur de Java 2 - Notions fondamentales
Il existe des commandes de débogage plus avancées dans Eclipse mais vous accomplirez déjà beaucoup avec les techniques simples que nous venons de voir. D’autres débogueurs, comme NetBeans, possèdent des commandes très similaires. Ce chapitre a introduit la notion de gestion des exceptions en vous donnant quelques astuces de test et de débogage. Le prochain traite de la programmation générique et de son application la plus importante : le cadre des collections Java.
Livre Java.book Page 607 Mercredi, 13. février 2008 2:34 14
12 Programmation générique Au sommaire de ce chapitre
✔ Pourquoi la programmation générique ? ✔ Définition d’une classe générique simple ✔ Méthodes génériques ✔ Liaisons pour variables de type ✔ Code générique et machine virtuelle ✔ Restrictions et limites ✔ Règles d’héritage pour les types génériques ✔ Types joker ✔ La réflexion et le système générique La programmation générique constitue la nouveauté la plus significative du langage de programmation Java depuis la version 1.0. L’apparition du générique dans Java SE 5.0 résulte de l’une des premières requêtes de spécifications Java, JSR 14, produite en 1999. Le groupe d’experts a passé environ cinq ans à travailler sur des spécifications et à tester des implémentations. Le générique était fort attendu car il permet d’écrire un code plus sûr et plus facile à lire qu’un code parsemé de variables Object et de transtypages. Il est tout particulièrement utile pour les classes de collection, comme ArrayList. Le générique est, du moins en apparence, semblable aux modèles de C++. En C++, comme en Java, les modèles ont d’abord été ajoutés au langage pour soutenir les collections lourdes en types. Malgré tout, au cours des années, d’autres utilisations ont été découvertes. Lorsque vous aurez lu ce chapitre, vous en trouverez peut-être encore d’autres.
Livre Java.book Page 608 Mercredi, 13. février 2008 2:34 14
608
Au cœur de Java 2 - Notions fondamentales
Pourquoi la programmation générique ? La programmation générique implique d’écrire du code qui puisse être réutilisé pour des objets de types différents. Il n’est pas utile, par exemple, de programmer des classes différentes pour collecter les objets String et File, puisque la classe ArrayList collecte les objets de n’importe quelle classe. C’est un exemple de programmation générique. Avant Java SE 5.0, la programmation générique en Java s’obtenait à chaque fois par l’héritage. La classe ArrayList conservait simplement un tableau de références Object : public class ArrayList // avant Java SE 5.0 { public Object get(int i) { . . . } public void add(Object o) { . . . } . . . private Object[] elementData; }
Cette approche présente deux problèmes. D’une part, il faut avoir recours au transtypage lorsque vous récupérez une valeur : ArrayList files = new ArrayList(); . . . String filename = (String) names.get(0);
D’autre part, il n’existe aucune procédure de vérification des erreurs. Vous pouvez ajouter des valeurs de n’importe quelle classe : files.add(new File(". . ."));
Cet appel se compile et s’exécute sans erreur. Partout ailleurs, transtyper le résultat de get sur une chaîne produira une erreur. Le générique propose une meilleure solution : les paramètres de type. La classe ArrayList dispose désormais d’un paramètre de type qui précise le type d’élément utilisé : ArrayList files = new ArrayList();
Votre code est alors plus facile à lire. Vous voyez immédiatement que cet ArrayList contient des objets String. Le compilateur utilisera également ces informations à bon escient. Aucun transtypage n’est nécessaire 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 méthode add d’un ArrayList possède un paramètre de type String. Cela est bien plus sûr que d’utiliser un paramètre Object. Le compilateur peut alors vérifier que vous n’insérez aucun objet d’un type erroné. Par exemple, l’instruction files.add(new File(". . .")); // ne peut ajouter que des objets String // à un ArrayList
ne se compilera pas. D’ailleurs, une erreur de compilation vaut bien mieux qu’une exception de transtypage de classe au moment de l’exécution. C’est l’appel des paramètres de type : ils facilitent la lecture des programmes et les sécurisent.
Livre Java.book Page 609 Mercredi, 13. février 2008 2:34 14
Chapitre 12
Programmation générique
609
Y a-t-il un programmeur générique dans la salle ? L’utilisation d’une classe générique comme ArrayList se fait sans grande difficulté. La plupart des programmeurs Java utiliseront simplement des types tel ArrayList, comme s’ils avaient été intégrés dans le langage, à l’instar des tableaux String[] (bien sûr, les ArrayList sont plus intéressants que les tableaux car ils peuvent grandir automatiquement). Mais l’implémentation d’une classe générique n’est pas aussi simple qu’il y paraît. Les programmeurs qui utilisent votre code voudront y intégrer toutes sortes de classes pour vos paramètres de type. Ils s’attendent donc à ce que tout fonctionne sans trop de restrictions ni messages d’erreurs confus. Si vous l’acceptez, votre mission, en tant que programmeur générique, consiste à anticiper toutes les utilisations potentielles de votre classe. Oui, mais jusqu’où ? C’est là un problème ordinaire que rencontraient les concepteurs de la bibliothèque de classes standard. La classe ArrayList possède une méthode addAll qui lui permet d’ajouter tous les éléments d’une autre collection. Un programmeur peut vouloir ajouter tous les éléments d’un ArrayList à un ArrayList. Bien entendu, le contraire ne doit pas être autorisé. Comment faire alors pour autoriser un appel et interdire l’autre ? Les concepteurs du langage Java ont inventé un nouveau concept ingénieux pour résoudre ce problème, le type joker. Les types joker sont plutôt abstraits, mais ils permettent à un concepteur de bibliothèque de rendre les méthodes aussi flexibles que possible. La programmation générique aborde trois niveaux de compétences. Au niveau le plus bas, vous utilisez uniquement les classes génériques, généralement des collections comme ArrayList, sans penser à la manière dont elles fonctionnent ni à leurs raisons d’être. La plupart des programmeurs d’applications se contenteront de ce niveau jusqu’à ce qu’un problème apparaisse. Vous risquez de rencontrer un message d’erreur déroutant lorsque vous mélangez différentes classes génériques ou que vous interfacez avec du code existant qui ne connaît pas les paramètres de type. A ce moment-là, il vous faudra compléter votre culture du générique Java pour résoudre les problèmes systématiquement, plutôt que tâtonner à chaque fois. Enfin, bien sûr, vous voudrez peut-être implémenter vos propres classes et méthodes génériques. Les programmeurs d’applications n’écriront probablement pas de grosses quantités de code générique. Les équipes de Sun ont déjà fait le plus gros et fourni des paramètres de type pour toutes les classes de collection. En règle générale, seul le code qui impliquait généralement beaucoup de transtypages de types très généraux (comme Object ou l’interface Comparable) profitera de l’utilisation des paramètres de type. Dans ce chapitre, vous découvrirez tout ce qu’il faut savoir pour implémenter votre propre code générique. Vous devrez toutefois utiliser ces connaissances principalement pour vous aider au moment du dépannage et satisfaire votre curiosité sur le fonctionnement interne des classes de collection avec paramètres.
Livre Java.book Page 610 Mercredi, 13. février 2008 2:34 14
610
Au cœur de Java 2 - Notions fondamentales
Définition d’une classe générique simple Une classe générique est une classe comprenant une ou plusieurs variables de type. Dans ce chapitre, nous utilisons une classe Pair simple à titre d’exemple. Elle nous permet de nous concentrer sur le générique, sans être distraits par les détails de stockage des données. Voici le code de la classe Pair générique : public class Pair { public Pair() { first = null; second = null; } public Pair(T first, T second) { this.first = first; this. second = second; } public T getFirst() { return first; } public T getSecond() { return second; } public void setFirst(T newValue) { first = newValue; } public void setSecond(T newValue) { second = newValue; } private T first; private T second; }
La classe Pair introduit une variable de type T, incluse entre < > après le nom de la classe. Une classe générique peut posséder plusieurs variables de type. La classe Pair aurait pu, par exemple, être définie avec des types séparés pour le premier et le deuxième champ : public class Pair { . . . }
Les variables de type seront utilisées tout au long de la définition de la classe pour spécifier les types de retour des méthodes 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 indiquées avec des majuscules, ce qui permet de les garder courtes. La bibliothèque Java utilise la variable E pour le type d’élément d’une collection, K et V pour les types de clés et de valeurs d’une table et T (et les lettres U et S, si nécessaire) pour "n’importe quel type".
Vous instanciez le type générique en remplaçant les types par les variables de type, comme Pair
On peut imaginer le résultat comme une classe ordinaire avec les constructeurs Pair() Pair(String, String)
et les méthodes String getFirst() String getSecond() void setFirst(String) void setSecond(String)
En d’autres termes, la classe générique agit à la manière d’un factory pour les classes ordinaires.
Livre Java.book Page 611 Mercredi, 13. février 2008 2:34 14
Chapitre 12
Programmation générique
611
Le programme du Listing 12.1 met en œuvre la classe Pair. La méthode minmax statique parcourt un tableau et calcule en même temps la valeur minimale et la valeur maximale. Elle utilise un objet Pair pour renvoyer les deux résultats. Souvenez-vous que la méthode compareTo compare deux chaînes et renvoie 0 si les chaînes sont identiques, un entier négatif si la première chaîne vient avant la seconde dans l’ordre alphabétique et un entier positif dans les autres cas. INFO C++ Au premier abord, les classes génériques en Java sont identiques aux classes de modèles en C++. La seule différence évidente réside dans le fait que Java ne possède aucun mot clé template spécial. Toutefois, comme vous le verrez dans ce chapitre, il existe des différences considérables entre ces deux mécanismes.
Listing 12.1 : PairTest1.java /** * @version 1.00 2004-05-10 * @author Cay Horstmann */ public class PairTest1 { public static void main(String[] args) { String[] words = { "Mary", "had", "a", "little", "lamb" }; Pair mm = ArrayAlg.minmax(words); System.out.println("min = " + mm.getFirst()); System.out.println("max = " + mm.getSecond()); } } class ArrayAlg { /** * Récupère le minimum et le maximum d’un tableau de chaînes. * @param a Un tableau de chaîne * @return Une paire avec la valeur min et la valeur max ou null * si a est nul ou vide */ public static Pair minmax(String[] a) { if (a == null || a.length == 0) return null; String min = a[0]; String max = a[0]; for (int i = 1; i < a.length; i++) { if (min.compareTo(a[i]) > 0) min = a[i]; if (max.compareTo(a[i]) < 0) max = a[i]; } return new Pair(min, max); } }
Livre Java.book Page 612 Mercredi, 13. février 2008 2:34 14
612
Au cœur de Java 2 - Notions fondamentales
Méthodes génériques Dans la section précédente, vous avez vu comment définir une classe générique. Vous pouvez aussi définir une seule méthode avec des paramètres de type : class ArrayAlg { public static T getMiddle(T[] a) { return a[a.length / 2]; } }
Cette méthode est définie dans une classe ordinaire, et non dans une classe générique. C’est toutefois une méthode générique, comme vous pouvez le voir aux signes qui l’entourent et à la variable de type. Sachez que les variables de type sont insérées après les modifieurs (public static, dans ce cas) et avant le type de retour. Vous pouvez définir des méthodes génériques dans des classes ordinaires et dans des classes génériques. Lorsque vous appelez une méthode générique, vous pouvez placer les types réels, entourés des signes , avant le nom de la méthode : String[] names = { "John", "Q.", "Public" }; String middle = ArrayAlg.getMiddle(names);
Dans ce cas, et donc la plupart du temps, vous pouvez omettre le paramètre de type de l’appel de méthode. Le compilateur dispose de suffisamment d’informations pour en déduire la méthode que vous souhaitez utiliser. Il fait correspondre le type des names (donc, String[]) avec le type générique T[] et en déduit que T doit être un String. Vous pouvez donc simplement appeler String middle = ArrayAlg.getMiddle(names);
Dans la plupart des cas, l’inférence de type pour les méthodes génériques fonctionne agréablement. Parfois, cependant, le compilateur se trompe et vous devrez déchiffrer un rapport d’erreur. Etudiez cet exemple : double middle = ArrayAlg.getMiddle(3.14, 1729, 0);
Le message d’erreur est le suivant : "found: java.lang.Number&java.lang.Comparable>, required: double". Vous verrez plus loin au cours de ce chapitre comment déchiffrer la déclaration de type "found". Bref, le compilateur a procédé à l’autoboxing des paramètres 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 l’interface Comparable, qui est en soi un type générique. Dans ce cas, le recours consiste à écrire tous les paramètres sous forme de valeurs double. ASTUCE Peter von der Ahé conseille cette astuce pour découvrir le type du compilateur qui lance un appel de méthode générique : introduisez une erreur à dessein et étudiez le message qui en résulte. Envisagez par exemple l’appel ArrayAlg.getMiddle("Hello", 0, null). Affectez le résultat à un JButton, ce qui ne peut pas être juste. Vous obtiendrez un rapport d’erreur "found: java.lang.Object&java.io.Serializable&java.lang. Comparable>". Autrement dit, vous pouvez affecter le résultat à Object, Serializable ou Comparable.
Livre Java.book Page 613 Mercredi, 13. février 2008 2:34 14
Chapitre 12
Programmation générique
613
INFO C++ En C++, les paramètres de type se placent après le nom de la méthode. Ceci peut amener à des ambiguïtés d’analyse désagréables. Par exemple, g(f(c)) peut signifier "appeler g avec le résultat de f(c)" ou "appeler g avec les deux valeurs booléennes f(c)".
Limites pour variables de type Par moments, une classe ou une méthode doit placer des restrictions sur des variables de type. En voici un exemple ordinaire. Nous voulons calculer le plus petit élément d’un tableau : class ArrayAlg { public static T min(T[] a) // presque correct { if (a == null || a.length == 0) return null; T smallest = a[0]; for (int i = 1; i < a.length; i++) if (smallest.compareTo(a[i]) > 0) smallest = a[i]; return smallest; } }
Mais un problème demeure. Etudiez le code de la méthode min. La variable smallest possède un type T, ce qui signifie qu’il pourrait s’agir d’un objet d’une classe arbitraire. Comment savoir alors que la classe à laquelle appartient T possède une méthode compareTo ? La solution consiste à restreindre T à une classe qui implémente l’interface Comparable, une interface standard disposant d’une seule méthode, compareTo. Pour y parvenir, vous devez donner une limite pour la variable de type T : public static T min(T[] a) . . .
En fait, l’interface Comparable est elle-même un type générique. Pour l’heure, nous allons ignorer cette complexité et les avertissements générés par le compilateur. Les types joker sont décrits plus loin et montrent comment utiliser correctement les paramètres de type avec l’interface Comparable. Désormais, la méthode générique min ne peut plus être appelée qu’avec des tableaux de classes qui implémentent l’interface Comparable, comme String, Date, etc. Appeler min avec un tableau Rectangle produit une erreur de délai de compilation car la classe Rectangle n’implémente pas Comparable. INFO C++ En C++, vous ne pouvez pas restreindre les types des paramètres de modèles. Lorsqu’un programmeur instancie un modèle avec un type inadapté, un message d’erreur (souvent abscons) est signalé dans le code du modèle.
Vous vous demanderez peut-être pourquoi utiliser le mot clé extends plutôt que le mot clé implements dans cette situation (après tout, Comparable est une interface). La notation
Livre Java.book Page 614 Mercredi, 13. février 2008 2:34 14
614
Au cœur de Java 2 - Notions fondamentales
indique que T doit être un sous-type du type limitant. T et le type limitant peuvent être une classe ou une interface. Le mot clé extends a été choisi car il constitue une approximation raisonnable du concept de sous-type et que les concepteurs Java ne souhaitaient pas ajouter un nouveau mot clé (comme sub). Une variable de type ou joker peut avoir plusieurs limites, par exemple T extends Comparable & Serializable
Les types limitants sont séparés par des esperluettes (&) car les virgules sont utilisées pour séparer les variables de type. Comme pour l’héritage Java, vous pouvez disposer d’autant de supertypes d’interfaces que vous le souhaitez, mais une seule des limites peut être une classe. Si vous disposez d’une classe agissant comme élément limitant, elle doit figurer en première place dans la liste. Dans le prochain exemple de programme (voir Listing 12.2), nous réécrivons la méthode minmax de manière à la rendre générique. La méthode calcule le minimum et le maximum d’un tableau générique et renvoie un Pair. 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 mm = ArrayAlg.minmax(birthdays); System.out.println("min = " + mm.getFirst().getTime()); System.out.println("max = " + mm.getSecond().getTime()); } }
G. A. J. K.
Hopper Lovelace von Neumann Zuse
class ArrayAlg { /** * Récupère le minimum et le maximum d’un tableau d’objets de type T. * @param a Un tableau d’objets de type T * @return Une paire avec la valeur min et la valeur max, * ou null si a est nul ou vide */ public static Pair minmax(T[] a) { if (a == null || a.length == 0) return null; T min = a[0]; T max = a[0];
Livre Java.book Page 615 Mercredi, 13. février 2008 2:34 14
Chapitre 12
Programmation générique
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(min, max); } }
Code générique et machine virtuelle La machine virtuelle ne possède pas d’objet de type générique (tous les objets appartiennent à des classes ordinaires). Une précédente version de l’implémentation générique pouvait même compiler un programme utilisant le générique dans des fichiers de classe qui s’exécutaient sur les machines virtuelles 1.0 ! Cette compatibilité en amont n’a été abandonnée que tard dans le développement du générique Java. Si vous utilisez le compilateur Sun pour compiler un code employant le générique Java, les fichiers de classe qui en résultent ne s’exécuteront pas sur les machines virtuelles avant la version 5.0. INFO Si vous souhaitez profiter des fonctionnalités du générique tout en conservant la compatibilité des bytecodes avec d’anciennes machines virtuelles, consultez l’adresse http://sourceforge.net/projects/retroweaver. Le programme Retroweaver réécrit les fichiers de classe de sorte qu’ils soient compatibles avec d’anciennes machines virtuelles.
Dès que vous définissez un type générique, un type brut correspondant est automatiquement fourni. Le nom du type brut correspond simplement au nom du type générique, les paramètres de type ayant été supprimés. Les variables de type sont effacées et remplacées par leurs types limitants (ou Object pour les variables sans limites). Par exemple, le type brut pour Pair 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 remplacée par Object. Il en résulte une classe ordinaire, que vous pourriez avoir implémentée avant l’ajout du générique au langage de programmation Java.
Livre Java.book Page 616 Mercredi, 13. février 2008 2:34 14
616
Au cœur de Java 2 - Notions fondamentales
Vos programmes peuvent contenir différentes sortes de Pair, comme Pair ou Pair , mais l’effacement les transforme tous en types Pair bruts. INFO C++ A cet égard, le générique Java est très différent des modèles C++. C++ produit différents types pour chaque instanciation de modèle, un phénomène dénommé "gonflement du code du modèle". Java ne souffre pas de ce problème.
Le type brut remplace les variables de type par la première limite ou par Object si aucune limite n’est donnée. Par exemple, la variable de type de la classe Pair ne dispose pas de limites explicites, le type brut remplace donc T par Object. Supposons que nous déclarions un type légèrement différent : public class Interval implements Serializable { public Interval(T first, T second) { if (first.compareTo(second) = 0) super.setSecond(second); } . . . }
Un intervalle de date est une paire d’objets Date ; nous voulons écraser les méthodes pour nous assurer que la deuxième valeur ne soit jamais inférieure à la première. Cette classe est effacée : class DateInterval extends Pair // après effacement { public void setSecond(Date second) { . . . } . . . }
Ceci peut surprendre, mais il existe une autre méthode setSecond, héritée de Pair, à savoir public void setSecond(Object second)
C’est à l’évidence une méthode différente car elle possède un paramètre d’un type différent, qui est Object et non Date. Or elle ne doit pas être différente. Regardez cette suite d’instructions : DateInterval interval = new DateInterval(. . .); Pair pair = interval; // OK--attribution à la superclasse pair.setSecond(aDate);
Livre Java.book Page 618 Mercredi, 13. février 2008 2:34 14
618
Au cœur de Java 2 - Notions fondamentales
On peut s’attendre à ce que l’appel à setSecond soit polymorphique et que la méthode appropriée soit appelée. Etant donné que pair fait référence à un objet DateInterval, il doit s’agir de DateInterval.setSecond. Le problème est que l’effacement du type interfère avec le polymorphisme. Pour le résoudre, le compilateur génère une méthode bridge dans la classe DateInterval : public void setSecond(Object second) { setSecond((Date) second); }
Pour connaître les raisons de cette réussite, suivons attentivement l’exécution de l’instruction pair.setSecond(aDate)
La variable pair a déclaré le type Pair et ce type n’a qu’une seule méthode appelée setSecond, à savoir setSecond(Object). La machine virtuelle appelle cette méthode sur l’objet auquel pair fait référence. Cet objet est de type DateInterval. Donc, la méthode DateInterval.setSecond(Object) est appelée. C’est la méthode bridge synthétisée. Elle appelle DateInterval.setSecond(Date), et c’est bien ce que nous voulons. Les méthodes bridge peuvent apparaître encore plus étranges. Supposons que la méthode DateInterval écrase aussi la méthode getSecond : class DateInterval extends Pair { public Date getSecond() { return (Date) super.getSecond().clone(); } . . . }
Dans le type effacé, il y a deux méthodes getSecond : Date getSecond() // défini dans DateInterval Object getSecond() // défini dans Pair
Ce code Java est impossible, deux méthodes ne peuvent pas avoir les mêmes types de paramètres, c’est-à-dire ici aucun. Toutefois, dans la machine virtuelle, les types de paramètres et le type de retour spécifient une méthode. Le compilateur peut donc produire des bytecodes pour deux méthodes qui ne diffèrent qu’au niveau de leur type de retour ; la machine virtuelle gérera correctement cette situation. INFO Les méthodes bridge ne sont pas restreintes aux types génériques. Nous avons déjà noté au Chapitre 5 que, depuis Java SE 5.0, une méthode peut spécifier un type de retour plus restrictif lors de l’écrasement d’une autre méthode. Par exemple : public class Employee implements Cloneable { public Employee clone() throws CloneNotSupportedException { ... } }
Les méthodes Object.clone et Employee.clone sont dites avoir des types de retour covariants. En fait, la classe Employee possède deux méthodes clone : Employee clone() // défini ci-dessus Object clone() // méthode bridge synthétisée, écrase Object.clone
La méthode bridge synthétisée appelle la méthode nouvellement définie.
Livre Java.book Page 619 Mercredi, 13. février 2008 2:34 14
Chapitre 12
Programmation générique
619
En résumé, notez bien ces points liés à la traduction du générique Java : m
Il n’existe pas de générique dans les machines virtuelles, uniquement des classes et des méthodes ordinaires.
m
Tous les types de paramètres sont remplacés par leurs limites.
m
Les méthodes bridge sont synthétisées pour préserver le polymorphisme.
m
Les transtypages sont insérés en fonction des besoins pour préserver la sécurité du type.
Appeler un code existant De grandes quantités de code Java ont été écrites avant Java SE 5.0. Si les classes génériques ne pouvaient pas interagir avec ce code, elles ne seraient probablement pas fréquemment utilisées. Heureusement, l’utilisation des classes génériques avec leurs équivalents bruts se fait en toute simplicité dans les API existantes. Etudions un exemple concret. Pour définir les étiquettes d’un JSlider, vous utilisez la méthode void setLabelTable(Dictionary table)
Au Chapitre 9, nous avons utilisé le code suivant pour remplir la table des étiquettes : Dictionary labelTable = new Hashtable (); 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é transformées en classes génériques. Nous pouvons donc former un Dictionary au lieu d’utiliser un Dictionary brut. Toutefois, lorsque vous passez l’objet Dictionary à setLabelTable, le compilateur émet un avertissement : Dictionary labelTable = . . .; slider.setLabelTable(labelTable); // ATTENTION
Après tout, le compilateur n’a aucune assurance des effets de setLabelTable sur l’objet Dictionary. Cette méthode pourrait remplacer toutes les clés par des chaînes, ce qui annulerait la garantie que les clés ont le type Integer. Les opérations à venir pourraient alors lancer de mauvaises exceptions de transtypage. Vous ne pouvez pas faire grand-chose avec cet avertissement, à part l’étudier 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 l’avertissement. Envisageons maintenant le cas contraire : vous obtenez un objet d’un type brut depuis une classe existante. Vous pouvez l’assigner à une variable de type avec paramètres, mais vous obtiendrez bien sûr un avertissement. Par exemple : Dictionary labelTable = slider.getLabelTable(); // ATTENTION
Très bien, revoyons l’avertissement et assurons-nous que la table des étiquettes contient réellement les objets Integer et Component. La garantie n’est bien entendu jamais absolue. Un codeur malveillant pourrait avoir installé un Dictionary différent dans le slider. Mais, une fois de plus, la
Livre Java.book Page 620 Mercredi, 13. février 2008 2:34 14
620
Au cœur de Java 2 - Notions fondamentales
situation n’est pas pire qu’avant 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 disparaître. L’annotation doit être placée avant la méthode dont le code génère l’avertissement, comme ceci : @SuppressWarnings("unchecked") public void configureSlider() { . . . }
Malheureusement, cette annotation désactive la vérification du code dans la méthode. Mieux vaut isoler un code pouvant se révéler peu sûr en méthodes séparées, afin qu’elles puissent être révisées plus facilement. INFO La classe Hashtable est une sous-classe concrète de la classe abstraite Dictionary. Dictionary et Hashtable ont été déclarées "obsolètes" depuis qu’elles ont été remplacées par l’interface Map et la classe HashMap de Java SE 1.2. Or, apparemment, elles existent toujours et continuent à produire des effets. Après tout, la classe JSlider n’a été ajoutée qu’à Java SE 1.3. Les programmeurs ne connaissaient-ils pas la classe Map à l’époque ? Vous espérez donc qu’ils vont adopter le générique dans un avenir proche ? C’est ainsi qu’il en va avec le code existant.
Restrictions et limites Dans les sections à venir, nous nous attarderons sur plusieurs restrictions à connaître lorsque vous travaillez avec le générique Java. La plupart de ces restrictions sont une conséquence de l’effacement des types.
Les paramètres de type ne peuvent pas être instanciés avec les types primitifs Il est impossible de remplacer un type primitif par un paramètre de type. Il n’existe donc pas de Pair, uniquement Pair. Ceci est dû, bien entendu, à l’effacement du type. Après l’effacement, la classe Pair possède des champs du type Object, et vous ne pouvez pas les utiliser pour stocker des valeurs double. Ceci est fort ennuyeux, mais cohérent avec le statut distinct des types primitifs dans le langage Java. Ce n’est toutefois pas une limitation terrible, il n’existe que huit types primitifs, et vous pouvez toujours les gérer avec des classes et des méthodes séparées lorsque les types d’enveloppe ne constituent pas un remplacement acceptable.
Les informations sur le type d’exécution ne fonctionnent qu’avec les types bruts Les objets de la machine virtuelle ont toujours un type non générique spécifique. Ainsi, toutes les demandes de renseignements sur le type ne produisent que du type brut. Par exemple, if (a instanceof Pair) // identique à un instanceof Pair
ne teste réellement que si a est un Pair de quelque type que ce soit. Ceci vaut également pour le test
Livre Java.book Page 621 Mercredi, 13. février 2008 2:34 14
Chapitre 12
Programmation générique
621
if (a instanceof Pair) // T est ignoré
ou le transtypage Pair p = (Pair) a; // ATTENTION--ne peut tester // que si a est un Pair
Pour vous le rappeler, vous obtiendrez un avertissement de compilateur dès que vous utilisez instanceof ou des expressions de transtypage qui impliquent des types génériques. Dans le même esprit, la méthode getClass renvoie toujours le type brut. Par exemple : Pair stringPair = . . .; Pair employeePair = . . .; if (stringPair.getClass() == employeePair.getClass()) // ils sont égaux
La comparaison se vérifie car les deux appels à getClass renvoient Pair.class.
Vous ne pouvez pas lancer ou intercepter des instances d’une classe générique Vous ne pouvez ni déclencher ni intercepter des objets d’une classe générique. En fait, une classe générique n’a même pas le droit d’étendre Throwable. Par exemple, la définition suivante ne pourra pas être compilée : public class Problem extends Exception { /* . . . */ } // ERREUR--impossible d’étendre Throwable
Vous ne pouvez pas non plus utiliser une variable de type dans une clause catch. Par exemple, la méthode suivante ne pourra pas être compilée : public static void doWork(Class t) { try { fonctionne } catch (T e) // ERREUR--impossible d’intercepter la variable de type { Logger.global.info(...) } }
Vous pouvez toutefois utiliser des variables de type dans les spécifications d’exceptions. La méthode suivante est autorisée : public static void doWork(T t) throws T // OK { try { fonctionne } catch (Throwable realCause) { t.initCause(realCause); throw t; } }
Livre Java.book Page 622 Mercredi, 13. février 2008 2:34 14
622
Au cœur de Java 2 - Notions fondamentales
Les tableaux de types avec paramètres ne sont pas autorisés Il est impossible de déclarer des tableaux de types avec paramètres comme Pair[] table = new Pair[10]; // ERREUR
En effet, après l’effacement, 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 déclenche une exception ArrayStoreException si vous essayez de stocker un élément d’un type erroné : objarray[0] = "Hello"; // ERREUR--le type de composant est Pair
Mais l’effacement rend ce mécanisme inefficace pour les types génériques. L’attribution objarray[0] = new Pair();
passerait la vérification de stockage du tableau mais entraîne toujours une erreur de type. Pour cette raison, les tableaux de types avec paramètres sont interdits. ASTUCE Si vous devez collecter des objets de type avec paramètres, utilisez simplement un ArrayList :
ArrayList fonctionne de manière sûre et efficace.
Vous ne pouvez pas instancier des variables de type Vous ne pouvez pas utiliser de variables de type dans des expressions comme new T(...), new T[...] ou T.class. Par exemple, le constructeur Pair suivant est interdit : public Pair() { first = new T(); second = new T(); } // ERREUR
L’effacement du type transformerait T en Object, et vous ne voulez certainement pas appeler un nouvel Object(). Pour contourner cela, vous pouvez construire des objets génériques par la réflexion, en appelant la méthode Class.newInstance. Malheureusement, les détails sont un peu complexes. Vous ne pouvez pas appeler : first = T.class.newInstance(); // ERREUR
L’expression T.class n’est pas autorisée. Vous devez plutôt concevoir l’API pour recevoir un objet Class, comme ceci : public static Pair makePair(Class cl) { try { return new Pair(cl.newInstance(), cl.newInstance()) } catch (Exception ex) { return null; } }
Cette méthode pourrait être appelée comme suit : Pair p = Pair.makePair(String.class);
Livre Java.book Page 623 Mercredi, 13. février 2008 2:34 14
Chapitre 12
Programmation générique
623
Remarquez que la classe Class est elle-même générique. Par exemple, String.class est une instance (en fait la seule instance) de Class. La méthode makePair peut donc transférer le type de la paire qu’elle crée. Vous ne pouvez pas construire un tableau générique : public static T[] minMax(T[] a) { T[] mm = new T[2]; . . . } // ERREUR
L’effacement du type amènerait cette méthode à construire un tableau Object[2] chaque fois. Si le tableau ne sert que de champ d’instance privé d’une classe, vous pouvez le déclarer sous forme d’Object[] et utiliser les transtypages lorsque vous récupérez les éléments. Par exemple, la classe ArrayList pourrait être implémentée ainsi : public class ArrayList { 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 nécessaire . . . }
La véritable implémentation n’est pas aussi propre : public class ArrayList { private E[] elements; public ArrayList() { elements = (E[]) new Object[10]; } . . . }
Ici, le transtypage E[] est un mensonge mais l’effacement de type le rend indétectable. Cette technique ne fonctionne pas pour notre méthode minmax puisque nous renvoyons un tableau T[] et qu’une erreur d’exécution apparaît si nous mentons sur son type. Supposons que nous implémentions : public static T[] minmax(T[] a) { Object[] mm = new Object[2]; . . .; return (T[]) mm; // se compile avec un avertissement }
L’appel String[] ss = minmax("Tom", "Dick", "Harry");
se compile sans avertissement. Une exception ClassCastException survient lorsque la référence Object[] est affectée à la variable String[]. Dans cette situation, vous pouvez utiliser la réflexion et appeler Array.newInstance : public static T[] minmax(T[] a) { T[] mm = (T[]) Array.newInstance(a.getClass().getComponentType(), 2); . . . }
Livre Java.book Page 624 Mercredi, 13. février 2008 2:34 14
624
Au cœur de Java 2 - Notions fondamentales
La méthode toArray de la classe ArrayList n’est pas aussi chanceuse. Elle doit produire un tableau T[] mais ne possède pas le type de composant. Il existe donc deux variantes : Object[] toArray() T[] toArray(T[] result)
La seconde méthode reçoit un paramètre de tableau. Si le tableau est assez grand, il est utilisé, sinon on crée un tableau de taille suffisant, à l’aide du type de composant de result.
Les variables de type ne sont pas valables dans des contextes statiques des classes génériques Vous ne pouvez pas référencer les variables de type dans des champs ou méthodes statiques. Par exemple, cette idée brillante ne fonctionnera pas : public class Singleton { public static T getSingleInstance() // ERREUR { if (singleInstance == null) construct new instance of T return singleInstance; } private static T singleInstance; // ERREUR }
Si c’était possible, un programme pourrait déclarer un Singleton pour partager un générateur de nombres aléatoires et un Singleton pour partager une boîte de dialogue de sélection de fichiers. Mais cela ne fonctionnera pas. Après l’effacement du type, il ne reste qu’une classe Singleton et un seul champ singleInstance. Pour cette raison, les champs et les méthodes statiques ayant des variables de type sont purement interdits.
Attention aux conflits après un effacement Les conditions entraînant des conflits lors de l’effacement des types génériques sont interdites. En voici un exemple. Supposons que nous ajoutions une méthode equals à la classe Pair, comme ceci : public class Pair { public boolean equals(T value) { return first.equals(value) && second.equals(value); } . . . }
Envisagez un Pair. De par son concept, il possède deux méthodes equals : boolean equals(String) // défini dans Pair boolean equals(Object) // hérité de Object
Mais l’intuition va nous égarer. L’effacement de la méthode boolean equals(T)
est boolean equals(Object)
ce qui entre en conflit avec la méthode Object.equals.
Livre Java.book Page 625 Mercredi, 13. février 2008 2:34 14
Chapitre 12
Programmation générique
625
La solution consiste, bien entendu, à renommer la méthode à l’origine du conflit. La spécification du générique mentionne une autre règle : "Pour prendre en charge la traduction par effacement, une restriction est imposée : une classe ou une variable de type ne peut pas, dans le même temps, être un sous-type de deux types d’interface qui constituent des paramétrages différents de la même interface." Par exemple, le code suivant est interdit : class Calendar implements Comparable { . . . } class GregorianCalendar extends Calendar implements Comparable { . . . } // ERREUR
GregorianCalendar implémenterait alors Comparable et Comparable, des paramétrages différents de la même interface. Le rapport de cette restriction avec l’effacement de type n’est pas clair. Après tout, la version non générique class Calendar implements Comparable { . . . } class GregorianCalendar extends Calendar implements Comparable { . . . }
est autorisée. La raison en est bien plus subtile. Il y aurait un conflit avec les méthodes bridge synthétisées. Une classe qui implémente Comparable récupère une méthode bridge public int compareTo(Object other) { return compareTo((X) other); }
Vous ne pouvez pas avoir deux méthodes de ce type pour différents types X.
Règles d’héritage pour les types génériques Lorsque vous travaillez avec les classes génériques, vous devez apprendre quelques règles sur l’héritage et les sous-types. Commençons par une situation que de nombreux programmeurs ne jugent pas intuitive. Envisagez une classe et une sous-classe comme Employee et Manager. Pair est-il une sous-classe de Pair ? Bizarrement, la réponse est "non". Par exemple, le code suivant ne sera pas compilé : Manager[] topHonchos = . . .; Pair result = ArrayAlg.minmax(topHonchos); // ERREUR
La méthode minmax renvoie Pair, et non Pair, et vous ne pouvez pas affecter l’une à l’autre. En général, il n’existe pas de relation entre Pair et Pair, quels que soient les éléments auxquels S et T sont reliés (voir Figure 12.1). Cette restriction peut paraître cruelle, mais elle est nécessaire à la sécurité des types. Supposons que nous soyons autorisés à transformer un Pair en Pair. Etudiez ce code : Pair managerBuddies = new Pair(ceo, cfo); Pair employeeBuddies = managerBuddies; // interdit, mais supposons que ce ne le soit pas employeeBuddies.setFirst(lowlyEmployee);
A l’évidence, la dernière déclaration est autorisée. Mais employeeBuddies et managerBuddies font référence au même objet. Nous avons donc réussi à apparier le directeur financier avec un simple employé, ce qui ne devrait pas être possible pour un Pair.
Livre Java.book Page 626 Mercredi, 13. février 2008 2:34 14
626
Au cœur de Java 2 - Notions fondamentales
Figure 12.1 Aucune relation d’héritage entre les classes de paires.
Employé
Paire
pas de relation !
Directeur
Paire
INFO Vous venez de voir une différence importante entre les types génériques 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 d’une protection spéciale. 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 paramètres en type brut. Par exemple, Pair est un sous-type du type brut Pair. Cette conversion est nécessaire pour l’interfaçage avec le code existant. Est-il possible de procéder à une conversion en type brut, puis de créer une erreur de type ? Malheureusement, oui. Etudiez cet exemple : Pair managerBuddies = new Pair(ceo, cfo); Pair rawBuddies = managerBuddies; // OK rawBuddies.setFirst(new File(". . .")); // avertissement de temps de // compilation uniquement
Ceci est assez effrayant. Sachez toutefois que ce n’est pas pire qu’avec les anciennes versions de Java. La sécurité de la machine virtuelle n’est pas remise en cause. Lorsque l’objet étranger est récupéré avec getFirst et attribué à une variable Manager, une exception ClassCastException est déclenchée, comme auparavant. Vous perdez juste le surcroît de sécurité apporté généralement par la programmation générique. Enfin, les classes génériques peuvent étendre ou implémenter d’autres classes génériques. A cet égard, elles ne sont pas différentes des classes ordinaires. Par exemple, la classe ArrayList implémente l’interface List. Cela signifie qu’un ArrayList peut être converti en List. Toutefois, comme vous venez de le voir, un ArrayList n’est pas un ArrayList ni un List. La Figure 12.2 précise ces relations.
Livre Java.book Page 627 Mercredi, 13. février 2008 2:34 14
Chapitre 12
Programmation générique
627
Figure 12.2 >
interface >> List
interface >> List
submit(Runnable task) Future submit(Runnable task, T result) Future submit(Callable task)
Le pool exécutera la tâche envoyée le plus tôt possible. Lorsque vous appelez submit, vous recevez un objet Future disponible pour enquêter sur l’état de la tâche. La première méthode submit renvoie un Future à l’aspect étrange. Vous pouvez utiliser cet objet pour appeler isDone, cancel ou isCancelled. Or la méthode get renvoie simplement null à la fin. La deuxième version de submit envoie aussi un Runnable et la méthode get de Future renvoie l’objet result donné à la fin de l’opération. La troisième version envoie un Callable. Le Future renvoyé obtient le résultat du calcul lorsqu’il est prêt. Lorsque vous avez terminé avec un pool de connexion, appelez shutdown. Cette méthode lance la séquence de fermeture du pool. Un executor fermé n’accepte plus les nouvelles tâches. Lorsque toutes les tâches sont terminées, les threads du pool meurent. Vous pouvez aussi appeler shutdownNow. Le pool annule alors toutes les tâches qui n’ont pas encore commencé et tente d’interrompre les threads en cours d’exécution. Voici, en bref, ce qu’il faut faire pour utiliser un pool de connexion : 1. Appelez la méthode statique newCachedThreadPool ou newFixedThreadPool de la classe Executors. 2. Appelez submit pour envoyer des objets Runnable ou Callable. 3. Pour pouvoir annuler une tâche ou si vous envoyez des objets Callable, restez sur les objets Future renvoyés. 4. Appelez shutdown lorsque vous ne voulez plus envoyer de tâches. L’exemple précédent produisait un grand nombre de threads courts, un par répertoire. Le programme du Listing 14.12 utilise un pool de threads pour lancer les tâches. Le programme affiche la taille de pool la plus grande pendant l’exécution. Ces informations ne sont pas disponibles dans l’interface ExecutorService. Pour cette raison, nous avons dû transtyper l’objet pool sur la classe ThreadPoolExecutor.
Livre Java.book Page 770 Mercredi, 13. février 2008 2:34 14
770
Au cœur de Java 2 - Notions fondamentales
Listing 14.12 : ThreadPoolTest.java import java.io.*; import java.util.*; import java.util.concurrent.*; /** * @version 1.0 2004-08-01 * @author Cay Horstmann */ public class ThreadPoolTest { public static void main(String[] args) throws Exception { Scanner in = new Scanner(System.in); System.out.print("Enter base directory e.g. /usr/local/jdk5.0/src): "); String directory = in.nextLine(); System.out.print("Enter keyword (e.g. volatile): "); String keyword = in.nextLine(); ExecutorService pool = Executors.newCachedThreadPool(); MatchCounter counter = new MatchCounter( new File(directory), keyword, pool); Future result = pool.submit(counter); try { System.out.println(result.get() + " matching files."); } catch (ExecutionException e) { e.printStackTrace(); } catch (InterruptedException e) { } pool.shutdown(); int largestPoolSize = ((ThreadPoolExecutor) pool).getLargestPoolSize(); System.out.println("largest pool size=" + largestPoolSize); } } /** * Cette tâche compte les fichiers d’un répertoire et de ses * sous-répertoires qui contiennent un mot clé donné. */ class MatchCounter implements Callable { /** * Construit un MatchCounter. * @param directory Le répertoire dans lequel commencer la recherche * @param keyword Le mot clé à rechercher * @param pool Le pool de threads pour envoyer les sous-tâches */
Livre Java.book Page 771 Mercredi, 13. février 2008 2:34 14
Chapitre 14
Multithreads
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 results = new ArrayList(); for (File file : files) if (file.isDirectory()) { MatchCounter counter = new MatchCounter( file, keyword, pool); Future result = pool.submit(counter); results.add(result); } else { if (search(file)) count++; } for (Future 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()) {
771
Livre Java.book Page 772 Mercredi, 13. février 2008 2:34 14
772
Au cœur de Java 2 - Notions fondamentales
String line = in.nextLine(); if (line.contains(keyword)) found = true; } in.close(); return found; } catch (IOException e) { return false; } } private private private private
File directory; String keyword; ExecutorService pool; int count;
} java.util.concurrent.Executors 5.0
•
ExecutorService newCachedThreadPool()
Renvoie un pool de threads en cache qui crée des threads selon les besoins et met fin aux threads inactifs depuis 60 secondes.
•
ExecutorService newFixedThreadPool(int threads)
Renvoie un pool de threads qui utilise le nombre donné de threads pour exécuter les tâches.
•
ExecutorService newSingleThreadExecutor()
Renvoie un Executor qui exécute les tâches de manière séquentielle dans un seul thread. java.util.concurrent.ExecutorService 5.0
• • •
Future submit(Callable task) Future submit(Runnable task, T result) Future submit(Runnable task)
Envoient la tâche donnée pour exécution.
•
void shutdown()
Arrête le service, en terminant les tâches déjà envoyées 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.
Exécution programmée L’interface ScheduledExecutorService possède des méthodes pour l’exécution programmée ou répétée des tâches. Il s’agit d’une généralisation de java.util.Timer permettant le pool de threads. Les méthodes newScheduledThreadPool et newSingleThreadScheduledExecutor de la classe Executors renvoient des objets qui implémentent l’interface ScheduledExecutorService.
Livre Java.book Page 773 Mercredi, 13. février 2008 2:34 14
Chapitre 14
Multithreads
773
Vous pouvez programmer un Runnable ou un Callable pour qu’ils ne s’exécutent qu’une fois, après un délai initial. Vous pouvez aussi programmer un Runnable pour qu’il s’exécute à intervalles réguliers. Voir les notes d’API pour plus de détails. java.util.concurrent.Executors 5.0
•
ScheduledExecutorService newScheduledThreadPool(int threads)
Renvoie un pool de threads qui utilise le nombre donné de threads pour programmer des tâches.
•
ScheduledExecutorService newSingleThreadScheduledExecutor()
Renvoie un Executor qui programme des tâches sur un seul thread. java.util.concurrent.ScheduledExecutorService 5.0
• •
ScheduledFuture schedule(Callable task, long time, TimeUnit unit) ScheduledFuture schedule(Runnable task, long time, TimeUnit unit)
Programment la tâche donnée après expiration du délai donné.
•
ScheduledFuture scheduleAtFixedRate(Runnable task, long initialDelay, long period, TimeUnit unit)
Programme la tâche donnée pour une exécution périodique, à chaque unité period, une fois le délai initial expiré.
•
ScheduledFuture scheduleWithFixedDelay(Runnable task, long initialDelay, long delay, TimeUnit unit)
Programme la tâche donnée pour une exécution périodique, avec des unités delay entre chaque réalisation d’un appel et le début du suivant, une fois le délai initial expiré.
Contrôle de groupes de tâches Vous avez vu comment utiliser un service executor sous forme de pool de threads pour augmenter l’efficacité de l’exécution de la tâche. Un executor sert parfois à des fins plus tactiques, simplement pour contrôler un groupe de tâches liées. Ainsi, vous pouvez annuler toutes les tâches dans un executor avec la méthode shutdownNow. La méthode invokeAny envoie tous les objets d’une collection d’objets Callable et renvoie le résultat d’une tâche terminée. Vous ne connaissez pas la tâche (c’était probablement celle qui s’est terminée le plus rapidement). Vous utiliseriez cette méthode pour un problème de recherche dans lequel vous êtes prêt à accepter n’importe quelle solution. Ainsi, supposons que vous deviez factoriser un grand entier (un calcul requis pour casser un code RSA). Vous pourriez envoyer plusieurs tâches, chacune tentant une factorisation à l’aide de nombres d’une plage différente. Dès que l’une de ces tâches a une réponse, votre calcul peut s’arrêter. La méthode invokeAll envoie tous les objets d’une collection d’objets Callable et renvoie une liste d’objets Future qui représentent les solutions à toutes les tâches. Vous pouvez traiter les résultats du calcul dès qu’ils sont disponibles, comme ceci : List tasks = . . .; List results = executor.invokeAll(tasks);
Livre Java.book Page 774 Mercredi, 13. février 2008 2:34 14
774
Au cœur de Java 2 - Notions fondamentales
for (Future result : results) processFurther(result.get());
L’inconvénient de cette méthode, c’est que vous risquez d’attendre sans raison valable si la première tâche prend du temps. Il serait logique d’obtenir les résultats dans l’ordre de leur disponibilité. Cela peut être arrangé avec ExecutorCompletionService. Commencez par un executor, obtenu de la manière ordinaire. Construisez ensuite un ExecutorCompletionService. Envoyez les tâches au service de réalisation. Le service gère une queue de blocage d’objets Future, contenant les résultats des tâches soumises lorsqu’elles deviennent disponibles. Une organisation plus efficace pour le calcul précédent correspond à ceci : ExecutorCompletionService service = new ExecutorCompletionService(executor); for (Callable 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 tasks) T invokeAny(Collection tasks, long timeout, TimeUnit unit)
• •
Exécutent les tâches données et renvoient le résultat de l’une d’elles. La seconde méthode déclenche une TimeoutException en cas de temporisation. • •
List invokeAll(Collection tasks) List invokeAll(Collection tasks, long timeout, TimeUnit unit)
Exécutent les tâches données et renvoient les résultats de toutes. La seconde méthode déclenche une TimeoutException en cas de temporisation. java.util.concurrent.ExecutorCompletionService 5.0 • ExecutorCompletionService(Executor e)
Construit une réalisation d’executor qui collecte les résultats de l’executor donné. • •
Future submit(Callable task) Future submit(Runnable task, T result)
Envoient une tâche à l’executor sous-jacent. •
Future take()
Supprime le résultat terminé suivant, blocage au cas où aucun résultat terminé n’est disponible. • •
Future poll() Future poll(long time, TimeUnit unit)
Suppriment le résultat terminé suivant ou null si aucun n’est disponible. La seconde méthode attend le délai indiqué.
Synchronizers Le package java.util.concurrent contient plusieurs classes qui permettent de gérer un jeu de threads de collaboration (voir Tableau 14.3). Ces mécanismes possèdent des fonctionnalités intégrées pour les patterns communs de rencontre des threads. Si vous disposez d’un jeu de threads de
Livre Java.book Page 775 Mercredi, 13. février 2008 2:34 14
Chapitre 14
Multithreads
775
collaboration qui suit l’un de ces motifs de comportement, réutilisez simplement la classe de bibliothèque appropriée au lieu de concocter une collection artisanale de verrous et de conditions. Tableau 14.3 : Synchronizers
Classe
Action
Quand l’utiliser
CyclicBarrier
Permet à un jeu de threads de patienter jusqu’à ce qu’un nombre prédéfini d’entre eux ait atteint une barrière commune, puis exécute, en option, une action de barrière.
Lorsque plusieurs threads doivent se terminer avant que leurs résultats ne puissent être utilisés.
CountDownLatch
Permet à un jeu de threads de patienter jusqu’à ce qu’un compteur ait été ramené à 0.
Lorsqu’un ou plusieurs threads doivent patienter jusqu’à ce qu’un nombre spécifié de résultats soit disponible.
Exchanger
Permet à deux threads d’échanger des objets lorsque les deux sont prêts pour l’échange.
Lorsque deux threads agissent sur deux instances de la même structure de données, l’un en remplissant une instance, l’autre en vidant l’autre instance.
SynchronousQueue
Permet à un thread de donner un objet à un autre thread.
Pour envoyer un objet d’un thread à un autre lorsque les deux sont prêts, sans synchronisation explicite.
Semaphore
Permet à un jeu de threads de patienter jusqu’à ce que les autorisations de poursuivre soient disponibles.
Pour limiter le nombre total de threads ayant accès à une ressource. Si le compte des autorisations est à un, à utiliser pour bloquer les threads jusqu’à ce qu’un autre thread donne une autorisation.
Sémaphores Un sémaphore est un élément qui gère plusieurs autorisations. Pour passer au-delà du sémaphore, un thread demande une autorisation en appelant acquire. Seul un nombre fixe d’autorisations est disponible, ce qui limite le nombre de threads autorisés à passer. D’autres threads peuvent émettre des autorisations en appelant release. Mais il ne s’agit pas de vrais objets d’autorisation. Le sémaphore conserve simplement un compteur. De plus, une autorisation n’a pas à être libérée par le thread qui l’acquiert. En fait, n’importe quel thread peut émettre n’importe quel nombre d’autorisations. S’il en émet plus que le maximum disponible, le sémaphore est simplement défini sur le compte maximal. Ils sont donc flexibles, mais peuvent paraître confus. Les sémaphores ont été inventés par Edsger Dijkstra en 1968 pour être utilisés sous la forme de primitives de synchronisation. M. Dijkstra a montré que les sémaphores pouvaient être efficacement implémentés et qu’ils sont suffisamment performants pour résoudre les problèmes communs de
Livre Java.book Page 776 Mercredi, 13. février 2008 2:34 14
776
Au cœur de Java 2 - Notions fondamentales
synchronisation des threads. Dans presque tous les systèmes d’exploitation, vous trouverez des implémentations de queues bornées utilisant des sémaphores. Bien entendu, les programmeurs d’applications n’ont pas à réinventer les queues bornées. Nous vous suggérons de n’utiliser les sémaphores que lorsque leur comportement concorde bien avec votre problème de synchronisation. Par exemple, un sémaphore ayant un nombre d’autorisations égal à 1 servira de porte qu’un 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 qu’un utilisateur appuie sur un bouton. Le thread travailleur tente d’acquérir une autorisation, puis attend qu’un clic de bouton autorise l’émission d’une autorisation.
Verrous Countdown Un CountdownLatch permet à un jeu de threads de patienter jusqu’à ce qu’un compteur ait atteint zéro. Le CountdownLatch ne sert qu’une fois. Lorsque le compteur a atteint zéro, vous ne pouvez pas le réutiliser. Il existe un cas spécial, où le compteur du verrou est à 1. Ceci implémente une porte d’utilisation unique. Les threads sont maintenus à la porte jusqu’à ce qu’un autre thread définisse le compte sur 0. Imaginons, par exemple, un jeu de threads qui ait besoin de données initiales pour effectuer son travail. Les threads travailleurs démarrent, puis patientent à la porte. Un autre thread prépare les données. Lorsqu’il est prêt, il appelle countDown et tous les threads travailleurs poursuivent. Vous pouvez utiliser un second verrou pour vérifier lorsque tous les threads travailleurs sont effectués. Initialisez le verrou avec le nombre de threads. Chaque thread travailleur décompte ce verrou juste avant de terminer. Un autre thread qui récolte les résultats du travail patiente sur le verrou et continue dès que tous les travailleurs ont terminé.
Barrières La classe CyclicBarrier implémente ce que l’on appelle une barrière. Imaginons que plusieurs threads travaillent sur certaines parties d’un calcul. Lorsque toutes les parties sont prêtes, il faut combiner les résultats. Ainsi, lorsqu’un thread a terminé avec une partie, nous le laissons s’exécuter contre la barrière. Lorsque tous les threads ont atteint la barrière, celle-ci cède et les threads peuvent poursuivre. Pour cela, vous construisez d’abord une barrière, en indiquant le nombre de threads participants : CyclicBarrier barrier = new CyclicBarrier(nthreads);
Chaque thread effectue un certain travail et appelle await sur la barrière lorsqu’il a terminé : public void run() { doWork(); barrier.await(); ... }
La méthode await prend un paramètre optionnel de temporisation : barrier.await(100, TimeUnit.MILLISECONDS);
Livre Java.book Page 777 Mercredi, 13. février 2008 2:34 14
Chapitre 14
Multithreads
777
Si l’un des threads attendant la barrière part, celle-ci se rompt (un thread peut partir s’il appelle await avec une temporisation ou parce qu’il a été interrompu). Dans ce cas, la méthode await de tous les autres threads déclenche une BrokenBarrierException. Les threads qui attendent déjà voient leur méthode await se terminer immédiatement. Vous pouvez fournir une action de barrière optionnelle, qui s’exécutera lorsque tous les threads auront atteint la barrière : Runnable barrierAction = ...; CyclicBarrier barrier = new CyclicBarrier(nthreads, barrierAction);
L’action peut récupérer le résultat de chaque thread. La barrière est dite cyclique car elle peut être réutilisée après libération de tous les threads en attente. Elle diffère en cela d’un CountDownLatch, qui ne peut être utilisé qu’une fois.
Exchanger Exchanger est utilisé lorsque deux threads travaillent sur deux instances du même tampon de données. Généralement, un thread remplit le tampon, tandis que l’autre consomme son contenu. Lorsque les deux ont terminé, ils échangent leurs tampons.
Queues synchrones Une queue synchrone est un mécanisme qui apparie un thread producteur et un thread consommateur. Lorsqu’un thread appelle put sur un SynchronousQueue, il bloque jusqu’à ce qu’un autre thread appelle take, et vice versa. A la différence d’Exchanger, les données ne sont transférées que dans une direction, du producteur au consommateur. Même si la classe SynchronousQueue implémente l’interface BlockingQueue, ce n’est pas vraiment une queue. Elle ne contient aucun élément. Sa méthode size renvoie toujours 0.
Exemple : pause et reprise d’une animation Envisagez un programme qui effectue un travail, actualise l’écran, puis attend que l’utilisateur regarde le résultat et appuie sur un bouton pour continuer, enfin, réalise la prochaine unité de travail. Un sémaphore avec un compteur d’autorisations de 1 peut servir à synchroniser le thread travailleur et le thread de répartition des événements. Le thread travailleur appelle acquire dès qu’il est prêt à mettre en pause. Le thread d’interface utilisateur appelle release dès que l’utilisateur clique sur le bouton Continue. Que se passe-t-il alors si l’utilisateur clique plusieurs fois sur le bouton lorsque le thread travailleur est prêt ? Une seule autorisation étant disponible, le compteur reste à 1. Le programme du Listing 14.13 concrétise cette idée. Il anime un algorithme de tri. Un thread travailleur trie un tableau, il s’arrête à intervalles réguliers et attend que l’utilisateur donne l’autorisation de poursuivre. L’utilisateur peut admirer un dessin de l’état actuel de l’algorithme et appuyer sur le bouton Continue pour permettre au thread travailleur de réaliser l’étape suivante. Nous n’avons pas voulu vous ennuyer avec le code d’un algorithme de tri, nous appelons donc simplement Arrays.sort, qui implémente l’algorithme de tri-fusion. Pour faire une pause dans
Livre Java.book Page 778 Mercredi, 13. février 2008 2:34 14
778
Au cœur de Java 2 - Notions fondamentales
l’algorithme, nous fournissons un objet Comparator qui attend le sémaphore. L’animation fait donc une pause dès que l’algorithme compare deux éléments. Nous dessinons les valeurs actuelles du tableau et surlignons les éléments comparés (voir Figure 14.8). INFO L’animation montre la fusion de plages triées plus petites dans de grandes plages, mais elle n’est pas totalement précise. L’algorithme de tri-fusion utilise un second tableau pour contenir des valeurs temporaires que nous ne verrons pas. Cet exemple n’a pas pour objet de préciser les algorithmes de tri, mais de montrer comment utiliser un sémaphore pour faire une pause sur un thread travailleur.
Figure 14.8 Animation d’un algorithme de tri.
Listing 14.13 : AlgorithmAnimation.java import import import import import import
java.awt.*; java.awt.geom.*; java.awt.event.*; java.util.*; java.util.concurrent.*; javax.swing.*;
/** * Ce programme anime un algorithme de tri. * @version 1.01 2007-05-18 * @author Cay Horstmann */ public class AlgorithmAnimation { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { JFrame frame = new AnimationFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } });
Livre Java.book Page 779 Mercredi, 13. février 2008 2:34 14
Chapitre 14
} } /** * Ce cadre montre le tableau en cours de tri, ainsi que les * boutons pour avancer d’un pas dans l’animation * ou l’exécuter 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 exécutable exécute un algorithme de tri. * Lorsque deux éléments sont comparés, l’algorithme * fait une pause et actualise un composant. */ class Sorter implements Runnable {
Multithreads
779
Livre Java.book Page 780 Mercredi, 13. février 2008 2:34 14
780
Au cœur de Java 2 - Notions fondamentales
/** * Construit un Sorter. * @param values Le tableau à trier * @param panel Le composant sur lequel afficher * la progression du tri */ public Sorter(ArrayComponent comp) { values = new Double[VALUES_LENGTH]; for (int i = 0; i < values.length; i++) values[i] = new Double(Math.random()); this.component = comp; this.gate = new Semaphore(1); this.run = false; } /** * Règle le trieur sur le mode "run". Appelé * sur le thread de répartition des événements. */ public void setRun() { run = true; gate.release(); } /** * Règle le trieur sur le mode "Step". Appelé * sur le thread de répartition des événements. */ public void setStep() { run = false; gate.release(); } public void run() { Comparator comp = new Comparator() { 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); } };
Livre Java.book Page 781 Mercredi, 13. février 2008 2:34 14
Chapitre 14
Multithreads
Arrays.sort(values, comp); component.setValues(values, null, null); } private private private private private private
Double[] values; ArrayComponent component; Semaphore gate; static final int DELAY = 100; volatile boolean run; static final int VALUES_LENGTH = 30;
} /** * Cet écran dessine un tableau et marque deux éléments dans * le tableau. */ class ArrayComponent extends JComponent { /** * Règle les valeurs à dessiner. Appelé sur le thread de tri. * @param values Le tableau de valeurs à afficher * @param marked1 Le premier élément marqué * @param marked2 Le second élément 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 répartition des événements { 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; }
781
Livre Java.book Page 782 Mercredi, 13. février 2008 2:34 14
782
Au cœur de Java 2 - Notions fondamentales
java.util.concurrent.CyclicBarrier 5.0
• •
CyclicBarrier(int parties) CyclicBarrier(int parties, Runnable barrierAction)
Construisent une barrière cyclique pour le nombre de parties donné. barrierAction est exécuté lorsque toutes les parties ont appelé await sur la barrière.
• •
int await() int await(long time, TimeUnit unit)
Attendent que toutes les parties aient appelé await sur la barrière ou jusqu’à la fin de la temporisation, auquel cas une TimeoutException est déclenchée. En cas de réussite, renvoient l’indice d’arrivée de cette partie. La première partie possède parties –1 indices, la dernière partie possède un indice 0. java.util.concurrent.CountDownLatch 5.0
•
CountdownLatch(int count)
Construit un verrou avec un compte à rebours donné.
•
void await()
Attend que le compteur de ce verrou ait atteint 0.
•
boolean await(long time, TimeUnit unit)
Attend que le compteur de ce verrou ait atteint 0 ou que la temporisation ait expiré. Renvoie true si le compteur vaut 0, false si la temporisation a expiré.
•
public void countDown()
Décompte du compte à rebours de ce verrou. java.util.concurrent.Exchanger 5.0
• •
V exchange(V item) V exchange(V item, long time, TimeUnit unit)
Bloquent jusqu’à ce qu’un autre thread appelle cette méthode, puis échangent l’élément avec l’autre thread et renvoient l’élément de l’autre thread. La deuxième méthode déclenche une TimeoutException après expiration de la temporisation. java.util.concurrent.SynchronousQueue 5.0
• •
SynchronousQueue() SynchronousQueue(boolean fair)
Construisent une queue synchrone qui permet aux threads de donner des éléments. Si fair vaut true, la queue favorise les threads attendant depuis le plus longtemps.
•
void put(V item)
Bloque jusqu’à ce qu’un autre thread appelle take pour prendre cet élément.
•
V take()
Bloque jusqu’à ce qu’un autre thread appelle put. Renvoie l’élément fourni par l’autre thread.
Livre Java.book Page 783 Mercredi, 13. février 2008 2:34 14
Chapitre 14
Multithreads
783
java.util.concurrent.Semaphore 5.0
• •
Semaphore(int permits) Semaphore(int permits, boolean fair)
Construisent un sémaphore avec le nombre maximal donné d’autorisations. Si fair vaut true, la queue favorise les threads attendant depuis le plus longtemps.
•
void acquire()
Attend d’acquérir une autorisation.
•
boolean tryAcquire()
Tente d’acquérir une autorisation ; renvoie false si aucune n’est disponible.
•
boolean tryAcquire(long time, TimeUnit unit)
Tente d’acquérir une autorisation avec le délai donné ; renvoie false si aucune n’est disponible.
•
void release()
Libère une autorisation.
Threads et Swing Nous l’avons vu dans l’introduction de ce chapitre, l’une des raisons poussant à utiliser les threads dans vos programmes est que ces derniers répondront mieux. Lorsqu’un programme doit réaliser une tâche de longue durée, vous pouvez lancer un autre travailleur au lieu de bloquer l’interface utilisateur. Attention toutefois à ce que vous faites dans un thread travailleur car, et cela peut surprendre, Swing n’est pas compatible avec les threads. Si vous tentez de manipuler des éléments d’interface utilisateur à partir de plusieurs threads, l’interface utilisateur peut s’endommager. Pour constater ce problème, exécutez le programme de test suivant du Listing 14.14. Lorsque vous cliquez sur le bouton Bad, un nouveau thread est démarré. Sa méthode run torture une liste déroulante, en ajoutant et supprimant des valeurs de manière aléatoire. 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) {} } }
Livre Java.book Page 784 Mercredi, 13. février 2008 2:34 14
784
Au cœur de Java 2 - Notions fondamentales
Testez-le. Cliquez sur le bouton Bad, puis cliquez plusieurs fois sur la zone déroulante. Déplacez la barre de défilement. Déplacez la fenêtre. Cliquez à nouveau sur le bouton Bad. Continuez à cliquer sur la zone déroulante. Enfin, vous devez voir un rapport d’exceptions (voir Figure 14.9). Que se passe-t-il ? Lorsqu’un élément est inséré dans la zone déroulante, celle-ci déclenche un événement pour actualiser l’affichage. Le code d’affichage se met ensuite en route, il lit la taille actuelle de la zone déroulante, puis se prépare à afficher les valeurs. Mais le thread travailleur continue à fonctionner, entraînant parfois une réduction du nombre des valeurs dans la zone déroulante. Le code d’affichage pense alors qu’il y a plus de valeurs dans le modèle que ce qu’il y en a réellement. Il demande des valeurs inexistantes, puis déclenche une exception ArrayIndexOutOfBounds. Cette situation aurait pu être évitée en permettant aux programmeurs de verrouiller l’objet zone déroulante pendant son affichage. Or, les concepteurs de Swing ont décidé de ne faire aucun effort pour rendre Swing compatible avec les threads, et ce pour deux raisons. Tout d’abord, la synchronisation prend du temps et personne ne voulait ralentir Swing encore plus. Et surtout, l’équipe Swing a étudié l’expérience que d’autres équipes avaient eue avec les boîtes à outils d’interfaces utilisateur compatibles avec les threads. Les constatations étaient peu encourageantes. Les programmeurs utilisant des boîtes à outils compatibles avec les threads ont été déroutés par les demandes de synchronisation et ont souvent créé des programmes sujets aux verrous morts. Figure 14.9 Rapports d’exception dans la console.
Exécution de tâches longues Lorsque vous utilisez des threads avec Swing, deux règles simples sont à respecter. m
Si une action est trop longue, effectuez-la dans un thread travailleur séparé et jamais dans le thread de répartition d’événements.
m
Ne touchez pas aux composants Swing dans un thread autre que le thread de répartition des événements.
Livre Java.book Page 785 Mercredi, 13. février 2008 2:34 14
Chapitre 14
Multithreads
785
La raison de la première règle est simple à comprendre. Si vous passez trop de temps dans le thread de répartition des événements, l’application semble "morte" car elle ne répond à aucun événement. Notamment, le thread de répartition des événements ne doit jamais réaliser d’appels en entrée/sortie, qui pourraient se bloquer indéfiniment, et il ne doit jamais appeler sleep (si vous devez attendre un délai spécifique, utilisez les événements de minuteur). La seconde règle est souvent appelée règle du thread unique pour la programmation Swing. Nous la verrons un peu plus loin. Ces deux règles semblent entrer en conflit. Supposons que vous déclenchiez un thread séparé pour qu’il exécute une tâche longue. Vous voulez généralement actualiser l’interface utilisateur pour signaler la progression lorsque votre thread travaille. Une fois la tâche terminée, vous devez actualiser à nouveau l’interface 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 régler sa valeur à partir de votre thread. Pour résoudre ce problème, il existe deux méthodes dans tous les threads pour ajouter des actions arbitraires à la queue des événements. Ainsi, supposons que vous vouliez régulièrement actualiser une étiquette dans un thread pour signaler une progression. Vous ne pouvez pas appeler label.setText à partir du thread. Utilisez plutôt les méthodes invokeLater et invokeAndWait de la classe EventQueue pour que cet appel soit exécuté dans le thread de répartition des événements. Vous placez donc le code Swing dans la méthode run d’une classe qui implémente l’interface Runnable. Vous créez ensuite un objet de cette classe et vous le passez à la méthode 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 méthode invokeLater retourne immédiatement lorsque l’événement est envoyé dans la queue des événements. La méthode run est exécutée de manière asynchrone. La méthode invokeAndWait attend que la méthode run ait réellement été exécutée. Dans le cas d’une mise à jour d’une étiquette de progression, la méthode invokeLater convient mieux. Les utilisateurs préféreraient que le thread travailleur progresse plus, plutôt que d’avoir un indicateur plus précis. Les deux méthodes exécutent la méthode run dans le thread de répartition des événements. Aucun nouveau thread n’est créé. Le Listing 14.14 montre comment utiliser la méthode invokeLater pour modifier en toute sécurité le contenu d’une zone déroulante. Si vous cliquez sur le bouton Good, un thread insère et supprime des nombres. Toutefois, la modification réelle a lieu dans le thread de répartition des événements. Listing 14.14 : SwingThreadTest.java import java.awt.*; import java.awt.event.*; import java.util.*;
Livre Java.book Page 786 Mercredi, 13. février 2008 2:34 14
786
Au cœur de Java 2 - Notions fondamentales
import javax.swing.*; /** * Ce programme montre qu’un thread qui s’exécute en * parallèle avec le thread de répartition des événements * peut générer 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 possède deux boutons pour remplir une zone * déroulante à partir d’un thread séparé. Le bouton "Good" * utilise la queue d’événement, le bouton "Bad" modifie directement * la zone déroulante. */ 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();
Livre Java.book Page 787 Mercredi, 13. février 2008 2:34 14
Chapitre 14
Multithreads
} }); panel.add(badButton); panel.add(combo); add(panel); pack(); } } /** * Cet exécutable modifie une zone déroulante en ajoutant et * supprimant des nombres aléatoirement. Cela peut entraîner * des erreurs car les méthodes de la zone déroulante ne sont * pas synchronisées et parce que le thread travailleur et le thread * de répartition des événements accèdent tous deux à la liste * déroulante. */ 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 exécutable modifie une zone déroulante en ajoutant et * supprimant aléatoirement des nombres. Pour s’assurer que * la zone déroulante n’est pas corrompue, les opérations de * modifications sont transmises au thread de répartition * des événements. */ class GoodWorkerRunnable implements Runnable { public GoodWorkerRunnable(JComboBox aCombo) {
787
Livre Java.book Page 788 Mercredi, 13. février 2008 2:34 14
788
Au cœur de Java 2 - Notions fondamentales
combo = aCombo; generator = new Random(); } public void run() { try { while (true) { EventQueue.invokeLater(new Runnable() { public void run() { int i = Math.abs(generator.nextInt()); if (i % 2 == 0) combo.insertItemAt(i, 0); else if (combo.getItemCount() > 0) combo.removeItemAt(i % combo.getItemCount()); } }); Thread.sleep(1); } } catch (InterruptedException e) { } } private JComboBox combo; private Random generator; } java.awt.EventQueue 1.1 static void invokeLater(Runnable runnable) 1.2
•
Amène la méthode run de l’objet runnable à s’exécuter dans le thread de répartition des événements après que les événements en attente ont été traités. •
static void invokeAndWait(Runnable runnable) 1.2
Amène la méthode run de l’objet runnable à s’exécuter dans le thread de répartition des événements après que les événements en attente ont été traités. Cet appel se bloque jusqu’à ce que la méthode run se termine. •
static boolean isDispatchThread() 1.2
Renvoie true si le thread exécutant cette méthode est le thread de répartition des événements.
Utilisation du travailleur Swing Lorsqu’un utilisateur émet une commande pour laquelle le traitement est long, vous devez déclencher un nouveau thread pour qu’il effectue le travail. Vous l’avez vu à la section précédente, ce thread doit utiliser la méthode EventQueue.invokeLater pour actualiser l’interface utilisateur. Plusieurs auteurs ont produit des classes pour simplifier cette tâche, l’une d’elles est arrivée jusqu’à Java SE 6. Ici, nous décrivons cette classe SwingWorker.
Livre Java.book Page 789 Mercredi, 13. février 2008 2:34 14
Chapitre 14
Multithreads
789
Le programme du Listing 14.15 possède des commandes pour charger un fichier texte et annuler le traitement du chargement. Testez le programme avec un fichier long, comme le texte complet du Comte de Monte-Cristo, fourni dans le répertoire gutenberg du code accompagnant l’ouvrage. Le fichier est chargé dans un thread séparé. Lorsque le fichier se lit, l’élément de menu Open est désactivé et l’élément Cancel est activé (voir Figure 14.10). Après que chaque ligne a été lue, un compteur de ligne dans la barre d’état est mis à jour. Une fois la procédure de lecture terminée, l’élément Open est réactivé, l’élément Cancel est désactivé et le texte de la ligne d’état passe à Done. Cet exemple montre les activités d’IU ordinaires d’une tâche en arrière-plan : m
après chaque unité de travail, actualiser l’IU pour afficher la progression ;
m
une fois le travail terminé, apporter une dernière modification à l’IU.
La classe SwingWorkerTask simplifie l’implémentation de cette tâche. Vous écrasez la méthode doInBackground pour effectuer le travail de longue haleine et vous appelez parfois publish pour communiquer la progression du travail. Cette méthode est exécutée dans un thread travailleur. La méthode publish amène une méthode process à s’exécuter dans le thread de répartition des événements pour traiter les données de progression. A la fin du travail, la méthode done est appelée dans le thread de répartition des événements pour que vous finissiez la mise à jour de l’IU. Figure 14.10 Chargement d’un fichier dans un thread séparé.
Dès que vous souhaitez travailler dans le thread travailleur, construisez un nouveau travailleur (chaque objet travailleur a pour but d’être utilisé une seule fois). Appelez ensuite la méthode execute. Vous appellerez généralement execute sur le thread de répartition des événements, mais ce n’est pas une obligation. On peut supposer qu’un travailleur produise un résultat d’un certain type ; SwingWorker implémente donc Future. Ce résultat peut être obtenu par la méthode get de l’interface Future. Puisque la méthode get se bloque jusqu’à ce que le résultat soit disponible, vous ne devez pas l’appeler immédiatement après execute. Il est conseillé de ne le faire que lorsque vous savez que le travail est achevé. Généralement, vous l’appelez depuis la méthode done (il n’y a aucune obligation d’appeler get, parfois, le traitement des données de progression suffit).
Livre Java.book Page 790 Mercredi, 13. février 2008 2:34 14
790
Au cœur de Java 2 - Notions fondamentales
Les données de progression intermédiaire et le résultat final peuvent avoir des types arbitraires. La classe SwingWorker possède ces types comme paramètres de type. Un SwingWorker produit un résultat du type T et des données de progression de type V. Pour annuler le travail en cours, utilisez la méthode cancel de l’interface Future. Lorsque le travail est annulé, la méthode get déclenche une CancellationException. Nous l’avons indiqué, l’appel du thread travailleur à publish entraîne des appels à process sur le thread de répartition des événements. Pour des raisons d’efficacité, les résultats de plusieurs appels à publish peuvent être regroupés en un seul appel à process. La méthode process reçoit un List contenant tous les résultats intermédiaires. Utilisons ce mécanisme pour lire un fichier texte. Or, un JTextArea est assez lent. Ajouter des lignes d’un fichier texte long (comme toutes les lignes du Comte de Monte-Cristo) prend un temps considérable. Pour montrer à l’utilisateur qu’une progression est en cours, nous allons afficher le nombre de lignes lues dans une ligne d’état. Ainsi, les données de progression sont composées 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 résultat final est le texte qui a été lu dans un StringBuilder. Nous avons donc besoin d’un SwingWorker. Dans la méthode doInBackground, nous lisons un fichier, ligne par ligne. Après 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 l’annulation ; // inutile dans vos programmes } return text; }
Nous appelons également sleep pendant un millième de seconde après chaque ligne pour que vous puissiez tester l’annulation 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 d’interface utilisateur par lot.
Livre Java.book Page 791 Mercredi, 13. février 2008 2:34 14
Chapitre 14
Multithreads
791
INFO Vous pouvez amener ce programme à se comporter de manière assez homogène en actualisant la zone de texte à partir du thread travailleur, mais cela n’est pas possible pour la plupart des composants Swing. Nous vous montrons l’approche générale dans laquelle les mises à jour des composants surviennent dans le thread de répartition des événements.
Dans la méthode process, nous ignorons tous les numéros de lignes à l’exception de la dernière et concaténons toutes les lignes pour une mise à jour unique de la zone de texte. @Override public void process(List 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 méthode done, la zone de texte est mise à jour avec la totalité du texte et l’élément de menu Cancel est désactivé. Remarquez que le travailleur est démarré dans l’écouteur d’événement pour l’élément de menu Open. Cette technique simple permet d’exécuter des tâches longues tout en assurant l’attention de l’interface 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 présente un thread travailleur qui exécute * une tâche 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); }
Livre Java.book Page 792 Mercredi, 13. février 2008 2:34 14
792
Au cœur de Java 2 - Notions fondamentales
}); } } /** * Ce cadre possède une zone de texte pour afficher le contenu d’un * fichier texte, un menu pour ouvrir un fichier et annuler * le procédé d’ouverture, ainsi qu’une ligne d’état 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 boîte de sélection de fichier int result = chooser.showOpenDialog(null); // si le fichier est sélectionné, le définir comme // icône de l’étiquette 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() {
Livre Java.book Page 793 Mercredi, 13. février 2008 2:34 14
Chapitre 14
Multithreads
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 méthode suivante s’exécute 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 l’annulation ; // inutile dans vos programmes } return text; } // les méthodes suivantes s’exécutent dans le thread // de répartition des événements @Override public void process(List 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");
793
Livre Java.book Page 794 Mercredi, 13. février 2008 2:34 14
794
Au cœur de Java 2 - Notions fondamentales
} textArea.append(b.toString()); } @Override public void done() { try { StringBuilder result = get(); textArea.setText(result.toString()); statusLine.setText("Done"); } catch (InterruptedException ex) { } catch (CancellationException ex) { textArea.setText(""); statusLine.setText("Cancelled"); } catch (ExecutionException ex) { statusLine.setText("" + ex.getCause()); } cancelItem.setEnabled(false); openItem.setEnabled(true); } private File file; private StringBuilder text = new StringBuilder(); }; private private private private private private
JFileChooser chooser; JTextArea textArea; JLabel statusLine; JMenuItem openItem; JMenuItem cancelItem; SwingWorker textReader;
public static final int DEFAULT_WIDTH = 450; public static final int DEFAULT_HEIGHT = 350; } javax.swing.SwingWorker 6 abstract T doInBackground()
•
Ecrase cette méthode pour réaliser la tâche en arrière-plan et renvoyer le résultat du travail. •
void process(List data)
Ecrase cette méthode pour traiter des données de progression intermédiaire dans le thread de répartition des événements. •
void publish(V... data)
Transfère les données de progression intermédiaires au thread de répartition des événements. Appelez cette méthode à partir de doInBackground.
Livre Java.book Page 795 Mercredi, 13. février 2008 2:34 14
Chapitre 14
•
Multithreads
795
void execute()
Programme ce travailleur pour une exécution sur un thread travailleur. •
SwingWorker.StateValue getState()
Récupère l’état de ce travailleur, entre PENDING, STARTED et DONE.
La règle du thread unique Chaque application Java démarre par une méthode main qui s’exécute dans le thread principal. Dans un programme Swing, le thread principal est court. Il programme la construction de l’interface utilisateur dans le thread de répartition des événements puis s’arrête. Une fois l’interface utilisateur construite, le thread de répartition des événements traite les notifications des événements, comme les appels à actionPerformed ou paintComponent. D’autres threads, comme celui qui envoie les événements dans la queue des événements, s’exécutent en coulisse mais ils sont invisibles pour le programmeur. Précédemment, au cours de ce chapitre, nous avons présenté la règle du thread unique : "Ne touchez pas aux composants Swing d’un thread autre que le thread de répartition des événements." Nous allons détailler cette règle. Il existe quelques exceptions. m
Vous pouvez ajouter et supprimer en toute sécurité des écouteurs d’événements de n’importe quel thread. Bien entendu, les méthodes d’écouteur seront appelées dans le thread de répartition des événements.
m
Un petit nombre de méthodes Swing sont compatibles avec les threads. Elles sont désignées dans la documentation API par la phrase "This method is thread safe, although most Swing methods are not” (cette méthode est compatible avec les threads, même si la plupart des méthodes 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 méthode repaint dans cet ouvrage ; la méthode revalidate est moins commune. Son objectif est de forcer la mise en page d’un composant une fois que le contenu a changé. L’AWT traditionnel possède une méthode validate pour forcer la mise en page d’un composant. Pour les composants Swing, vous devez simplement appeler revalidate à la place (toutefois, pour forcer la mise en page d’un JFrame, vous devrez toujours appeler validate ; un JFrame est un Component mais pas un JComponent).
Historiquement, la règle du thread unique était plus permissive. N’importe quel thread avait le droit de construire des composants, de définir leurs propriétés et de les ajouter dans des conteneurs, tant qu’aucun des composants n’avait été réalisé. Un composant est réalisé s’il peut recevoir des événements de dessin ou de validation. C’est le cas dès que les méthodes setVisible(true) or pack (!) ont été appelées sur le composant ou si le composant a été ajouté à un conteneur qui a été réalisé.
Livre Java.book Page 796 Mercredi, 13. février 2008 2:34 14
796
Au cœur de Java 2 - Notions fondamentales
Cette version de la règle du thread unique était commode. Elle vous permettait de créer l’interface graphique dans la méthode main, puis d’appeler setVisible(true) sur le cadre de niveau supérieur de l’application. Il n’existait aucune planification gênante d’un Runnable sur le thread de répartition d’événements. Malheureusement, certains implémenteurs de composants ne faisaient pas attention aux subtilités de la règle du thread unique. Ils lançaient des activités sur le thread de répartition des événements sans jamais s’inquiéter de vérifier si le composant était réalisé. Ainsi, si vous appelez setSelectionStart or setSelectionEnd sur un JTextComponent, un mouvement caret est planifié dans le thread de répartition des événements, même si le composant n’est pas visible. Il aurait pu être possible de détecter et de résoudre ces problèmes, mais les concepteurs Swing ont choisi la solution de facilité. Ils ont décrété qu’il n’était jamais sûr d’accéder aux composants depuis n’importe quel thread autre que le thread de répartition des événements. Vous devez donc construire l’interface utilisateur dans le thread de répartition des événements, à l’aide de l’appel à 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 l’ancienne version de la règle du thread unique, en initialisant l’interface utilisateur sur le thread principal. Ces programmes portent le léger risque qu’une partie de l’initialisation de l’interface utilisateur entraîne des actions sur le thread de répartition des événements entrant en conflit avec les actions du thread principal. Nous l’avons dit au Chapitre 7, vous ne devez pas faire partie des malchanceux qui vont perdre leur temps à déboguer un thread intermittent. Vous devez donc simplement suivre la règle stricte du thread unique. Vous avez maintenant atteint la fin du Volume I de Au cœur de Java. Vous avez abordé les bases du langage de programmation Java et les parties de la bibliothèque standard dont vous avez besoin pour la plupart des projets de programmation. Nous espérons que vous avez profité de votre visite des bases de Java et que vous aurez récolté des informations utiles en route. Pour les sujets avancés, comme le réseau, AWT/Swing avancé, la sécurité et l’internationalisation, reportez-vous au Volume II.
Livre Java.book Page 797 Mercredi, 13. février 2008 2:34 14
Annexe Les mots clés de Java
Mots clés
Signification
Voir Chapitre
abstract
Abstraite (pour une classe ou une méthode)
5
assert
Pour localiser des erreurs internes au programme
11
boolean
Type booléen
3
break
Sortie d’un switch ou d’une boucle
3
byte
Type entier sur 8 bits
3
case
Clause case d’un switch
3
catch
Clause d’un block try interceptant une exception
11
char
Le type de caractère Unicode
3
class
Définition de classe
4
const
Inutilisé
continue
Redémarre une boucle à l’itération suivante
3
default
Cas par défaut d’un switch
3
do
Début d’une boucle do/while
3
double
Type d’un nombre flottant en double précision
3
else
Clause else d’une instruction if
3
extends
Définit la classe parent d’une classe
4
Livre Java.book Page 798 Mercredi, 13. février 2008 2:34 14
798
Au cœur de Java 2 - Notions fondamentales
Mots clés
Signification
Voir Chapitre
final
Constante, classe ou méthode qui ne peut être surchargée
5
finally
La partie d’un bloc try qui est toujours exécutée
11
float
Type d’un nombre flottant en simple précision
3
for
Boucle
3
goto
Inutilisé
if
Instruction conditionnelle
3
implements
Définit la ou les interfaces qu’une classe implémente
6
import
Importe un package
4
instanceof
Teste si un objet est une instance d’une classe
5
int
Type d’un entier sur 32 bits
3
interface
Type abstrait contenant des méthodes devant être implémentées par une classe
6
long
Type d’un entier long sur 64 bits
3
native
Méthode implémentée par le système hôte
new
Instancie un nouvel objet ou un tableau
3
null
Une référence vide
3
package
Un package de classes
4
private
Restreint la visibilité aux méthodes de la classe
4
protected
Restreint la visibilité aux méthodes de la classe, à ses sous-classes et aux autres classes du même package
5
public
Fonctionnalité accessible par les méthodes de toutes les classes
4
return
Retour d’une méthode
3
short
Type d’un entier sur 16 bits
3
static
Constante ou variable instanciée une seule fois, commune à tous les objets de la classe
3
strictfp
Utilise les règles strictes pour les calculs à virgule flottante
2
super
Accès à la superclasse ou à son constructeur
5
Volume II
Livre Java.book Page 799 Mercredi, 13. février 2008 2:34 14
Annexe
Les mots clés de Java
Mots clés
Signification
switch
Instruction de sélection
synchronized
Méthode ou bloc de code atomique dans un thread
this
Argument implicite d’une méthode ou du constructeur de la classe
4
throw
Lance une exception
11
throws
Déclare les exceptions que peut lancer une méthode
11
transient
Modificateur des données non persistantes
12
try
Partie de code susceptible d’intercepter des exceptions
11
void
Qualifie une méthode qui ne renvoie rien
3
volatile
S’assure qu’un champ est logiquement accessible par plusieurs threads
while
Boucle
799
Voir Chapitre 3 Volume II
Volume II 3
Livre Java.book Page 800 Mercredi, 13. février 2008 2:34 14
Livre Java.book Page 801 Mercredi, 13. février 2008 2:34 14
Index
Symboles $, caractère de séparation des classes 269 $, dans noms de variables et méthodes 271 && (et logique), opérateur 54 , (virgule) pour séparer des fichiers JAR 519 < (inférieur à), opérateur 54 (supérieur à), opérateur 54 >= (supérieur ou égal à), opérateur 54 || (ou logique), opérateur 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 méthodes accept, méthode 482 Accès aux champs privés 180 aux données privées 137 aux fichiers, boîte de dialogue 472 aux packages 166
aux variables locales 273 protégé aux classes 197 AccessibleObject, classe 233, 236 Accessoire Voir Composants Accessor Voir Méthodes d’accès Accolades délimitant les blocs 43, 78 Action icône 411 barre d’outils 422 interface 409 fonctions prédéfinies 351 méthodes 345 objet, actions prédéfinies 346 Action, classe 351 ActionEvent, classe 330, 336, 361 ActionListener, interface 275, 329, 330 ActionMap, classe 348 actionPerformed, méthode 329, 330, 331, 409 Actions 344 Activer une option de menu 416 ActiveX 11 Actualisation d’une fenêtre 302 Adaptateurs, classes Voir Classes add, méthode 94, 122, 211, 213, 330, 373, 394, 410, 646 addItem, méthode 399, 401 addLayoutComponent, méthode 449 addWindowListener, méthode 343 AdjustmentEvent, classe 361 Adresses Web Voir Web, adresses
Affichage boîte de dialogue 463 chaîne 317 dans un composant 300 image 324 mise en forme 72 texte 302 after, méthode 120 Agrégation de classes 115 Aide Voir Bulle Ajout bouton 330 champ de texte 380 composants 300 Algorithme 693 fdlibm 57 personnalisé 699 align, attribut d’applet 519 Alignement 440 Alignement, constantes 382 alt, attribut d’applet 521 Analyse classes 229 objets à l’exécution 232 piles 561 Anonymes Voir Tableaux ou Classes API documentation en ligne 66 JNLP 505 notes 64 Preferences 540 Appel constructeur 155 méthode 144, 186 par valeur/par référence 144
Livre Java.book Page 802 Mercredi, 13. février 2008 2:34 14
802
Index
append, méthode 69, 387 Applet afficher 516 exécuter 515 visualisateur 516 Applet, classe 518, 524, 526, 527, 535 AppletContext, classe 520, 531 AppletContext, méthode 535 Applets 14, 19, 37, 451, 513, 534 affichage par le navigateur 528 balises HTML 527 object 521 communication interapplets 520, 528 contexte 498, 528 conversion d’une application 517 fichiers JAR 498 passer des informations 521 taille 518 Applications convertir en applets 517 graphiques 35 multithread 13 Arborescence de package 166 des répertoires Java 27 Arbre 664 rouge-noir 664 Architecture Modèle-Vue-Contrôleur 366, 367 neutre de Java 11 archive, attribut d’applet 519 Archive, fichier 165 Arithmétiques, opérateurs 52 Array, classe 237 ArrayDeque, classe 671 arrayGrow, méthode 238 ArrayIndexOutOfBoundsExcept ion, exception 551 ArrayList 210, 213, 660 brut 216 classe 212 classe générique 609 et Vector, synchronisation 661 typée 216
ArrayList, classe 652 ArrayList, type 609 Arrays, classe 102, 103, 202, 204, 281 ArrayStoreException, exception 622 Arrêt, thread 719 Arrière-plan, couleur 313 Arrondi, erreurs 48 ASCII 317 Aspect et comportement Voir Look and feel assert, macro 567 Assertions 567 activer 568 désactiver 568 vérifications 567 Atomiques, opérations 731 AudioClip, classe 118 Augmenter la taille d’un tableau 237 Autoboxing 217, 218 compilateur 218 Autorisations sémaphores 775 Avertissement message 503 await, méthode 736, 776 AWT 289, 316 événements 359 modèle d’événement 327 AWTEvent, classe 359
B Bac à sable 502 code signé 503 Balises HTML 518 pour la documentation Voir javadoc Barre d’outils 420 composants 422 icône action 422 séparateur 422 verticale 422 Barrières 776 cycliques 776 BasicButtonUI, classe 371
BasicService, classe 512 BasicService, interface 507 before, méthode 120 Bibliothèque de routines 10 installation 26 Java 2D 305 classes 306 Java standard 159 Java, documentation 27 mathématique fdlibm 57 publique, code source 26 Swing Voir aussi Swing BigDecimal, classe 94, 96 BigInteger, classe 94, 96 Binaire code 731 recherche 696 Binaires, opérateurs 55 binarySearch, méthode 104, 281, 697 Bits, ensembles 703 BitSet, classe 703 Blocage queue 754 Blocs 78 code source 43 d’initialisation 154 d’instructions 79 Blocs synchronisés 743 Boîtes de dialogue A propos 462 affichage 463 avec aperçu 477 cadre propriétaire 461, 467 communes 483 composant accessoire 477 conception 427 Couleurs 485 création 461 de saisie 452 fermeture 463 Fichier 472 classes, constructeurs et méthodes 481 filtres 474 gestionnaires d’événements 461 méthodes 460
Livre Java.book Page 803 Mercredi, 13. février 2008 2:34 14
Index
modales/non modales 451, 462 personnalisées 461 échange de données 466 Mot de passe 466 Polices 427, 431 prédéfinies 452 messages 453 options 453 types d’options 453 types de messages 453 réutilisation 473 standard 452 taille 461 valeurs par défaut 466 vue de fichier 476 Booléen, opérateur 54 BorderFactory, classe 394, 397 BorderLayout classe 376 gestionnaire de mise en forme 374 Bordures à angles arrondis 395 composées 394 titre 394 types de 394 Boucles 82 déterminées 86 do/while 84 for 86, 87 for each 97 quitter les 92 while 82 Bouton ajout 330 associer une action 347 création 330 écouteur 484 icône 453 label 453 Boutons radio 390 dans menus 411 groupe 391 BoxLayout, gestionnaire de mise en forme 426 break, instruction 90 bridge, méthode 618 brighter, méthode 313
bulk, operation 691 Bulle d’aide 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 référence 148 chaînes 62 champs et méthodes statiques 141 classe string 63 classes imbriquées 264 constructeur 181 virtuel 226 déclaration et définition de variables 51 fonction membre statique 44 héritage 178 multiple 254 liste d’initialisation 152 méthodes 134 en ligne 190 modèle de vecteur 212 namespace 160 pointeurs 109, 119 this 154 tableaux 100 transtypage 192 using 160 Cadres affichage 300 ajout de composants 300 collections 681 contenu 300 coordonnées 296 création 291 icône 297 position 294 propriétaires 462, 467 taille 294
803
par défaut 296 Calendar, classe 120 constantes 121 Callable 764 Callback Voir Rappel Callbacks 261 classes internes anonymes 264 Caractères $, dans noms de variables et méthodes 271 $, de séparation de classes 269 ASCII 317 d’écho 383 mnémoniques 414 types de 48 Unicode 50, 317 Carte 673 Carte synchronisée 688 Cartes de hachage faibles 677 liées 678 case, mot clé 90 Cases à cocher 387 dans menus 411 cast, méthode 635 catch, clause 226 CD-ROM d’accompagnement 5 CENTER, constante 382 Centrage d’une chaîne 319 Chaîne, construire 69 Chaînée (liste), manipulation 658 Chaînes 60 affichage 317 centrage 319 concaténation 60 coordonnées 318 d’héritage 184 dimensions 318 en C++ 62 formatage 72 inaltérables 61 sous-chaînes 60 test d’égalité 62 Champs altérables, copie 136 anchor 429 commentaires javadoc 171 contenu des 232
Livre Java.book Page 804 Mercredi, 13. février 2008 2:34 14
804
Index
Champs (suite) d’instance 113 et variables locales 134 final 138 modification 135 de données 113, 228, 233 altérables 136 copie 255 initialisation 154 privés 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 fill 429 final 189 gridheight 429 gridwidth 429 initialisation 151 explicite 152 insets 429 privés 137, 233, 271 accès 180, 197 statiques 138, 142 initialisation 155 volatiles 745 charAt, méthode 65 Chargeur de classe 280 Checked exception Voir Exception vérifiée Chemin d’exécution 24 de classe Voir CLASSPATH Choix d’options 387 Class classe 224, 231, 241, 498, 635, 639 classe générique 634 objet 225 paramètre 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 accès protégé 197 adaptateurs 341, 361 agrégation 115 ajout dans un package 161 analyse 229 des caractéristiques 227 anonymes 271, 335, 343 bibliothèque Java 2D 306 C++ 44 caractère de séparation ($) 269 chargeur de 280 chemin d’accès Voir CLASSPATH chemin de 168 commentaires javadoc 169 conception 173 concrètes 307 et abstraites 192 ConsoleWindow 593 création à l’exécution 280 d’exception 554 de base Voir Superclasses définies par l’utilisateur 165 dépendance 115 dérivation 178 dérivées Voir Sous-classes diagrammes 115 encapsulation 178 enfant Voir Sous-classes énumération 221 enveloppes 281, 369 extension 113, 178 externes, référence 268 extraction des informations 228 final 189
génériques 210 ArrayList 609 définition 610 héritage 625 héritageVoir Héritage hiérarchie 184 imbriquées 278 en C++ 264 inaltérables 255 internes 264, 334 anonymes 264, 274, 334, 343 déclaration 277 état d’un objet 265 locales 271 portée 269 sécurité 269 statiques 276 syntaxe 265 Java 44 locales méthodes 273 visibilité 272 parent Voir Superclasses personnalisées 128 proxy 280 propriétés 284 relation entre classes 115 répertoires 165 réutilisation 116 sous-classes Voir Sous-classes statiques internes 276 superclasses Voir Superclasses Swing 292 Timer 261 ClassLoader, classe 570 CLASSPATH 31 Clause finally 558 throws 549 clé/valeur, paire 673 Clonage 210 d’objets 255 et copie 256 exceptions 259 superficiel 255 clone, méthode 209, 254, 255 Cloneable, interface 254, 257
Livre Java.book Page 805 Mercredi, 13. février 2008 2:34 14
Index
COBOL 177 Code binaire 731 de hash 661 des exemples 5 existant 619 fichiers bibliothèque 26 HTML 38 point 49 signé 503 unités 49 code, attribut d’applet 519 codebase, attribut d’applet 519 codePointAt, méthode 64 Collection 761 cadre 681 classée 654 Collection et Map, interface 681 concrète 651 emballage léger 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 d’objets 665 compareTo, méthode 63, 65, 248, 249, 252, 282, 665 Compilateur 12 installation 21 javac 28, 167 localisation de fichiers 167 Compilation erreurs 34 instructions 23 Component, classe 294, 313, 359, 382
Composants 301 accessoires 477 ajout dans un cadre 300 barre d’outils 422 coordonnées 296 d’interface graphique, caractéristiques 367 mise en forme 372 Swing 370, 477 Concaténation de chaînes 60 Conception de classes 173 Concordance de propriétés 536 Concrète, collection 651 Concrètes Voir Classes ou Méthodes ConcurrentModificationExceptio n, exception 656 Condition, objet 735, 736 Conditionnelles, instructions 79 Configuration chemin d’exécution 24 emplacement des fichiers 540 paires clé/valeur 536 préférences d’application 535 Conflits après effacement 624 Consignation 570 API 570 basique 571 exceptions 573 gestionnaires 576 hiérarchie 571 localisation des messages 575 modifier la configuration 574 niveaux 572 Console, classe 72 ConsoleHandler, classe 586 ConsoleWindow, classe 593 const, mot clé 52 Constantes 52 mathématiques 56 statiques 139 Constructeurs 117, 132 appel 155 d’un autre constructeur 153 bouton 330 généralités 133 par défaut 151
805
Construction d’une ellipse 308 d’une ligne 309 Constructor classe 227, 231, 635 classe générique 635 Container, classe 334, 374 contentPane, classe 374 Contenu d’un cadre 300 d’un champ 232 Contextes statiques 624 continue, instruction 93 Contrôleur de composant d’interface 367 Conventions du livre 4 Conversion classes 122 entiers 49 méthode printf 73 type flottant 58 numérique 57, 190 primitif 217 Voir aussi Transtypage Conversion, collections et tableaux 692 Coordonnées cadre/composants 296 d’une chaîne 318 d’une ellipse 308 événements de la souris 358 grille GridBagLayout 428 Java 2D 305 objet Graphics 302 Copie et clonage 256 intégrale 257 tableaux 99 variable 255 copyArea, méthode 323, 326 copyTo, méthode 99 Couleurs 312 d’arrière-plan 313 par défaut 314 personnalisées 313 sélection 483 système 314
Livre Java.book Page 806 Mercredi, 13. février 2008 2:34 14
806
Index
CountdownLatch 776 createBevelBorder, méthode 397 createCompoundBorder, méthode 394, 398 createCustomCursor, méthode 354, 359 createDialog, méthode 489 createEmptyBorder, méthode 397 createEtchedBorder, méthode 397 createLineBorder, méthode 397 createLoweredBevelBorder, méthode 397 createMatteBorder, méthode 397 createRaisedBevelBorder, méthode 397 createScreenCapture, méthode 599 createTitledBorder, méthode 394, 397 Création boîte de dialogue 461 bouton 330 curseur 402 panneau 302 système de menu 90 Crible d’Eratosthène 704 Curseurs 354 création 402 repères 403, 407 Cursor, classe 353 CyclicBarrier, classe 776
D darker, méthode 313 Date, classe 117 DateFormatSymbols, classe 128 Débogage assertions 567 techniques de mise au point 587 Décalage, opérateurs 56 Déclaration classe 154, 189, 277 abstraite 193 final 189 interne 277 locale 272
interface 249 matrice 105 méthode 134 publique 188 virtuelle 182 tableaux 97 variables 45 Décompiler un fichier 732 Décrémentation, opérateurs 54 Deep copy Voir Copie intégrale default, mot clé 90 DefaultButtonModel, classe 371 Définition méthodes 135 variables 51 Délégation d’événement 330 Délégué 240 Démon, Thread 725 Dépendance de classes 115 Dépréciées Voir Méthodes Deque 670 interface 670 Deque, classe 671 deriveFont, méthode 317 Dériver une classe 178 Désactiver une option de menu 416 Désallouer une ressource 558 Dessiner en Java 301 Destruction des objets 158 Diagrammes de classe 115 Dichotomique, recherche 282 Dictionary, classe 619 Différence entre les interfaces et les implémentations 644 dispose, méthode 158 do/while 84 doclet 173 docs, répertoire 27 Documentation API en ligne 66 commentaires Voir javadoc installation 26 Données échange 466 privées, accès 137 publiques 137 types de 45
double, type 53 Drapeaux 74 draw, méthode 305 drawImage, méthode 323, 325 drawRectangle, méthode 305 drawString, méthode 302, 322 Dump 605 Dynamique liaison Voir Liaison tableau 237
E EAST, constante 382 Eclipse 31 Ecouteur 328 bouton 484 case à cocher 388 d’événement 328 notification 328 de changement de propriété 346, 478 de plusieurs sources d’événement 345 menus 409 sans appel de méthode 337 Ecran, taille 297 Effacement conflits 624 type 617 type de retour 617 types génériques 636 variables de type 616 Elément plus grand d’un tableau 693 d’une liste chaînée 693 d’une liste de tableau 693 supprimer 649 Ellipse2D, classe 308 Ellipses, construction 308 else, mot clé 80 Emballages et vues 685 légers de collection 686 Empêcher l’héritage 189 Employee, classe 133 EmployeeTest, classe 129 Encapsulation 113, 173, 178
Livre Java.book Page 807 Mercredi, 13. février 2008 2:34 14
Index
endsWith, méthode 65 Enregistreur 576 Ensemble de bits 703 de propriétés 702 sous-ensemble 686 ensureCapacity, méthode 211 Entiers 46 Entrée de texte utilisateur 380 fichiers 76 lecture 70 utilisateur, boîte de dialogue 466 Entrées, énumération 674 Enumération 59, 701 classes 221 entrées 674 type 222 Enveloppe Voir Classes Enveloppes 217 Environnements de développement 28, 372 Epoch, point fixe 120 equals 199 equals, méthode 65, 104, 198, 284 d’Object 209 propriétés 200 equalsIgnoreCase, méthode 62, 65 Equité 735 Eratosthène, crible 704 Erreurs compilation 160, 181 localisation 34 recherche 30 traitement 548 Erreurs d’arrondi 48 Etat d’un objet 113, 265 d’un thread 724 Etendre une classe 113 Etiquettes 382 HTML 383 instructions 92 Evaluation optimisée 54 Evénements action 329
AWT 359, 360, 595 modèle 327 classes et méthodes 362 de bas niveau 361 de bouton radio 391 de case à cocher 388 de la souris 352 de liste combinée 399 délégation 330 écouteur sans appel de méthode 337 écouteurs de plusieurs sources 345 généralités 327 interaction 329 objets 328 sémantiques 361 source des 328 souris coordonnées 358 Event listener Voir Ecouteur d’événement 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 finally 558 ConcurrentModificationExcepti on 656 conseils pour leur utilisation 564 consignation 573 en C++ 561 enchaîner 557 EventTrace 595 généralités 548 gestionnaire 224, 226 getCause 558
807
hiérarchie 550 IllegalAccessException 233 IllegalStateException 649 interception 226 InterruptedException 709, 714 lancement 552, 553 look and feel 338 multiples 557 non récupérée 725 non vérifiée 226 NoSuchElementException 647 programmation générique 621 relancer une exception 557 RuntimeException 552 signalement au compilateur 551 try 226 UnsupportedOperationExceptio n 674, 688 vérifiées 224, 257 Exchanger 777 Exécutable, thread 721 Exécution bloc 80 de programmes dans une page Web 19, 37 identification de type 224 interrompre 92 programmée 772 réflexion 223 executor 774 Executors, classe 768, 769, 772 Exemples de programmes installation 26 Explicites Voir Paramètres Expressions génériques, traduction 617 extends, mot clé 178 Extension de classes 178 Externes Voir Objets ou classes Extraction des commentaires 172
F Factory Voir Méthodes FAQ 20 fdlibm, bibliothèque mathématique 57 Fenêtre, actualisation 302
Livre Java.book Page 808 Mercredi, 13. février 2008 2:34 14
808
Index
Fenêtres modales et non-modales 462 Fermeture boîte de dialogue 463 Java 158 programme 35 Fichiers archive 165 audio 526 boîtes de dialogue pour sélection 472 décompiler 732 entrée et sortie 76 image 526 JAR Voir JAR Fichiers journaux filtres 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 méthodes 476 fill, méthode 104 Filter, classe 587 Filtres 474 All files (Tous fichiers) 475 fichiers journaux 579 journaux 571 final champ d’instance 138 mot clé 52, 182, 189, 272, 273 finalize, méthode 158 finally, 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 mathématiques 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, fichiers journaux 579 Formes 2D 305 forName, méthode 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 d’événement, généralités 327 Gestionnaires consignation 576 d’événement, boîte de dialogue 461 d’exception 224
implémentation 226 non récupérée 725 d’invocation 280 de mise en forme 372, 420 BorderLayout 374 BoxLayout 426 FlowLayout 372 GridBagLayout 426, 427 personnalisés 446 Swing 426 enregistreur 576 par défaut 576 paramètres 577 get, méthode 122, 232 champs 126 getActionCommand, méthode 336, 392, 394 getActionMap, méthode 352 getApplet, méthode 535 getAppletContext, méthode 535 getAppletInfo, méthode 526 getApplets, méthode 535 getAscent, méthode 322 getAudioClip, méthode 527 getAvailableFontFamilyNames, méthode 315 getBaseline, méthode 440 getCause 558 getClass, méthode 209, 210, 224 getClickCount, méthode 352, 358 getCodeBase, méthode 527 getColor, méthode 488 getComponentType, méthode 238 getConstructors, méthode 228, 231 getDeclaredConstructors, méthode 228, 231 getDeclaredFields, méthode 228, 231 getDeclaredMethods, méthode 228, 231 getDeclaringClass, méthode 231 getDescent, méthode 322 getDescription, méthode 482, 483 getDocumentBase, méthode 527 getExceptionTypes, méthode 231 getFamily, méthode 321 getFields, méthode 228, 231
Livre Java.book Page 809 Mercredi, 13. février 2008 2:34 14
Index
getFirst, méthode 628 getFontMetrics, méthode 322 getFontName, méthode 321 getFontRenderContext, méthode 318, 322 getGregorianChange, méthode 127 getHeight, méthode 322 getIcon, méthode 483 getImage, méthode 299, 527 getInputMap, méthode 348, 352 getKeyStroke, méthode 347 getLeading, méthode 322 getLength, méthode 238 getLineMetrics, méthode 318 getLocalGraphicsEnvironment, méthode 316 getMethod, méthode 241 getMethods, méthode 228, 231 getModifiers, méthode 227, 232 getModifiersEx, méthode 359 getModifiersExText, méthode 359 getName, méthode 224, 225, 232, 241 getName, méthode de Font 321 getParameter, méthode 524 getParameterInfo, méthode 526 getParameterTypes, méthode 232 getPassword, méthode 384 getPath, méthode 474 getPredefinedCursor, méthode 353 getProxyClass, méthode 284 getScreenSize, méthode 299 getSelectedFile, méthode 474 getSelectedFiles, méthode 474 getSelectedItem, méthode 399, 401 getSelectedObjects, méthode 392 getSelection, méthode 392, 394 getSize, méthode 299 getSource, méthode 336 getStackTrace, méthode 561 getStringBounds, méthode 318 getTime, méthode 122 getType, méthode 227 getTypeDescription, méthode 483
getValue, méthode 345 getValue, méthode d’Action 351 getWidth, méthode 306, 307, 319 getX, méthode 352 getY, méthode 352 GMT 120 goto, mot réservé 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 d’aide 431 gestionnaire de mise en forme 426, 427 objet champs 428 GridLayout classe 373 constructeur 380 Groupe de boutons radio 391 Groupes de tâches 773 GroupLayout 436 GroupLayout, classe 444, 445 GUI 288, 327 composants caractéristiques 367 généralités 288
H Handler, classe 585 Hash code 661 collision 662 table 661
809
hashCode 202 hashCode, méthode 284 Hashtable, classe 700 height 297 height, attributs d’applets 518 Helper Voir Méthodes Héritage 115, 177 chaîne d’ 184 classes et sous-classes 192 Java 2D 306 JFrame 294 JPanel 294 classes génériques 625 empêcher 189 equals 199 généralités 178 hiérarchie 184 utilisation 244 Hiérarchie classe Shape 308 d’héritage 184 des opérateurs 58 événements AWT 359 Historique de Java 15 Holder, type 219 HTML 17 balises 518 boutons, étiquettes, options de menu 383 label 334
I Icône 346, 477 action 411 dans barre d’outils 422 boîte de dialogue 452 bouton 330, 453 cadre 297 menu 410 option de menu 345 vue de fichier 476 Idées fausses sur Java 17 if, instruction 79 if/else if 82 IllegalAccessException 233 IllegalStateException, exception 649
Livre Java.book Page 810 Mercredi, 13. février 2008 2:34 14
810
Index
ImageIcon classe 334 constructeur 334 ImageIO, classe 325 Images 323 affichage 324 en mosaïque 323 fractales 323 mise à l’échelle 326 Images et sons 526 Implémentation et interfaces 644 queue 644 implements, mot clé 249 Implicites Voir Paramètres import, instruction 159 Imports statiques 161 Inaltérables Voir Chaînes ou Classes Incrémentation, opérateurs 54 indexOf, méthode 65 Indice, tableau 96 Informations de configuration 540 Informations relatives à une classe 228 Initialisation blocs 154 champs de données 154 de texte 381 explicites 152 par défaut 151 statique 156 tableaux 98 variables 51 Inlining 189 InputEvent, classe 359 insertItemAt, méthode 399, 401 insertSeparator, méthode 410 Installation bibliothèque et documentation 26 compilateur et outils 21 exemples de programmes 26 instructions 23 messages 24 répertoire par défaut 23
instanceof, opérateur 191, 253 Instanciation 622 Instructions blocs 79 break 90 composées 80 conditionnelles 79 continue 93 d’installation et de compilation 23 étiquetées 92 goto 92 import 159 switch 90 Integer classe 219, 282 type 45 Interception d’exceptions 226 Interface 248 callbacks 261 Comparable 248, 665 d’itération 646 de balisage 257 de collection 643, 646 de marquage 257 déclaration 249 écouteur 328, 361 et implémentations différence 644 graphique activer/désactiver des options 416 boîte 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 entrée de texte 380 étiquettes 382 événements 327 listes d’options 398 menus 408 contextuels 412
mises en forme 372 sophistiquées 425 raccourcis clavier 414 séquence de tabulation 450 utilisateur Voir GUI zones de texte 384 graphique utilisateur 288 graphique Voir aussi Gestionnaires de mise en forme List 682 méthodes 248 pour les collections, Collection et Map 681 propriétés 253 publique 180 utilisateur graphique Voir GUI Internet et Java 14 Interpréteur 12 Interrompre thread 718 interrupted, méthode 720 InterruptedException, exception 709, 714, 718 Interruption du flux d’exécution 92 intValue, méthode 219 InvocationHandler, interface 280 invoke, méthode 241, 280 isAccessible, méthode 236 isEnabled, méthode 351 isInterrupted, méthode 720 isJavaIdentifierPart, méthode 50 isJavaIdentifierStart, méthode 50 isProxyClass, méthode 284 isSelected, méthode 388, 390, 412 isTraversable, méthode 476 ItemEvent, classe 361 ItemSelectable, interface 392 Itérateurs, Java et STL 648 Itération, interface 646 Iterator, méthode 646
J J++ 240 Jambage ascendant/descendant 318 JApplet, classe 514
Livre Java.book Page 811 Mercredi, 13. février 2008 2:34 14
Index
JAR 5 fichiers 26, 165, 166, 492 programme d’archivage 492 Java applications et applets 530 architecture neutre 11 bibliothèque de routines 10 et C# 18 et C++ 18 exécuter un programme dans une page Web 19 historique 15 idées fausses 17 installation des outils 21 Internet 14 livre blanc 8 machine virtuelle 15 modèle de pointeur 10 orienté objet 9 plug-in 513 portabilité 12 répertoires 27 sécurité 19 syntaxe 9 termes clés 8 Java 2D bibliothèque 305 types de coordonnées 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 réflexion 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.filechooser, package 475, 476 javax.swing.filechooser.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 hiérarchie d’héritage 294 JFrame.add, méthode 301 JLabel, classe 382, 383 JLayeredPane 300 JMenu classe 410 constructeur 410 JMenu.add, méthode 409 JMenuItem classe 411, 416, 417 constructeur 416 JNLP, Java Network Launch Protocol 499 API 505
811
Jokers 627 capturer 631 limites 628 sans limites 630 JOptionPane 263 JOptionPane, classe 452, 460 Journaux enregistrements 570 filtres 571 mise en forme 571 JPanel, classe 391 hiérarchie d’héritage 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 développement Java JDK Voir JDK
L Label bouton 453 texte 383 Voir aussi Etiquettes
Livre Java.book Page 812 Mercredi, 13. février 2008 2:34 14
812
Index
Langages de script 20 Java 53, 122, 250 appel de méthodes 144 constructeurs 117 mots clés 4 Pascal 112 procéduraux 9, 112 Visual Basic 327 lastIndexOf, méthode 65 layoutContainer, méthode 449 LayoutManager classe personnalisée 446 interface 449 méthodes 446 LayoutManager2, interface 446 Lecture des entrées 70 Lecture seule, champs 135 LEFT, constante 382 Légers, emballages de collection 686 length, méthode 65, 214 Lexicographique, ordre 665 Liaison dynamique 182, 186 statique 187 Libellés Voir Etiquettes ou Label Ligne construction 309 de base 318 de séparation, menus 410 Ligne de commande outils 28 paramètres 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 chaînée 652, 658 plus grand élément 693 pour une queue 644
de tableaux 660 plus grand élément 693 Listener interface Voir Interface écouteur Listes d’options 398 suppression/ajout d’élément 399 de tableaux 97 accès aux éléments 212 allocation 211 capacité 211 de tableaux génériques 210 Integer 217 listFiles, méthode 475 Localisation arguments 575 de fichiers par le compilateur 167 des classes dans les packages 160 des erreurs de compilation 34 messages de consignation 575 lock, méthode 750 Logger, classe 584 LogRecord, classe 586 Longueur des tableaux 99 Look and feel 289, 369, 452 Mac 337 Metal 289, 337, 338 modification 337, 338 Motif 289, 338 Synth 291 Windows 289, 337 LookAndFeelInfo, classe 341
M Machine virtuelle 15 architecture neutre 11 effacement de types génériques 636 programmation générique 615 main, méthode 44, 141 chargement 224 makeButton, méthode 335 Manager, classe 178 Manifeste 493
Manipulation, liste chaînée 658 Map 673 Map, interface pour les collections 681 Masse, opérations 691 Math, classe 56 constantes 139 Math.random, méthode 102 Mathématiques, fonctions et constantes 56 Matisse 426, 436 mise en page 440 Matrice 105 MAX_PRIORITY 724 Mécanisme de réflexion 227 Mélanger 694 menuCanceled, méthode 417 menuDeselected, méthode 417 MenuListener, classe 417 Menus 408 activer et désactiver des options 416 avec cases à cocher et boutons radio 411 contextuels 412 création 90, 408 déroulants, méthodes 413 écouteur 409 icône 410 imbriqués 417 ligne de séparation 410 Macintosh 409 options 410 raccourcis 414 sous-menus 408 supprimer des options 416 Windows 409 Messages à l’installation 24 avertissement 503 boîtes de dialogue 453 d’erreur 31 sur plusieurs lignes 454 Metal 289, 337 Method, classe 227, 231, 241, 640 Méthodes 44, 113 abstraites 193 accès aux données privées 137
Livre Java.book Page 813 Mercredi, 13. février 2008 2:34 14
Index
add 646 appel 186 par référence 144 par valeur 144 binarySearch 697 boîtes de dialogue 460 caractères $ dans nom 271 classe locale 273 commentaires javadoc 170 compareTo 665 concrètes 193 createScreenCapture 599 d’accès 121, 135 d’altération 121 d’une interface 248 de RectangularShape 308 déclaration 134 définition 135 dépréciées 120, 158, 293 en ligne 134 factory 141 final 189 génériques définition 612 traduction 617 hashCode 202 helper 334 interrupted 720 isInterrupted 720 Iterator 646 natives 140 nombre variable de paramètres 220 notify 748 paramètres 44, 144, 219 pointeurs 240 privées 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 méthode MIN_PRIORITY 724 minimumLayoutSize, méthode 449 Mise en forme d’interfaces introduction 372 sophistiquées 425 de l’affichage 72 personnalisée 446 sans gestionnaire 445 Mix-in 255 Modale/non modale Voir Boîtes de dialogue Modalité, fenêtres 462 Modèle d’événement AWT 327 de composant d’interface 367 de conception 366 Modèle-Vue-Contrôleur Voir Architecture Modificateurs de visibilité 198 Modification champ d’instance 135 look and feel 338 Modifier, classe 227 Moniteurs 745 Mort, verrou 747 Mosaïque 323 Motif 289, 338 Mots clés abstract 194 case 90 class 42 const 52 default 90 else 80 extends 178 final 52, 182, 189, 272, 273 implements 249 import 160 langage Java 4 package 160
813
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, méthode 352 mouseDragged, méthode 354 mouseEntered, méthode 355 MouseEvent, classe 358, 361, 414 mouseExited, méthode 355 MouseListener, interface 355 MouseMotionListener, interface 355 mouseMoved, méthode 354 mousePressed, méthode 352 mouseReleased, méthode 352 MouseWheelEvent, classe 361 multiply, méthode 94 Multitâche 707 Multithread 13, 707 Mutator Voir Méthodes d’altération
N name, attribut d’applet 520 Natives Voir Méthodes NavigableMap, classe 691 NavigableSet, classe 670, 691 new, opérateur 133 newCondition, méthode 736 newInstance, méthode 225, 227, 280 newProxyInstance, méthode 280, 284 nextDouble, méthode 70 nextInt, méthode 70 nextLine, méthode 70 Niveaux de consignation 572 Noms de packages 158 de paramètres 153 de variables et méthodes ($) 271
Livre Java.book Page 814 Mercredi, 13. février 2008 2:34 14
814
Index
NORM_PRIORITY 724 NORTH, constante 382 NoSuchElementException 647 Notes API 64 Notification aux écouteurs d’événement 328 Notify, méthode 748 Nouveau, thread 721 null, valeur 119 Number, classe 217 NumberFormat, classe 220
O object attribut d’applet 519 balise HTML 521 Object, classe 198, 209, 210, 280 Objets 113 Action, actions prédéfinies 346 analyse à l’exécution 232 champs d’instance 113 Class 225 clonage 255 Color 313 comparaison 665 construction 117, 150 de condition 735, 736 destruction 158 et variables objet 118 état 265 événement 328 externes 266 File 473 Graphics 301 KeyStroke, associer aux actions 347 Modèle-Vue-Contrôleur, interaction 370 non-objets 198 sérialisés 519 variables 117 verrou 732 Opérateurs arithmétiques 52 binaires 55 booléens 54 d’incrémentation 54
de décalage 56 de décrémentation 54 hiérarchie 58 instanceof 191, 253 new 133 relationnels 54 surcharge 95 Opérations atomiques 731 de masse 691 optionnelles 689 Optionnelles, opérations 689 Ordre lexicographique 665 org.omg.CORBA, package 219 Orientée objet Voir Programmation outer 266 Outils de ligne de commande 28
P Package accès 166 ajout d’une classe 161 arborescence 166 arithmétique 45 classes publiques/non publiques 167 commentaires javadoc 172 courant 167 d’extension 292 de réflexion 240 disponible 67 hiérarchie 159 import 159 localisation des classes 160 look and feel 337 nom 158, 159, 171 par défaut 161 plombage 165 préfixe 159 répertoires 161 sealing Voir Package, plombage sécurité 165 sous-packages 159 standard 159 utilisation 159 verrouiller 498
visibilité 164 Voir aussi nom du package Page Web pour exécuter des programmes Java 19 paintComponent, méthode 302, 304, 360, 453 PaintEvent, classe 360 Paire, clé/valeur 536, 673 Panneaux ajout d’un bouton 330 création 302 Paquets Voir Package ParameterizedType, classe 640 Paramètres de ligne de commande 101 de type 216, 608 de type primitif 147 des méthodes 44, 144, 219 explicites 134 explicites et implicites 134 implicites 134, 281 nom des 153 nombre variable 220 parseInt, méthode 220 Partager un accès 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 Planification coopérative 721 Planification préemptive 721 play, méthode d’URL 527 Plug-in Java, panneau de contrôle 501 Plus grand élément d’un tableau 693 d’une liste chaînée 693 d’une liste de tableau 693 Poids, champs 428 Point2D, classe 307
Livre Java.book Page 815 Mercredi, 13. février 2008 2:34 14
Index
Pointeurs C++ 119 de méthodes 240 délégué 240 Java 10 Points de code 49, 63 caractères complémentaires 49 Polices Voir Fontes Polymorphisme 185, 189, 245 Pool de threads 768, 769 Portabilité de Java 12, 53 Portée d’un bloc 78 de classe interne 269 Positionner un cadre 294 pow, méthode 56, 140 Preferences API 540 classe 545 référentiel 541 preferredLayoutSize, méthode 449 Primitif Voir Types Principe de substitution 185 print, méthode 44 printf conversions 73 date et heure 74 drapeaux 74 méthode 72 println, méthode 44, 282 printStackTrace, méthode 227 PrintStream, classe 206 PrintWriter, classe 78 Priorité, queues 671 Priorités d’un thread 724 private, mot clé 132, 138 Privé(e)s Voir Méthodes, Classes, Champs ou Données Processus et thread 707 Programmation événementielle 327 orientée objet 9, 112 encapsulation Voir Encapsulation héritage Voir Héritage polymorphisme Voir Polymorphisme
Programmation générique définition 608 et transtypage 608 exceptions 621 machine virtuelle 615 tableaux 622 Programmes exécuter dans une page Web 19 terminer 341 Properties, classe 539 Propriétés d’accès, interface ButtonModel 371 d’un thread 724 des classes proxy 284 des interfaces 253 ensembles 702 génériques 254 méthode equals 200 protected, mot clé 197, 244 Protégé, accès aux classes 197 Proxy 247, 280 Proxy, classe 280 méthodes 285 public, mot clé 42, 132, 257 putValue, méthode 345, 351
Q Queue 670 de blocage 754 de priorité 671 implémentations 644 liste chaînée 644 synchrone 777 tableau circulaire 644 Queue, classe 670 QuickSort 102 Quitter un programme 341 une boucle 92
R Raccourcis clavier 414 raiseSalary, méthode 134 Ramasse-miettes 119, 158, 212 Rappel, fonctions 243
815
Recherche d’erreurs 30 dichotomique 282 Recherche binaire 696 Rectangle englobant 308, 319 Rectangle2D, classe 306 RectangularShape, classe 308 ReentrantLock, classe 732 Référence externe, syntaxe 268 Réflecteur 223 Réflexion 177, 232, 280 bibliothèque 223 créer un tableau générique 237 Règle du thread unique 785, 795 Relationnels, opérateurs 54 Relations entre les classes 115 remove, méthode 410 removeAllItems, méthode 399, 401 removeItem, méthode 399, 401 removeItemAt, méthode 399 removeLayoutComponent, méthode 449 Remplissage, tableaux 213 repaint, méthode 302 Répertoires classes 165 de compilation 129 de Java 27 de travail 473 installation par défaut 23 javadoc 169 package 161 sélection par l’utilisateur 474 replace, méthode 65 Résolution de surcharge 150 Ressources 495 nom relatif 496 Retours covariants 187 Réutilisation boîtes de dialogue 473 classes 116 revalidate, méthode 381 RIGHT, constante 382 Robot, classe 602 Rouge-noir, arbre 664 run, méthode 714
Livre Java.book Page 816 Mercredi, 13. février 2008 2:34 14
816
Index
RuntimeException, classe 563 RuntimeException, exception 552
S Saisie des données 70 Scanner, classe 70, 71, 78 SDK 5 Sécurité 10, 19, 197, 233, 280 classes internes 269 héritage 244 Java Web Start 505 packages 165 Sémaphores, autorisations 775 Séparé, thread 713 Séquence de tabulation 450 Sérialisation 519 ServiceManager, classe 512 ServletException, exception 557 set, méthode 122, 213 setAccelerator, méthode 415, 416 setAccessible, méthode 233, 236 setAction, méthode 410 setActionCommand, méthode 392, 394 setBackground, méthode 313, 315 setBorder, méthode 398 setBounds, méthode 294, 295, 299 setColor, méthode 313, 315, 488 setColumns, méthode 381, 384, 387 setCurrentDirectory, méthode 473 setCursor, méthode 359 setDefaultCloseOperation, méthode 463 setEchoChar, méthode 384 setEditable, méthode 380, 399, 401 setEnabled, méthode 351, 416 setFileFilter, méthode 475 setFileSelectionMode, méthode 474 setFileView, méthode 476 setFirst, méthode 628 setFont, méthode 322, 381
setForeground, méthode 315 setFormatter, méthode 579 setGregorianChange, méthode 127 setHorizontalTextPosition, méthode 411 setIcon, méthode 383 setIconImage, méthode 294, 299 setJMenuBar, méthode 408 setLabelTable, méthode 403 setLayout, méthode 374 setLineWrap, méthode 387 setLocation, méthode 294 setMnemonic, méthode 415, 416 setMultiSelectionEnabled, méthode 474 setNextFocusableComponent, méthode 451 setPaintLabels, méthode 404 setPaintTicks, méthode 404 setRect, méthode 307 setResizable, méthode 294, 299 setRows, méthode 384, 387 Sets de hachage liés 678 setSelected, méthode 388, 390, 412 setSelectedFile, méthode 474, 481 setSnapToTicks, méthode 408 setTabSize, méthode 387 setText, méthode 380, 383 setTime, méthode 122 setTitle, méthode 294, 299 setToolTipText, méthode 422 setUser, méthode 466 setVisible(true) 466 setVisible, méthode 298, 466 setWrapStyleWord, méthode 387 Shallow Voir Clonage superficiel Shape classe 308 interface 305 show, méthode 293, 413 showConfirmMessage, méthode 453 showDocument, méthode 535 showInputDialog, méthode 453 showMessageDialog, méthode 453
showOpenDialog, méthode 473, 474 showOptionDialog, méthode 453 showSaveDialog, méthode 473, 474 showStatus, méthode 535 shutdown, méthode 769 signal, méthode 737 signalAll, méthode 736 Signature d’une méthode 150, 187 du code 503 Sites Web Voir Web, adresses size, méthode 211, 212 SocketHandler 576 SoftBevelBorder classe 395, 398 constructeur 398 Solaris 24 sort, méthode 102, 104, 249 Sortie, fichiers 76 Source des événements 328 Souris événements 352 nombre de clics 353 Sous-chaînes 60 Sous-classes concrètes 306 méthodes 179, 187, 190 symétrie et égalité 201 Sous-ensembles 686 sqrt, méthode 56 src, répertoire 27 Stack, classe 702 StackTraceElement, classe 563 Standard Template Library Voir STL startsWith, méthode 66 static, mot clé 141, 155 Statiques Voir Champs, Méthodes, Constantes ou Variables STL 644 stop, méthode 752 strictfp, mot clé 53 StrictMath, classe 57 String, classe 60, 62, 65 StringBuffer, classe 69
Livre Java.book Page 817 Mercredi, 13. février 2008 2:34 14
Index
StringBuilder, classe 69 Structure, collections 681 subList, méthode 686 Substitution, principe de 245 substring, méthode 63, 66, 687 super, mot clé 180 super.paintComponent, méthode 303 Superclasse 178, 185, 233 abstraite 193 méthodes 179 Object 198 Suppression éléments 649 de menu 416 espaces en-tête/fin de chaîne 381 options dans une liste 399 partie décimale d’un réel 190 Surcharge de méthodes 150 résolution de 150 suspend, méthode 752 swap, méthode 146 swapHelper, méthode générique 631 Swing 288, 289, 338, 426 bibliothèque 365 composants 477 package 345 séquence de tabulation 450 swing.defaultlaf, propriété 337 SwingConstants, interface 254, 382 SwingUtilities, classe 472 SwingUtilities.updateComponent TreeUI, méthode 338 SwingWorker, classe 794 switch, instruction 90 Synchronisation blocs 743 carte 688 communication des threads sans synchronisation 727 des accès 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 méthodes 44 référence externe 268 Synth 291 System, classe 72, 104, 540 System.exit, méthode 341 System.out, constante 139 System.runFinalizersOnExit, méthode 158 SystemColor, classe 314
T Table de hash 661 Tableau circulaire, pour une queue 644 liste 660 plus grand élément 693 Tableaux 96 anonymes 99 autoboxing 218 C++ 100 copie 99 déclaration 97 dynamiques 237 génériques 237 initialisation 98 irréguliers 108 liste de Voir Listes longueur 99 multidimensionnels 105 paramètres variables 221 programmation générique 622 remplacement d’élément 213 remplissage 213 taille 210 augmenter 237 transtypage 237 tri 102, 250 Tables de méthodes 188 Tabulation, séquence 450
Tâches groupe 773 longues 784 Taille boîtes de dialogue 461 cadres 294 par défaut 296 de l’écran 297 fonte 317 tableaux 210 augmenter 237 Tampon Exchanger 777 Temporisation 750 Termes clés Java 8 Terminé, thread 722 Test d’égalité des chaînes 62 Texte affichage 302 entrée 380 mise en forme 315 this mot clé 134, 153, 181, 268 pointeur C++ 154 Thread 707 arrêt 719 barrières 776 bloqué 722 collections 760 communication sans synchronisation 727 démon 725 en attente 722 et processus 707 états 724 exécutable 721 interrompre 718 multithread 707 nouveau 721 partager un accès 727 pool 768, 769 priorités 724 propriétés 724 séparé 713 synchronisation 727 synchroniser des accès 731 terminé 722 travailleur 789 unique 785, 795
817
Livre Java.book Page 818 Mercredi, 13. février 2008 2:34 14
818
Index
ThreadDeath 722 Throwable, classe 227, 554, 563 throws, clause 549 Timer, classe 261, 263 toArray, méthode 213 toBack, méthode 299 toFront, méthode 299 toLowerCase, méthode 66 Toolkit 263 Toolkit, classe 299, 300, 354, 359 toString, méthode 117, 204, 209, 219, 284, 453 générique 233 Touches associer une action 347 désactiver 349 raccourcis clavier 415 toUpperCase, méthode 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, méthode 66, 381 trimToSize, méthode 211, 212 TrueType 317 try, exception 226 Type brut 615 caractère 48 de données 45 de retour covariants 618 effacer 617 de variables 50 double 53 effacer 617, 624 entiers 46 énuméré 59, 222 générique 248 instancier 610, 622
holder 219 identification à l’exécution 224 interface 636 joker 627 numériques, conversion 57 paramètres 608 primitifs 147, 620 primitifs, conversion 217 virgule flottante 47 TypeVariable, classe 640 Typographie 318
U UIManager, classe 340 UIManager.setLookAndFeel, méthode 338 UML (Unified Modeling Language) 115 Unchecked exceptions Voir Exceptions non vérifiées Unicode 50, 317 Unités de code 49, 63 caractères complémentaires 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, méthode 94, 220 Variables 50 caractères $ dans nom 271 copie 255 de type 610 effacer 616 déclaration 45 final 273 homonymes 79 initialisation 51 limites 613
locales 133, 151 accès 273 et champs d’instance 134 final 273 objet 117, 119 portée 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 flottante 53 types 47 Visibilité 198 classes locales 272 dans un package 164 méthodes 188, 249 verrouiller les packages 498 Visualisateur, applet 516 void, mot clé 44 Volatiles, champs 745 Vues de composant d’interface 367 de fichier 476 non-modifiables 687 sous contrôle 689 synchronisées 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 l’ouvrage 2
Livre Java.book Page 819 Mercredi, 13. février 2008 2:34 14
Index
while, instruction 82 width 297 attribut d’applet 518 WildcardType>, classe 640 Window, classe 293, 294, 299 Window.show, méthode 293 WindowAdapter, classe 342 WindowClosing événement 415 méthode 341, 342
WindowEvent classe 344, 361 événement 341 WindowListener classe 344 interface 341 Windows 24, 294 gestion de mise en forme 425 nmake 131 WindowStateListener, classe 344
819
Wirth, Niklaus 11, 112
X X11 301 XML 17
Z Zones de texte 380, 384 retour automatique à la ligne 387
Entièrement mis à jour pour la nouvelle plateforme Java Standard Edition 6, le désormais classique Au cœur de Java est l’ouvrage de référence en programmation Java. À la fois complet et concret, il traite avec précision tous les éléments importants du langage et de la bibliothèque Java. Les notions abordées sont illustrées de programmes de démonstration, aisément maîtrisables et adaptés aux situations concrètes de programmation (gestion des clics de bouton, création de boîtes de dialogue, calculs complexes, affichage d’images en mosaïque, pause et reprise d’animations, etc.). Ils constitueront un excellent point de départ pour tous vos développements. Ce premier volume présente les nouveautés de Java SE 6 (compatibilités, améliorations graphiques, nouvelles classes, développement des Web Services…) et est entièrement consacré aux notions fondamentales du langage, ainsi qu’aux bases de la programmation d’interfaces utilisateur. Il aborde notamment en détail : • les bases de la programmation orientée objet • les structures fondamentales de Java • la programmation générique • les interfaces et les classes internes • le cadre des collections • le mécanisme de réflexion de Java et de proxy • la simultanéité • le modèle d’écouteur d’événement • la conception d’interfaces graphiques (boîte à outils Swing) • la gestion des exceptions L’ouvrage intéressera à la fois les étudiants en programmation Java et les développeurs professionnels en quête d’un support didactique et pratique. Retrouvez tous les codes sources des exemples de l’ouvrage sur le site www.pearsoneducation.fr.
À propos des auteurs...
Cay Horstmann est professeur d’informatique à l’université d’État de San José (Californie). Il est également consultant en développement Java, C++ et Internet. Grand spécialiste de Java, il est bien connu des professionnels pour ses fréquentes interventions comme conférencier. 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é à l’université du Connecticut. Il est le co-fondateur des éditions Apress, spécialisées en programmation. Le traitement du code XML, la programmation réseau, les bases de données, l’internationalisation, la sécurité et la création d’interfaces utilisateur avancées font l’objet d’un second volume : (Au cœur de Java, 8e édition, Volume 2, Fonctions avancées, Pearson Education, 2008). Public : étudiants en informatique et développeurs 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 Tél. : 01 72 74 90 00 Fax : 01 42 05 22 17 www.pearson.fr
Image de couverture : © Getty Images/Photodisc