Anda di halaman 1dari 1070

1, rue Thnard 75005 Paris Fax : 01 44 41 46 00 http://www.oemweb.com Pour toute question technique concernant ce livre : http://www.volga.fr/ devjava213@volga.

fr (consulter pralablement les instructions page xxxviii)

OEM nest li aucun constructeur. Ce livre mentionne des noms de produits qui peuvent tre des marques dposes ; toutes ces marques sont reconnues. Javatm et HotJavatm sont des marques de Sun Microsystems, Inc. (http://java.sun.com). Winzip est une marque dpose de Nico Mak Computing, Inc. Winzip est distribu en France exclusivement par AB SOFT, Parc Burospace 14, 91572 Bievres cedex. Tel 01 69 33 70 00 Fax 01 69 33 70 10 (http://www.absoft.fr). UltraEdit est une marque de IDM Computer Solutions, Inc. (http://www.idmcomp.com). Nos auteurs et nous-mmes apportons le plus grand soin la ralisation et la production de nos livres, pour vous proposer une information de haut niveau, aussi complte et fiable que possible. Pour autant, OEM ne peut assumer de responsabilit ni pour lutilisation de ce livre, ni pour les contrefaons de brevets ou atteintes aux droits de tierces personnes qui pourraient rsulter de cette utilisation. LE PHOTOCOPILLAGE TUE LE LIVRE Le code de la proprit intellectuelle du 1er juillet 1992 interdit expressment la photocopie usage collectif sans lautorisation des ayants droits. En application de la loi du 11 mars 1957, il est interdit de reproduire tout ou partie du prsent livre, et ce sur quelque support que ce soit, sans lautorisation de lditeur. OEM 2000 ISBN 2-7464-0204-1

Pierre-Yves Saumont Antoine Mirecourt

Dans la collection La Rfrence:

Le Dveloppeur JAVA 2 Mise en oeuvre et solutions - Les applets Windows 98 Internet et Internet Explorer 5 Le nouveau guide technique et pratique du PC Office 2000 Word 2000 Dreamweaver 3 Access 2000 et VBA Le guide du dveloppeur Excel 2000 Flash 4 GoLive 4.0 Mac & PC Le guide de l'utilisateur Windows Programmation avec Java et WFC Excel 2000 et VBA Le guide du dveloppeur XML Le guide de l'utilisateur AutoCAD 2000 Le guide de l'utilisateur Le Newton Nouveau dictionnaire des tlcommunications, de l'Informatique et de l'Internet Paint Shop Pro 6 Windows 2000 Professionnel Le guide de l'utilisateur Active Server Pages 3.0 Le guide du dveloppeur Le guide officiel des imprimantes Achat Utilisation Entretien Red Hat Linux Le guide de l'utilisateur

Introduction Pourquoi Java ? Qu'est-ce que Java ? Java est un langage orient objets Java est extensible l'infini Java est un langage haute scurit Java est un langage simple apprendre Java est un langage compil Les compilateurs natifs Les compilateurs de bytecode Les compilateurs JIT La technologie HotSpot Le premier langage du troisime millnaire ? Voir Java luvre

xxiii xxiii xxix xxix xxxii xxxiii xxxiv xxxvi xxxvi xxxvii xxxviii xxxix xl xli

VI

LE DVELOPPEUR JAVA 2
Support technique Aller plus loin Chapitre 1 : Installation de Java xli xliv 1

Configuration ncessaire 3 Ordinateur 3 Systme 3 Mmoire 4 Disque 4 Installation du J2SDK 4 Ce que contient le J2SDK 7 Configuration de l'environnement 14 Le chemin d'accs aux programmes excutables 15 Le chemin d'accs aux classes Java 17 Amliorer le confort d'utilisation 20 Le Java 2 Runtime Environment 22 Installation du J2RE Windows 26 Installation de la documentation en ligne 27 Un navigateur HTML compatible Java 29 HotJava 30 Netscape Navigator 30 Internet Explorer 32 Un diteur pour la programmation 33 Ce que doit offrir un diteur 34 UltraEdit 34 Quelques petits fichiers batch qui vous simplifieront la vie 35 Un environnement de dveloppement 37 Avantages 37 Inconvnients 39 Quel environnement ? 39 Forte for Java 40 O trouver les informations ? 41 Le site Web de Sun 41

SOMMAIRE
Le JDC Les autres sites Web ddis Java Le site du Dveloppeur Java 2 Les sites consacrs au portage du J2SDK Les Newsgroups

VII

42 43 43 44 45

Chapitre 2 : Votre premier programme Premire version : un programme minimal Analyse du premier programme Premire applet Analyse de la premire applet Une faon plus simple d'excuter les applets Rsum Exercice

47 48 52 63 65 71 72 73

Chapitre 3 : Les objets Java Tout est objet Les classes Pertinence du modle Les instances de classes Les membres de classes Les membres statiques Les constructeurs Cration d'un objet La destruction des objets : le garbage collector Comment retenir les objets : les handles Cration des handles Modifier l'affectation d'un handle Rsum Exercice

75 78 78 80 83 84 85 86 89 94 95 97 97 99 100

VIII

LE DVELOPPEUR JAVA 2
Chapitre 4 : Les primitives et les handles Les primitives Utiliser les primitives Valeurs par dfaut des primitives Diffrences entre les objets et les primitives Les valeurs littrales Le casting des primitives Le casting explicite Casting d'une valeur entire en valeur flottante Casting d'une valeur flottante en valeur entire Formation des identificateurs Porte des identificateurs Porte des identificateurs de mthodes Les objets n'ont pas de porte Les chanes de caractres Constantes Utilisation de final avec des objets Accessibilit Retour sur les variables statiques Masquage des identificateurs de type static Java et les pointeurs Exercices Premire question (page 112) Deuxime question (page 138) Rsum Chapitre 5 : Crez vos propres classes Tout est objet (bis) L'hritage Les constructeurs Rfrence la classe parente 101 103 105 109 111 117 119 120 121 122 122 125 130 131 132 136 137 137 137 145 148 149 149 150 151 153 154 155 158 161

SOMMAIRE
La redfinition des mthodes Surcharger les mthodes Signature d'une mthode Optimisation Surcharger les constructeurs Les constructeurs par dfaut Les initialiseurs Les initialiseurs de variables d'instances Les initialiseurs d'instances Ordre d'initialisation Mthodes : les valeurs de retour Surcharger une mthode avec une mthode de type diffrent Distinction des mthodes surcharges par le type uniquement Le retour Rsum Exercice

IX

161 164 165 167 171 174 182 183 184 186 188 192 194 196 198 199

Chapitre 6 : Les oprateurs Java et les autres langages L'oprateur d'affectation Raccourci Les oprateurs arithmtiques deux oprandes Les oprateurs deux oprandes et le sur-casting automatique Les raccourcis Les oprateurs un oprande Les oprateurs relationnels Les oprateurs logiques Les oprateurs d'arithmtique binaire

203 204 205 206 207 208 211 213 216 225 229

LE DVELOPPEUR JAVA 2
Consquences du sur-casting automatique sur l'extension de zro Utilisation des oprateurs d'arithmtique binaire avec des valeurs logiques Utilisation de masques binaires L'oprateur trois oprandes ?: Les oprateurs de casting Les oprateurs de casting et les valeurs littrales L'oprateur new L'oprateur instanceof Priorit des oprateurs Rsum Chapitre 7 : Les structures de contrle La squence Le branchement par appel de mthode L'instruction de branchement return L'instruction conditionnelle if L'instruction conditionnelle else Les instructions conditionnelles et les oprateurs ++ et -Les instructions conditionnelles imbriques La boucle for L'initialisation Le test L'incrmentation Le bloc d'instructions Modification des indices l'intrieur de la boucle Imbrication des boucles for Type des indices Porte des indices Sortie d'une boucle par return Branchement au moyen des instructions break et continue

236 240 241 243 244 246 246 246 247 252 253 254 254 257 257 260 262 262 267 267 268 270 271 272 272 273 275 278 279

SOMMAIRE
Utilisation de break et continue avec des tiquettes L'instruction while L'instruction switch L'instruction synchronized Rsum Chapitre 8 : Laccessibilit Les packages (O) Chemins d'accs et packages L'instruction package Placement automatique des fichiers .class dans les rpertoires correspondant aux packages Excution du programme Chemin d'accs par dfaut pour les packages L'instruction import Packages accessibles par dfaut Les fichiers .jar Cration de vos propres fichiers .jar ou .zip Comment nommer vos packages ? Ce qui peut tre fait (Quoi) static Les variables static Les mthodes static Initialisation des variables static Les initialiseurs statiques final Les variables final Les variables final non initialises Les mthodes final Les classes final synchronized native transient

XI

281 282 288 295 296 297 301 305 305 308 309 310 311 315 316 317 318 319 320 320 323 324 325 327 327 328 329 331 331 333 333

XII

LE DVELOPPEUR JAVA 2
volatile abstract Les interfaces Qui peut le faire (Qui) public protected package private Autorisations d'accs aux constructeurs Rsum Chapitre 9 : Le polymorphisme Le sur-casting des objets Retour sur l'initialisation Le sur-casting Le sur-casting explicite Le sur-casting implicite Le sous-casting Le late binding Les interfaces Utiliser les interfaces pour grer des constantes Un embryon d'hritage multiple Le polymorphisme et les interfaces Pas de vrai hritage multiple Quand utiliser les interfaces ? Hritage et composition Rsum Chapitre 10 : Les tableaux et les collections Les tableaux Dclaration 333 334 336 336 337 337 338 338 339 345 347 349 350 352 357 358 359 361 367 368 370 371 375 378 381 385 387 388 388

SOMMAIRE
Initialisation Initialisation automatique Les tableaux littraux Les tableaux de primitives sont des objets Le sur-casting implicite des tableaux Les tableaux d'objets sont des tableaux de handles La taille des tableaux Les tableaux multidimensionnels Les tableaux et le passage d'arguments Copie de tableaux Les vecteurs Le type Stack Le type BitSet Les tables (Map) Les tables ordonnes (SortedMap) Le type Hashtable Les collections Les listes (List) Les ensembles (Set) Les ensembles ordonns (SortedSet) Les itrateurs Les itrateurs de listes Les comparateurs Les mthodes de la classe Collections Exemple : utilisation de la mthode sort() Rsum Chapitre 11 : Les objets meurent aussi Certains objets deviennent inaccessibles Que deviennent les objets inaccessibles ? Le garbage collector Principe du garbage collector

XIII

389 390 391 393 393 398 400 404 406 409 413 417 418 419 421 421 422 424 426 427 427 429 430 439 441 445 447 447 452 454 454

XIV

LE DVELOPPEUR JAVA 2
Optimiser le travail du garbage collector Les finaliseurs Contrler le travail du garbage collector Rfrences et accessibilit Les rfrences faibles SoftReference WeakReference PhantomReference Les queues Exemple d'utilisation de rfrences faibles Autres formes de contrle du garbage collector La finalisation et l'hritage La finalisation et le traitement d'erreur Contrler le garbage collector l'aide des options de l'interprteur Rsum Chapitre 12 : Les classes internes Les classes imbriques Les classes membres Instances externes anonymes Classes membres et hritage Remarque concernant les classes membres Les classes locales Les handles des instances de classes locales Les classes anonymes Comment sont nommes les classes anonymes Rsum Chapitre 13 : Les exceptions Stratgies de traitement des erreurs 456 458 463 467 469 471 471 472 472 473 477 484 486 486 487 489 489 497 506 508 511 515 517 522 525 527 529 530

SOMMAIRE
Signaler et stopper Corriger et ressayer Signaler et ressayer La stratgie de Java Les deux types d'erreurs de Java Les exceptions Attraper les exceptions Dans quelle direction sont lances les exceptions ? Manipuler les exceptions Modification de l'origine d'une exception Crer ses propres exceptions La clause finally Organisation des handlers d'exceptions Pour un vritable traitement des erreurs D'autres objets jetables Les exceptions dans les constructeurs Exceptions et hritage Rsum Chapitre 14 : Les entres/sorties Principe des entres/sorties Les streams de donnes binaires Les streams d'entre Streams de communication Streams de traitement Les streams de sortie Streams de communication Streams de traitement Les streams de caractres Les streams d'entre Streams de communication Streams de traitement

XV

531 531 531 531 532 534 535 537 538 540 549 554 558 560 562 562 562 563 565 566 567 567 567 567 568 569 569 570 571 571 571

XVI

LE DVELOPPEUR JAVA 2
Les streams de sortie Streams de communication Streams de traitement Les streams de communication Lecture et criture d'un fichier Les streams de traitement Exemple de traitement : utilisation d'un tampon Exemple de traitement : conversion des fins de lignes Compression de donnes Dcompression de donnes La srialisation Les fichiers accs direct Rsum Chapitre 15 : Le passage des paramtres Passage des paramtres par valeur Passage des paramtres par rfrence Passer les objets par valeur Le clonage des objets Clonage de surface et clonage en profondeur Clonage en profondeur d'un objet de type inconnu Clonabilit et hritage Interdire le clonage d'une classe drive Une alternative au clonage : la srialisation Une autre alternative au clonage Rsum Chapitre 16 : Excuter plusieurs processus simultanment Qu'est-ce qu'un processus ? Avertissement Comment fonctionnent les threads 572 572 573 573 575 576 582 583 584 590 591 594 599 601 601 602 605 606 609 615 616 619 620 625 627 629 630 630 631

SOMMAIRE
Principes de synchronisation Les oprations atomiques Granularit de la synchronisation Atomicit apparente Crer explicitement un thread Excuter plusieurs threads simultanment Caractristiques des threads Contrler les threads Utilisation des groupes pour rfrencer les threads Grer la rpartition du temps entre les threads La priorit La synchronisation Problmes de synchronisation Mise en uvre d'un dmon Communication entre les threads : wait et notifyAll Rsum Exercice Chapitre 17 : RTTI et Rflexion Le RTTI ou comment Java vrifie les sous-castings explicites Connatre la classe d'un objet Instancier une classe l'aide de son objet Class Connatre la classe exacte d'un objet Utiliser la rflexion pour connatre le contenu d'une classe Utilit de la rflexion Utiliser la rflexion pour manipuler une classe interne Utiliser la rflexion pour crer des instances Conclusion : quand utiliser la rflexion ? Chapitre 18 : Fentres et boutons Les composants lourds et les composants lgers

XVII

637 637 640 641 641 643 649 650 653 659 662 664 672 682 685 689 690 693 693 696 700 701 702 702 708 710 710 713 714

XVIII

LE DVELOPPEUR JAVA 2
Les composants lourds Les composants lgers Le look & feel Les fentres Hirarchie des fentres Java Structure d'une fentre Les layout managers Crer une application fentre Quel vnement intercepter ? Intercepter les vnements de fentre Les vnements et les listeners Utilisation des composants Utilisation des layout managers Philosophie des layout managers Bien utiliser les layout managers Utilisation du FlowLayout Utilisation d'un layout plusieurs niveaux Utilisation du GridBagLayout Rendre l'interface rellement indpendante du systme Affichage sans layout manager Crer votre propre layout manager Les lments de l'interface utilisateur Les boutons Les vnements de souris Les menus Les fentres internes Le plaf (Pluggable Look And Feel) Exemples de composants Rsum Chapitre 19 : Le graphisme Les primitives graphiques 714 715 716 717 717 718 721 721 723 724 724 733 736 736 742 746 751 755 762 765 766 769 770 777 785 790 796 805 820 821 821

SOMMAIRE
Les objets graphiques Un composant pour les graphismes Les diffrentes primitives L'affichage du texte Les polices de caractres Les images Obtenir une image Surveiller le chargement d'une image Conclusion

XIX

822 822 828 844 845 853 854 856 864

Chapitre 20 : Applets et Rseaux Les applets Cration d'une applet Problmes de compatibilit entre les versions Avertissement Deuxime avertissement Fonctionnement d'une applet Passer des paramtres une applet Agir sur le navigateur Afficher un nouveau document Afficher un message dans la barre d'tat Afficher des images Les sons Optimiser le chargement des applets l'aide des fichiers d'archives Les applets et la scurit Mettre en place les autorisations Comment Java utilise les fichiers d'autorisations Utiliser le security manager avec les applications Accder un URL partir d'une application Conclusion

865 865 866 867 869 869 870 871 876 877 880 881 881 883 884 885 889 890 890 894

Chapitre 21 : Prsentation des Java-Beans Le concept de Beans Le Beans Development Kit Installation du BDK Utilisation de la BeanBox Ajouter un beans dans la fentre de composition Slection d'un bean dans la fentre de composition Dplacement d'un bean Redimensionner un bean Modifier les proprits d'un beans Installer un handler d'vnement Relier des proprits Enregistrer les beans Gnrer une applet Cration d'un Bean Codage d'un bean Cration d'un conteneur de test Packager un bean Prparation du manifeste Cration de l'archive Utilisation du bean dans la BeanBox Rsum Chapitre 22 : Les servlets et les Java Server Pages Qu'est-ce qu'une Servlet ? Structure et cycle de fonctionnement d'une servlet HTTP Ce dont vous avez besoin Installation du JSWDK 1.0.1 Configuration du chemin d'accs aux classes Modification du fichier startserver.bat Cration d'un raccourci Le rpertoire webpages

897 897 899 899 902 903 904 905 905 905 906 910 912 914 915 916 918 919 919 921 922 922 925 926 927 928 929 930 930 930 931

SOMMAIRE
Dmarrage du serveur Accs au serveur Arrter le serveur Configuration du serveur Test d'une servlet Cration d'une servlet Configuration de la variable d'environnement classpath Codage de la servlet Utilisation de la premire servlet Description de la premire servlet Une servlet pour exploiter un formulaire Codage de la servlet Le formulaire Utilisation du formulaire Problmes de synchronisation Comparaison avec Perl Perl Java Les servlets pour remplacer les SSI Un compteur d'accs Les Java Server Pages Directives Dclarations Scriplets Expressions Le code gnr Rsum

XXI

932 932 933 934 936 939 940 940 941 941 947 948 952 954 957 958 958 959 959 961 965 966 967 967 967 968 971

Annexe A : Les classes String et StringBuffer L'oprateur + Les constructeurs de la classe String La classe StringBuffer

973 973 974 978

XXII

LE DVELOPPEUR JAVA 2
Annexe B : Les mots rservs Annexe C : Configuration de UltraEdit UltraEdit n'est pas gratuit ! Installation et configuration de UltraEdit Index 979 981 983 983 989

Introduction

AVA VIENT DAVOIR 5 ANS ! C'EST PEU, DANS L'ABSOLU, MAIS c'est une ternit l'chelle du dveloppement de l'informatique. Qu'en est-il aujourd'hui des objectifs qui ont prsid son lancement ?

Pourquoi Java ?
Java devait tre un langage multi-plate-forme qui permettrait, selon le principe1 propos par Sun Microsystems, son concepteur, d'crire une

1. Write once, run everywhere. crire une fois, utiliser partout.

XXIV

LE DVELOPPEUR JAVA 2
fois pour toutes des applications capables de fonctionner dans tous les environnements. L'objectif tait de taille, puisqu'il impliquait la dfinition d'une machine virtuelle Java (JVM) sur laquelle les pro1 grammes crits devaient fonctionner, ainsi que la ralisation de cette machine virtuelle dans tous les environnements concerns. Sun Microsystems se chargeait par ailleurs de la ralisation d'une machine 2 virtuelle dans les environnements Solaris et Windows , laissant d'autres le soin d'en faire autant pour les autres environnements (et en particulier Mac OS, le systme d'exploitation des Macintosh). Afin que le langage devienne un standard, Sun Microsystems promettait d'en publier les spcifications et de permettre tous d'utiliser gratuitement son compilateur. Ds l'annonce de Java, le monde de l'informatique se divisait en quatre camps : ceux qui pensaient, pour des raisons varies, que cela ne marcherait jamais, ceux qui pensaient, pour des raisons tout aussi varies, qu'il fallait absolument que a marche, ceux qui ne voulaient absolument pas que cela marche, et ceux qui trouvaient qu'il tait urgent d'attendre pour voir. Ceux qui pensaient que a ne marcherait jamais considraient essentiellement qu'une machine virtuelle interprtant un langage intermdiaire (appel bytecode) ne serait jamais suffisamment performante. Il

1. Dans le jargon informatique, la dfinition est souvent appele spcification. De mme, la ralisation dun exemplaire plus ou moins conforme la spcification est appele implmentation. 2. Au mois de dcembre 1998, Sun a annonc le portage du JDK sous Linux, ce qui tait rclam de manire virulente par les adeptes de ce systme, qui ne comprenaient pas pourquoi Sun donnait la priorit au dveloppement de la version fonctionnant sous Windows, le systme dexploitation de Microsoft, prsent comme lennemi historique. Ctait mconnatre la raison conomique. Ce qui a convaincu Sun est plus srement laccroissement sensible du nombre dutilisateurs de Linux que des raisons idologiques. On trouve maintenant le J2SDK pour Linux sur le site de Sun, mais toujours avec quelques mois de retard sur la version Windows. ce jour (mai 2000), la version disponible est la 1.2.2. Dautres organisations produisent des JDK pour Linux. Blackdown.org effectue son propre portage du J2SDK de Sun, ce qui ne prsente plus gure dintrt maintenant que la version de Sun est disponible. IBM est galement trs impliqu dans le support de Linux et fournit ce jour sa propre version du JDK 1.1.8. Cette socit est galement la premire proposer une version d'valuation de son propre J2SDK 1.3.0 disponible en tlchargement sur son site web, l'adresse http://www.alphaworks.ibm.com/tech/linuxjdk/.

INTRODUCTION
1

XXV

faut dire que les exemples prcdents avaient laiss des traces . D'autres considraient qu'il serait impossible d'imposer un standard ouvert dans le monde des PC vou l'hgmonie d'un seul diteur dtenant le quasi-monopole des systmes d'exploitation. Java apparaissait effectivement comme une tentative de casser le monopole de Windows. En effet, ce monopole reposait (et repose encore !) sur un cercle vicieux : tous les PC (ou presque) doivent utiliser Windows parce que toutes les applications importantes sont dveloppes pour cet environnement. Pour que cela change, il faudrait que les diteurs portent leurs produits sous un environnement diffrent : cela ne serait pas du tout rentable, puisque trs peu de PC utilisent un autre environnement que Windows. En revanche, une application dveloppe en Java pourrait fonctionner (sans aucune modification, pas mme une recompilation) dans n'importe quel environnement disposant d'une JVM. Pour cette raison, un certain nombre d'diteurs et d'utilisateurs voulaient absolument que cela fonctionne et imaginaient voir l la fin de la domination de Microsoft ! Et chacun de se mettre rver. Un diteur important (Corel) annona mme qu'il allait rcrire toutes ses applications en Java. Cette tentative a depuis t abandonne, mais dautres produits commencent tre distribus sous forme de classes Java, excutables sur nimporte quel ordinateur disposant dune ma2 chine virtuelle . La mme raison entranait Microsoft prendre immdiatement le contre-pied de Java en proposant une technologie concurrente. Les concepteurs de Windows, en effet, arguaient qu'il tait inefficace et inutile de dvelopper des applications pour une machine virtuelle et qu'il suffisait de dvelopper une interface commune pouvant tre adapte sous diffrents environnements. Ainsi, les dveloppeurs n'taient pas tenus d'utiliser un nouveau langage. Il leur suffisait de programmer en fonction d'une nouvelle interface de programmation : ActiveX. Bien sr, cette API ne serait ni ouverte (ce qui permettrait certains de

1. Pascal UCSD, une version du langage Pascal compil en pseudocode intermdiaire (pCode), qui tait cens tre portable sur tout environnement disposant dun interprteur adquat. 2. On trouve galement des logiciels spcialiss dans le dploiement dapplications Java qui permettent dinstaller simultanment une application et la JVM ncessaire. Le leader inconstest, dans ce domaine, est InstallShield Java Edition 3.0 (www.installshield.com/java).

XXVI

LE DVELOPPEUR JAVA 2
prtendre que Microsoft se rserve des fonctions pour son usage per1 sonnel afin d'avantager ses propres applications ) ni gratuite. La quatrime catgorie d'utilisateurs pensait probablement que les promesses de Java taient tout fait allchantes, mais qu'on n'a jamais crit des applications avec des promesses, et qu'il fallait donc rester l'coute pour voir ce que cela donnerait. La version 1.0 de Java, bien qu'assez rassurante en ce qui concerne le problme, essentiel, des performances, confirmait certaines craintes dans le domaine de la richesse fonctionnelle. Dune part, si finalement la machine virtuelle se rvlait suffisamment rapide pour la plupart des applications, elle restait cependant une solution inapplicable pour certains types de traitements en temps rel. D'autre part, dans le but d'offrir une interface utilisateur unifie pour tous les environnements, celle-ci tait rduite au plus petit dnominateur commun, ce que de nombreux dveloppeurs trouvaient notoirement insuffisant. Ajoutez cela que certaines parties du langage, crites par des quipes de programmation autonomes, prsentaient un look and feel sensiblement diffrent du reste. Le tableau tait loin d'tre noir, mais il n'tait pas franchement blanc non plus. Le ct positif tait le ralliement gnralis au standard pour ce qui concerne les applications diffuses sur Internet. Les deux principaux navigateurs (Netscape Navigator et Internet Explorer) disposaient en effet de JVM (machines virtuelles Java), mme si celles-ci donnaient parfois l'impression d'un certain cafouillage. Il faut noter que, dans le cas d'Explorer, le ralliement au langage Java tait tout fait opportuniste, Microsoft continuant promouvoir sa technologie concurrente, tout en essayant de dtourner le standard Java en proposant une JVM incomplte, ce qui allait conduire une confrontation avec Sun Microsystems devant les tribunaux amricains. Avec l'apparition de la version 1.1 de Java, les choses se compliquaient quelque peu. En effet, cette version prsentait de nombreuses amliorations. On pouvait mme dire qu'il s'agissait de la pre-

1. Les rcentes prises de position du Dpartment of Justice amricain semblent en partie leur donner raison !

INTRODUCTION

XXVII

mire version rellement utile, mme si certains aspects taient encore inachevs (en particulier l'interface utilisateur toujours limite). Cependant, le problme le plus grave tait que les utilisateurs qui avaient fond de rels espoirs sur le concept Write once, run everywhere en taient pour leurs frais. Pour profiter des amliorations, une grande partie des programmes devait tre rcrite. L'inconvnient majeur n'tait pas le travail de rcriture. Celui-ci tait en effet assez limit, de nombreuses modifications n'ayant pour but que d'unifier la terminologie par exemple, la mthode reshape() , permettant de modifier la taille et la position d'un composant, devenant setBounds() . Le rel problme tait que les programmes conformes la version 1.1 ne fonctionnaient pas avec les machines virtuelles Java 1.0, du moins sils utilisaient les nouvelles fonctionnalits. Bien sr, on peut arguer de ce que le langage tant encore jeune, Sun Microsystems devait corriger le tir au plus tt, et en tout cas avant que les principaux systmes d'exploitation soient livrs en standard avec une machine virtuelle. En effet, dans l'tat actuel du march, un diteur souhaitant diffuser une application crite en Java ne peut se reposer sur les JVM dj installes. Il lui faut donc distribuer en mme temps que son application la JVM correspondante. Dans le cas des PC sous Windows ou sous Linux et des machines fonctionnant sous Solaris, l'environnement Unix de Sun Microsystems, celle-ci est propose gratuitement par cet diteur sous la forme du J2RE ( Java 2 Runtime Environment ). Le problme se complique dans le cas des autres environnements (cas du Macintosh, par exemple) et surtout dans le cas des applications distribues sur Internet (les applets). Pour qu'une applet Java 1.0 puisse tre utilise par un navigateur, il faut que celui-ci possde une JVM, ce qui n'est pas toujours le cas, mais est tout de mme assez frquent. En revanche, pour qu'une applet Java 1.1 soit utilisable dans les mmes conditions, il faut que la machine virtuelle Java soit compatible 1.1, ce qui est dj plus rare. Microsoft Internet Explorer dispose d'une telle JVM depuis la version 4.0 et Netscape Navigator depuis la version 4.05. Cette version prsente toutefois de nombreuses incompatibilits, et il est prfrable dutiliser au moins la version 4.5. Netscape avait annonc le support total de Java 2 dans la version 5 de son navigateur. Toutefois, celle-ci na jamais vu le jour et

XXVIII

LE DVELOPPEUR JAVA 2
Netscape annonce maintenant firement la disponibilit prochaine de la version 6 qui, bien entendu, supportera totalement Java 2. Cette version devant tre distribue tous les clients dAOL (qui a rcemment rachet Netscape), on nous promet donc une compatibilit prochaine des navigateurs de la plupart des abonns de ce fournisseur daccs, ce qui reprsente une part considrable du march. Toutefois, les kits de connexion distribus actuellement par AOL contiennent toujours... Micrososft Internet Explorer ! En ce qui concerne Java 2, il n'existe simplement aucun navigateur courant capable de faire fonctionner les applets conformes cette version. Cela est d'autant plus frustrant qu'elle apporte enfin une solution satisfaisante certains problmes srieux, comme les limitations de l'interface utilisateur. Pourquoi, alors, choisir de dvelopper des applications en Java 2. D'une part, parce que cette version permet de rsoudre de faon lgante et productive certains problmes poss par les versions prcdentes. D'autre part, parce que l'inconvnient cit prcdemment ne concerne que les applets (diffuses sur Internet), et non les applications (qui peuvent tre diffuses accompagnes de la dernire ver1 sion de la machine virtuelle Java) . Enfin, parce que le langage Java a maintenant acquis une telle maturit et une telle notorit qu'il ne fait nul doute que, dans un dlai trs court, tous les navigateurs seront quips d'une machine virtuelle Java 2. En effet, ceux-ci sont conus pour que la JVM puisse tre mise jour de faon simple par l'utilisateur, en tlchargeant la nouvelle version. (Ainsi, la version Macintosh d'Internet Explorer est livre avec deux JVM diffrentes que l'utilisateur peut slectionner.) Sun Microsystems propose dailleurs gratuitement un plug-in permettant la plupart des navigateurs dutiliser la machine virtuelle Java 2. Ce plug-in est inclus dans les versions du J2SDK et du J2RE disponibles sur le CD-ROM accompagnant ce livre.
1. Si vous tes plus particulirement intress par le dveloppement d'applets diffuses sur Internet, signalons que le livre Le Dveloppeur Java 2, Mise en uvre et solutions - Les applets, publi chez le mme diteur, est consacr la cration d'applets compatibles avec tous les navigateurs bien qu'exploitant en grande partie les avantages de Java 2. Ce livre suppose toutefois une bonne connaissance des principes fondamentaux de Java et ne doit donc tre abord qu'aprs l'tude de celui-ci.

INTRODUCTION

XXIX

Qu'est-ce que Java ?


Java a t dvelopp dans le but d'augmenter la productivit des programmeurs. Pour cela, plusieurs axes ont t suivis.

Java est un langage orient objets


Les premiers ordinateurs taient programms en langage machine, c'est-dire en utilisant directement les instructions comprises par le processeur. Ces instructions ne concernaient videmment que les lments du processeur : registres, oprations binaires sur le contenu des registres, manipulation du contenu d'adresses mmoire, branchement des adresses mmoire, etc. Le premier langage dvelopp a t l'assembleur , traduisant d'une part mot mot les instructions de la machine sous forme de mnmoniques plus facilement comprhensibles, et masquant dautre part la complexit de certaines oprations en librant le programmeur de l'obligation de dcrire chaque opration dans le dtail (par exemple, choisir explicitement un mode d'adressage). L'assembleur, comme le langage machine, permet d'exprimer tous les problmes qu'un ordinateur peut avoir rsoudre. La difficult, pour le programmeur, rside en ce que le programme (la solution du problme) doit tre exprim en termes des lments du processeur (registres, adresses, etc.) plutt qu'en termes des lments du problme luimme. Si vous avez programmer un diteur de texte, vous serez bien ennuy, car l'assembleur ne connat ni caractres, ni mots, et encore moins les phrases ou les paragraphes. Il ne permet de manipuler que 1 des suites plus ou moins longues de chiffres binaires . Certains programmeurs pensrent alors qu'il serait plus efficace d'exprimer les programmes en termes des lments du problme rsou1. Les assembleurs perfectionns permettent nanmoins de manipuler des adresses de blocs mmoire contenant des donnes pouvant tre exprimes dans les listings sources sous forme de chanes de caractres littrales, ce qui simplifie considrablement l'criture et la lecture des programmes.

XXX

LE DVELOPPEUR JAVA 2
dre. Les premiers ordinateurs taient utiliss uniquement pour les calculs numriques. Aussi, les premiers langages dits de haut niveau exprimaient tous les problmes en termes de calcul numrique. En ce qui concerne le droulement des programmes, ces langages reproduisaient purement et simplement les fonctions des processeurs : tests et branchements. Est rapidement apparue la ncessit de mettre un frein l'anarchie rgnant au sein des programmes. Les donnes traites par les ordinateurs tant en majorit numriques, c'est au droulement des programmes que fut impose une structuration forte. Ainsi, le droulement des programmes devenait (en thorie) plus facilement lisible (et donc ceux-ci devenaient plus faciles entretenir), mais les donnes tait toujours essentiellement des nombres (mais de diffrents formats) et ventuellement des caractres, voire des chanes de caractres, ou mme des tableaux de nombres ou de chanes, ou encore des fichiers (contenant, bien sr, des nombres ou des chanes de caractres). Le pionnier de ces langages dits structurs fut Pascal, que sa conception extrmement rigide limitait essentiellement l'enseignement. Le langage C, universellement employ par les programmeurs professionnels, marqua l'apoge de ce type de programmation. Tous les problmes se prsentant un programmeur ne concernent pas forcment des nombres ou des caractres uniquement. Cela parat vident, mais cette vidence tait probablement trop flagrante pour entrer dans le champ de vision des concepteurs de langages, jusqu' l'apparition des premiers langages orients objets. Ceux-ci, en commenant par le prcurseur, Smalltalk, permettent d'exprimer la solution en termes des lments du problme, plutt qu'en termes des outils employs. Cela reprsente un norme pas en avant pour la rsolution de problmes complexes, et donc pour la productivit des programmeurs. Bien sr, toute mdaille a son revers. En termes de performances pures, rien ne vaudra jamais un programme en assembleur. Toutefois, dans le cas de projets trs complexes, il est probable que l'criture et surtout la mise au point de programmes en assembleur, voire en langages structurs, prendraient un temps tendant vers l'infini. Le langage orient objets le plus rpandu est sans quivoque C++. Il s'agit d'une version de C adapte au concept de la programmation

INTRODUCTION

XXXI

par objets. Dans ce type de programmation, on ne manipule pas des fonctions et des procdures, mais des objets qui s'changent des messages. Le principal avantage, outre le fait que l'on peut crer des objets de toutes natures reprsentant les vritables objets du problme traiter, est que chaque objet peut tre mis au point sparment. En effet, une fois que l'on a dfini le type de message auquel un objet doit rpondre, celui-ci peut tre considr comme une bote noire. Peu importe sa nature. Tout ce qu'on lui demande est de se comporter conformment au cahier des charges. Il devient ainsi extrmement facile de mettre en uvre un des concepts fondamentaux de la programmation efficace : d'abord crire un programme de faon qu'il fonctionne. Ensuite, l'amliorer afin quil atteigne une rapidit suffisante. Ce concept est particulirement important, sinon le plus important de la programmation oriente objets. En effet, avec les langages des gnrations prcdentes, cette approche, souvent utilise, conduisait gnralement une solution mdiocre, voire catastrophique. Il tait d'usage de dvelopper un prototype fonctionnel, c'est--dire un programme conu rapidement dans le seul but de reproduire le fonctionnement souhait pour le programme final. Les programmeurs les plus srieux utilisaient pour cela un langage de prototypage. Une fois un prototype satisfaisant obtenu, ils rcrivaient le programme dans un langage plus performant. L'avantage tait qu'ils avaient ainsi lassurance de repartir sur des bases saines lors de l'criture de la version finale. L'inconvnient tait qu'il fallait faire deux fois le travail, puisque l'intgralit du programme tait rcrire. De plus, il tait ncessaire de connatre deux langages diffrents, ce qui n'augmentait pas la productivit. Une autre approche consistait dvelopper un prototype dans le mme langage que la version finale, avec la ferme dcision de rcrire ensuite, de faon propre, les lments qui auraient t mal conus au dpart. Satisfaisante en thorie, cette solution n'tait pratiquement jamais rellement applique, la version finale tant presque toujours la version initiale agrmente de nombreux repltrages l o il aurait fallu une rcriture totale, celle-ci s'avrant impossible en raison des nombreuses connexions apparues en cours de dveloppement entre des parties du code qui auraient d rester indpendantes.

XXXII

LE DVELOPPEUR JAVA 2
Les langages orients objets, et en particulier Java, apportent une solution efficace et lgante ce problme en dfinissant de manire trs stricte la faon dont les objets communiquent entre eux. Il devient ainsi tout fait possible de faire dvelopper les parties d'une application par des quipes de programmeurs totalement indpendantes. C'est d'ailleurs le cas pour le langage Java lui-mme, comme on peut s'en apercevoir en examinant les noms choisis pour les mthodes des diffrents objets du langage. Certains sont courts et obscurs, d'autres longs et explicites, en fonction de la personnalit du programmeur les ayant choisis. (Ces diffrences ont d'ailleurs tendance disparatre dans les nouvelles versions du langage, marquant en cela une volont d'unification apprciable.)

Java est extensible l'infini


Java est crit en Java. Idalement, toutes les catgories d'objets (appeles classes) existant en Java devraient tre dfinies par extension d'autres classes, en partant de la classe de base la plus gnrale : la classe Object. En ralit, cela n'est pas tout fait possible et certaines classes doivent utiliser des mthodes (nom donn ce que nous considrerons pour l'instant comme des procdures) natives, c'est--dire ralises sous forme de sous-programmes crits dans le langage correspondant la machine ou au systme sur lequel est excut le programme. Le but clairement annonc des concepteurs du langage Java est de rduire les mthodes natives au strict minimum. On peut en voir un bon exemple avec les composants Swing , intgrs Java 2. Ceux-ci remplacent les composants d'interface utilisateur (boutons, listes droulantes, menus, etc.) prsents dans les versions prcdentes et qui faisaient appel des mthodes natives. Les composants Swing sont intgralement crits en Java, ce qui simplifie la programmation et assure une portabilit maximale grce une indpendance totale par rapport au systme. (Il est nanmoins tout fait possible d'obtenir un aspect conforme celui de l'interface du systme utilis. Ainsi, le mme programme tournant sur une machine Unix, sur un PC et sur un Macintosh pourra, au choix du programmeur, disposer d'une interface strictement identique ou, au contraire, respecter le look and feel du systme utilis.)

INTRODUCTION

XXXIII

Par ailleurs, Java est extensible l'infini, sans aucune limitation. Pour tendre le langage, il suffit de dvelopper de nouvelles classes. Ainsi, tous les composants crits pour traiter un problme particulier peuvent tre ajouts au langage et utiliss pour rsoudre de nouveaux 1 problmes comme s'il s'agissait d'objets standard .

Java est un langage haute scurit


Contrairement C++, Java a t dvelopp dans un souci de scurit maximale. L'ide matresse est qu'un programme comportant des erreurs ne doit pas pouvoir tre compil. Ainsi, les erreurs ne risquent pas d'chapper au programmeur et de survivre aux procdures de tests. De la mme faon, en dtectant les erreurs la source, on vite qu'elles se propagent en s'amplifiant. Bien sr, il n'est pas possible de dtecter toutes les erreurs au moment de la compilation. Si on cre un tableau de donnes, il est impossible au compilateur de savoir si l'on ne va pas crire dans le tableau avec un index suprieur la valeur maximale. En C++, une telle situation conduit l'criture des donnes dans une zone de la mmoire n'appartenant pas au tableau, ce qui entrane, au mieux, un dysfonctionnement du programme et, au pire (et le plus souvent), un plantage gnralis.

1. Notez toutefois que certaines classes Java doivent communiquer avec le matriel constituant l'ordinateur sur lequel elles sont utilises. Pour ajouter des classes grant des aspects matriels nouveaux, il est souvent ncessaire d'ajouter des mthodes natives. Ainsi, l'enregistrement sonore n'est pas possible en Java 2 1.2. Pour crer une classes s'interfaant avec l'entre micro d'un ordinateur, il est indispensable de faire appel une mthode native. Il en est de mme pour toutes les interfaces avec des priphriques non pris en charge par une version donne de Java 2, et ce tant que ne sera pas dfini un standard d'interfaage logiciel. Les programmeurs se trouvent donc devant un dilemme : dvelopper leurs propres mthodes natives, et perdre ainsi la compatibilit avec les autres plates-formes, ou attendre que des extensions standard soient intgres au langage. C'est le cas, par exemple, de Javasound, une extension permettant de grer la lecture et l'enregistrement d'un grand nombre de formats audio. Cette extension est disponible pour PC et Solaris pour les versions 1.1 et 1.2. Il est peu probable qu'elle soit porte sous cette forme vers d'autres environnements. En revanche, elle est intgre au langage partir de la version 1.3. On a alors de bonnes raisons d'esprer qu'elle devienne rapidement disponible dans tous les environnements.

XXXIV

LE DVELOPPEUR JAVA 2
Avec Java, toute tentative d'crire en dehors des limites d'un tableau ne conduit simplement qu' l'excution d'une procdure spciale qui arrte le programme. Il n'y a ainsi aucun risque de plantage. Un des problmes les plus frquents avec les programmes crits en C++ est la fuite de mmoire. Chaque objet cr utilise de la mmoire. Une fois qu'un objet n'est plus utilis, cette mmoire doit tre rendue au systme, ce qui est de la responsabilit du programmeur. Contrairement au dbordement d'un tableau, ou au dbordement de la pile (autre problme vit par Java), la fuite de mmoire, si elle est de faible amplitude, n'empche ni le programme ni le systme de fonctionner. C'est pourquoi les programmeurs ne consacrent pas une nergie considrable traquer ce genre de dysfonctionnement. C'est aussi pourquoi les ressources de certains systmes diminuent au fur et mesure de leur utilisation. Et voil une des raisons pour lesquelles vous devez redmarrer votre systme de temps en temps aprs un plantage. (C'est le cas de Windows. Ce n'est pas le cas d'autres systmes qui se chargent de faire le mnage une fois que les applications sont termines. Cependant, mme dans ce cas, les fuites de mmoire demeurent un problme tant que les applications sont actives.) Avec Java, vous n'aurez jamais vous proccuper de restituer la mmoire une fois la vie d'un objet termine. Le garbage collector (littralement le ramasseur de dchets) s'en charge. Derrire ce nom barbare se cache en effet un programme qui, ds que les ressources mmoire descendent au-dessous d'un certain niveau, permet de rcuprer la mmoire occupe par les objets qui ne sont plus utiles. Un souci de moins pour le programmeur et surtout un risque de bug qui disparat. Bien sr, l encore, il y a un prix payer. Vous ne savez jamais quand le garbage collector s'excutera. Dans le cas de contrle de processus en temps rel, cela peut poser un problme. (Il existe cependant d'ores et dj des versions de Java dans lesquelles le fonctionnement du garbage collector est contrlable, mais cela sort du cadre de ce livre.)

Java est un langage simple apprendre


Java est un langage relativement simple apprendre et lire. Beaucoup plus, en tout cas, que C++. Certains lecteurs nous ont demand

INTRODUCTION

XXXV

s'il tait raisonnable d'entreprendre l'apprentissage de Java alors qu'ils n'avaient aucune exprience d'un autre langage. Non seulement la rponse est oui, mais c'est mme la situation idale. En effet, il est prfrable d'aborder Java avec un regard neuf. Nombreux sont les programmeurs rompus aux subtilits de C++ qui veulent s'essayer la programmation en Java. Ils rencontrent souvent de srieux problmes car ils tentent simplement de reproduire les programmes C++ en les traduisant mot mot. Les questions qu'ils posent le plus souvent sont : "Comment gre-t-on les pointeurs en Java ?" ou "Comment peut-on allouer un bloc de mmoire ?" La rponse est simplement : "On ne le fait pas !", ce qui a pour effet de les dstabiliser compltement. L'analogie avec les langues naturelles est frappante. Un Franais apprenant une langue exotique est souvent drout lorsqu'il pose la question "Comment dit-on 1 Oui ?" et qu'il s'entend rpondre "On ne le dit pas !" Il faut en gnral quelque temps pour admettre que l'on puisse trs bien se passer de ce mot qui nous parat pourtant essentiel. Cette simplicit a aussi un prix. Les concepts les plus complexes (et aussi les plus dangereux) de C++ ont t simplement limins. Est-ce dire que Java est moins efficace ? Pas vraiment. Tout ce qui peut tre fait en C++ (sauf certaines erreurs graves de programmation) peut l'tre en Java. Il faut seulement parfois utiliser des outils diffrents, plus simples manipuler, et pas forcment moins performants. Contrairement d'autres langages, la simplicit de Java est en fait directement lie la simplicit du problme rsoudre. Ainsi, un problme de dfinition de l'interface utilisateur peut tre un vritable casse-tte avec certains langages. Avec Java, crer des fentres, des boutons et des menus est toujours d'une simplicit extrme, ce qui laisse au programmeur la possibilit de se concentrer sur le vritable problme qu'il a rsoudre. Bien sr, avec d'autres langages, il est possible d'utiliser des bibliothques de fonctions ou de procdures pour traiter les problmes d'intendance. L'inconvnient est double. Tout d'abord, ces bibliothques ne sont pas forcment crites dans le langage utilis et, si elles le sont, elles ne sont gnralement pas disponibles sous forme de sources, ce qui est facilement comprhensi-

1. Il n'est nullement besoin d'aller chercher au fin fond de l'Amazonie ou sur une le perdue du Pacifique pour trouver un tel exemple. C'est le cas, par exemple, en finnois.

XXXVI

LE DVELOPPEUR JAVA 2
ble, les programmeurs prfrant prserver leurs produits des regards indiscrets. Il en rsulte que les programmes utilisant ces bibliothques ne sont pas portables dans un autre environnement. De plus, si elles sont disponibles dans les diffrents environnements pour lesquels vous dveloppez, rien ne dit qu'elles le seront encore demain. Avec Java, pratiquement tout ce dont vous avez besoin est inclus dans le langage, ce qui, outre le problme de portabilit, rsout galement celui du cot. (Certaines bibliothques cotent fort cher.) De plus, si vous avez quand mme besoin d'acqurir une bibliothque de classes, il vous suffit d'en slectionner une conforme au label Pure Java , ce qui vous assure qu'elle sera portable dans tous les environnements compatibles Java, prsents ou venir, et ce au niveau excutable, ce qui garantit la prennit de votre investissement.

Java est un langage compil


Java est un langage compil, c'est--dire qu'avant d'tre excut, il doit tre traduit dans le langage de la machine sur laquelle il doit fonctionner. Cependant, contrairement de nombreux compilateurs, le compilateur Java traduit le code source dans le langage d'une machine virtuelle, appele JVM (Java Virtual Machine). Le code produit, appel bytecode, ne peut pas tre excut directement par le processeur de votre ordinateur. (Cependant, rien n'interdit de fabriquer un processeur qui excuterait directement le bytecode Java.) Le bytecode peut ensuite tre confi un interprteur , qui le lit et l'excute. En principe, l'interprtation du bytecode est un processus plus lent que l'excution d'un programme compil dans le langage du processeur. Cependant, dans la plupart des cas, la diffrence est relativement minime. Elle pose toutefois un problme pour les applications dont la vitesse d'excution est un lment critique, et en particulier pour les applications temps rel ncessitant de nombreux calculs comme la simulation 3D anime. Il existe cela plusieurs solutions.

Les compilateurs natifs


Une des solutions consiste utiliser un compilateur natif, traduisant directement le langage Java en un programme excutable par le processeur de la machine. Le programme ainsi produit n'est plus portable

INTRODUCTION

XXXVII

dans un environnement diffrent. Cependant le programme source reste portable en thorie, du moment qu'il existe galement un compilateur natif dans les autres environnements. Cette solution prsente toutefois deux inconvnients. Le premier est que, le code produit par le compilateur n'tant plus portable, il faut distribuer autant de versions diffrentes de l'application qu'il y a de systmes cibles. En clair, si l'application doit tourner sur PC et sur Macintosh, il faudra que le support employ pour la distribution (par exemple un CD-ROM) contienne les deux versions. Le second inconvnient n'est pas inhrent l'utilisation d'un compilateur natif, mais il est beaucoup plus grave. En effet, il est tentant pour le concepteur du compilateur, de se simplifier la tche en demandant l'utilisateur d'apporter quelques lgres modifications son programme. Dans ce cas, c'est tout simplement la portabilit des sources qui est remise en cause. Donc, avant de vous dcider choisir un compilateur natif, vrifiez s'il est compatible avec un programme Pure Java sans aucune modification.

Les compilateurs de bytecode


Une autre approche, pour amliorer la vitesse d'excution des programmes Java, consiste compiler le bytecode dans le langage du processeur de la machine cible. De cette faon, la portabilit est prserve jusqu'au niveau du bytecode, ce qui peut tre suffisant dans bien des cas. Il reste alors assurer la diffusion du code compil en autant de versions qu'il y a de systmes cibles. Cette approche est satisfaisante pour la diffusion d'applications vers des systmes cibles connus. En particulier, la diffusion sur support physique (CD-ROM, par exemple) se prte bien ce type de traitement. En effet, choisir un support physique implique gnralement de connatre le systme cible, car il faut s'tre assur que celui-ci est bien capable de lire le support physique. Ainsi, la diffusion d'une application pour PC et Macintosh implique d'utiliser un format de CD-ROM hybride pouvant tre lu dans ces deux environnements. Si un compilateur de bytecode existe pour chacun d'eux, cela peut constituer une solution satisfaisante. Il peut s'agir d'un mme compilateur, fonctionnant dans l'un des environnements, ou mme dans un environnement totalement diffrent, et produisant du code natif pour l'environnement cible. On

XXXVIII

LE DVELOPPEUR JAVA 2
parle alors de cross-compilation. Un compilateur fonctionnant sur PC peut ainsi produire du code natif PC et Macintosh, au choix de l'utilisateur. Ce type de solution est cependant trs rare, les cross-compilateurs servant le plus souvent produire du code natif pour des machines ne disposant pas de systme de dveloppement propre (consoles de jeu, par exemple).

Les compilateurs JIT


La solution prcdente prsente un inconvnient majeur si l'ensemble des environnements cibles n'est pas connu. Par exemple, si vous crez une application devant tre diffuse sur Internet, vous souhaiterez que celle-ci fonctionne sur tous les environnements compatibles Java, prsents et venir. La compilation du bytecode la source (par le programmeur) n'est pas, dans ce cas, une solution envisageable. Les compilateurs JIT (Just In Time) sont supposs rsoudre ce problme. Un compilateur JIT fonctionne sur la machine de l'utilisateur. Il compile le bytecode la vole, d'o l'appellation Just In Time Juste temps . Gnralement, l'utilisateur peut choisir d'activer ou non le compilateur JIT. Lorsque celui-ci est inactif, le programme est excut par l'interprteur. Dans le cas contraire, le programme en bytecode est compil dans le langage du processeur de la machine hte, puis excut directement par celui-ci. Le gain de temps d'excution est videmment rduit en partie en raison du temps ncessaire la compilation. Pour un programme s'excutant de faon totalement indpendante de l'utilisateur, le gain peut tre nul. En revanche, pour un programme interactif, c'est--dire attendant les actions de l'utilisateur pour effectuer diverses oprations, le gain peut tre substantiel, car le compilateur peut fonctionner pendant que l'utilisateur consulte l'affichage. Pour un gain maximal, il faut cependant que le programme soit conu de telle faon qu'il soit charg dans sa totalit ds le dbut de l'utilisation afin de pouvoir tre compil en arrire-plan. En effet, Java est conu pour que les classes ncessaires au fonctionnement du programme soient charges selon les besoins, afin que seules celles qui seront rellement utilises soient transmises, cela, bien sr, afin d'optimiser les temps de chargement sur Internet. En revanche, pour bnficier au maximum des avantages d'un compilateur JIT, il faut autant que possible tlcharger les classes l'avance afin qu'elles soient com-

INTRODUCTION

XXXIX

piles, et cela sans savoir si elles seront rellement utilises. L'usage d'un compilateur JIT lorsque les classes sont tlcharges la demande se traduit le plus souvent par un faible gain de vitesse, voire parfois un ralentissement de l'excution.

La technologie HotSpot
La technologie HotSpot a t dveloppe par Sun Microsystems pour palier les inconvnients des compilateurs JIT. Le principe d'un compilateur JIT peut tre rsum de la faon suivante : "perdre un peu de temps pour en gagner beaucoup". En effet, la compilation prend du temps, mais cette perte de temps doit pouvoir permettre d'en gagner grce une vitesse d'excution suprieure du code compil. Toutefois, ce gain de temps dpend de deux lments : la nature du code et les techniques de compilation employes. Si on suppose qu'une mthode est interprte en une seconde et compile en deux secondes, et si l'excution du code compil prend une demi-seconde on constate que le gain n'est rel que si la mthode est excute au moins trois fois. (Les temps indiqus ici sont purement fantaisistes. Il s'agit uniquement de faire comprendre le principe.) Ainsi, un compilateur JIT perd beaucoup de temps compiler des mthodes qui ne seront excutes qu'une seule fois. La technologie HotSpot permet de minimiser cet inconvnient grce une approche "intelligente" de la compilation. Intgre la version 1.3 (et disponible sparment pour la version 1.2), cette technologie semble trs prometteuse. Il faut toutefois bien comprendre que le gain de performance qu'il est ainsi possible d'obtenir dpend troitement du type de programme et du type d'optimisation mis en uvre, et surtout de l'adquation entre les deux. Une application bureautique individuelle impose de ce point de vue des contraintes totalement diffrentes de celles qui psent sur un serveur. Qui plus est, les contraintes lies l'optimisation d'un serveur dpendent essentiellement du nombre de requtes qui doivent tre servies simultanment. Ainsi, il n'est pas rare de voir des serveurs commerciaux prsenter des performances trs sduisantes pour quelques centaines d'accs et s'crouler lamentablement au-del d'un certain seuil. Il est toujours prfrable d'avoir des performances infrieures mais proportionnelles la charge impose au programme. Ce problme est connu des programmeurs sous le terme anglo-saxon de

XL

LE DVELOPPEUR JAVA 2
scalability , qui dsigne la facult pour un programme de conserver des performances proportionnelles la charge qui lui est impose. Il s'agit l d'un problme essentiel en ce qui concerne la programmation des serveurs 1. Pour cette raison, il est inutile de tester la technologie HotSpot avec un programme spcifiquement tudi pour ce type de test. Il est aussi facile d'crire un programme dmontrant l'crasante supriorit de cette technologie, que de faire le contraire, c'est--dire crire un programme fonctionnant beaucoup plus vite avec un simple interprteur qu'avec n'importe quelle technique d'optimisation. Java 1.3 reprsente une nette avance en termes de performances en ce qui concerne les applications relles, tant pour la vitesse d'excution que pour l'empreinte mmoire, c'est--dire l'espace occup en mmoire par le programme pendant son excution.

Le premier langage du troisime millnaire?


Java s'annonce comme une des volutions majeures de la programmation. Pour la premire fois, un langage efficace, performant, standard et facile apprendre (et, de plus, gratuit) est disponible. Il satisfait aux besoins de l'immense majorit des dveloppeurs et reprsente une opportunit de se librer un peu de la tour de Babel des langages actuels (ainsi que de la domination de certains monopoles). Pour que Java soit un succs, il faut qu'il soit ds aujourd'hui appris comme premier langage de programmation par les programmeurs en cours de formation ; c'est dans cet esprit que ce livre a t conu, non pas pour s'adresser aux programmeurs rompus aux secrets de C++, qui ont plutt besoin

1. Ce critre n'est pas le seul prendre en compte. En effet, un serveur prsente la particularit de fonctionner en permanence, contrairement une application bureautique, qui fonctionne au maximum pendant quelques heures avant d'tre quitte. Un serveur doit donc offrir des performances constantes dans le temps. Si un tel programme prsente des fuites de mmoire, celles-ci s'accumulent au point de provoquer parfois un plantage ncessitant le redmarrage du systme. Toutefois, avant d'en arriver l, on observe gnralement une nette dgradation des performances, due au morcellement de la mmoire et la diminution des ressources disponibles. Java est vraiment le langage qui permet au mieux d'viter ce genre de problmes.

INTRODUCTION

XLI

d'un manuel de rfrence et d'une description des diffrences entre ces deux langages, mais ceux qui veulent s'imprgner directement des concepts fondamentaux de Java. Tout comme on ne matrise pas une langue trangre en traduisant sa pense depuis sa langue maternelle, il n'est pas efficace d'essayer de traduire en Java des programmes labors dans un autre langage. Pour que Java devienne le premier langage du troisime millnaire, il faut qu'apparaisse une gnration de programmeurs pensant directement en Java. Puissions-nous y contribuer modestement.

Voir Java luvre


Si vous souhaitez voir Java luvre en situation relle, vous pouvez visiter le site de Volga Multimdia, ladresse http://www.volga.fr/. Vous y trouverez Codexpert, une applet proposant un test du Code de la Route simulant lexamen thorique du permis de conduire, qui vous montrera un exemple de ce que Java permet de raliser. La Figure I.1 montre laffichage de Codexpert. Vous pouvez galement trouver une version plus perfectionne l'adresse http://www.volga.fr/beta. Cette version comporte un dcodeur de fichiers audio au format GSM permettant le streaming (c'est--dire l'coute sans chargement pralable) avec un liaison 14 Kbps. Ce dcodeur est crit entirement en Java. Cette applet implmente par ailleurs en Java 1.0 (compatible avec tous les navigateurs) le modle de gestion des vnements de Java 1.3. Si vous voulez savoir comment tout cela fonctionne, vous pouvez mme vous procurer le listing source ainsi qu'une description dtaille dans la suite de ce livre, publie chez le mme diteur sous le titre Le Dveloppeur Java 2 - Mise en uvre et solutions - Les applets. (Voir page XLII.)

Support technique
Si vous rencontrez des problmes lors de la compilation ou de lexcution des programmes de ce livre, vous pouvez consulter le site Web qui lui est consacr, ladresse http://www.volga.fr/java2. Nous y diffuserons les rponses aux questions les plus frquemment poses. Si vous ne trou-

XLII

LE DVELOPPEUR JAVA 2

Figure I.1 : Exemple dapplet Java : Codexpert, le test du Code de la Route.

vez pas l la solution vos problmes, vous pouvez nous contacter ladresse devjava213@volga.fr. Nous rpondrons toutes les questions concernant ce livre. Quant aux problmes concernant Java en gnral, nous essaierons dy rpondre dans la mesure du temps disponible. Toutefois, avant de nous contacter, veuillez prendre en considration les points suivants :

La plupart des problmes rencontrs par les utilisateurs concernent

la compilation et l'excution des programmes et sont dus une mauvaise configuration de la variable d'environnement classpath ou un non-respect des majuscules dans les noms des fichiers.

INTRODUCTION

XLIII

Aussi, joignez toujours votre envoi une copie de votre fichier de configuration (autoexec.bat sous MS-DOS).

Envoyez-nous une copie du programme posant problme en pice Si

jointe, et non en copiant son contenu dans votre courrier lectronique. la compilation ou l'excution partir d'un environnement de programmation comme UltraEdit produisent un message d'erreur, essayez de compiler ou d'excuter le programme partir de la ligne de commande, dans une fentre console (fentre MS-DOS sous Windows).

Si l'erreur persiste, faites une copie d'cran du message d'erreur en


incluant la ligne de commande qui l'a provoque et collez-la dans votre courrier. Pour faire une copie d'cran dans une fentre MS-DOS sous Windows, procdez de la faon suivante : 1. Cliquez sur le bouton Marquer, dans la barre d'outils :

2. Slectionnez la zone copier, en incluant la ligne de commande :

XLIV

LE DVELOPPEUR JAVA 2
3. Cliquez sur le bouton Copier pour placer une copie du message, sous forme de texte, dans le presse-papiers :

4. Collez le contenu du presse-papiers dans votre message.

Aller plus loin


Contrairement dautres auteurs, nous nous sommes attachs exposer dans ce livre les concepts fondamentaux de Java et de la programmation oriente objets. Vous ny trouverez donc que peu de recettes applicables sans quil soit ncessaire de comprendre les mcanismes sous-jacents. La pagination limite (plus de 1 000 pages tout de mme !) ne nous a pas permis de traiter la mise en uvre en situation relle des concepts abords, ni de dtailler les points les plus spcifiques. Si vous souhaitez aller plus loin, nous vous suggrons de vous reporter au second volume de la srie Le Dveloppeur Java 2 , intitul Mise en uvre et solutions - Les applets . Dans ce volume, nous laisserons de ct la thorie pour analyser des applications relles et exposer la faon dont les principes de la programmation objet et de Java sont exploits dans des situations concrtes. Les points suivants (entre autres) y sont abords :

Stratgie du dploiement dapplets sur le Web : comment concevoir


des applets afin quelles soient utilisables par le plus grand nombre possible de navigateurs, prsents et venir.

INTRODUCTION

XLV

Optimisation du chargement des applets : comment tirer le meilleur


parti possible de Java pour rduire au minimum les temps de chargement, rels et apparents.

Le traitement des images : techniques doptimisation pour la manipulation des images dans le cas des applets diffuses sur Internet.

Cration de composants personnaliss ultra-lgers : comment obtenir le look & feel de votre choix (Metal, par exemple) dans vos applets, y compris dans les navigateurs qui ne sont compatibles quavec Java 1.0.

Utilisation des threads pour grer la mise en cache des images sur
le Web : comment rendre le chargement des images instantan.

Le traitement du son : lAPI standard des versions de Java disponibles avec la plupart des navigateurs est trs pauvre en ce qui concerne le son. Il est pourtant possible de raliser des choses aussi complexes que la synthse du son en Java, sans laide daucune bibliothque supplmentaire.

Le streaming : comment utiliser des donnes au fur et mesure de


leur transmission ; application au streaming audio.

Utilisation

des classes Sun : ces classes non standard offrent de nombreuses possibilits supplmentaires. Pourquoi sen priver ! Pierre-Yves Saumont et Antoine Mirecourt, Paris, mai 2000

Note de lditeur Vous pouvez commander Le Dveloppeur Java 2, Mise en uvre et solutions - Les Applets chez votre libraire habituel ou, dfaut, sur le site des ditions OEM, ladresse http://www.oemweb.com ou encore sur le site de la librairie Eyrolles, l'adresse http://www.librairie-eyrolles.com.

Attention : Le livre Le Dveloppeur Java 2, Mise en uvre et solutions Les Applets est bas sur les connaissances dveloppes dans le prsent volume. Il est donc conseill de ne laborder quaprs avoir tudi celui-ci.

Chapitre 1 : Installation de Java

Installation de Java

CRIRE DES PROGRAMMES EN JAVA NCESSITE QUE VOUS DISPOsiez d'un certain nombre d'outils. Bien entendu, il vous faut un ordinateur quip d'un disque dur et d'une quantit de mmoire suffisante, ainsi que d'un systme d'exploitation. Vous pouvez utiliser n'importe quel ordinateur, condition que vous soyez en mesure de trouver le logiciel indispensable. Sun Microsystems diffuse gratuitement le compilateur et les autres outils ncessaires pour les environnements Windows, Linux et Solaris. En revanche, si vous utilisez un Macintosh, il vous faudra vous procurer les outils ncessaires auprs d'un autre fournisseur. Dans ce premier chapitre, nous supposerons que vous disposez d'un PC sous Windows 98. Les manipulations effectuer sont cependant pratiquement identiques dans le cas des autres environnements.

LE DVELOPPEUR JAVA 2
L'criture des programmes, elle, est strictement identique. En revanche, la faon de les excuter est diffrente sur un Macintosh, car son systme d'exploitation (Mac OS) ne dispose pas d'un mode ligne de commande. Vous devez donc utiliser un environnement de programmation qui fournisse une fentre de commande dans laquelle vous pourrez taper les commandes ncessaires. Sun Microsystems diffuse sur son site Web les dernires versions de ses logiciels. Les trois plus importants sont :

Le J2SDK

(Java 2 Software Development Kit), qui contient javac, le compilateur qui transforme votre programme source en bytecode, java, le programme permettant l'excution du bytecode constituant les applications, l'AppletViewer, pour excuter les applets, javadoc, un programme permettant de crer automatiquement la documentation de vos programmes au format HTML, et d'autres utilitaires2.

La documentation, qui contient la liste de toutes les classes Java.


Cette documentation est absolument indispensable au programmeur. Elle est au format HTML et doit tre consulte l'aide d'un navigateur.

Le J2RE (Java 2 Runtime Environment), qui contient tout ce qui est


ncessaire pour diffuser vos applications aux utilisateurs. Pour tre certain de disposer des dernires versions, vous pouvez vous connecter l'adresse :
http://java.sun.com/

1. Jusqu' la version 1.2, le J2SDK tait nomm JDK (Java Development Kit). De trs nombreux programmeurs (y compris chez Sun Microsystems) continuent d'utiliser cette dnomination. De la mme faon, le J2RE tait nomm simplement JRE. 2. Il existe deux versions du J2SDK, appeles respectivement J2SE (Java 2 Standard Edition) et J2EE (Java 2 Enterprise Edition). La seconde est une extension de la premire qui contientt des lments logiciels supplmentaires tels que le kit de dveloppement des Java Beans. Dans ce livre, nous ne parlerons que de J2SE.

CHAPITRE 1 INSTALLATION DE JAVA

et tlcharger ces trois lments (ainsi que bien d'autres !). Cependant, tant donn le volume de donnes que cela reprsente (30 Mo pour le J2SDK, 22 Mo pour la documentation, 5 Mo pour le J2RE en version amricaine et 7 Mo pour la version internationnale), le tlchargement peut durer plusieurs heures. Aussi, nous avons inclus le J2SDK, sa documentation et le J2RE sur le CD-ROM accompagnant ce livre. Si vous souhaitez vrifier qu'il s'agit bien de la dernire version, connectez-vous l'adresse indique la page prcdente. De toute faon, les versions fournies sur le CD-ROM sont suffisantes pour tous les exemples du livre. Le J2SDK et le J2RE figurant sur le CD-ROM portent le numro de version 1.3.0. Java a pris la dnomination Java 2 partir de la version 1.2.0.

Configuration ncessaire
Pour utiliser Java et le J2SDK, vous devez disposer d'une configuration suffisante. Nous dcrirons ici la configuration minimale pour les utilisateurs disposant d'un PC sous Windows.

Ordinateur
Sur PC, Java 2, dans sa version 1.3, ncessite un PC quip au moins d'un processeur Pentium Intel fonctionnant 166 MHz. Il va de soi qu'il s'agit d'un minimum et qu'il est souhaitable de disposer au moins d'un processeur deux fois plus rapide. Le temps de dveloppement s'en ressentira, surtout si vous compilez de grosses applications. Pour ce qui est de l'excution des programmes, vous devez, si possible, disposer d'une machine correspondant la configuration minimale sur laquelle votre application doit fonctionner. Vous serez ainsi en mesure de tester votre application dans les conditions relles, ce qui vitera de mauvaises surprises vos utilisateurs. Le fonctionnement avec des processeurs compatibles est possible mais n'est pas garanti par Sun.

Systme
Le J2SDK 1.3 est compatible avec les systmes d'exploitation Windows 95, Window 98, Windows NT 4.0 et Windows 2000. (Il existe des versions pour

LE DVELOPPEUR JAVA 2
les PC fonctionnant sous d'autres systmes, tel Linux.) L'utilisation d'un systme d'exploitation 16 bits (Windows 3.1) est exclue.

Mmoire
Vous devez disposer d'un minimum de 32 Mo de mmoire RAM pour faire fonctionner des applications fentres. L'utilisation d'applets dans un navigateur quip du plug-in Java ncessite 48 Mo. Une quantit de mmoire plus importante peut tre ncessaire pour certaines applications lourdes. On constate gnralement une forte diminution des performances en dessous de ces limites.

Disque
L'installation du J2SDK ncessite un espace libre de 65 Mo sur le disque dur. Pour une installation complte de la documentation, vous devez disposer de 120 Mo supplmentaires.

Installation du J2SDK
Pour installer le J2SDK, vrifiez tout d'abord que vous disposez, sur votre disque dur, d'un espace suffisant. Il faut 52 Mo pour une installation complte et 27 Mo pour une installation minimale. Toutefois, le processus d'installation utilise un certain nombre de fichiers temporaires. Pour une installation complte, l'espace utilis pendant l'installation monte jusqu' 65 Mo. Si vous souhaitez ajouter la documentation (ce qui est tout fait indispensable), vous devez disposer de 120 Mo supplmentaires. Pour lancer l'excution du J2SDK, excutez le programme :
j2sdk1_3_0-win.exe

se trouvant dans le dossier Java2 sur le CD-ROM. Suivez les instructions affiches. Le programme vous demande tout d'abord d'indiquer un dos-

CHAPITRE 1 INSTALLATION DE JAVA

Figure 1.1 : Choix d'un dossier d'installation.

sier d'installation (Figure 1.1). Il est fortement conseill d'accepter le dossier propos (c:\jdk1.3)1. Cela n'a techniquement aucune importance, mais facilite la dtection des erreurs en cas de problme. Lorsque le programme vous demande quels sont les composants que vous souhaitez installer, cochez ceux qui vous intressent, dcochez les autres (Figure 1.2). L'option Old Native Interface Header Files n'est absolument pas ncessaire. Les options Demos et Java Sources sont trs intressantes si vous disposez d'un espace suffisant sur votre disque dur. (Les dmos occupent prs de 5 Mo et les sources environ 20 Mo.) Il est trs instructif d'examiner les programmes de dmonstration afin de voir comment les problmes y sont rsolus. Quant aux sources de Java (crites en Java), leur tude est la meilleure faon de se familiariser avec les aspects les plus intimes du langage, mais cela demande toutefois d'avoir dj de bonnes notions de base !
1. Malgr tous les efforts faits par Sun Microsystems depuis la version 1.2 pour imposer la nouvelle dnomination (J2SDK), on voit ici qu'il reste encore du travail accomplir. Le rpertoire d'installation par dfaut devrait, logiquement, tre c:\j2sdk1.3.

LE DVELOPPEUR JAVA 2

Figure 1.2 : Les options d'installation.

Le programme d'installation place les fichiers dans le dossier jdk1.3, sur votre disque dur. Une fois l'installation termine, le programme configure l'environnement pour le J2RE (qui est automatiquement install en mme temps que le J2SDK), puis vous propose de consulter un fichier (README.txt) contenant diverses informations concernant cette version du J2SDK (Figure 1.3). Vous pouvez vous dispenser de le faire, une version HTML de ce document se trouvant dans le rpertoire d'installation du J2SDK. Outre qu'elle est beaucoup plus agrable consulter, elle contient de nombreux liens actifs vers les lments de la documentation et vers des pages du site Web de Sun Microsystems. Vous pourrez consulter ce document plus tard l'aide de votre navigateur. Le J2SDK est l'ensemble minimal que vous devez installer pour compiler et excuter des programmes Java. Cependant, avant de pouvoir utiliser le compilateur, il est ncessaire de configurer l'environnement, comme nous le verrons dans une prochaine section. Il est galement quasiment indispensable d'installer la documentation en ligne, ce que nous ferons bientt.

CHAPITRE 1 INSTALLATION DE JAVA

Figure 1.3 : Affichage du fichier README.txt.

Ce que contient le J2SDK


A l'installation, le J2SDK est plac dans un dossier qui comporte plusieurs sous-dossiers. Dans la version actuelle, ce dossier est nomm jdk1.3. Vous trouverez ci-aprs une description succincte du contenu de chacun de ces sous-dossiers, avec quelques commentaires. jdk1.3\bin Ce dossier contient essentiellement les fichiers excutables du J2SDK, c'est-dire :
java.exe

Le lanceur d'applications. C'est lui qui permet d'excuter les programmes que vous crivez. java.exe est trs souvent appel l'interprteur Java. Il s'agit d'un abus de langage. Les programmes Java sont compils, c'est--dire traduits dans un langage particulier, appel bytecode,

LE DVELOPPEUR JAVA 2
qui est le langage de la machine virtuelle Java (JVM). C'est la solution choisie par les concepteurs de ce langage pour que les programmes soient portables d'un environnement un autre. Chaque environnement compatible Java possde une JVM capable d'excuter le bytecode. Le bytecode peut tre excut de plusieurs faons. Il peut tre traduit dans le langage du processeur rel quipant l'ordinateur. C'est ce que fait un compilateur JIT. Il peut galement tre interprt par un programme appel interprteur. Jusqu' la version 1.1, l'implmentation de Sun Microsystems ne comportait qu'un interprteur. C'est pourquoi l'habitude a t prise de parler de l'interprteur Java. La version 1.2 comportait la fois un compilateur JIT et un interprteur. Par dfaut, l'excution des programmes tait effectue l'aide de ce compilateur1. La version 1.3 fait appel un programme trs sophistiqu permettant l'optimisation de l'excution du bytecode grce la technologie HotSpot. (Pour plus de dtails, voir l'Introduction.) Le programme java.exe n'est en fait qu'un lanceur qui charge le bytecode en mmoire et dclenche l'excution d'un interprteur ou d'un compilateur, selon la configuration. Nous continuerons toutefois, conformment l'usage tabli, de dsigner l'ensemble par le terme d'interprteur Java.2
javac.exe

Le compilateur Java. Il permet de traduire vos programmes Java en bytecode excutable par l'interprteur.
appletviewer.exe

Ce programme permet de tester les applets Java, prvues pour tre intgres dans des pages HTML.
jdb.exe

Le dbogueur Java. Il facilite la mise au point des programmes grce de nombreuses options permettant de surveiller leur excution. Il est cependant beaucoup plus facile utiliser lorsqu'il est intgr un IDE (Integrated Development Environment).
1. En fait, la version 1.1 comportait galement un compilateur JIT, mais celui-ci tait inactif par dfaut. 2. Les lanceurs java.exe et javaw.exe sont galement installs dans le rpertoire c:\windows. Pour plus de dtails, reportez-vous la section consacre l'installation du J2RE, page 27.

CHAPITRE 1 INSTALLATION DE JAVA


javap.exe

Ce programme dsassemble les fichiers compils et permet donc d'examiner le bytecode.


javadoc.exe

Javadoc est un utilitaire capable de gnrer automatiquement la documentation de vos programmes. Attention, il ne s'agit pas de la documentation destine aux utilisateurs finaux ! Ce programme est prvu pour documenter les bibliothques de classes qui sont destines tre utilises par les programmeurs. L'ensemble de la documentation de Java a t ralise l'aide de cet utilitaire, qui produit un rsultat sous forme de pages HTML. Si vous dveloppez des classes Java devant tre employes par d'autres programmeurs, cet outil vous sera indispensable.
javah.exe

Ce programme permet de lier des programmes Java avec des mthodes natives, crites dans un autre langage et dpendant du systme. (Ce type de programme n'est pas portable.)
jar.exe

Un utilitaire permettant de compresser les classes Java ainsi que tous les fichiers ncessaires l'excution d'un programme (graphiques, sons, etc.). Il permet en particulier d'optimiser le chargement des applets sur Internet.
jarsigner.exe

Un utilitaire permettant de signer les fichiers archives produits par jar.exe, ainsi que de vrifier les signatures. C'est un lment important de la scurit, et galement de l'efficacit. Nous verrons en effet que l'utilisation d'applets signes (dont on peut identifier l'auteur) permet de se dbarrasser des contraintes et limitations trs restrictives imposes ce type de programme. Le dossier bin contient encore une foule d'autres choses dont nous ne nous proccuperons pas pour le moment.

10
jdk1.2/demo Ce dossier comporte quatre sous-dossiers :
applets

LE DVELOPPEUR JAVA 2

Le dossier applets contient un ensemble de programmes de dmonstration sous forme d'applets. Chaque exemple comprend le fichier source (.java), le fichier compil (.class) et un fichier HTML permettant de faire fonctionner l'applet dans un navigateur. Certains exemples comportent galement des donnes telles que sons ou images. Ces exemples sont intressants pour avoir un aperu des possibilits offertes par Java, en particulier dans le domaine graphique.
jfc

Les exemples figurant dans le dossier jfc concernent essentiellement les nouveaux composants Swing de l'interface utilisateur. Si vous voulez en avoir un catalogue complet, excutez le programme SwingSet2. Ceux qui connaissaient la version prcdente (SwingSet, livre avec Java 1.2) pourront constater les immenses progrs en ce qui concerne la vitesse d'excution. Pour les autres, essayez le programme Metalworks. Lancez ce programme (en faisant un double clic sur l'icne du fichier Metalworks.jar), droulez le menu File et slectionnez New pour crer une nouvelle fentre de message (Figure 1.4). Metalworks est la simulation d'un programme de courrier lectronique. Droulez ensuite le menu Drag. Celui-ci comporte trois options : Live, pour afficher les fentres pendant leur dplacement, Outline, pour n'afficher que leur contour, et Old and Slow, pour utiliser la mthode de la version prcdente. Comparez le rsultat obtenu avec la premire et la troisime option, et mesurez ainsi les progrs accomplis !
sound

Ce dossier contient une application mettant en valeur les nouvelles fonctionnalits de Java 1.3 en matire de traitement des fichiers donnes audio et midi (Figure 1.5). Ces fonctionnalits avaient t an-

CHAPITRE 1 INSTALLATION DE JAVA

11

Figure 1.4 : Le programme de dmonstration Metalworks.

nonces pour la version 1.2, mais avaient subi un certain retard. Presque tout ce qui tait promis est maintenant prsent, y compris l'enregistrement audio. Toutefois, il manque encore la prise en charge de formats audio fortement compresss permettant le streaming. Par ailleurs, quelques imperfections perturbent la lecture des fichiers audio. Avec un processeur 300 MHz, la continuit de la lecture de certains formats n'est pas assure cent pour cent. Si cela n'offre pas d'inconvnient pour la parole, il n'en va pas de mme pour la musique. Les exemples au format rmf sont pratiquement incoutables dans ces conditions.
jpda

Ce dossier contient des exemples destins tre utiliss avec la Java Platform Debugger Architecture, ainsi que les sources de cette application.

12

LE DVELOPPEUR JAVA 2

Figure 1.5 : Une dmonstration des possibilits audio et midi de Java 2.

jdk1.3/docs Ce dossier n'existe que si vous avez install la documentation de Java (ce qui est absolument indispensable tout programmeur srieux). La partie la plus importante de la documentation se trouve dans le sous-dossier api. Il s'agit de la documentation de toutes les classes standard de Java. Le sous-dossier tooldocs contient les informations concernant les diffrents outils livrs avec Java.

jdk1.3/include et jdk1.3/include-old Ces dossiers contiennent des fichiers header C qui permettent de lier des programmes Java avec sous-programmes crits en C. Il est ainsi possible de raliser certaines applications faisant appel des ressources spcifiques un systme donn. Ces programmes ne sont videmment plus portables dans un autre environnement.

CHAPITRE 1 INSTALLATION DE JAVA


jdk1.3/lib

13

Ce dossier contient divers lments utilitaires mais ne contient plus les classes standard Java comme dans la versions 1.1. Depuis la version 1.2, celles-ci ont t reportes dans un nouveau dossier nomm jdk1.3/ jre/lib.

jdk1.3/jre Ce dossier contient les lments ncessaires l'excution des programmes, regroups sous le nom de Java 2 Runtime Environment. Il est galement utilis par le compilateur puisqu'il contient les classes standard Java. Le J2RE tant systmatiquement install avec le J2SDK, il n'est normalement pas ncessaire de l'installer sparment. jdk1.3/jre/bin Dans ce sous-dossier se trouvent les excutables utiliss par le J2RE, et en particulier :
java.exe

Une autre copie du lanceur d'applications, identique celle se trouvant dans le rpertoire jdk1.3/bin. jdk1.3/jre/bin/hotspot Ce dossier contient la JVM HotSpot, utilise par dfaut pour excuter les programmes Java. jdk1.3/jre/bin/classic Ce dossier contient la JVM classique, c'est--dire n'utilisant qu'un interprteur. Une option de la ligne de commande permet d'utiliser cette JVM pour excuter un programme. Il peut y avoir, pour cela, deux raisons. La premire consiste vrifier la diffrence de perfor-

14

LE DVELOPPEUR JAVA 2
mance entre les deux options. La seconde concerne le dbogage des programmes. Si un programme ne fonctionne pas comme prvu, il est toujours intressant de vrifier si le mme problme se pose avec la JVM classique. Cela est particulirement important pour les applications lourdes, telles que les serveurs. Au cas o vous auriez signaler aux programmeurs de Sun Microsystems un ventuel bug, ceux-ci vous demanderont systmatiquement si le problme se pose de la mme faon avec les deux JVM. La technologie HotSpot est trs prometteuse, mais encore un peu jeune. Il faut s'attendre quelques imperfections mineures. jdk1.3/jre/lib Ce dossier contient les lments ncessaires au fonctionnement de l'interprteur et du compilateur Java, et en particulier l'ensemble des classes standard compiles, contenues dans le fichier rt.jar. Vous ne devez surtout pas dcompresser ce fichier qui est utilis sous cette forme. On y trouve galement toutes sortes d'autres ressources ncessaires au fonctionnement de Java. jdk1.3/src.jar Ce fichier contient la quasi-intgralit des sources de Java, c'est--dire les programmes Java ayant servi produire les classes publiques compiles figurant dans le fichier rt.jar. Il y manque toutefois les classes prives java.* et sun.*. Il est trs instructif, une fois que vous avez acquis une certaine connaissance du langage, d'tudier ces fichiers afin de comprendre comment les concepteurs du langage ont rsolu les problmes qui se posaient eux. Si vous voulez examiner ces sources, vous pouvez les dcompresser l'aide du programme jar.exe ou encore de Winzip. Nous en verrons un exemple dans un prochain chapitre.

Configuration de l'environnement
La configuration de l'environnement comporte deux aspects :

CHAPITRE 1 INSTALLATION DE JAVA

15

Le chemin d'accs aux programmes excutables. Le chemin d'accs aux classes Java.
Le chemin d'accs aux programmes excutables
Pour configurer le chemin d'accs, ouvrez, l'aide d'un diteur de texte, le fichier autoexec.bat se trouvant dans la racine de votre disque dur. Ce fichier doit contenir quelques lignes semblables celles-ci :

@if errorlevel 1 pause @echo off mode con codepage prepare=((850) c:\windows\command... mode con codepage select=850 keyb fr,,c:\windows\command\keyboard.sys set blaster=a220 i5 d1 t4 set path=c:\windows;c:\progra~1\ultraedt

Votre fichier peut tre trs diffrent de celui-ci, mais l'important est de savoir s'il contient ou non une commande de configuration du chemin d'accs aux programmes excutables. Localisez la ligne commenant par set path, ou simplement path - les deux formes tant quivalentes -, et ajoutez la fin de celle-ci la commande suivante :
;c:\jdk1.3\bin

Dans l'exemple prcdent, la ligne complte devient :


set path=c:\windows;c:\progra~1\ultraedt;c:\jdk1.3\bin

ou :
path c:\windows;c:\progra~1\ultraedt;c:\jdk1.3\bin

16
un second.

LE DVELOPPEUR JAVA 2
Note 1 : Si la ligne se terminait dj par un point-virgule, n'en ajoutez pas Note 2 : Vous pouvez taper en majuscules ou en minuscules, cela n'a pas d'importance.
peut tout aussi bien commencer par path (sans le signe gal). Ces deux formes sont quivalentes. Si votre fichier autoexec.bat ne comportait pas de ligne commenant par set path, ajoutez simplement la ligne suivante la fin du fichier :
setpath=c:\jdk1.3\bin

Note 3 : La ligne commenant par

set path

ou :
path c:\jdk1.3\bin

La variable d'environnement path indique Windows les chemins d'accs qu'il doit utiliser pour trouver les programmes excutables. Les programmes excutables du J2SDK se trouvent dans le sous-dossier bin du dossier dans lequel le J2SDK est install. Ce dossier est normalement c:\jdk1.3. Si vous avez install le J2SDK dans un autre dossier, vous devez videmment modifier la ligne en consquence.

Attention : Windows cherche les programmes excutables tout d'abord


dans le dossier partir duquel la commande est tape, puis dans les dossiers dont les chemins d'accs sont indiqus par la variable path, dans l'ordre o ils figurent sur la ligne de configuration set path. Ainsi, si vous avez install le J2SDK 1.2.2, votre fichier autoexec.bat contient (par exemple) la ligne :
setpath=c:\windows\;c:\jdk1.2.2\bin

Si vous ajoutez le chemin d'accs au rpertoire bin du J2SDK 1.3 ainsi :

CHAPITRE 1 INSTALLATION DE JAVA


setpath=c:\windows\;c:\jdk1.2.2\bin;c:\jdk1.3\bin

17

le compilateur du J2SDK 1.3 ne sera jamais excut car son fichier excutable porte le mme nom que dans la version 1.2.2. Le chemin d'accs cette version se trouvant avant celui de la nouvelle version dans la ligne setpath, c'est l'ancienne version qui sera excute1.

Note : Pour que les modifications apportes au fichier autoexec.bat soient


prises en compte, vous devez redmarrer Windows. Le plus simple est de redmarrer votre PC.

Le chemin d'accs aux classes Java


Le chemin d'accs aux classes Java peut tre configur exactement de la mme faon l'aide de la variable d'environnement classpath, en ajoutant au fichier autoexec.bat une ligne :
setclasspath = ...

Cependant, vous n'avez en principe pas besoin de configurer cette variable pour l'instant. En effet, nous n'utiliserons dans un premier temps que les classes standard de Java (en plus de celles que nous crerons directement dans nos programmes) et le compilateur saura les trouver !

Note : Le J2SDK a t modifi depuis la version 1.2.0 pour quil ne soit


plus ncessaire de configurer le chemin daccs aux classes Java. Cependant, cette modification ne fonctionne que si aucun chemin daccs nest indiqu. En effet, si Java trouve toujours ses propres classes, il ne trouvera les vtres que dans les cas suivants :

Aucun chemin n'est indiqu par la variable classpath et vos classes se


trouvent dans le rpertoire courant.
1. En revanche, les lanceurs java.exe et javaw.exe tant installs galement dans le rpertoire de windows, les nouvelles versions remplacent les anciennes. Les programmes sont donc compils avec l'ancienne version et excuts avec la nouvelle, ce qui est une source d'erreur potentielle.

18

LE DVELOPPEUR JAVA 2

Le chemin d'accs vos classes est indiqu par la variable classpath.


Il existe de nombreux cas dans lesquels un chemin daccs peut avoir t configur sur votre machine :

Vous avez utilis ou vous souhaitez utiliser une version prcdente du


J2SDK ou JDK.

Vous utilisez des extensions Java qui ncessitent la configuration de la


variable classpath.

Vous avez install des applications qui ont configur cette variable.
Dans les deux premiers cas, il vous suffit de savoir comment faire cohabiter les diffrentes versions de Java ainsi que les extensions. Cest ce que nous expliquerons dans quelques instants. Le troisime cas est beaucoup plus sournois. Vous pouvez avoir install une application qui aura configur la variable classpath sans vous en informer. Les applications correctement crites ajoutent leurs chemins daccs ceux existants. Pour autant, certaines applications agressives ne respectent pas cette politesse lmentaire et suppriment simplement le chemin daccs indiqu dans le fichier autoexec.bat. Dans le cas du J2SDK 1.3, mme les applications non agressives posent des problmes. En effet, le chemin daccs aux classes Java ntant pas indiqu dans le fichier autoexec.bat, elles ajoutent simplement une ligne setclasspath, ce qui a pour effet dinhiber la configuration automatique du chemin daccs au rpertoire courant par le J2SDK. Une application telle que Adobe PhotoDeluxe est la fois agressive et trs rpandue : trs rpandue car elle est livre avec de nombreux scanners ; agressive car elle supprime le chemin daccs existant et le remplace par le sien. Dautres applications tentent dajouter leur chemin daccs ceux existants mais, ne trouvant rien dans autoexec.bat, elles se contentent de crer leur chemin. Le rsultat est que, lorsque vous essayez d'excuter un programme, Java ne le trouve pas, mme s'il est dans le rpertoire courant. Vous obtiendrez le message :

Exceptioninthread"main"java.lang.NoclassDefFoundError:xxx

CHAPITRE 1 INSTALLATION DE JAVA

19

o xxx est le nom du programme que vous essayez d'excuter. Si vous obtenez ce message, ouvrez votre fichier autoexec.bat et vrifiez sil sy trouve une ligne telle que :

set classpath = C:\Program Files\PhotoDeluxe 2.0\AdobeConnectables

Si cest le cas, remplacez cette ligne par :

set classpath = .;C:\Program Files\PhotoDeluxe 2.0\AdobeConnectables

Le point dsigne tout simplement le rpertoire courant. De cette faon, le chemin daccs de PhotoDeluxe est ajout au chemin daccs courant et tout rentre dans lordre ! Vous pouvez traiter prventivement le problme des applications non agressives en ajoutant votre fichier autoexec.bat la ligne :
set classpath = .

Vous pouvez galement indiquer explicitement le chemin daccs vos classes Java. Si votre rpertoire de travail est c:\java, voici la commande utiliser :

set classpath = c:\java

De cette faon, les futures applications dtecteront la prsence de cette ligne et ajouteront leur chemin daccs. En ce qui concerne les applications agressives, il ny a gure dautre solution que de protger le fichier autoexec.bat contre lcriture. Toutefois, cette solution nest possible que pour vous et non pour les utilisateurs de vos programmes. Si vous distribuez des applications Java, il vous faudra tenir compte de ce problme.

20

LE DVELOPPEUR JAVA 2
Note 1 : Il est gnralement beaucoup plus pratique d'utiliser le point
pour dsigner le rpertoire courant que d'indiquer explicitement un rpertoire de travail. Dans le second cas, vous devrez modifier la variable classpath chaque fois que vous changerez de rpertoire.

Note 2 : Vous pouvez parfaitement modifier temporairement le chemin


daccs aux classes Java, par exemple laide dun fichier batch tel que :
set ancienclasspath = %classpath% set classpath = java monprogramme set classpath = %ancienclasspath% set ancienclasspath=

(La dernire ligne efface la variable ancienclasspath afin de ne pas gaspiller l'espace mmoire affect l'environnement.)

Attention : Pour pouvoir effectuer cette manipulation, vous devez disposer dun environnement de taille suffisante. Sous Windows, si vous obtenez un message indiquant que la taille de lenvironnement est insuffisante, augmentez celle-ci laide de longlet Mmoire de la bote de dialogue des proprits du raccourci servant excuter le fichier batch, comme indiqu sur la Figure 1.6.

Note : La variable d'environnement

classpath ne peut tre configure qu' l'aide de la commande set classpath=. Il n'existe pas de forme quivalente sans le mot set.

Amliorer le confort d'utilisation


Pour utiliser le J2SDK, vous devrez ouvrir une fentre MS-DOS. Si vous utilisez la commande Commandes MS-DOS du sous-menu Programmes du menu Dmarrer de la barre de tches, la fentre s'ouvrira par dfaut dans le dossier c:\windows, alors que vous prfreriez srement qu'elle s'ouvre dans un dossier spcifique. Par exemple, nous utiliserons le dossier c:\java pour y placer nos programmes. Pour simplifier le travail, vous pouvez crer sur votre bureau une icne qui ouvrira une fentre MS-DOS dans le dossier c:\java. Pour crer une telle icne, procdez de la faon suivante :

CHAPITRE 1 INSTALLATION DE JAVA

21

Figure 1.6 : Modification de la taille de l'environnement d'une session DOS.

1. Ouvrez le dossier c:\windows, localisez le fichier command.com et faitesle glisser sur votre bureau. Windows ne dplace pas le fichier mais cre un raccourci (Figure 1.7).

2. Renommez ce raccourci pour lui donner un nom plus significatif, par


exemple Java 2.

3. Cliquez l'aide du bouton droit de la souris sur l'icne cre. Un menu


contextuel est affich. Slectionnez l'option Proprits, pour afficher la fentre des proprits du raccourci (Figure 1.8).

Figure 1.7 : Cration d'un raccourci pour lancer une session DOS.

22

LE DVELOPPEUR JAVA 2
4. Cliquez sur l'onglet Programme de la fentre affiche et modifiez l'option Rpertoire de travail pour indiquer le dossier dans lequel vous allez placer vos programmes Java, par exemple c:\java. Vous pouvez galement, si vous le souhaitez, modifier l'icne du raccourci en cliquant sur le bouton Changer d'icne.

5. Cliquez sur l'onglet Police et slectionnez la police de caractres qui


vous convient. La police idale dpend de vos gots et de la configuration de votre cran. Nous avons choisi la police 10 x 20.

6. Refermez la fentre. Vous pourrez maintenant ouvrir une fentre MSDOS directement dans le dossier c:\java en faisant simplement un double clic sur l'icne.

Le Java 2 Runtime Environment


Le J2SDK contient tout ce qui est ncessaire pour dvelopper des programmes Java. (Ou, plus exactement, presque tout le minimum ncessaire. D'une

Figure 1.8 : Modification des proprits du raccourci.

CHAPITRE 1 INSTALLATION DE JAVA

23

part il manque, en effet, un diteur, et, d'autre part, on peut ajouter une multitude d'outils pour amliorer la productivit du programmeur.) En particulier, le J2SDK contient une machine virtuelle Java (ou, plus exactement, deux machines virtuelles utilisant des technologies diffrentes) permettant d'excuter les programmes. Cependant, les programmes que vous dvelopperez seront probablement conus pour tre utiliss par d'autres. Une phase importante de la vie d'un programme est ce que l'on appelle le dploiement, terme qui englobe toutes les faons dont un programme est diffus vers ses utilisateurs. La faon traditionnelle de dployer une application consiste faire une copie du code excutable sur un support magntique et diffuser ce support vers les utilisateurs. Le support magntique peut contenir simplement le code de l'application, que les utilisateurs devront excuter depuis le support. C'est le cas, par exemple, de certains CD-ROM dits cologiques. Ce terme signifie que l'excution du programme ne ncessite aucune modification du systme de l'utilisateur. Dans la plupart des cas, cependant, l'application ne peut tre utilise qu'aprs une phase d'installation, qui inclut la copie de certains lments sur le disque dur de l'utilisateur, et la modification du systme de celui-ci. Dans le cas d'une application multimdia fonctionnant sous Windows, l'installation comporte gnralement la cration d'un dossier, la copie dans ce dossier d'une partie du code excutable, la cration dans ce dossier de fichiers de configuration, la copie dans le dossier systme de Windows d'autres parties du code excutable, la cration d'un ou de plusieurs raccourcis permettant de lancer l'excution partir du bureau ou du menu Dmarrer de la barre de tches, la cration de nouvelles entres ou la modification d'entres existantes dans la base de registres, etc. Les donnes utilises par l'application peuvent galement tre copies sur le disque dur, ou tre utilises depuis le CD-ROM, ce qui peut constituer un moyen (limit) de protection de l'application contre la copie. La procdure qui vient d'tre dcrite concerne les applications dont le code est directement excutable par le processeur de l'ordinateur de l'utilisateur. Dans le cas d'une application Java, ce n'est pas le cas, puisque le programme est compil en bytecode, c'est--dire dans le langage d'un pseudo-processeur appel JVM, ou machine virtuelle Java . Si le dveloppeur sait que chaque utilisateur dispose d'une machine quipe

24

LE DVELOPPEUR JAVA 2
d'une JVM, nous sommes ramens au cas prcdent : celui-ci peut se prsenter dans le cadre d'un dploiement interne, chez des utilisateurs appartenant tous une mme entreprise et disposant tous d'une machine dont la configuration est connue. Cela peut tre le cas, d'une certaine faon, lorsque l'application est diffuse sous forme d'applet, c'est--dire sous la forme d'un programme intgr une page HTML diffuse, par exemple, sur Internet ou sur un rseau d'entreprise. Chaque utilisateur est alors suppos disposer d'un navigateur quip d'une JVM. Si ce n'est pas le cas, c'est lui de se dbrouiller. En particulier, il lui revient de mettre jour son navigateur afin qu'il soit quip d'une JVM correspondant la version de Java utilise pour votre application. Ainsi, une JVM 1.0 ne pourra faire fonctionner les programmes crits en Java 1.1 ou Java 2 (1.2 ou 1.3)1. (En revanche, les nouvelles JVM sont compatibles avec les anciens programmes.) S'il s'agit d'une application devant tre diffuse au public sur un CD-ROM, vous ne pouvez pas supposer que chaque utilisateur sera quip d'une JVM. Cela viendra peut-tre un jour, du moins on peut l'esprer. C'est seulement alors que Java sera devenu vritablement un langage universel. En attendant ce jour, il est ncessaire d'accompagner chaque application d'une machine virtuelle. C'est cette fin que Sun Microsystems met la disposition de tous, gratuitement, le J2RE, ou Java 2 Runtime Environment. Celuici contient tous les lments ncessaires pour faire fonctionner une application Java, par opposition au J2SDK, qui contient tout ce qui est ncessaire pour dvelopper une application. Le J2RE peut tre diffus librement sans

1. Cette affirmation n'est pas totalement exacte. En fait, une JVM 1.0 est parfaitement capable d'excuter du bytecode produit par un compilateur d'une version plus rcente. En revanche, elle ne peut excuter un programme faisant appel des classes standard des versions suivantes, moins de lui rendre celles-ci accessibles. Si ces classes peuvent tre installes avec l'application, il est nettement prfrable d'installer aussi une JVM rcente, qui permettra d'obtenir de bien meilleures performances. Pour une applet diffuse sur Internet, cela n'est pas possible car seules les classes peuvent tre tlcharges. (Il s'agit d'une contrainte lie la scurit des applets.) Cela peut tre fait pour certaines classes qui sont utilises seules, mais c'est pratiquement impossible pour d'autres, comme les classes Swing, qui sont troitement connectes et ncessiteraient le tlchargement d'une dizaine de mgaoctets. Il existe cependant des solutions pour parvenir un rsultat quivalent. Elles dpassent toutefois le cadre de ce livre et sont dcrites dans l'ouvrage Le Dveloppeur Java 2, Mise en uvre et solutions - Les applets, chez le mme diteur.

CHAPITRE 1 INSTALLATION DE JAVA

25

payer aucun droit Sun Microsystems. Vous trouverez sur le site de Sun, l'adresse :
http://java.sun.com/j2se/1.3/jre/download-windows.html

les versions Solaris, Windows et bientt Linux, du J2RE. La version Windows est galement prsente sur le CD-ROM accompagnant ce livre.

Note : Il existe deux versions du JRE, avec ou sans support de l'internationalisation. La version sans est plus compacte mais ne convient que pour les applications utilisant exclusivement l'anglais. Vous prfrerez probablement la version internationale. C'est celle qui a t choisie pour figurer sur le CD-ROM accompagnant ce livre. Bien sr, si vous voulez que votre application soit galement utilisable sur d'autres systmes (Macintosh, par exemple), il faudra vous procurer l'quivalent pour ces systmes. Pour trouver o vous procurer ces lments, vous pouvez vous connecter l'adresse :

http://java.sun.com/cgi-bin/java-ports.cgi

Si vous tes plus particulirement intress par Java sur Macintosh, visitez le site Java d'Apple l'adresse :

http://devworld.apple.com/java/

Vous y trouverez des liens permettant de tlcharger le MRJ 2.2 (Mac Os Runtime for Java) et le MRJ SDK 2.2, qui permet aux programmeurs d'installer le MRJ avec leur application Java. C'est donc l'quivalent du JRE pour le Macintosh. Vous trouverez galement sur ce site de nombreuses informations sur l'utilisation de Java sous Mac Os, et en particulier un Tutorial qui vous guidera pas pas dans l'criture, la compilation et l'excution d'une application Java pour Macintosh.

26
Installation du J2RE Windows

LE DVELOPPEUR JAVA 2

L'installation du J2RE est inutile pour le dveloppement de programmes Java. Elle n'est pas non plus ncessaire pour le dploiement des applications. En effet, le J2RE pour Windows est diffus sous la forme d'un programme excutable permettant de raliser automatiquement son installation. Ce programme, nomm j2re1_3_0-win-i.exe1, peut tre appel par votre programme d'installation. Vous pouvez galement demander l'utilisateur de le lancer lui-mme. (Si vous diffusez plusieurs applications Java, l'utilisateur devra probablement avoir le choix d'installer ou non le J2RE selon qu'il aura dj install ou non d'autres applications comportant cet lment.) Pourquoi, alors, installer le J2RE ? Il y a deux raisons cela. La premire est qu'il est prfrable de tester votre application avec le J2RE avant de la diffuser. La seconde est qu'il est possible de ne pas diffuser intgralement le J2RE, mais seulement les parties qui sont indispensables pour l'excution de votre application. La liste des fichiers indispensables figure dans le fichier Readme install dans le mme dossier que le J2RE. Si vous dcidez d'installer vous-mme le J2RE comme une partie de votre application (sans utiliser le programme d'installation fourni), vous ne devez pas essayer de reproduire la procdure d'installation, mais installer le J2RE dans un sous-dossier de votre application. En ne procdant pas de cette faon, vous risqueriez, chez certains utilisateurs, d'craser une installation existante, ce qui pourrait empcher le fonctionnement des applications Java prcdemment installes. L'inconvnient de ne pas utiliser l'installation standard est que le disque de l'utilisateur se trouve encombr par autant d'exemplaires du J2RE qu'il y a d'applications Java2.

1. Il s'agit de la version internationale. La version US est nomme j2re1_3_0-win.exe. 2. Il existe des logiciels d'installation et de dploiement des applications Java qui permettent de grer trs simplement ces problmes. Le plus efficace est incontestablement InstallShield for Java. Ce programme dtecte la prsence de JVM sur le disque de l'utilisateur et lui permet de slectionner celle qu'il souhaite utiliser, ou d'installer celle qui accompagne l'application. Pour plus de dtails, reportez-vous au site Web de la socit InstallShield, l'adresse http://www.installshield.com/.

CHAPITRE 1 INSTALLATION DE JAVA

27

Sous Windows, si vous utilisez le programme d'installation fourni, le J2RE est install par dfaut dans le dossier c:\ProgramFiles\JavaSoft\JRE\1.3. Le programme d'installation se charge d'effectuer les modifications ncessaires dans la base de registre de Windows. Il n'est pas ncessaire de configurer la variable d'environnement path, car les lanceurs d'application java.exe (pour les applications en mode console) et javaw.exe (pour les applications fentres) sont installs galement dans le dossier Windows, qui fait automatiquement partie du chemin d'accs par dfaut. Par ailleurs, le programme d'installation associe l'extension .jar avec le lanceur javaw.exe, de telle faon qu'il est possible d'excuter un programme Java en faisant un double clic sur son icne, condition que celui-ci (et tous les lments dont il a besoin pour fonctionner) ait t plac dans une archive comportant le manifeste adquat.1

Note : Le programme
classpath.

jre.exe n'utilise pas la variable d'environnement Si un chemin d'accs doit tre indiqu pour les classes de votre application, cela peut tre fait l'aide d'une option de la ligne de commande lorsque le lanceur java.exe est employ. Dans le cas des archives excutables, il est prfrable d'organiser les classes de l'application de faon qu'aucun chemin d'accs ne soit ncessaire. Nous reviendrons sur ce point dans un prochain chapitre.

Installation de la documentation en ligne


La documentation en ligne est un lment absolument indispensable pour le dveloppeur d'applications Java. Celle-ci peut tre consulte directement sur le site de Sun Microsystems, mais cela implique des heures de connexion Internet qui peuvent coter fort cher. Il est beaucoup plus conomique de tlcharger cette documentation. Elle est disponible sous la forme d'un fichier compress de type zip2 sur le site de Sun Microsystems.

1. Un manifeste est un fichier contenant des informations sur une archive. Pour qu'une archive soit excutable, son manifeste doit indiquer la classe qui comporte le point d'entre du programme. Tout cela deviendra plus clair la lecture de la suite de ce livre. 2. Le format zip est destin aux utilisateurs de Windows. La documentation est galement disponible dans d'autres formats plus courants sur d'autres plates-formes.

28

LE DVELOPPEUR JAVA 2
Afin de vous viter une longue attente, nous avons galement inclus cette documentation sur le CD-ROM accompagnant ce livre. Vous la trouverez sous la forme d'un fichier nomm j2sdk1_3_0-doc.zip. Ce fichier doit tre dcompact l'aide d'un utilitaire tel que Winzip. (Winzip est un programme en shareware, c'est--dire que vous pouvez l'utiliser gratuitement pour l'essayer. S'il convient l'usage que vous voulez en faire et si vous dcidez de le garder, vous devrez le payer.) C'est un investissement indispensable ! Winzip est disponible partout sur Internet. La version franaise est disponible exclusivement l'adresse :
http://www.absoft.fr/

Vous trouverez galement une copie de ce programme sur le CD-ROM accompagnant ce livre. La documentation, une fois dcompacte, doit tre place dans le sousdossier docs du dossier d'installation du J2SDK. Le chemin d'accs complet est donc gnralement :
c:\jdk1.3\docs

Toutefois, vous n'avez pas vous proccuper de l'emplacement de la documentation si vous avez install le J2SDK dans le dossier propos par dfaut (c:\jdk1.3). Dans ce cas, il vous suffit d'indiquer comme dossier cible, lors du dcompactage l'aide de Winzip, la racine de votre disque dur ( c:\). Les fichiers constituant la documentation sont en effet archivs avec leurs chemins d'accs complets correspondant une installation standard. Une raison de plus pour ne pas modifier le dossier d'installation ! Pour consulter la documentation, vous devrez utiliser un navigateur. N'importe quel navigateur convient pour cet usage. Cependant, si vous dveloppez des applets, vous aurez besoin d'un navigateur compatible avec Java 2. Autant faire d'une pierre deux coups en utilisant le mme. La plupart des navigateurs ne sont pas d'une ergonomie poustouflante lorsqu'il s'agit d'accder un fichier local. Pour simplifier l'accs la do-

CHAPITRE 1 INSTALLATION DE JAVA

29

cumentation, il est donc conseill d'utiliser une des possibilits offertes par les navigateurs. La solution la plus simple consiste placer sur votre bureau un raccourci vers le document index de la documentation. Ce document est, en principe, contenu dans le fichier index.html se trouvant dans le sous-dossier docs du dossier d'installation du J2SDK. Ce document comporte plusieurs liens vers divers documents accompagnant le J2SDK (fichier readme, copyright, licence d'utilisation, etc.), ainsi que vers les applets de dmonstration et vers plusieurs pages du site de Sun Microsystems consacr Java. Le lien le plus important pointe vers la documentation de l'API. Il ouvre le document index.html du dossier docs/api. C'est le plus souvent ce document que vous accderez. Il est donc utile de crer un raccourci pour celui-ci, ce que vous pouvez faire trs simplement de la faon suivante :

1. Localisez le document index.html en ouvrant le dossier jdk1.3 puis le


dossier docs et enfin le dossier api.

2. Cliquez sur ce document avec le bouton droit de la souris et faites-le


glisser sur votre bureau. Lorsque vous relchez le bouton de la souris, un menu contextuel est affich.

3. Slectionnez l'option Crer un ou des raccourci(s) ici. Windows cre un


raccourci sur votre bureau. Vous n'aurez qu' faire un double clic sur cette icne pour ouvrir votre navigateur et accder directement la table des matires de la documentation. Vous pouvez galement ajouter ce document la liste des signets ou des favoris (selon le navigateur que vous utilisez).

Un navigateur HTML compatible Java


Nous avons vu que vous aurez besoin d'un navigateur HTML pour consulter la documentation de Java, ainsi que pour vous connecter aux diffrents sites Web sur lesquels vous trouverez toutes les informations concernant les dernires volutions du langage. N'importe quel navigateur peut convenir pour cet usage. En revanche, si vous souhaitez dvelopper des applets

30

LE DVELOPPEUR JAVA 2
(c'est--dire des applications pouvant tre dployes sur Internet pour fonctionner l'intrieur de pages HTML), vous devrez les tester dans un navigateur compatible Java. Lequel choisir ? La rponse est diffrente selon le public auquel s'adressent vos applets.

HotJava
Si vos applets sont destines un usage interne, par exemple au sein d'un rseau d'entreprise, vous avez peut-tre la matrise (au moins partielle) du choix de l'quipement. Dans ce cas, vous devez choisir un navigateur qui vous simplifie la tche. Si ce navigateur est destin uniquement afficher des pages HTML contenant des applets Java, et si tous les utilisateurs disposent de PC sous Windows ou d'ordinateurs Sun sous Solaris, le navigateur de Sun Microsystems, HotJava 3.0, est un bon candidat. Il est disponible gratuitement l'adresse suivante :
http://java.sun.com/products/hotjava/

Il est galement prsent sur le CD-ROM accompagnant ce livre. Le navigateur HotJava est entirement crit en Java. Il peut d'ailleurs tre intgr certaines applications. C'est celui qui vous garantira la compatibilit maximale avec le standard Java. En revanche, pour accder des sites Web sur Internet, il prsente certains inconvnients. La plupart des sites Web sont tests sous Netscape Navigator et Internet Explorer. Si vous accdez ces sites avec HotJava, attendez-vous ce que le rsultat affich soit un peu, voire trs, diffrent de ce que les concepteurs avaient souhait. Par ailleurs, HotJava n'est compatible ni avec les plug-ins Netscape, ni avec les composants ActiveX.

Netscape Navigator
Si vous souhaitez tester vos applets dans l'environnement le plus rpandu, vous devez possder une copie de Netscape Navigator. Ce navigateur est en effet le plus utilis dans le monde. Cela ne devrait toutefois pas durer en

CHAPITRE 1 INSTALLATION DE JAVA

31

raison des efforts acharns de Microsoft. (En France, la situation est un peu diffrente.) Il est gratuit, et son code source est diffus librement. De plus, il peut tre mis jour automatiquement en ligne pour s'adapter aux nouvelles versions de la machine virtuelle Java (JVM) de Sun. Il est compatible avec le JDK 1.1 depuis la version 4 ( condition, pour les versions 4.0 4.05, d'effectuer une mise jour). Il peut tre tlcharg gratuitement depuis le site de Netscape, l'adresse :
http://www.netscape.com/download/

Malheureusement, Navigator comporte de nombreux inconvnients. En effet, et paradoxalement si l'on tient compte du fait que Netscape dfend avec acharnement le standard Java aux cts de Sun et d'IBM contre Microsoft, il est beaucoup moins compatible que son concurrent ! On peut citer quelques dsagrments rencontrs par ses utilisateurs :

Il ne permet d'utiliser qu'un seul fichier d'archive pour une applet, Les fentres ouvertes par des applets ont une hauteur infrieure de 18
pixels par rapport la hauteur spcifie.

alors que son concurrent (Internet Explorer) en accepte un nombre quelconque.

Il restreint plus que ncessaire l'accs aux paramtres du systme pour

les applets (ce qui interdit de prendre en compte de faon dynamique le problme prcdent).

Sa JVM 1.1 ne permet pas l'excution des programmes compatibles 1.1


mais compils avec la version 1.2 (Java 2) du compilateur.

Sa gestion de la mise en cache des classes est pour le moins spcifique, ce qui conduit un ralentissement global des applets. Nous voulons bien croire que Netscape soit le chevalier blanc qui lutte contre la volont hgmonique de Microsoft, mais il lui faudrait tout de mme commencer par balayer devant sa propre porte. C'tait l'intention annonce par Netscape avec la version 5 de son navigateur. Cette version

32

LE DVELOPPEUR JAVA 2
devait tre totalement compatible Java 2. Entre-temps, Netscape a t rachet par AOL et la version 5 n'est jamais sortie. Netscape annonce maintenant une version 6 qui devrait combler les utilisateurs de Java. L'alliance entre AOL et Netscape est en effet conforte par un certain nombre d'accords entre Sun Microsystems et AOL. Etant donn le nombre d'abonns ce fournisseur d'accs Internet et les dboires que subit actuellement Microsoft devant la justice amricaine, on tient peut-tre l une occasion de voir se gnraliser des navigateurs compatibles Java 2. Au moment o ces lignes sont crites, une prversion est disponible sur le site de Netscape.

Internet Explorer
De nombreux utilisateurs, surtout en France, utilisent le navigateur Internet Explorer. Celui-ci est galement gratuit, mais son code source n'est pas disponible. Le but peine voil de son diteur est de lutter contre la diffusion du standard Java, dans lequel il voit un risque de contestation de son hgmonie sur le march du logiciel pour PC. La stratgie adopte consiste diffuser gratuitement en trs grosse quantit (en l'intgrant Windows) un produit incomplet par certains aspects (il manque l'implmentation de RMI, qui fait de l'ombre la technologie concurrente de Microsoft), et qui, dans d'autres domaines, tend le standard Java en proposant des fonctionnalits supplmentaires. Le but de la manuvre est de rendre les utilisateurs dpendant de ces fonctions qui constituent des amliorations, mais qui ne seraient en fait, d'aprs certains, que des piges pour rendre les utilisateurs captifs. La justice amricaine a d'ailleurs tranch, la suite d'une action intente par Sun Microsystems : Internet Explorer, pas plus que les autres produits du mme diteur, ne peut porter la marque ou le logo Java, moins d'tre mis en conformit avec le standard. Il n'en reste pas moins que Internet Explorer 5 est beaucoup plus compatible avec le standard Java que Netscape Navigator 4 ! Toutes les applets que nous avons pu crire et tester avec les outils de Sun Microsystems ont toujours fonctionn sans problme avec le navigateur de Microsoft. En revanche, nous avons pass des nuits blanches chercher pourquoi ces mmes applets crites en pur Java ne fonctionnaient pas avec Navigator 4. La solution a toujours t un bidouillage trs peu satisfaisant et certainement pas conforme l'esprit de Java. Dans ces conditions, qui dvoie le standard ?

CHAPITRE 1 INSTALLATION DE JAVA

33

Notez que Internet Explorer peut tre modifi en remplaant la machine virtuelle Java d'origine par une autre 100 % conforme au standard1 .

Un diteur pour la programmation


Un outil important, absolument ncessaire pour dvelopper des programmes en Java, comme dans la plupart des autres langages, est lditeur de texte. Dans le cas de Java, vous pouvez utiliser n'importe quel diteur. Le bloc-notes de Windows peut convenir, mme si ce n'est vraiment pas l'idal, tant ses fonctions sont limites. Vous pouvez galement employer un programme de traitement de texte, pour autant qu'il dispose d'une fonction permettant d'enregistrer en mode texte seulement, c'est--dire sans les enrichissements concernant les attributs de mise en page. L aussi, c'est loin d'tre la solution idale, tant donn la lourdeur de ce type d'application.

Attention : Le bloc-notes de Windows ajoute systmatiquement l'extension .txt aux noms des fichiers que vous crez, mme si ceux-ci comportent dj une extension. Ainsi, si vous enregistrez un programme dans le fichier PremierProgramme.java , le bloc-notes transforme son nom en PremierProgramme.java.txt. Vous devez alors renommer ce fichier pour pouvoir le compiler car le compilateur Java exige que les programmes sources soient placs dans des fichiers comportant l'extension .java. Cela n'est pas trs pratique. De plus, il existe un risque d'erreur potentiel important d une particularit de Windows. Ce systme peut tre configur (c'est le cas, par dfaut) pour masquer les extensions des fichiers dont le type est connu. Le type txt (texte) tant obligatoirement connu, Windows, s'il est

1. Ne confondez pas conforme et compatible. Internet Explorer n'est pas conforme au standard car il y ajoute un certain nombre de fonctions. Pour autant, il n'en est pas moins en grande partie compatible (sauf en ce qui concerne la technologie RMI). Il est mme, l'heure actuelle, plus compatible que Netscape Navigator. Le problme vient de ce que, pour le dveloppeur, une JVM doit tre capable de faire tourner tous les programmes conformes au standard, mais rien d'autre que ceux-ci. Sinon, le programmeur peut, presque son insu, utiliser des fonctions qui seront incompatibles avec d'autres navigateurs. A l'heure actuelle, la seule solution consiste tester les applets avec tous les navigateurs prsents sur le march, ce qui n'est pas une mince affaire.

34

LE DVELOPPEUR JAVA 2
configur de cette faon, affichera le nom du fichier sans l'extension. Le programme PremierProgramme.java.txt apparatra donc sous la forme PremierProgramme.java. Lorsque le compilateur refusera de le compiler, vous aurez peut-tre un peu de mal localiser l'erreur. Il est donc nettement prfrable d'utiliser, pour crire des programmes, un outil mieux adapt.

Ce que doit offrir un diteur


La meilleure solution consiste donc utiliser un diteur de texte, plus sophistiqu que le bloc-notes, mais dbarrass de la plupart des fonctions de mise en page d'un traitement de texte. En fait, la fonction la plus importante d'un diteur de texte destin au dveloppement de programmes est la capacit de numroter les lignes. En effet, les programmes que vous crirez fonctionneront rarement du premier coup. Selon la philosophie de Java, un nombre maximal d'erreurs seront dtectes par le compilateur, qui affichera une srie de messages indiquant, pour chaque erreur, sa nature et le numro de la ligne o elle a t trouve. Si votre diteur n'affiche pas les numros de lignes, vous risquez d'avoir beaucoup de mal vous y retrouver. Il existe de nombreux diteurs offrant des fonctions plus ou moins sophistiques d'aide au dveloppement de programmes. L'une d'elles consiste afficher les mots cls du langage dans une couleur diffrente, ce qui facilite la lecture du code et aide dtecter les erreurs. En effet, si vous tapez un mot cl, suppos s'afficher, par exemple, en rouge, et que celui-ci reste affich en noir, vous saurez immdiatement que vous avez fait une faute de frappe. D'autres fonctions sont particulirement utiles, comme la mise en forme automatique du texte (en tenant compte de la syntaxe du langage), qui permet de mettre en vidence la structure des programmes.

UltraEdit
Un des diteurs les plus simples utiliser et les plus performants est UltraEdit, dvelopp par la socit IDM. Vous pouvez tlcharger la dernire version disponible l'adresse :

CHAPITRE 1 INSTALLATION DE JAVA


http://www.idmcomp.com

35

Il s'agit d'un produit en shareware, que vous pouvez donc tester gratuitement pendant 45 jours. Si vous dcidez de le conserver au-del, vous devrez le payer. Le prix est trs raisonnable (30$) et vous pouvez payer en ligne sur le site d'IDM. Vous trouverez une copie de cet diteur sur le CD-ROM accompagnant ce livre. Il est quip d'un dictionnaire Java, ncessaire pour l'affichage en couleur des mots du langage. D'autres dictionnaires (pour d'autres langages de programmation) sont disponibles sur le site d'IDM. L'installation, la configuration et l'utilisation d'UltraEdit sont dcrites succinctement l'Annexe C. N'oubliez pas, si vous dcidez de conserver ce programme, qu'il ne s'agit pas d'un logiciel gratuit. Payer la licence des logiciels que vous utilisez est la seule faon de rmunrer le travail des programmeurs.

Quelques petits fichiers batch qui vous simplifieront la vie


Si vous n'installez pas UltraEdit, vous pouvez toutefois vous simplifier la vie en crant trois petits fichiers batch qui faciliteront la compilation et l'excution de vos programmes1 . Le premier sera appel ca.bat (pour compiler application ou compiler applet) et contiendra la ligne suivante :
javac %1.java

Crez ce fichier et enregistrez-le dans le dossier dans lequel vous allez stocker vos programmes (c:\java, par exemple). Ainsi, vous pourrez compiler un programme en tapant simplement :
ca prog 1. Si vous dcidez d'installer UltraEdit, vous vous simplifierez encore beaucoup plus la vie, comme vous pouvez le constater en lisant l'Annexe C. Attention, cependant. Si vous l'essayez, vous ne pourrez plus vous en passer !

36
au lieu de :
javac prog.java

LE DVELOPPEUR JAVA 2

Ce n'est pas grand-chose, mais lorsque vous aurez compil 20 fois le mme programme, vous apprcierez l'conomie. De la mme faon, crez le fichier va.bat contenant la ligne :
appletviewer %1.java

ainsi que le fichier ra.bat contenant la ligne :


java %1

Vous pourrez ainsi lancer une application en tapant :


ra prog

(l'conomie, ici, n'est que de deux caractres, car il n'est pas ncessaire de taper l'extension .class) et visualiser une applet dans l'AppletViewer en tapant :
va prog

au lieu de :
appletviewer prog.java

Si vous n'tes pas convaincu de l'intrt de ces petits fichiers, tapez 50 fois le mot appletviewer et on en reparle juste aprs... OK ?

CHAPITRE 1 INSTALLATION DE JAVA

37

Note : Vous tes peut-tre surpris de voir l'utilisation du programme


AppletViewer, suppos afficher une page HTML, avec un fichier source Java. Cela est simplement d au fait que nous utiliserons une petite astuce, comme nous le verrons au chapitre suivant, pour viter d'avoir crer un fichier HTML pour chacune des applets que nous testerons.

Un environnement de dveloppement
Dvelopper des applications l'aide d'un simple diteur de texte et d'un compilateur, cela peut paratre compltement dpass l'heure du RAD (Rapid Application Development - Dveloppement rapide d'applications). Il existe de nombreux outils pour amliorer la productivit des programmeurs. C'est le cas, en particulier, des environnements de dveloppement, encore appels IDE (Integrated Development Environment).

Avantages
Un environnement de dveloppement peut amliorer la productivit de deux faons totalement diffrentes. La premire consiste aider le programmeur dans les tches mnagres, par exemple en numrotant les lignes. L'aide fournie peut aller beaucoup plus loin. Ainsi, il peut tre possible de lancer la compilation depuis l'diteur, et d'afficher le rsultat dans une fentre. Cela constitue un norme avantage car la logique imprative du dveloppement de programme consiste corriger les erreurs une par une (pas plus d'une la fois) en commenant par la premire. En effet, dans de nombreux cas, une premire erreur de syntaxe perturbe tellement le compilateur qu'il dtecte toutes sortes d'autres erreurs qui ne sont que le fruit de son imagination (pour autant qu'un compilateur puisse avoir de l'imagination). Par exemple, si vous oubliez un point-virgule la fin d'une ligne, le compilateur pense que l'instruction en cours n'est pas termine. Il lit donc la ligne suivante comme s'il s'agissait de la suite de cette instruction et trouve gnralement une erreur. Puis il continue avec la suite de la ligne, et trouve alors une autre erreur, puisque le premier mot de la ligne a t absorb

38

LE DVELOPPEUR JAVA 2
par la premire erreur. L'erreur se propage ainsi jusqu' la fin de la ligne. Si vous oubliez une fin de bloc, l'erreur peut se propager trs loin dans le programme. La rgle (du moins jusqu' ce que vous ayez acquis une bonne exprience de l'utilisation du compilateur Java) est donc de ne corriger qu'une seule erreur la fois. Malheureusement, si votre programme comporte plusieurs erreurs (et nous venons de voir que c'est trs souvent le cas), celles-ci sont affiches les unes derrire les autres en faisant dfiler l'affichage. Les fentres DOS n'tant pas extensibles (elles ne comportent, au maximum, que cinquante lignes), si le texte affich dfile au-del du haut de la fentre, vous ne pouvez plus y accder et vous ne pouvez donc plus prendre connaissance de la premire erreur ! Une solution ce problme consiste rediriger la sortie gnre par le compilateur vers un fichier, puis ouvrir ce fichier l'aide de l'diteur de texte. Malheureusement, cela n'est pas possible. En effet, pour une raison inconnue, mais vraiment trange, le compilateur de Sun n'utilise pas, pour l'affichage, la sortie standard (standard output), mais la sortie d'erreurs du DOS (standard error). Cela ne serait pas grave sous Unix, mais il se trouve que, sous DOS, standard error ne peut tre redirig ! Un environnement de dveloppement prsente donc le grand avantage d'afficher les erreurs dans une fentre que vous pouvez faire dfiler. Il possde parfois d'autres fonctions plus ou moins utiles, comme la facult de lancer la compilation du programme en cours (ou son excution) en cliquant sur un bouton, la capacit de l'diteur dtecter les erreurs de syntaxe, ou encore la possibilit d'accder la documentation concernant un mot cl en slectionnant celui-ci dans votre programme. Mais l'intrt des systmes de dveloppement va beaucoup plus loin, grce l'utilisation des JavaBeans. Ces grains de caf (au fait, pour ceux qui ne le savaient pas, Java veut dire caf, d'o le logo reprsentant une tasse fumante !) sont des composants Java de nature un peu spciale. Leur particularit est qu'ils respectent une norme permettant un programme d'analyser leur contenu partir de la version compile. Il est ainsi possible aux environnements de dveloppement de les intgrer aux programmes que vous crivez. Au lieu de programmer un bouton pour une interface, vous pouvez alors simplement utiliser le JavaBean correspondant en slectionnant l'icne d'un bouton dans une barre d'outils et en la faisant glisser sur

CHAPITRE 1 INSTALLATION DE JAVA

39

la fentre reprsentant l'interface de votre programme. Le dveloppement peut tre ainsi considrablement acclr. (Vous pouvez, bien sr, dvelopper vos propres JavaBeans.) Un autre avantage essentiel des environnements de dveloppement est l'intgration des fonctions de dbogage qui, sans eux, sont disponibles sous la forme du dbogueur livr avec le J2SDK.

Inconvnients
Les systmes de dveloppement prsentent toutefois certains inconvnients. Tout d'abord, ils sont souvent payants. Par ailleurs, vous subissez les inconvnients lis aux avantages : utiliser un bouton sous forme de JavaBean est certainement plus rapide que de le programmer soi-mme, encore que la programmation d'un bouton en Java soit extrmement simple. Mais si vous souhaitez un bouton d'un type particulier, vous ne serez gure avanc. Par ailleurs, pouvoir utiliser des boutons, ou tous autres composants, sans savoir comment ils fonctionnent n'est probablement pas votre but si vous lisez ce livre. Toutefois, les avantages des systmes de dveloppement en termes d'ergonomie sont tout fait indniables. A vous de voir si cela justifie l'investissement dans un premier temps. A terme, une fois que vous aurez appris programmer en Java, il ne fait nul doute que vous utiliserez un tel environnement. Cependant, vous aurez appris crire du code de faon pure et dure, et vous serez mieux mme de choisir l'environnement qui vous convient. Sachez qu'il est parfaitement possible d'crire des applications Java sans ce type d'outils si vous connaissez le langage. En revanche, et contrairement ce que certains voudraient faire croire, il est parfaitement impossible de dvelopper des applications srieuses avec ce type d'outils si vous n'avez pas une bonne connaissance du langage.

Quel environnement?
Le choix d'un environnement est une affaire de got. Il en existe de nombreux, plus ou moins sophistiqus, tels que Forte for Java, de Sun Microsystems (gratuit), VisualAge for Java, d'IBM, JBuilder, de InPrise (ex

40

LE DVELOPPEUR JAVA 2
Borland), Visual Caf, de Symantec, ou PowerJ, de Sybase, dont la version Learning Edition, disponible en tlchargement sur Internet, est gratuite. Tous ces produits utilisent le compilateur, l'interprteur de bytecode, le dbogueur et la documentation du J2SDK. Un environnement mrite une mention spare : il s'agit de Visual J++. Comme d'habitude, la volont de son diteur (Microsoft) n'est pas vraiment de supporter le standard Java. Ainsi, ce systme n'est pas compatible avec l'utilisation des JavaBeans, concurrents directs des technologies ActiveX et COM. De plus, il mlange allgrement la technologie Java avec des technologies propritaires lies Windows. Ainsi, les applications cres ne sont plus portables. Le but est clairement de gnrer une base d'applications utilisant les technologies propritaires pour inciter les diteurs d'autres systmes les implmenter. Si vous tes intress par Java pour les avantages que peut apporter une portabilit totale des programmes au niveau excutable sur tous les systmes, il est prfrable de ne pas utiliser ce type de produit. (Pour tre tout fait honnte, Visual J++ peut fonctionner dans un mode 100 % compatible Java. Dans ce cas, il ne prsente aucun avantage particulier par rapport ses concurrents.) En revanche, Visual J++ permet de dvelopper des applications natives en utilisant le langage Java, qui est tout de mme beaucoup plus sr et facile apprendre que C++. C'est l un avantage important qui peut vous permettre de mieux rentabiliser le temps pass apprendre programmer en Java.

Note : Le choix d'un environnement est quelque chose de tout fait


personnel. Notre avis est que le plus simple est souvent le plus efficace. Nous avons choisi, pour tous les exemples de ce livre, d'utiliser UltraEdit en crant simplement trois commandes personnalises pour compiler et excuter un programme, ou charger l'AppletViewer. Nous vous conseillons fortement d'en faire autant. Pour plus de dtails, reportez-vous lAnnexe C.

Forte for Java


Suite au rachat de la socit NetBeans, Sun Microsystems distribue maintenant gratuitement un environnement de programmation entirement crit en Java et nomm Forte for Java (Community Edition). Cet environnement est particulirement bien adapt au dveloppement d'applications Java

CHAPITRE 1 INSTALLATION DE JAVA

41

l'aide de JavaBeans. De plus, il est totalement gratuit. Toutefois, il n'est pas conseill de l'utiliser pour l'apprentissage du langage car il masque un certain nombre de dtails techniques. C'est en revanche un excellent choix pour le dveloppement rapide d'applications, une fois votre apprentissage termin. En effet, plonger directement dans la manipulation d'un tel environnement a de quoi effrayer ceux qui n'ont pas acquis en Java les bases ncessaires. Un nombre incroyable d'informations sont prsentes l'utilisateur qui se trouve rapidement dbord. En revanche, une fois que vous aurez appris coder manuellement tous les dtails d'une application, vous apprcierez de n'avoir qu' configurer les proprits des objets l'aide d'une interface graphique et de voir le code Java correspondant crit automatiquement. Forte for Java peut tre tlcharg sur le site de Sun Microsystems l'adresse :
http://www.sun.com/developers/info/articles/2000/forte.html

O trouver les informations?


Java est un langage jeune. (Il a eu cinq ans le 23 mai 2000.) Il y a autour de ce langage un perptuel bouillonnement de nouveauts. Il est donc particulirement important de vous tenir au courant des nouvelles. Pour cela, il existe plusieurs sources privilgies.

Le site Web de Sun


La premire source officielle est le site de Sun Microsystems ddi Java. Vous le trouverez l'adresse :
http://java.sun.com

Il est libre d'accs et comporte toutes les informations gnralistes concernant le langage, et en particulier toutes les annonces de nouveaux produits et dveloppements. Les informations spcifiques au J2SDK 1.3 se trouvent l'adresse :

42
http://java.sun.com/j2se/1.3/

LE DVELOPPEUR JAVA 2

L'essentiel de la documentation est install dans le sous-dossier docs du dossier d'installation du J2SDK. Toutefois, certaines documentations spcifiques ne s'y trouvent pas. Vous pourrez les consulter l'adresse :
http://java.sun.com/docs

Enfin, vous trouverez les informations sur les produits relatifs Java l'adresse :
http://java.sun.com/products/

Le JDC
Ces initiales signifient Java Developer Connection et dsignent un site encore plus important pour les programmeurs. Il est galement propos par Sun Microsystems et concerne cette fois les aspects techniques du langage. Son adresse est :
http://developer.java.sun.com/

C'est ici, en particulier, que sont proposes en tlchargement les diffrentes versions du J2SDK. Ce site est accessible uniquement aux utilisateurs enregistrs, mais l'enregistrement est gratuit. Vous y trouverez de trs nombreux articles sur Java et les sujets annexes. Vous pourrez galement vous abonner au Java(sm) Developer Connection(sm) Tech Tips, qui est une lettre mensuelle expdie par courrier lectronique et concernant diverses astuces de programmation en Java. Une visite de ce site est absolument indispensable toute personne dsirant programmer en Java. Il donne entre autres une liste des ressources disponibles concernant Java, l'adresse :
http://developer.java.sun.com/developer/techDocs/resources.html

CHAPITRE 1 INSTALLATION DE JAVA

43

Les autres sites Web ddis Java


Il existe de nombreux sites consacrs la programmation en Java. Vous y trouverez de tout, et en particulier toutes sortes d'applets prtes l'emploi. Pour trouver ces sites, vous pouvez utiliser un moteur de recherche comme Yahoo. Parmi les sites les plus intressants, on peut citer le Java Developers Journal, l'adresse :
http://www.sys-con.com/java/

qui contient de nombreux articles, plutt gnralistes, et surtout de trs nombreux liens vers les socits ditant des produits relatifs Java. Le principal reproche que l'on puisse faire ce site est qu'il est littralement bourr d'images animes, ce qui rend son chargement particulirement long et pnible. Le site Java Boutique, l'adresse :
http://javaboutique.internet.com/

offre la particularit de proposer en tlchargement un grand nombre d'IDE. Vous y trouverez galement une liste de liens vers les sites des diteurs de tous les IDE existants. Le site JavaWorld est galement un des plus intressants pour ses articles techniques. Vous le trouverez ladresse :
http://www.javaworld.com/

Le site du Dveloppeur Java 2


Un site est consacr ce livre. Vous pourrez y tlcharger les programmes sources (au cas o vous auriez perdu le CD-ROM) et y trouver des informa-

44

LE DVELOPPEUR JAVA 2
tions concernant les erreurs ventuelles qu'il pourrait contenir, mesure qu'elles nous seront signales. Ce site se trouve l'adresse :
http://www.volga.fr/java2/

Les sites consacrs au portage du J2SDK


Plusieurs sites sont consacrs au portage du J2SDK sous diffrents environnements. En ce qui concerne le portage sous Mac OS, vous pouvez consulter l'adresse suivante :
http://devworld.apple.com/java/

Pour ce qui concerne le portage sous Linux, vous pouvez consulter le site de Sun Microsystems, ainsi que celui de Blackdown.org, l'adresse :
http://www.blackdown.org/

La version Linux du J2SDK 1.3 de Sun devrait tre disponible trois mois aprs la version Windows. Un autre site intressant est celui d'IBM. Cette socit avait dj propos gratuitement l'IBM Development Kit 1.1.8 pour Linux. Les dveloppeurs attendaient donc une version 1.2, sans se faire trop d'illusions sur les dlais, IBM ne nous ayant jusqu'ici gure surpris par sa ractivit. Il faut bien reconnatre que, cette fois, IBM a tonn tout le monde en proposant ds le 5 mai 2000 une version prliminaire 1.3.0 en tlchargement gratuit. Toutefois, cette situation pourrait bien voluer. En effet, dans le questionnaire accompagnant cette version d'valuation, il est demand aux utilisateurs s'ils seraient prts payer une somme "raisonnable" pour ce kit de dveloppement. En attendant, cette version est tlchargeable l'adresse :
http://ibm.com/alphaworks/tech/linuxjdk/

CHAPITRE 1 INSTALLATION DE JAVA

45

Les Newsgroups
Il existe plus de 40 groupes de nouvelles consacrs au langage Java, certains tant ddis des IDE dditeurs tels que Borland (InPrise). La plupart sont en anglais. Il existe cependant un groupe de nouvelle en franais dont le nom est :
fr.comp.lang.java

Sur ces groupes de nouvelles, les utilisateurs peuvent donner leur avis ou poser des questions concernant Java. La qualit des rponses est variable. Noubliez pas, avant de poser une question, de vrifier quil na pas t rpondu quelques jours auparavant une question similaire. Parmi les groupes de nouvelles en anglais, nombreux sont ceux qui sont consacrs divers aspects de la programmation en Java. Le plus gnraliste est :
comp.lang.java.programmer

Toutefois, il existe des sites spcialiss sur des sujets plus particuliers tels que Corba, les JavaBeans, les interfaces graphiques, le graphisme 2D, les bases de donnes, la scurit, ainsi que divers IDE.

Chapitre 2 : Votre premier programme

Votre premier programme

ANS CE CHAPITRE, NOUS CRIRONS UN PREMIER PROGRAMME en Java. (Nous en crirons en fait plusieurs versions.) Nous aurons ainsi l'occasion de tester l'installation du J2SDK et de nous familiariser avec l'criture du code Java. Il est possible que vous ne compreniez pas immdiatement toutes les subtilits des lments du langage que nous emploierons. C'est normal. Nous y reviendrons en dtail au cours des chapitres suivants. Pour l'instant, il ne s'agit que d'une sance d'imprgnation. Laissez-vous porter, mme si vous vous sentez un peu dsorient au dbut. Tout cela s'claircira en temps utile. Surtout, ne vous dcouragez pas si quelque chose ne fonctionne pas comme cela est indiqu. Vrifiez que vous avez tap les exemples en respectant exactement la typographie, et surtout les majuscules. Java est particulirement exigeant dans ce domaine.

48

LE DVELOPPEUR JAVA 2

Premire version: un programme minimal


Dmarrez votre diteur de texte, si cela n'est pas dj fait, et saisissez le programme suivant, en respectant scrupuleusement la mise en forme :
// Mon premier programme en Java public class PremierProgramme { public static void main(String[] argv) { System.out.println("Ca marche !"); } }

Enregistrez ce programme sous le nom PremierProgramme.java, dans le dossier que vous avez cr spcialement pour cet usage au chapitre prcdent (normalement, c:\java).

Note : Si vous utilisez l'diteur UltraEdit, vous verrez certains mots s'afficher en couleur au moment o vous enregistrerez le programme. En effet, UltraEdit se sert de l'extension du fichier (.java) pour dterminer son contenu et utiliser le dictionnaire adquat. L'utilisation des couleurs vous facilite la lecture et vous permet d'identifier immdiatement certaines fautes de frappe. Ainsi, normalement, la premire ligne est affiche en vert ple, les mots class, public, static et void en bleu, et le mot String en rouge. Ouvrez maintenant une fentre MS-DOS, par exemple en faisant un double clic sur l'icne du raccourci que nous avons cr au chapitre prcdent. L'indicatif affich doit correspondre au chemin d'accs du dossier contenant votre programme (c:\java). Si ce n'est pas le cas, changez de dossier en utilisant les commandes MS-DOS (cd.. pour remonter d'un niveau dans l'arborescence des dossiers, et cd <nom_de_dossier> pour ouvrir le dossier choisi. Vous pouvez galement utiliser la commande cd avec le chemin d'accs complet du dossier atteindre, par exemple cd c:\java.) Une fois dans le dossier contenant votre programme, tapez la commande :
javac PremierProgramme.java

CHAPITRE 2 VOTRE PREMIER PROGRAMME

49

puis la touche Entre, ce qui a pour effet de lancer la compilation de votre programme. Si vous avez cr les fichiers batch dcrits au chapitre prcdent, vous pouvez galement taper :
ca PremierProgramme

Normalement, votre PC doit s'activer pendant un certain temps, puis vous rendre la main en affichant de nouveau l'indicatif de MS-DOS. Si vous obtenez le message :
Commande ou nom de fichier incorrect

c'est soit que vous avez fait une faute de frappe dans le mot javac, soit que votre variable d'environnement path n'est pas correctement configure, soit que le J2SDK n'est pas correctement install. Commencez par vrifier si vous n'avez pas fait de faute de frappe. La commande javac doit tre spare du reste de la ligne par un espace. Si vous avez utilis le fichier batch, essayez sans. (La faute de frappe est peut-tre dans ce fichier.) Si vous n'avez pas fait de faute et que vous obtenez toujours le mme message, vrifiez si la variable path est correctement configure en tapant la commande :
path

(puis la touche Entre). Vous devez obtenir le rsultat suivant :


path=c:\jdk1.3\bin

La ligne peut comporter d'autres chemins d'accs, mais celui-ci doit y figurer, ventuellement spar des autres par des points-virgules. Si le chemin d'accs n'est pas correct, ou si vous avez un doute, tapez la commande :

50
path c:\jdk1.3\bin

LE DVELOPPEUR JAVA 2

Cette commande est identique au rsultat que vous auriez d obtenir prcdemment, la diffrence que le signe gal est remplac par un espace. Essayez ensuite de compiler de nouveau le programme.

Note : La commande path utilise ainsi modifie la variable path pour la vie de la fentre MS-DOS. Si vous fermez cette fentre et en ouvrez une autre, il faudra recommencer. Vous devrez donc, de toutes les faons, corriger votre fichier autoexec.bat.
Si vous obtenez toujours le mme message, c'est que le programme javac.exe n'est pas install dans le dossier c:\jdk1.3\bin. Modifiez alors le chemin d'accs en consquence ou procdez une nouvelle installation. Si vous obtenez le message :

Error: Can't read: PremierPrograme.java

vrifiez si vous n'avez pas fait une faute de frappe dans le nom du fichier (comme ici, o nous avons volontairement omis un m). Si le nom du fichier vous semble correct, c'est peut-tre que vous avez fait une faute de frappe en tapant son nom lors de l'enregistrement. Dans ce cas, renommez-le. Enfin, assurez-vous que vous avez bien enregistr le fichier dans le dossier dans lequel vous essayez de lancer le compilateur. Noubliez pas, galement, que si vous avez crit votre programme laide du bloc-notes de Windows, vous devez supprimer lextension .txt qui est automatiquement ajoute par ce programme.

Note : On pourrait penser qu'il vaut mieux s'en tenir des noms courts,
mais cela ne rsout pas le problme. En effet, le compilateur Java exige que l'extension du fichier soit .java, ce qui oblige le DOS utiliser un nom long, mme si celui-ci ne dpasse pas huit caractres. Si vous obtenez le message :

CHAPITRE 2 VOTRE PREMIER PROGRAMME


Exception in thread "main" java.lang.NoClassDefFoundError: PremierProgramme

51

cest que la variable classpath est mal configure. Reportez-vous la section Les chemins daccs aux classes Java, dans le chapitre prcdent (page 17). Pour le vrifier, tapez la commande :
set

et la touche ENTRE. Si une des lignes affiches commence par classpath, cest probablement la cause du problme. Enfin, si vous obtenez un message du genre :
PremierProgramme.java:3: '{' expected. public static void main(String[] argv) ^ 1 error

c'est que vous avez fait une erreur de frappe l'intrieur du programme. Essayez de la corriger et recommencez. (Ici, le compilateur indique que l'erreur se trouve la ligne 3.) Une fois que le programme est compil correctement, excutez-le en tapant la commande :

java PremierProgramme

puis la touche Entre ou, plus simplement, si vous avez cr les fichiers batch du chapitre prcdent :
r

52

LE DVELOPPEUR JAVA 2
puis les touches F3 et ENTRE, ce qui est tout de mme beaucoup plus court. (La touche F3 recopie la fin de la ligne prcdente, ce qui est rendu possible parce que les commandes ca et ra font toutes deux la mme longueur, ce qui n'est pas le cas des commandes javac et java.) Votre programme doit tre excut et afficher la ligne :

Ca marche !

Si vous obtenez un message d'erreur du type :


java.lang.ClassFormatError: PremierPrograme (wrong name)

c'est que vous avez fait une faute de frappe (ici, nous avons oubli volontairement un m.) Si vous utilisez les fichiers batch, cela ne devrait pas arriver, sauf dans un cas particulier. En effet, si vous ne respectez pas les majuscules lors de la compilation, le compilateur s'en moque. En revanche, lexcution ne peut se faire que si le nom du fichier est tap avec les majuscules. Nous verrons pourquoi dans une prochaine section.

Note : Si vous utilisez UltraEdit et si vous l'avez configur comme nous


l'indiquons l'Annexe C, vous pouvez compiler le programme en tapant simplement la touche F11 et l'excuter en tapant F12. Normalement, vous ne devriez pas obtenir une erreur d'excution pour ce programme. En effet, Java est conu pour qu'un maximum d'erreurs soient dtectes lors de la compilation. Comme ce programme ne fait quasiment rien, il devrait s'excuter sans problme.

Analyse du premier programme


Nous allons maintenant analyser tape par tape tout ce que nous avons fait jusqu'ici afin de bien comprendre comment tout cela fonctionne.

CHAPITRE 2 VOTRE PREMIER PROGRAMME


// Mon premier programme en Java class PremierProgramme { public static void main(String[] argv) { System.out.println("Ca marche !"); } }

53

La premire ligne de notre programme est une ligne de commentaire. Elle commence par //. Tout ce qui est compris entre ces deux caractres et la fin de la ligne est ignor par le compilateur. (En revanche, d'autres outils peuvent utiliser le texte contenu dans ce type de commentaire.)

// Mon premier programme en Java class PremierProgramme { public static void main(String[] argv) { System.out.println("Ca marche !"); } }

La ligne suivante commence par le mot class, qui veut dire que nous allons dfinir une nouvelle classe Java, suivi du nom de cette classe, qui est aussi le nom de notre programme. Nous verrons au chapitre suivant ce que sont exactement les classes. Sachez simplement pour l'instant les choses suivantes :

Un programme comporte au moins une classe. Cette classe doit obligatoirement porter le mme nom que le fichier

dans lequel le programme est enregistr. Le fichier possde, en plus, l'extension .java. caractres diffrents. Ainsi, PremierProgramme n'est pas identique premierProgramme ou PREMIERprogramme. ticulire. Elles sont simplement des sparateurs, au mme titre que les

En Java, les majuscules et les minuscules sont considres comme des

En Java, les fins de lignes n'ont gnralement pas de signification par-

54

LE DVELOPPEUR JAVA 2
espaces ou les tabulations. On peut le plus souvent utiliser comme sparateur entre deux mots Java n'importe quelle combinaison d'espace, de tabulations et de fins de lignes. Cette particularit est d'ailleurs mise profit pour mettre en forme le code d'une faon qui facilite sa lecture, comme nous le verrons plus loin. Cependant, la premire ligne de notre programme montre un exemple contraire. Dans ce type de commentaire, le dbut est marqu par deux barres obliques (pas forcment en dbut de ligne, d'ailleurs) et la fin de ligne marque la fin du commentaire. C'est pratique, mais pas trs logique.

La deuxime ligne se termine par le caractre {, qui marque le dbut

d'un bloc. Nous verrons plus loin ce que reprsente exactement un bloc. Sachez seulement pour l'instant que la dclaration d'une nouvelle classe est suivie de sa dfinition, et que celle-ci est incluse dans un bloc, dlimit par les caractres { et }. Ici le caractre marquant le dbut du bloc se trouve la fin de la deuxime ligne. Le caractre marquant la fin du bloc se trouve sur la dernire ligne du programme. En effet, celui se trouvant sur l'avant-dernire ligne correspond l'ouverture d'un autre bloc, la fin de la troisime ligne.

Il existe de nombreuses faons de formater le code Java. Aucune n'est obligatoire. Il est cependant indispensable de respecter une certaine logique. Nous avons pour habitude (comme peu prs tout le monde) de passer la ligne suivante immdiatement aprs l'ouverture d'un bloc, sauf si la totalit du bloc peut tenir sur la ligne. Par ailleurs, nous indenterons d'une tabulation le code se trouvant l'intrieur du bloc. Enfin, le caractre de fermeture du bloc se trouvera toujours sur une ligne isole (sauf dans le cas o le bloc tient sur une seule ligne) et sera align sur le dbut de la ligne comportant le caractre d'ouverture du bloc. De cette faon, la structure du code est parfaitement lisible. Vous pouvez adopter une autre habitude si vous le souhaitez. Cependant, il est conseill de s'en tenir rigoureusement une seule manire.
// Mon premier programme en Java class PremierProgramme { public static void main(String[] argv) { System.out.println("Ca marche !"); } }

CHAPITRE 2 VOTRE PREMIER PROGRAMME

55

La troisime ligne est donc dcale d'une tabulation car elle se trouve l'intrieur du bloc qui vient d'tre ouvert. C'est la premire ligne de la dfinition de la classe que nous sommes en train de crer. Cette ligne est un peu plus complexe. La partie fondamentale est :
void main() { main() { signifie que nous allons dfinir une mthode, ce qui est indiqu entre autres, par les parenthses (). Une mthode est une sorte de procdure appartenant une classe. Lorsqu'elle est excute, elle prend des paramtres de types prcis et renvoie une valeur de type tout aussi prcis. Le mot main est le nom de la mthode que nous allons dfinir. Le mot void dsigne le type de valeur renvoy par la mthode. Et justement, void signifie rien, c'est--dire aucune valeur. Notre mthode ne renverra aucune valeur. Elle ne sera donc pas utilise pour la valeur qu'elle renvoie, mais pour l'interaction qu'elle pourra avoir avec l'environnement (ce que l'on appelle les effets de bord). Les paramtres que prend la mthode sont placs entre les parenthses, chaque paramtre ayant un nom, prcd de son type. Dans l'exemple simplifi, la mthode ne prend pas de paramtres. Dans notre programme rel, elle prend un paramtre, dont le nom est argv et qui est de type String[]. Nous verrons plus loin ce que cela signifie. Sachez seulement pour l'instant que notre mthode n'utilisera pas ce paramtre, mais que nous sommes obligs de le faire figurer ici. Sans ce paramtre, le programme sera correctement compil, mais ne pourra pas tre excut.

Les mots public et static dcrivent chacun une caractristique de notre mthode. public indique que notre mthode peut tre utilise par n'importe qui, c'est--dire par d'autres classes, ou par l'interprteur Java. Oublier le mot public n'empcherait pas le programme d'tre compil normalement, mais cela empcherait l'interprteur de l'utiliser. En effet, l'interprteur a pour fonction principale d'excuter la mthode main du programme qu'on lui soumet. Le mot static a une signification un peu plus complexe, qui s'claircira dans un prochain chapitre. Sachez simplement pour l'instant que certaines mthodes sont de type static et d'autres non.

56

LE DVELOPPEUR JAVA 2
Pour rsumer, retenez qu'une application Java doit contenir une classe :

portant le mme nom que le fichier dans lequel elle est enregistre, comportant (au moins) une mthode : de type public et static appele main, ayant un argument de type String[].
Note 1 : Vous trouverez le plus souvent dans la littrature consacre Java le paramtre de la mthode main sous la forme main(String argv[]). Sachez que les deux notations :
String argv[] String[] argv

sont acceptables. String argv[] est pratiquement toujours employ, probablement parce que le message d'erreur affich par l'interprteur Java lorsque ce paramtre manque est :
void main(String argv[]) is not defined

Cependant, nous verrons au chapitre traitant des tableaux que la notation :


String[] argv

est beaucoup plus logique. Par ailleurs, le nom du paramtre n'est pas obligatoirement argv. Vous pouvez utiliser n'importe quel nom, condition de n'utiliser que des lettres, des chiffres et le caractre _, et de commencer par une lettre ou par _. Vous pouvez utiliser pratiquement autant de caractres que vous le souhaitez. (Nous avons test un programme utilisant des noms de plus de quatre mille caractres sans problme.)

CHAPITRE 2 VOTRE PREMIER PROGRAMME

57

main, le programme est compil sans erreur mais ne s'excute pas. Il en est de mme, d'ailleurs, si votre programme ne comporte pas de mthode main. Cela peut sembler en contradiction avec la philosophie de Java, qui consiste dtecter un maximum d'erreurs lors de la compilation. Il n'en est rien. L'erreur n'est pas en effet d'omettre la mthode main ou son paramtre, mais de tenter d'excuter le programme. Il est parfaitement normal de vouloir compiler une classe qui ne constitue pas un programme excutable mais simplement un lment qui sera utilis par une application ou une applet.

Note 2 : Nous avons dit que si vous oubliez le paramtre de la mthode

// Mon premier programme en Java class PremierProgramme { public static void main(String[] argv) { System.out.println("Ca marche !"); } }

La quatrime ligne du programme constitue le corps de la mthode main. Elle comporte un appel une mthode et un paramtre. La mthode appele est println. Cette mthode prend un paramtre de type chane de caractres et affiche sa valeur sur la sortie standard, en la faisant suivre d'un saut de ligne. Le paramtre est plac entre parenthse. Ici, il s'agit d'une valeur littrale, c'est--dire que la chane de caractres est crite en toutes lettres entre les parenthses. Pour indiquer qu'il s'agit d'une valeur littrale, la chane de caractres est place entre guillemets. La mthode println ne tombe pas du ciel lorsque l'interprteur Java en a besoin. Pour qu'il puisse l'utiliser, il faut lui indiquer o il peut la trouver. Les mthodes Java n'existent que dans des objets, qui peuvent tre des classes, ou des instances de classes. Nous verrons au prochain chapitre la diffrence (fondamentale) qui existe entre une classe et une instance de classe. (Sachez simplement pour l'instant que ce qui est static appartient une classe et ce qui ne l'est pas une instance.) La mthode println laquelle nous faisons rfrence ici appartient l'objet out. Cet objet appartient lui-mme la classe System.

58

LE DVELOPPEUR JAVA 2
Si vous voulez obtenir des informations sur une classe standard du langage Java, vous devez utiliser la documentation fournie. Si vous ouvrez celle-ci l'aide de votre navigateur Web en faisant un double clic sur l'icne du raccourci que nous avons cr au chapitre prcdent, vous accdez directement au document index.html se trouvant dans le dossier c:\jdk1.3\ docs\api\. En haut du cadre principal, cliquez sur le lien Index, puis sur la lettre S, et faites dfiler la page jusqu' l'entre System. Une fois l'entre System localise, cliquez sur le lien correspondant. (Pour trouver plus rapidement une entre, vous pouvez utiliser la fonction de recherche de mot de votre navigateur.) Vous accdez alors la description de la classe System (Figure 2.1). Le haut de la page montre sa hirarchie. Nous verrons dans un prochain chapitre ce que cela signifie. Notez cependant une chose importante : le nom complet de la classe est java.lang.System. Vous trouvez ensuite une description de la classe, puis une liste des ses champs (fields), et enfin, une liste de ses mthodes. Vous pouvez constater que la classe System possde un champ out, qui est un objet de type PrintStream. De plus, cet objet est static. En cliquant sur le lien PrintStream, vous obtenez l'affichage de la documentation de cette classe. Vous pouvez alors constater qu'elle contient la mthode println(String x), celle que nous utilisons dans notre programme. Peut-tre vous posez-vous la question suivante : Comment se fait-il que nous pouvons utiliser la classe java.lang.System en y faisant rfrence uniquement l'aide du nom System ? En fait, le nom de la classe est simplement System. L'expression java.lang est en quelque sorte le chemin d'accs cette classe. Si vous dcompressez le contenu du fichier src.jar (se trouvant dans le dossier d'installation du J2SDK), vous pourrez constater que vous obtenez un sous-dossier nomm java, que celui-ci contient son tour un sous-dossier nomm lang, et qu'enfin celui-ci contient un fichier nomm System.java. Bien sr, il s'agit l des sources de la classe. La classe System compile est le fichier System.class. Vous ne trouverez pas ce fichier car il est inclus dans le fichier darchive rt.jar, comme vous pouvez le voir sur la Figure 2.2. Cependant, son chemin d'accs, l'intrieur du fichier darchive, est identique celui du fichier source.

CHAPITRE 2 VOTRE PREMIER PROGRAMME

59

Figure 2.1 : La documentation de la classe System.

Cela ne rpond toutefois pas la question : Comment fait l'interprteur pour savoir qu'il doit chercher la classe System avec le chemin d'accs c:\jdk1.3\lib\java\lang\ ? (A partir de maintenant, nous considrerons

60

LE DVELOPPEUR JAVA 2

Figure 2.2 : Organisation des classes dans le fichier darchive rt.jar.

comme tout fait transparente l'utilisation du fichier de classes archives rt.jar et raisonnerons comme sil sagissait dun dossier normal.) En fait, la premire partie du chemin d'accs ( c:\jdk1.3\lib\) est utilise automatiquement par Java. Il nest donc pas ncessaire de lindiquer. La deuxime partie du chemin d'accs n'est pas ncessaire non plus, car Java utilise galement, mais dans un processus totalement diffrent, le chemin d'accs java.lang comme chemin par dfaut. Toutes les classes se trouvant dans java.lang sont donc directement accessibles tous les programmes Java.

Note : La distinction entre les deux chemins d'accs par dfaut est importante. En effet, le premier est extrieur Java et ne dpend que du systme. Sa syntaxe peut tre diffrente d'un systme l'autre, particulirement en

CHAPITRE 2 VOTRE PREMIER PROGRAMME

61

ce qui concerne les sparateurs de niveaux (\ pour MS-DOS et Windows, / pour Unix). En revanche, pour assurer la portabilit des applications, la deuxime partie du chemin d'accs est traduite par Java d'une faon identique sur tous les systmes. C'est ainsi que le sparateur de niveaux employ est le point. Vous pouvez parfaitement remplacer la ligne :
System.out.println("Ca marche !");

par :
java.lang.System.out.println("Ca marche !");

mais pas par :


java\lang\System.out.println("Ca marche !");

ce qui entranerait trois erreurs de compilation (bien que le programme ne comporte que deux erreurs !). La mthode println envoie son argument (ici la chane de caractres) la sortie standard. Gnralement, il s'agit de l'cran, mais cela n'est pas vraiment du ressort de Java. Le systme est libre d'en faire ce qu'il veut. (Sous MS-DOS, la sortie standard - standard output - peut tre redirige, contrairement la sortie d'erreurs - standard error.)

Note : Si vous avez consult la documentation de Java comme indiqu


prcdemment, vous avez certainement remarqu qu'il existe galement une mthode print. La plupart des livres sur Java indiquent que la diffrence entre ces deux mthodes est l'ajout d'un caractre fin de ligne la fin de la chane affiche. Il existe cependant une diffrence plus importante. En effet, l'criture vers la sortie standard peut faire appel un tampon, qui est une zone de mmoire recevant les caractres afficher. Normalement, le contrle de ce tampon est dvolu au systme. Java peut nanmoins provoquer le vidage du tampon afin d'tre sr que les caractres soient relle-

62

LE DVELOPPEUR JAVA 2
ment affichs. Sous MS-DOS, le systme affiche les caractres au fur et mesure de leur entre dans le tampon. Une chane de caractres affiche par la mthode print sera donc visible immdiatement l'cran, comme dans le cas de la mthode println. La seule diffrence sera bien le passage la ligne dans le second cas. En revanche, rien ne vous garantit qu'il en sera de mme avec d'autres systmes. Un autre systme peut trs bien garder les caractres dans le tampon et ne les afficher que lorsque l'impression est termine, ou lorsque le tampon est plein, ou encore lorsqu'un caractre spcial est reu (le plus souvent, une fin de ligne). La mthode println, en plus d'ajouter une fin de ligne, provoque le vidage du tampon, et donc assure que l'affichage des caractres est effectivement ralis. (En tout tat de cause, le tampon est vid et tous les caractres sont affichs au plus tard lorsque la sortie standard est referme.) Notez enfin que la ligne contenant l'appel la mthode println est termine par un point-virgule. Le point-virgule est un sparateur trs important en Java. En effet, nous avons vu que les fins de ligne n'ont pas de signification particulire, sauf l'intrieur des commentaires commenant par //. Vous pouvez donc ajouter des sauts de ligne entre les mots du langage sans modifier la syntaxe. La contrepartie est que vous devez indiquer Java la terminaison des instructions.

Attention : En Java, contrairement ce qui est pratiqu par d'autres


langages, le point-virgule est toujours obligatoire aprs une instruction, mme si celle-ci est la dernire d'un bloc. Dans notre programme, la quatrime ligne est la dernire d'un bloc. Bien que le point-virgule soit redondant dans ce cas (l'instruction se termine forcment puisque le bloc qui la contient se termine), son omission entrane automatiquement une erreur de compilation. Cette lgre contrainte vous vitera bien des soucis lorsque vous ajouterez des lignes la fin d'un bloc !

// Mon premier programme en Java class PremierProgramme { public static void main(String[] argv) { System.out.println("Ca marche !"); } }

CHAPITRE 2 VOTRE PREMIER PROGRAMME

63

Les deux dernires lignes de notre programme comportent les caractres de fin de bloc. Le premier correspond la fermeture du bloc contenant la dfinition de la mthode main. Il est donc align sur le dbut de la ligne contenant le caractre d'ouverture de bloc correspondant. Ce type d'alignement n'est pas du tout obligatoire. Il est cependant trs utile pour assurer la lisibilit du code. Le deuxime caractre de fin de bloc ferme le bloc contenant la dfinition de la classe PremierProgramme. Il est align de la mme faon sur le dbut de la ligne contenant le caractre d'ouverture.

Premire applet
Le programme que nous venons de crer tait une application. C'est un bien grand mot pour un si petit programme, mais c'est l la faon de dsigner les programmes qui doivent tre excuts depuis une ligne de commande. Les applets sont un autre type de programmes conus pour tre excuts l'intrieur d'une page HTML, dans une surface rectangulaire rserve cet effet. Les diffrences entre applications et applets sont multiples, mais on peut les classer en deux catgories :

Les diffrences qui dcoulent de la nature des applets (le fait qu'elles
doivent tre excutes dans une page HTML).

Les diffrences qui ont t introduites volontairement, en gnral pour


des raisons de scurit. Dans la premire catgorie, on trouve un certain nombre de diffrences dues la nature obligatoirement graphique des applets. Une applet peut tre totalement invisible sur la page HTML qui la contient et peut effectuer un traitement ne ncessitant aucun affichage. Pour autant, elle est quand mme construite partir d'un lment de nature graphique. Dans la deuxime catgorie, on trouve toutes les limitations imposes aux applets pour les empcher d'attenter la scurit des ordinateurs. Les applets

64

LE DVELOPPEUR JAVA 2
tant essentiellement destines tre diffuses sur Internet, il leur est normalement interdit d'crire ou de lire sur le disque de l'utilisateur. Elles ne peuvent (en principe) accder qu'au disque du serveur sur lequel elles rsident. Toute autre possibilit doit tre considre comme un bug qui risque de ne plus exister dans les prochaines versions du langage. De la mme faon, les applets ne devraient pas, en principe, ouvrir des fentres sur l'cran. En effet, une fentre peut tre utilise pour faire croire l'utilisateur qu'il s'agit d'une application et en profiter pour lui demander son numro de carte bancaire, et le transmettre un vilain pirate qui en fait collection. En fait, il n'est pas interdit une applet d'ouvrir une fentre, mais celle-ci est clairement identifie comme tant une fentre d'applet, l'aide d'un gros bandeau qui occupe une partie de l'espace disponible (et complique singulirement la tche des programmeurs). Il existe cependant une exception ces limitations : il s'agit des applets signes, dont l'auteur peut tre identifi, et pour lesquelles ces limitations peuvent tre leves. Pour notre premire applet, nous serons donc soumis certaines contraintes. Pas celles qui empchent d'accder au disque, puisque notre programme ne le fait pas. En revanche, nous nutiliserons pas la sortie standard. De plus, la structure impose aux applets est assez diffrente de celle impose aux applications. Voici le code de notre premire applet :
// Ma premire applet Java public class PremiereApplet extends java.applet.Applet { public void init() { add(new java.awt.Label("a marche !")); } }

Tapez ce programme et enregistrez-le sous le nom PremireApplet.java. Compilez-le comme nous l'avons fait pour le programme prcdent. Corrigez les erreurs ventuelles. Une fois le programme compil sans erreur, tapez le code HTML suivant :
<html> <body> <applet code="PremiereApplet" width="200" height="150">

CHAPITRE 2 VOTRE PREMIER PROGRAMME


</applet> </body> </html>

65

et enregistrez-le dans le fichier PremiereApplet.htm. Pour excuter l'applet, tapez la commande :


appletviewer PremiereApplet.htm

L'applet est excute par l'AppletViewer et vous devez obtenir le rsultat indiqu par la Figure 2.3. Vous pouvez galement ouvrir le document PremireApplet.htm avec un navigateur compatible Java.

Analyse de la premire applet


Cette premire applet mrite quelques commentaires. Nous commencerons par l'examen du code, ligne par ligne.

Figure 2.3 : Lapplet affiche dans lAppletViewer.

66

LE DVELOPPEUR JAVA 2
// Ma premire applet Java public class PremiereApplet extends java.applet.Applet { public void init() { add(new java.awt.Label("a marche !")); } }

Nous passerons sur la premire ligne, qui est identique celle du programme prcdent. La deuxime ligne, en revanche, prsente deux diffrences : le nom de la classe est prcd du mot public (nous verrons plus loin ce que cela signifie) et il est suivi des mots extends java.applet.Applet, qui signifient que notre classe est dfinie par extension d'une classe existante, la classe Applet. Celle-ci peut tre atteinte en utilisant le chemin d'accs java.applet. De cette faon, toutes les fonctionnalits de cette classe seront disponibles dans la ntre. En particulier, la mthode init qui nous intresse plus particulirement. Si vous consultez le fichier Applet.java, qui contient les sources de la classe Applet et qui se trouve dans le dossier dont le chemin d'accs est src\java\applet\ une fois le fichier scr.jar dsarchiv, comme vous pouvez le dduire de ce qui est indiqu sur deuxime ligne du programme, vous constaterez que la mthode init ne prend aucun paramtre et ne fait rien :

/** * Called by the browser or applet viewer to inform * this applet that it has been loaded into the system. It is always * called before the first time that the <code>start</code> methodis * called. * <p> * A subclass of <code>Applet</code> should override this method if * it has initialization to perform. For example, an applet with * threads would use the <code>init</code> method to create the * threads and the <code>destroy</code> method to kill them. * <p> * The implementation of this method provided by the * <code>Applet</code> class does nothing.

CHAPITRE 2 VOTRE PREMIER PROGRAMME


* * @see java.applet.Applet#destroy() * @see java.applet.Applet#start() * @see java.applet.Applet#stop() */ public void init() { }

67

Les dix-huit premires lignes sont des lignes de commentaires. Il s'agit d'une forme de commentaire diffrente de celle que nous avons dj vue. Le dbut en est marqu par les caractres /* et le compilateur javac ignore tout ce qui se trouve entre ces deux caractres et les caractres */, qui marquent la fin des commentaires. (En revanche, le programme javadoc utilise les commandes qui sont insres dans ces commentaires pour gnrer automatiquement la documentation du programme. Nous y reviendrons.) Les deux lignes qui nous intressent sont les deux dernires, qui dfinissent la mthode init. Cette dfinition est vide, ce qui signifie que la mthode init ne fait rien. En fait, ce qui nous intresse n'est pas ce que fait cette mthode, mais le fait qu'elle soit automatiquement appele lorsque l'applet est charge par le navigateur. Ainsi, il nous suffit de redfinir cette mthode pour lui faire faire ce que nous voulons. Nous reviendrons dans un prochain chapitre sur cette possibilit de redfinir des mthodes existantes. Considrez seulement, pour le moment, que nous utilisons non pas ce que fait la mthode, mais le fait qu'elle soit excute automatiquement.
// Ma premire applet Java public class PremiereApplet extends java.applet.Applet { public void init() { add(new java.awt.Label("a marche !")); } }

la troisime ligne de notre applet, nous dclarons la mthode init. Elle est de type void, c'est--dire qu'elle ne retourne aucune valeur. Elle est

68
public, Applet,

LE DVELOPPEUR JAVA 2
ce qui est obligatoire parce que la mthode d'origine, dans la classe l'est. (Nous y reviendrons.) Elle n'utilise aucun paramtre. (Cependant, les parenthses sont obligatoires.)
// Ma premire applet Java public class PremiereApplet extends java.applet.Applet { public void init() { add(new java.awt.Label("a marche !")); } }

La ligne suivante dfinit ce que fait la mthode init. Elle utilise la mthode add pour ajouter quelque chose. Si vous consultez la documentation de Java, vous constaterez que la classe Applet ne comporte pas de mthode add. D'o vient celle-ci alors ? Nous avons vu prcdemment que le compilateur tait capable d'accder par dfaut aux classes se trouvant dans java.lang. Ici, cependant, nous n'avons pas indiqu de classe. Ce mcanisme ne peut donc pas tre mis en uvre. De plus, lorsque l'on invoque une mthode sans indiquer autre chose, cette mthode est recherche dans la classe dans laquelle se trouve l'invocation, c'est--dire Applet. C'est encore une fois la documentation de Java qui nous donne la rponse. En effet, la page consacre la classe Applet nous indique toute la hirarchie de celle-ci :
java.lang.Object | +----java.awt.Component | +----java.awt.Container | +----java.awt.Panel | +----java.applet.Applet

Nous voyons ici que la classe java.applet.Applet drive de la classe java.awt.Panel. Pour respecter la terminologie de Java, on dit que la classe

CHAPITRE 2 VOTRE PREMIER PROGRAMME


java.applet.Applet

69

constitue une extension de la classe java.awt.Panel qui est elle-mme une extension de la classe java.awt.Container. Cette classe se trouve elle-mme une extension de la classe java.awt.Component, son tour extension de la classe java.lang.Object. De cette faon, la classe java.applet.Applet hrite de toutes les mthodes des classes parentes. La documentation de la classe java.applet.Applet donne d'ailleurs la liste des mthodes hrites. En faisant dfiler la page vers le bas, vous constaterez que la mthode add est hrite de la classe java.awt.Component. Comme notre classe PremiereApplet est une extension de la classe java.applet.Applet (comme nous l'avons indiqu la deuxime ligne du programme), elle hrite galement de cette mthode.

Note : Vous vous demandez peut-tre de quoi la classe que nous avons appele PremierProgramme tait l'extension. De rien, tant donn que nous n'avions pas utilis le mot extends ? Non, car en fait toute classe cre sans mentionner explicitement qu'elle est l'extension d'une classe particulire est automatiquement l'extension de la classe la plus gnrale, java.lang.Object.
Ce que la mthode add ajoute notre applet est tout simplement son argument, plac entre parenthses. Nous devons ajouter un lment susceptible d'afficher du texte. Il existe plusieurs possibilits. La plus simple est de crer un objet de la classe java.awt.Label. Cet objet affiche simplement une chane de caractres. Pour crer un tel objet, nous utilisons le mot cl new, suivi du nom de la classe dont l'objet est une instance, et des ventuels paramtres.

Attention : Il ne s'agit plus ici de crer une classe drivant d'une autre
classe, mais un objet qui est un reprsentant d'une classe existante. Ce concept, absolument fondamental, sera tudi en dtail au chapitre suivant. La syntaxe utiliser pour crer une instance d'une classe consiste en le nom de la classe, suivi d'un ou de plusieurs paramtres placs entre parenthses. (Cette syntaxe est en fait identique celle utilise pour l'invocation d'une mthode. Cela est d au fait que l'on utilise, pour crer une instance, une mthode particulire qui porte le mme nom que la classe. Cette mthode est appele constructeur.) Pour dterminer les paramtres fournir, reportez-vous une fois de plus la documentation de Java. Cliquez sur le lien

70

LE DVELOPPEUR JAVA 2
Index, puis sur L et trouvez la ligne Label. Cliquez sur le lien correspondant. Vous pouvez galement slectionner java.awt dans le cadre suprieur gauche pour afficher la liste des classes correspondantes. Slectionnez ensuite Label dans le cadre infrieur droit. Vous devez obtenir la page consacre la classe Label : en faisant dfiler cette page, vous trouverez la liste des constructeurs de cette classe comme indiqu sur la Figure 2.4.

Figure 2.4 : Les constructeurs de la classe Label.

CHAPITRE 2 VOTRE PREMIER PROGRAMME

71

Vous pouvez constater qu'il en existe trois. (Si le fait qu'il puisse exister trois mthodes portant le mme nom vous perturbe, ne vous inquitez pas. Nous expliquerons cela bientt.) Nous avons utilis celui prenant pour paramtre une chane de caractres. Nous avons par ailleurs, comme dans l'exemple prcdent, choisi d'utiliser une chane sous forme littrale, c'est-dire incluse entre guillemets. Notez, au passage, l'utilisation d'un , qui est possible ici car nous utilisons le mme jeu de caractres que Windows alors que cela ne l'tait pas pour notre programme prcdent, laffichage se faisant laide du jeu de caractres employ par MS-DOS. (Plus exactement, il aurait t possible de produire un tel caractre, mais de faon beaucoup plus complexe.) Si vous avez du mal trouver le sur votre clavier, sachez que vous pouvez l'obtenir en maintenant la touche ALT enfonce et en tapant 0199 sur le pav numrique.
// Ma premire applet Java public class PremiereApplet extends java.applet.Applet { public void init() { add(new java.awt.Label("a marche !")); } }

Les deux dernires lignes contiennent les caractres de fermeture de blocs, aligns comme dans l'exemple prcdent.

Une faon plus simple d'excuter les applets


Si vous procdez comme nous l'avons fait, vous devrez, pour chaque applet que vous crirez, crer un fichier HTML. C'est un peu pnible et cela encombre inutilement votre disque. Si vous utilisez essentiellement l'AppletViewer, vous pouvez profiter d'une de ses particularits pour vous simplifier le travail. En effet, ce programme n'a que faire du code HTML, il ne s'intresse qu'au tag (c'est le nom que l'on donne aux mots cls HTML) <applet> et ignore tout le reste. Il vous suffit d'ajouter au dbut de votre programme une ligne de commentaire contenant les tags ncessaires, de la faon suivante :

72

LE DVELOPPEUR JAVA 2
//<applet code="PremiereApplet" width="200" height="150"></applet> public class PremiereApplet extends java.applet.Applet { public void init() { add(new java.awt.Label("a marche !")); } }

pour que vous puissiez ouvrir directement le fichier source Java l'aide de l'AppletViewer. Si vous avez cr les fichiers batch du chapitre prcdent, vous pourrez alors compiler votre applet en tapant :
ca PremireApplet

et l'excuter ensuite en tapant :


va

puis la touche F3.

Rsum
Ce chapitre est maintenant termin. Tout ce qui a t abord n'a pas t expliqu en dtail car les implications sont trop profondes et trop ramifies. Il faudrait pouvoir tout expliquer d'un seul coup. Ne vous inquitez pas si certains points restent encore trs obscurs. C'est normal. Tout s'claircira par la suite. Par ailleurs, ne prenez pas les deux programmes que nous avons crits dans ce chapitre comme exemple de la meilleure faon de programmer en Java. Nous avons employ ici certaines techniques que nous viterons l'avenir, lorsque nous aurons pu approfondir certains points. Pour l'instant, vous devez tre capable de comprendre les principes gnraux qui gouvernent la cration d'une applet et d'une application. Vous tes

CHAPITRE 2 VOTRE PREMIER PROGRAMME

73

galement capable de compiler et de tester un programme. De plus, et c'est peut-tre le plus important, vous commencez avoir un aperu de la faon d'utiliser non seulement la documentation de Java, pour connatre les caractristiques des diffrentes classes standard et les paramtres employer avec leurs mthodes et constructeurs, mais galement les sources de Java pour amliorer votre comprhension des principes mis en uvre.

Exercice
A titre d'exercice, nous vous suggrons de refermer ce livre et de rcrire, compiler et tester les deux programmes que nous avons tudis. Pour que l'exercice soit profitable, vous ne devez absolument pas consulter ce livre. En revanche, vous pouvez consulter autant que vous le souhaitez la documentation ou les sources de Java.

Chapitre 3 : Les objets Java

Les objets Java

ANS CE CHAPITRE, NOUS ALLONS COMMENCER UNE TUDE plus thorique qui permettra d'claircir les points rests obscurs dans les explications du chapitre prcdent. Il est trs important, pour crire de faon efficace des programmes Java, de bien comprendre la philosophie du langage. Nous essaierons de rendre les choses aussi claires que possible, et utiliserons pour cela de nombreuses comparaisons. N'oubliez pas que ces comparaisons ne sont gnralement valables que sous un point de vue limit. Elles ne servent qu' illustrer les points abords, et vous ne devez pas les pousser plus loin que nous le faisons (ou alors, sous votre propre responsabilit !). Java est un langage orient objet. C'est l plus qu'un concept la mode. C'est une vritable philosophie de la programmation. Un programmeur crit gnralement un programme pour exprimer la solution d'un problme. Il faut entendre problme au sens large. Il peut s'agir d'un problme de cal-

76

LE DVELOPPEUR JAVA 2
cul, mais galement du traitement d'une image, de la gestion de la comptabilit d'une entreprise, ou de la dfense de la plante contre des extraterrestres tout gluants. Imaginer que le programme, en s'excutant, permet de trouver la solution du problme est une erreur. Le programme EST la solution. L'excuter, ce n'est qu'exprimer cette solution sous une forme utilisable, le plus souvent en la traduisant sous une apparence graphique (au sens large : le texte blanc affich sur un cran noir, c'est aussi une forme d'expression graphique), ou sonore, voire mme tactile. (On ne connat pas encore de priphrique de sortie olfactive.) Un autre lment important prendre en compte, pour l'expression de la solution au problme trait, est le temps. Tout programme peut tre excut manuellement, sans le secours d'un ordinateur. Le temps ncessaire, dans ce cas, l'expression de la solution sous une forme utilisable est gnralement rdhibitoire. crire un programme consiste donc exprimer la solution d'un problme dans un langage qui pourra tre traduit d'une faon ou d'une autre en quelque chose de comprhensible pour un utilisateur, humain ou non. (Il est frquent que la sortie d'un programme soit destine servir d'entre un autre programme.) Le langage utilis pour dcrire la solution du problme peut tre quelconque. On peut trs bien crire n'importe quel programme en langage naturel. Le seul problme est qu'il n'existe aucun moyen, dans ce cas, de le faire excuter par un ordinateur. Il nous faut donc utiliser un langage spcialement conu dans ce but. Chaque processeur contient son propre langage, constitu des instructions qu'il est capable d'excuter. On peut exprimer la solution de n'importe quel problme dans ce langage. On produit alors un programme en langage machine. La difficult vient de ce qu'il est difficile de reprsenter certains lments du problme dans ce langage, toujours trs limit. On a donc mis au point des langages dit volus, censs amliorer la situation. Les premiers langages de ce type, comme FORTRAN, taient trs orients vers un certain type de problme : le calcul numrique. (FORTRAN signifie d'ailleurs FORmula TRANslator, tout un... programme !) Ce type de langage ne fait d'ailleurs que reproduire le fonctionnement d'un processeur, en un peu plus volu.

CHAPITRE 3 LES OBJETS JAVA

77

L'usage des ordinateurs se rpandant, on a t amens rapidement traiter des problmes autres que le calcul numrique. L'lment fondamental des problmes traiter tait l'algorithme, c'est--dire la mthode de traitement. On a donc cr des langages spcialement conus dans cette optique. Cependant, il est rapidement apparu que cette approche tait mal adapte certains problmes. En effet, l'criture d'un programme consiste modliser la solution du problme, et donc modliser par la mme occasion les lments de l'univers du problme. Tant qu'il s'agissait de nombre, tout allait bien... et encore ! Le traitement des trs grands nombres entiers tait trs difficile. Aucun langage ne permettait de reprsenter un nombre de 200 chiffres, tout simplement parce qu'aucun processeur ne disposait des moyens de les manipuler. La structure de l'univers du processeur s'imposait donc au programmeur pour reprsenter la solution de son problme. crire un programme de gestion de personnel, par exemple, posait un problme difficile : si chaque personne tait attribus un nom, un prnom, un ge, une photo, une adresse, etc., comment manipuler simplement les donnes correspondant chaque personne ? Il tait possible d'utiliser des structures de donnes reprsentant chacun de ces lments, mais il tait impossible de les regrouper simplement. Aucun langage ne possdait le type de donnes personne, et de toute faon, cela n'aurait servi rien, car ce type recouvrait forcment une ralit trs diffrente d'un programme un autre. Un certain nombre de langages ont t mis au point pour tenter d'exprimer la solution de problmes concernant des structures trs diffrentes des nombres manipuls par les langages prcdents. Par exemple, Lisp a t cr pour traiter des listes. Au dpart, il tait surtout destin au traitement du langage naturel, et aux problmes d'intelligence artificielle. Le langage tait donc conu comme un outil spcifique, sur le modle des problmes traiter. Cette approche tait tout fait justifie jusqu' ce que les programmeurs se mettent en tte d'utiliser leur langage pour exprimer d'autres types de problmes. Les Lispiens ont donc prtendu ( raison) que tout problme pouvait tre modlis l'aide de listes. D'autres, pendant ce temps, dmontraient que tout problme peut tre modlis sous forme de graphe. D'autres encore montraient que la structure de pile pouvait servir tout. Tous avaient raison, tout comme on peut dmontrer que l'on peut tout faire avec un simple couteau. On peut mme tout faire sans aucun outil ; par exemple construire un super-ordinateur main nue, en partant de rien. C'est d'ailleurs ce que

78

LE DVELOPPEUR JAVA 2
l'homme a ralis, ce qui prouve bien que c'est possible. Question productivit, le tableau est moins gai. Il a tout de mme fallu plusieurs millions d'annes pour en arriver l. Ds que l'on accorde de l'importance la productivit, toutes les belles dmonstrations ne valent plus rien. Avec la gnralisation de l'usage des ordinateurs, est apparue la ncessit d'un langage permettant d'exprimer de faon simple et efficace n'importe quel problme. Les langages orients objet rpondent cet objectif.

Tout est objet


Le principe fondamental est que le langage doit permettre d'exprimer la solution d'un problme l'aide des lments de ce problme. Un programme traitant des images doit manipuler des structures de donnes reprsentant des images, et non leur traduction sous forme de suite de 0 et de 1. Les structures de donnes manipules par un programme de gestion de personnel doivent reprsenter des personnes, avec toutes les informations pertinentes, qu'il s'agisse de texte, de dates, de nombres, d'images ou autres. Java est l'aboutissement (pour le moment, en tout cas) de ce concept. Pour Java, l'univers du problme traiter est constitu d'objets. Cette approche est la plus naturelle, car elle correspond galement notre faon d'apprhender notre univers. La modlisation des problmes en est donc facilite l'extrme. Tout est donc objet. Le terme d'objet est peut-tre mal choisi. Il ne fait pas rfrence aux objets par opposition aux tres anims. Objet signifie simplement lment de l'univers.

Les classes
Les objets appartiennent des catgories appeles classes. L'ensemble des classes dcoupe l'univers. (Par univers, il faut entendre videmment univers du problme traiter.)

CHAPITRE 3 LES OBJETS JAVA

79

Si notre problme concerne les animaux, nous pouvons crer une classe que nous appellerons animal. Si nous devons considrer les chiens, les chats et les canaris, nous crerons trois classes drives de la classe Animal : les classes Chien, Chat et Canari. Peu importe que cette division ne soit pas pertinente dans l'univers rel. Il suffit qu'elle le soit dans celui du problme traiter. Nous reprsenterons cette division de l'univers comme indiqu sur la Figure 3.1. Nous pouvons, si ncessaire, crer de nouvelles classes drives, comme le montre la Figure 3.2. Il est d'usage de reprsenter ainsi sous forme d'arbre invers, la hirarchie des classes. La classe la plus gnrale se trouve la racine (en haut, puisque

Animal

Chien

Chat

Canari

Figure 3.1 : Une reprsentation de lunivers du problmes traiter.

Animal

Chien

Chat

Canari

Teckel

Labrador

Caniche

Figure 3.2 : Des classes drives ont t ajoutes.

80

LE DVELOPPEUR JAVA 2
l'arbre est invers). Ces deux exemples sont incomplets. En effet, toutes les classes drivent d'une mme classe, la plus gnrale : la classe Objet. L'arbre prcdent doit donc tre complt comme indiqu sur la Figure 3.3.

Pertinence du modle
La structure que nous construisons ainsi reprsente l'univers du problme traiter. S'il s'agit d'un problme de l'univers rel, on peut tre tent d'essayer de construire un modle du monde rel. C'est l un pige dans lequel il faut viter de tomber, et ce pour deux raisons. La premire est qu'un modle du monde rel prendra en compte de trs nombreux critres non pertinents en ce qui concerne notre problme. La deuxime est que ce que nous croyons tre la structure du monde rel est en ralit la structure d'un modle perceptif qui dpend de trs nombreux facteurs (culturels et psychologiques en particulier) qui sont le plus souvent totalement parasites en ce qui concerne les problmes que nous avons traiter. Par exemple, selon que nous sommes plutt ports vers le crationnisme ou l'volutionnisme, nous prfrerons probablement l'un ou l'autre des modles prsents sur les Figures 3.4 et 3.5.

Objet

Animal

Chien

Chat

Canari

Teckel

Labrador

Caniche

Figure 3.3 : La hirarchie complte.

CHAPITRE 3 LES OBJETS JAVA

81

Objet

Humain

Non humain

Animal
Figure 3.4 : Modle crationniste.

Vgtal

Chose

Objet

Vivant

Non vivant

Humain

Animal

Vgtal

Figure 3.5 : Modle volutionniste.

Peu importe lequel de ces modles correspond le mieux au monde rel. La seule chose qui compte vritablement est de savoir lequel est le plus efficace pour la reprsentation de notre problme.

82

LE DVELOPPEUR JAVA 2
De la mme faon, certaines caractristiques des objets peuvent justifier ou non la cration de classes distinctes. Ainsi, si vous crivez un programme de gestion de personnel, vous crerez probablement une classe Employ et une classe Cadre. Si vous voulez effectuer des traitements tenant compte du sexe, de l'ge et de l'adresse des membres du personnel, vous pourrez opter pour diffrentes structures, comme on peut le voir sur la Figure3.6.

Objet

Employ

Cadre

Employ homme
ge : adresse :

Employe femme
ge : adresse :

Cadre homme
ge : adresse :

Cadre femme
ge : adresse :

Objet

Femme

Homme

Femme employe
ge : adresse :

Femme cadre
ge : adresse :

Homme employ
ge : adresse :

Homme cadre
ge : adresse :

Figure 3.6 : Deux structures diffrentes pour le mme problme.

CHAPITRE 3 LES OBJETS JAVA

83

Ces deux structures peuvent sembler pertinentes, puisque si l'ge et l'adresse peuvent prendre n'importe quelles valeurs et sont susceptibles de changer (hlas), le sexe est une donne qui dtermine bien deux classes distinctes. Cependant, dans le cadre du problme qui nous concerne, il est probablement beaucoup plus simple et efficace d'utiliser un modle comme celui de la Figure 3.7.

Les instances de classes


Considrez le modle de la Figure 3.8. Il est vident que le rapport entre milou et Chien n'est pas de mme nature que le rapport entre Chien et Animal. Chien est une sous-classe de Animal. Dans la terminologie de Java, on dit que la classe Chien tend la classe Animal. En revanche, milou n'est pas une classe. C'est un reprsentant de la classe Chien. Selon la terminologie Java, on dit que milou est une instance de Chien.

Objet

sexe : ge : adresse :

Employ

sexe : ge : adresse :

Cadre

Figure 3.7 : Une structure plus simple et plus efficace.

Animal

Chien

Chat

Canari

milou

rantanplan

flix

sylvestre

titi

Figure 3.8 : Sous-classes et instances.

84
Les membres de classes

LE DVELOPPEUR JAVA 2

Attention : pige ! Nous allons maintenant parler des membres d'une classe. Contrairement ce que l'on pourrait croire, ce terme ne dsigne pas les reprsentants de la classe (nous venons de voir qu'il s'agissait des instances), mais, en quelque sorte, ses caractristiques. Dans l'exemple de gestion du personnel de la Figure 3.7, sexe, ge et adresse sont des membres de la classe Employ. Tous trois sont membres d'un mme type : il s'agit de variables, c'est--dire d'lments qui peuvent prendre une valeur. Chaque employ a un ge, un sexe et une adresse. (Il en est de mme, dans cet exemple, pour chaque cadre.)
Les employs et les cadres ne se contentent pas forcment de possder des variables. Ils peuvent galement tre capables de faire quelque chose ! Pour cela, il faut qu'ils possdent des mthodes. Les mthodes dcrivent simplement des procdures qui peuvent tre excutes pour :

renvoyer une valeur, modifier l'environnement.


Par exemple, nous pouvons dfinir une classe Employ possdant une variable matricule, et une mthode afficherMatricule().

Note : Nous respecterons ici l'usage typographique de Java :

Les noms de classes commencent par une majuscule. Les noms d'instances, de variables ou de mthodes commencent par
une minuscule.

Lorsqu'un nom est compos de plusieurs mots, les mots sont accols
Les noms de mthodes sont faciles distinguer des noms de variables car ils sont suivis de parenthses encadrant les ventuels paramtres. Si une mthode ne prend pas de paramtres, les parenthses sont vides mais doivent tre prsentes.

et chacun deux (sauf le premier, pour lequel les rgles ci-dessus s'appliquent) commence par une majuscule.

CHAPITRE 3 LES OBJETS JAVA

85

Objet

Employ
variable : matricule mthode : afficherMatricule()

Figure 3.9 : La nouvelle structure de la classe Employ.

Notre nouvelle classe Employ se prsente donc comme indiqu sur la Figure 3.9. Il faut noter que si la mthode afficherMatricule() se contente d'afficher la valeur de la variable matricule, d'autres mthodes comme, par exemple, la mthode afficherAge() devraient effectuer un certain nombre d'oprations pour pouvoir mener bien leur tche. Il s'agirait probablement (suivant le modle choisi pour le reste de notre programme) d'interroger un objet pour connatre la date du jour, effectuer une soustraction (date du jour moins date de naissance), puis une srie de conversions afin d'afficher l'ge.

Les membres statiques


Il semble aller de soi que le matricule d'un employ soit une caractristique de cet employ, et non une caractristique de sa classe. Cependant, la diffrence n'est pas toujours aussi vidente. Si nous crons une classe Chien tendant la classe Animal, nous pouvons attribuer la classe Chien une variable nombreDePattes. Il est cependant probable que cette variable aura la valeur 4 pour toutes les instances de Chien. En effet, tous les animaux n'ont pas quatre pattes, mais tous les chiens ont quatre pattes. Il serait donc inutile que chaque instance de Chien possde cette variable. Il suffit qu'il partage l'usage d'une variable commune toute la classe. Ce type de variable est dit statique. Le terme correspondant en Java est static.

Note : Si le problme traiter vous oblige prendre en compte le fait


qu'un chien puisse n'avoir que trois, deux, une seule ou mme aucune

86

LE DVELOPPEUR JAVA 2
patte, il vous suffira de crer la variable statique nombreNormalDePattes et une variable non statique nombreDePattes. Il vous sera alors possible de dfinir une mthode estropi() qui retournera la valeur false (faux) si les variables nombreDePattes et nombreNormalDePattes ont des valeurs gales, et true (vrai) dans le cas contraire. Dans le cas de notre classe Employ, nous supposerons que nous utiliserons comme matricule le numro d'ordre de l'employ. Le premier employ cr aura le numro 1, le deuxime le numro 2, etc. Le problme qui se pose est alors de garder en mmoire le numro du dernier employ cr. La solution consiste ajouter une variable statique. La Figure 3.10 montre la structure obtenue. Le matricule est propre chaque employ (chaque instance d'Employ). La variable statique nombre est propre la classe Employ. (Nous l'avons appele nombre au lieu de numro, car, dans le cadre de notre problme, le numro attribu correspond au nombre d'employs. Bien sr, dans la vie relle, cela se passe rarement de cette faon, car des employs peuvent tre supprims sans que leurs numros soient raffects.)

Les constructeurs
Vous vous demandez peut-tre comment fait-on pour crer une instance partir d'une classe ? C'est trs simple. Il suffit, pour cela, de faire appel un constructeur.

Objet

Employ
variables : static nombre matricule mthode : afficherMatricule()

Figure 3.10 : Troisime version de la classe Employ.

CHAPITRE 3 LES OBJETS JAVA

87

Un constructeur est une mthode qui a pour particularit de possder le mme nom que la classe laquelle elle appartient. Si l'usage typographique de Java est respect, il est donc trs facile de distinguer un constructeur d'une mthode ordinaire : sa premire lettre est en majuscule. (Il est galement facile de le distinguer du nom de la classe laquelle il appartient, puisqu'il est suivi de parenthses encadrant ses ventuels arguments.) Voyons comment pourrait se prsenter un constructeur pour notre classe Employ. Pour crer un employ, il faut lui attribuer un matricule. Pour cela, il suffira d'interroger la classe pour connatre le nombre d'employs, d'augmenter ce nombre d'une unit et d'utiliser le rsultat comme matricule. Voici peu prs ce que cela pourrait donner :
class Employ extends Object { int matricule; static int nombre; Employ() { matricule = ++nombre; } void afficherMatricule() { System.out.println(matricule); } }

Nous avons ici crit explicitement que la classe Employ tend la classe Object, ce qui n'tait pas ncessaire, puisque nous avons vu prcdemment que toutes les classes tendent par dfaut la classe Object. Nous aurions pu tout aussi bien crire :

class Employ {

Penchons-nous maintenant sur la deuxime ligne. Dans celle-ci, nous dclarons notre premire variable. Ce sera un nombre entier (int). Le nom que nous lui donnons est matricule.

88
int matricule;

LE DVELOPPEUR JAVA 2

La troisime ligne fait exactement la mme chose en dclarant une variable entire appele nombre.
static int nombre;

Comme nous avons employ le mot static, il n'existera qu'un seul exemplaire de cette variable, qui appartiendra la classe Employ et non ses instances.

Attention : Tout ce que nous venons de dcrire est trs particulier. En

effet, les variables de type int ne sont pas des objets ordinaires. Nous ne pouvons en dire plus pour le moment. Tout cela deviendra clair au prochain chapitre. On trouve ensuite la dfinition du constructeur de la classe Employ :
Employ() { matricule = ++nombre; }

la premire ligne, nous dclarons une mthode. Cette dclaration comporte le nom de la mthode et les paramtres qu'elle emploie. Le nom tant identique celui de la classe laquelle la mthode appartient, nous savons qu'il s'agit d'un constructeur. Cette mthode sera donc excute automatiquement par Java lorsque nous crerons une instance (un objet) de la classe Employ. De plus, s'agissant d'un constructeur, cette mthode ne possde pas d'indication de type de valeur de retour. Entre les parenthses se trouve la liste des arguments, ou paramtres, de la mthode. Chaque argument est compos d'un nom de classe et d'un nom d'instance, spars par un espace. S'il y a plusieurs arguments, ils sont spars par des virgules. Ici, nous n'utilisons aucun argument. La dclaration de la mthode se termine par une accolade ouvrante qui ouvre un bloc. Ce bloc contient la dfinition de la mthode. Il se termine par une accolade fermante, la quatrime ligne.

CHAPITRE 3 LES OBJETS JAVA


La dclaration ne comporte qu'une seule ligne :
matricule = ++nombre;

89

Sa signification (simplifie) est : Augmenter d'une unit la valeur de nombre et attribuer cette valeur matricule. Il faut bien comprendre que cette ligne fait deux choses diffrentes :

Elle augmente la valeur de nombre ; Elle attribue la valeur de nombre matricule.


Cela est tout fait diffrent de la ligne suivante :

matricule = (nombre + 1);

qui attribuerait bien la valeur de nombre + 1 matricule, mais laisserait nombre inchang. Nous avons ajout ensuite une mthode qui affiche le matricule l'aide de ce que nous avons tudi au chapitre prcdent. Cette mthode n'tant pas un constructeur, elle doit tre prcde de l'indication du type de valeur qu'elle fournit. Cette valeur est appele valeur de retour. Cette mthode ne retourne aucune valeur. Il existe pour cela, comme nous l'avons vu au chapitre prcdent, un type particulier : void, qui signifie rien ou aucune valeur.

Cration d'un objet


Voyons maintenant ce qui se passe lorsque nous crons un objet, c'est-dire une instance d'une classe. Nous avons vu que la cration d'une instance mettait en jeu l'excution du constructeur de sa classe. Ce qui nous manque, c'est la syntaxe utiliser pour dclencher cette cration.

90

LE DVELOPPEUR JAVA 2
Pour crer un objet en Java, il faut utiliser le mot cl new, suivi du nom du constructeur de la classe, avec les parenthses et les arguments ventuels. Par exemple, pour crer une instance de la classe Employ (ou, en d'autres termes un objet de type Employ), nous utiliserons la syntaxe suivante :
new Employ();

Les questions qui se posent immdiatement sont : Que devient l'objet que nous avons cr, et comment pouvons-nous l'utiliser ? Pour rpondre ces questions, nous allons tester le programme suivant, version lgrement modifie du prcdent :
public class Test { public static void main(String[] argv) { new Employe(); } } class Employe { int matricule; static int nombre; Employe() { matricule = ++nombre; } void afficherMatricule() { System.out.println(matricule); } }

Les modifications que nous avons apportes au programme sont les suivantes :

Notre classe a t renomme Employe, sans accent, pour viter les


problmes.

CHAPITRE 3 LES OBJETS JAVA

91

Nous avons cr une classe Test contenant une mthode main() de


faon pouvoir excuter notre programme. Saisissez ce programme, enregistrez-le dans un fichier que vous nommerez Test.java, compilez-le et excutez-le. Si vous obtenez des messages d'erreurs, corrigez celles-ci et recommencez jusqu' ce que le programme soit compil et excut sans erreur. (N'oubliez pas de respecter la majuscule. Le nom du fichier doit tre exactement identique au nom de la classe comportant la mthode main().)

Note : Le programme se trouve galement sur le CD-ROM accompagnant


ce livre. Cependant, nous vous recommandons avec la plus haute insistance de saisir les programmes plutt que de les copier depuis le CD-ROM. Votre apprentissage sera bien plus efficace si vous faites cet effort. En particulier, chaque erreur que vous ferez et corrigerez augmentera votre exprience plus que tout autre exercice. Que fait ce programme ? Pour la partie qui nous intresse, il cre une instance de Employe, ce qui est ralis par la ligne :
new Employe();

Lorsque cette ligne est excute, Java cherche la classe Employe et la trouve dans le mme dossier que la classe Test, qui constitue notre programme. En effet, lors de la compilation, le compilateur Java cre autant de fichiers .class qu'il y a de classes cres dans le programme compil. Vous pouvez vrifier que le dossier Java contient bien les fichiers Test.class et Employe.class. Java vrifie si la classe Employe possde un constructeur ayant un argument correspondant l'objet que nous lui passons, c'est-dire, dans notre cas, pas d'argument du tout. Comme c'est bien le cas, ce constructeur est excut. Une instance d'Employe est donc cre dans la mmoire de l'ordinateur. Que pouvons-nous faire de cette instance ? Dans l'tat actuel des choses, rien. Avec certains langages, comme l'assembleur, lorsque vous crez une structure de donnes quelconque, vous devez lui allouer de la mmoire. Par exemple, si vous devez crer une variable entire capable de reprsenter

92

LE DVELOPPEUR JAVA 2
des valeurs comprises entre - 30 000 et + 30 000, vous devrez rserver une zone de mmoire constitue de deux octets. Une fois cette zone rserve, vous pouvez y crire ce que vous voulez, quand vous le voulez. Vous pouvez mme crire une valeur s'tendant sur trois octets ou mme plus. Ce faisant, vous crirez dans une zone non rserve, ce qui pourra avoir les consquences les plus imprvisibles, allant de la simple erreur dans le rsultat du programme au blocage total du systme. Avec Java, rien de tout cela n'est possible. Lorsque vous crez un objet, vous ne savez pas du tout o Java le place en mmoire. Tant que vous le tenez, vous pouvez l'utiliser. Si vous le lchez, Java ne vous le rendra que si vous tes capable de lui indiquer de faon non quivoque de quel objet il s'agit. Vous ne pouvez pas dire : Je voudrais afficher le numro matricule de l'employ que j'ai cr il y a cinq minutes peine. En revanche, vous pouvez le faire au moment de la cration, lorsque vous tenez en quelque sorte l'objet en main. Pour faire rfrence un membre d'un objet, il suffit d'indiquer le nom de l'objet suivi du nom du membre, en les sparant l'aide d'un point. (Rappelons que nous connaissons pour l'instant deux sortes de membres : les variables et les mthodes.) Modifiez le programme Test.java de la faon suivante :
public class Test2 { public static void main(String[] argv) { (new Employe()).afficherMatricule(); } } class Employe { int matricule; static int nombre; Employe() { matricule = ++nombre; }

CHAPITRE 3 LES OBJETS JAVA


void afficherMatricule() { System.out.println(matricule); } }

93

et enregistrez-le dans le fichier Test2.java. Compilez-le et excutez-le. Cette fois, vous devez obtenir le rsultat suivant :
1

C'est--dire l'affichage du matricule de l'employ cr par votre programme. C'est plutt rassurant. Nous voil maintenant certains qu'un objet, instance de la classe Employe, a bien t cr. Sommes-nous, pour autant, plus avancs ? Pas vraiment. En effet, ds que l'excution de la ligne :

(new Employe()).afficherMatricule();

est termine, l'objet n'est plus accessible. Il se trouve toujours dans la mmoire, mais vous l'avez lch et n'avez plus aucun moyen de le rattraper. Il arrive souvent que l'on cre des objets de cette faon, que nous appellerons anonyme. Par exemple, si vous disposez d'un objet appel rectangle et que cet objet possde une mthode setColor prenant pour paramtre une couleur, afin de s'afficher dans cette couleur, vous pourrez employer la syntaxe suivante :
rectangle.setColor(new Color(255, 0, 0));

pour afficher le rectangle en rouge. L'instruction new Color(255, 0, 0) cre un objet, instance de la classe Color, avec pour composante Rouge Vert et Bleu les valeurs 255, 0 et 0, et passe cet objet la mthode setColor de l'objet rectangle. L'objet peut rester accessible ou non, suivant ce qu'en fait l'objet rectangle.

94

LE DVELOPPEUR JAVA 2
Dans le cas de notre programme T e s t 2 , une fois la mthode afficherMatricule() excute, l'objet Employe n'est plus disponible. Il continue d'exister un certain temps en mmoire avant d'tre limin. Comment ? C'est l une des particularits de Java.

La destruction des objets :legarbagecollector


Avec certains langages, le programmeur doit s'occuper lui-mme de librer la mmoire en supprimant les objets devenus inutiles. C'est une des principales sources de mauvais fonctionnement des programmes. En effet, il est frquent que des parties de la mmoire restent occupes par des objets dont la vie est termine. La tche qui incombe au programmeur pour s'assurer que ce n'est jamais le cas est trs importante, alors que les consquences du problme paraissent minimes. Quelques octets de plus ou de moins, ce n'est pas a qui va faire une grosse diffrence. Malheureusement, il y a l un phnomne cumulatif. Petit petit, le nombre d'objets inutiliss restant en mmoire augmente et des troubles commencent apparatre. Ce phnomne (appel memory leaks ou fuites de mmoire) est bien connu des utilisateurs car il concerne en tout premier lieu le programme qu'ils utilisent le plus souvent : le systme d'exploitation. Imaginez un programme de quelque quinze millions de lignes de code ! Combien de structures de donnes resteront, un moment ou un autre, occuper inutilement de la mmoire alors qu'elles ne sont plus utilises ? Il est donc ncessaire de tout remettre zro rgulirement en redmarrant le systme. Il en est de mme avec certains programmes d'application, par exemple le traitement de texte que vous utilisez. Laissez-le en mmoire en permanence et travaillez chaque jour sur de gros documents. Il finira irrmdiablement par se planter. Les programmeurs qui l'ont conu ont probablement estim que la grande majorit des utilisateurs quitteraient le programme chaque soir pour le recharger le lendemain, ce qui aurait pour effet de librer la mmoire. Avec Java, le problme est rsolu de faon trs simple : un programme, appel garbage collector, ce qui signifie littralement ramasseur d'ordures, est excut automatiquement ds que la mmoire disponible devient infrieure un certain seuil. De cette faon, vous pouvez tre assur qu'aucun objet inutilis n'encombrera la mmoire au point d'tre une cause de problmes.

CHAPITRE 3 LES OBJETS JAVA

95

Certains langages plus anciens possdaient dj un garbage collector. Cependant, il prsentait gnralement un inconvnient majeur. Le processeur ne pouvant excuter qu'un seul programme la fois, le programme principal devait tre arrt pendant l'opration de nettoyage de la mmoire. Java tant un langage multithread, c'est--dire capable d'excuter plusieurs processus simultanment, la mise en action du garbage collector n'arrte pas le programme principal mais entrane simplement un ralentissement. (Il sagit l dune approximation comme nous le verrons plus loin. Cela supprime-t-il tous les problmes ? Malheureusement pas tous, mais une grande partie. La plupart des programmes modernes tant interactifs, c'est--dire attendant des actions de l'utilisateur, le garbage collector peut s'excuter en tche de fond, sans que l'utilisateur ne remarque quoi que ce soit. En effet, la vitesse laquelle celui-ci utilise la souris, ou mme le clavier, il reste au processeur suffisamment de temps entre chaque bouton cliqu ou chaque touche presse pour faire autre chose. En revanche, pour les programmes devant s'excuter en temps rel, un ralentissement peut poser un problme. Le garbage collector tant un processus asynchrone (c'est--dire dont il n'est pas possible de contrler prcisment la synchronisation avec d'autres processus), Java n'est normalement pas adapt ce type de programme. Cependant, il existe dj des versions spciales de Java quipes d'un garbage collector synchrone qui peuvent rsoudre ce type de problme au prix, videmment, d'une plus grande complexit, puisque le programmeur doit prendre en charge la synchronisation. Nous reviendrons, dans un chapitre ultrieur, sur le fonctionnement du garbage collector.

Comment retenir les objets: les handles


Nous avons donc vu que nous pouvions crer un objet facilement l'aide de l'oprateur new et du constructeur de l'objet. Nous pouvons utiliser immdiatement l'objet ainsi cr. Si nous voulons pouvoir l'utiliser ultrieurement, il faut, avant de confier cet objet Java, s'assurer que nous pourrons le rcuprer. Pour cela, il suffit de pouvoir l'identifier. Dans le langage courant, il est frquent d'utiliser des artifices pour identifier les objets. Si vous avez un chien, vous pouvez le dsigner par mon chien. Si vous avez

96

LE DVELOPPEUR JAVA 2
un chien noir et un chien blanc, vous pourrez les identifier par mon chien noir et mon chien blanc. Mais parviendrez-vous vous faire obir en disant Mon chien blanc, couch !. Il est beaucoup plus simple de leur donner chacun un identificateur unique. Dans ce cas prcis, il s'agira d'un nom. Ainsi vous pourrez, par exemple, appeler votre chien noir Mdor et votre chien blanc Azor et leur donner des ordres individuels. (Toute comparaison a ses limites, et vous objecterez peut-tre qu'il est parfaitement possible de dresser un chien pour qu'il obisse un ordre tel que Mon chien blanc, couch ! et pas Mon chien noir, couch !. C'est tout simplement que votre chien aura appris que son nom est Mon chien blanc.) Dans le langage courant, les personnes, les animaux domestiques et certains objets (les bateaux, les villes, les pays, les fleuves, etc.) sont identifis l'aide d'un nom propre. D'autres objets sont identifis par un moyen diffrent : numro d'immatriculation, titre (pour une uvre littraire ou musicale), etc. En Java, tous les objets sont logs la mme enseigne : ils sont identifis l'aide d'un handle. Le mot handle signifie poigne, en franais, ce qui est tout fait vocateur, puisqu'il sert manipuler l'objet correspondant. Cependant, il n'est pas entr dans le langage courant des programmeurs, et nous utiliserons donc le terme d'origine, au masculin, comme l'usage s'est tabli, au risque de froisser les puristes de la langue franaise. On assimile parfois le handle au nom de l'objet. Cette assimilation est incorrecte. En effet, la notion de nom voque gnralement l'unicit et la spcificit. On dit mon nom et non mes noms, alors qu'il est vident que nous en avons plusieurs. Par ailleurs, nous pensons comme si notre nom nous tait spcifique, ce qui n'est pas le cas (nous avons des homonymes). De plus, nous avons du mal imaginer que chaque objet (une cuiller, une aiguille coudre, une pomme, une ide, etc.) puisse avoir un nom. En Java, les objets peuvent avoir plusieurs handles. Autant que vous le souhaitez. En revanche, un handle ne peut correspondre qu' un seul objet simultanment. Il n'y a pas d'homonyme, ou plutt, il n'y a pas de situation dans laquelle l'homonymie cre une ambiguit (sauf, videmment, dans l'esprit du programmeur !). Un handle peut toutefois parfaitement changer d'objet. Dans la terminologie de Java, on dit que le handle pointe vers l'objet correspondant. Il est donc possible de modifier l'affectation d'un handle pour le faire pointer vers un autre objet. Il faut simplement que le

CHAPITRE 3 LES OBJETS JAVA

97

type du nouvel objet soit compatible avec le handle. En effet, chaque fois que vous crerez un handle, vous devrez indiquer vers quel type d'objet il peut pointer.

Cration des handles


Rien n'est plus simple que de crer un handle d'objet. Il suffit pour cela de le dclarer en indiquant vers quelle classe d'objets il est susceptible de pointer. Par exemple, pour crer le handle alpha pouvant pointer vers une instance d'Employe, nous crirons :
Employe alpha;

Vers quoi pointe ce handle ? Pour l'instant, vers rien. En effet, nous avons dclar le handle, ce qui suffit le faire exister. En revanche, nous ne l'avons pas initialis, c'est--dire que nous ne lui avons pas attribu sa valeur initiale. Pour l'instant, il ne pointe donc vers rien.

Attention : Notre apprentissage se fait petit petit. Il faut donc commencer par des approximations. Nous verrons un peu plus loin que, dans certains cas, les handles sont automatiquement initialiss par Java.

Modifier l'affectation d'un handle


Nous allons aborder ici un des piges de Java. Pour modifier l'affectation d'un handle, c'est--dire pour le faire pointer vers un objet (ou vers un autre objet, s'il tait dj initialis), il faut utiliser un oprateur. Il aurait t naturel d'utiliser un oprateur tel que pointe vers, ou -->, mais malheureusement, les concepteurs de Java, qui sont videmment des programmeurs, ont prfr propager l'usage tabli du signe gal. Nous allons voir que cet usage est totalement incohrent. De plus, il nous oblige utiliser un autre oprateur pour la vraie galit, ce qui est tout de mme un comble ! Nous avons vu qu'un objet pouvait tre cr l'aide de son constructeur et de l'oprateur new. Nous ne pouvons affecter un premier handle un objet nouvellement cr qu'au moment de sa cration. En effet, si nous crivons les lignes :

98
Employe alpha; new Employe();

LE DVELOPPEUR JAVA 2

il n'y a ensuite aucun moyen d'tablir un lien entre le handle cr la premire ligne et l'objet cr la seconde. Il faut donc effectuer l'affectation en mme temps que la cration, de la faon suivante :

Employe alpha; alpha = new Employe();

Ce qui ne signifie en aucun cas que le handle alpha soit gal l'objet cr, mais simplement qu'il pointe vers cet objet. Il pourra donc servir manipuler cet objet. Java nous permet mme de condenser ces deux lignes en une seule sous la forme :
Employe alpha = new Employe();

Si nous utilisons cette technique (sous l'une ou lautre de ces deux formes) dans notre programme, nous pouvons obtenir le mme rsultat tout en conservant la possibilit de rutiliser notre objet :
public class Test3 { public static void main(String[] argv) { Employe alpha = new Employe(); alpha.afficherMatricule(); alpha.afficherMatricule(); } } class Employe { int matricule; static int nombre;

CHAPITRE 3 LES OBJETS JAVA


Employe() { matricule = ++nombre; } void afficherMatricule() { System.out.println("Matricule " + matricule); } }

99

Modifiez le programme Test2.java comme indiqu dans le listing ci-dessus et enregistrez-le dans le fichier Test3.java. (Nous avons introduit d'autres modifications pour amliorer la sortie. Nous reviendrons plus loin sur leur signification.) Compilez et excutez ce programme. Vous pouvez constater qu'il nous a t possible d'afficher deux fois le matricule pour un mme objet, ce qui aurait t tout fait impossible sans l'utilisation d'un handle. Il nous aurait videmment t possible d'utiliser deux fois la ligne :
(new Employe()).afficherMatricule(); (new Employe()).afficherMatricule();

mais le rsultat n'aurait pas du tout t le mme. Si vous n'tes pas convaincu, essayez ! Vous obtiendrez le rsultat suivant :
Matricule 1 Matricule 2

En effet, la deuxime ligne cre un deuxime objet, diffrent du premier, et qui reoit donc le numro matricule suivant.

Rsum
Dans ce chapitre, vous avez appris comment les objets sont crs et comment on peut utiliser des handles pour les manipuler. Au chapitre suivant,

100

LE DVELOPPEUR JAVA 2
nous approfondirons notre tude des handles, aprs avoir prsent les primitives, que nous avons d'ailleurs dj utilises dans nos exemples. Vous devez vous rappeler les choses suivantes :

Les objets sont des instances de classes. Les objets peuvent tre crs volont l'aide de l'oprateur new et
des constructeurs.

Un handle peut tre cr en le dclarant, c'est--dire en indiquant son


nom, prcd du nom de la classe des objets auxquels il peut tre affect.

Pour faire pointer un handle vers un objet, on utilise l'oprateur = en


plaant, gauche, le handle, et droite du signe = une rfrence l'objet vers lequel il doit pointer.

Exercice
A titre d'exercice, essayez de reconstruire le programme Test3.java sans consulter le livre. Modifiez la mthode main() pour crer deux instances d' Employe ayant chacune un handle diffrent. Appelez la mthode afficherMatricule() pour afficher le matricule de chaque instance. Affectez ensuite le handle du deuxime objet au premier, puis essayez de nouveau d'afficher les matricules des deux objets. Est-ce possible ? Qu'est devenu le deuxime objet ? Les rponses sont dans le chapitre suivant.

Chapitre 4 : Les primitives et les handles

Les primitives et les handles

U CHAPITRE PRCDENT, NOUS AVONS COMMENC L'TUDE DES handles. Nous avons galement utilis, sans les tudier en dtail, des lments d'un type nouveau permettant de manipuler des nombres entiers. Avant de poursuivre notre tude des handles, nous devons nous arrter sur ces lments diffrents que l'on appelle primitives. Mais, pour comprendre la ncessit des primitives, il faut dire quelques mots de la faon dont les donnes sont conserves en mmoire. Les donnes manipules par un programme peuvent rsider dans les registres du processeur. Avec certains langages (l'assembleur, par exemple), il est possible de manipuler les registres du processeur. Ce n'est pas le cas en Java.

102

LE DVELOPPEUR JAVA 2
Les donnes peuvent rsider dans une zone de mmoire spciale que l'on appelle pile (stack). Il s'agit d'une structure de donnes dans laquelle le dernier lment entr est le premier disponible. Cette structure fonctionne un peu comme un distributeur de bonbons, constitu d'un tube dans lequel se trouve un ressort. On charge le distributeur en plaant les bonbons dans le tube, ce qui a pour effet de comprimer le ressort. Le dernier bonbon entr se trouve au niveau de l'ouverture du tube. Si on enlve un bonbon, le ressort pousse ceux qui restent vers le haut afin que le bonbon suivant soit disponible. Dans un ordinateur, la pile fonctionne de faon semblable, except qu'au lieu de dplacer tous les lments vers le bas lorsque l'on ajoute un lment, c'est le haut de la pile qui se dplace vers le haut. (Cette faon de faire est beaucoup plus efficace car on n'a pas dplacer tous les lments.) Certains langages de programmation permettent au programmeur de manipuler la pile, et en particulier de la faire dborder dans d'autres structures de donnes, ce qui peut avoir des consquences graves. Ce n'est pas le cas de Java. En Java, contrairement dautres langages, le programmeur ne dispose daucun moyen direct daccder la pile. La pile est une structure performante, mais qui prsente l'inconvnient de ne pouvoir tre utilise sans en connatre la taille. (Ce qui nest pas un problme en Java pour la raison voque ci-dessus.) Les donnes peuvent galement rsider dans une autre structure, que l'on appelle tas (heap). Le tas est moins performant que la pile, mais sa taille est dynamique. Il n'est pas ncessaire de la connatre pour l'utiliser. Elle s'tend au fur et mesure des besoins tant que la mmoire est disponible. Tous les objets Java sont placs dans cette structure, de faon transparente pour l'utilisateur. Leur adresse n'a pas tre connue du programmeur puisque le systme des handles permet de manipuler les objets. Le programmeur n'a donc pas s'occuper de l'allocation de la mmoire, ce qui rduit considrablement les risques d'erreurs. Si un objet voit sa taille crotre, il peut tre dplac volont par Java sans que cela change quoi que ce soit pour le programmeur. C'est donc une structure souple, sre, mais peu performante. Les donnes peuvent galement tre places dans des structures de donnes moins transitoires, voire quasi permanentes. Il s'agit, par exemple, des fichiers enregistrs sur disque, des zones de mmoire morte, etc. Contrairement aux autres structures de donnes, celles-ci persistent aprs l'arrt du programme.

CHAPITRE 4 LES PRIMITIVES ET LES HANDLES

103

Les primitives
Nous avons dit au chapitre prcdent que tout, en Java, tait objet, c'est-dire instance de classes. En fait, ce n'est pas tout fait vrai. Comme nous venons de le voir, les objets sont toujours crs dans une zone de mmoire dont les performances ne sont pas optimales. Java possde une classe permettant de crer des objets de type nombre entier. Il s'agit de la classe Integer. Si nous voulons effectuer une mme opration 1000 fois, par exemple afficher un compteur comptant le nombre de fois que l'opration est effectue, nous pouvons le faire de la faon suivante :

Crer un objet nombre entier et lui donner la valeur 0. Tant que la valeur du nombre entier est diffrente de 1000, augmenter
cette valeur de 1 et l'afficher. Dune part, utiliser pour cela un objet de type Integer (la classe Java utilise pour reprsenter les nombres entiers) serait particulirement peu efficace, car il faut accder 1 000 fois cet objet. D'autre part, cet objet ne sert rien d'autre qu' compter le nombre d'itrations et afficher sa valeur. Et encore s'agit-il l d'un exemple dans lequel la valeur de la variable est utilise pour l'affichage. Dans la plupart des exemples rels, ce type de variable ne sert que de compteur. Aucune des caractristiques spcifiques d'un objet n'est utilise ici. Il en est de mme pour la plupart des calculs numriques. Les concepteurs de Java ont donc dot ce langage d'une srie d'lments particuliers appels primitives. Ces lments ressemblent des objets, mais ne sont pas des objets. Ils sont crs de faon diffrente, et sont galement manipuls en mmoire de faon diffrente. Cependant, ils peuvent tre envelopps dans des objets spcialement conus cet effet, et appels enveloppeurs (wrappers). Le Tableau 4.1, dcrit les primitives, disponibles en Java.

104
Primitive
char byte short int long

LE DVELOPPEUR JAVA 2

tendue 0 65 535 - 128 + 127 - 32 768 + 32 767 - 2 147 483 648 + 2 147 483 647 de - 263 (+ 263 - 1), soit de - 9 223 372 036 854 775 808 + 9 223 372 036 854 775 807 de 1.4E-45 3.40282347E38 de 4.9E-324 1.7976931348623157E308
true

Taille 16 bits 8 bits 16 bits 32 bits

64 bits 32 bits 64 bits 1 bit 0 bit

float double boolean void

ou false

Tableau 4.1 : Les primitives de Java.

Java dispose galement de classes permettant d'envelopper les primitives, comme indiqu sur le Tableau 4.2. Classe
Character Byte Short Integer Long Tableau 4.2 : Les enveloppeurs.

Primitive
char byte short int long

CHAPITRE 4 LES PRIMITIVES ET LES HANDLES

105

Classe
Float Double Boolean Void BigInteger BigDecimal

Primitive
float double boolean void -

Tableau 4.2 (suite) : Les enveloppeurs

Les classes BigInteger et BigDecimal sont utilises pour reprsenter respectivement des valeurs entires et dcimales de prcision quelconque. Il n'existe pas de primitives quivalentes. Notez que le type char, servant reprsenter les caractres, est un type non sign sur 16 bits, conformment au standard UNICODE. (Il n'existe pas d'autre type numrique non sign.) En revanche, contrairement l'usage dans d'autres langages, le type boolean n'est pas un type numrique.

Note : linverse de ce qui se passe avec les autres langages, la taille des
diffrentes primitives est toujours la mme en Java, quel que soit l'environnement. Sur tous les ordinateurs, du plus petit PC au super-calculateur, un int ou un float feront toujours 32 bits et un long ou un double feront toujours 64 bits. C'est une des faons d'assurer la portabilit des programmes.

Utiliser les primitives


Les primitives sont utilises de faon trs simple. Elles doivent tre dclares, tout comme les handles d'objets, avec une syntaxe similaire, par exemple :
int i; char c;

106

LE DVELOPPEUR JAVA 2
double valeurFlottanteEnDoublePrcision; boolean fini;

Comme les handles d'objets, il est ncessaire de leur affecter une valeur avant de les utiliser. Elles doivent donc tre initialises. Si vous n'initialisez pas une primitive, vous obtiendrez un message d'erreur. Par exemple, la compilation du programme suivant :
public class primitives { public static void main(String[] argv) { int i; char c; double valeurFlottanteEnDoublePrcision; boolean fini; System.out.println(i); System.out.println(c); System.out.println(valeurFlottanteEnDoublePrcision); System.out.println(fini); } }

produit quatre messages d'erreur :


primitives.java:7: Variable i may not have been initialized. System.out.println(i); ^ primitives.java:8: Variable c may not have been initialized. System.out.println(c); ^ primitives.java:9: Variable valeurFlottanteEnDoublePrcision may not have been initialized. System.out.println(valeurFlottanteEnDoublePrcision); ^ primitives.java:10: Variable fini may not have been initialized. System.out.println(fini); ^ 4 errors

CHAPITRE 4 LES PRIMITIVES ET LES HANDLES

107

Pour initialiser une primitive, on utilise le mme oprateur que pour les objets, c'est--dire le signe =. Cette utilisation du signe gal est moins choquante que dans le cas des objets, car il s'agit l de rendre la variable gale une valeur, par exemple :

int i; char c; double valeurFlottanteEnDoublePrcision; boolean fini; i = 12; c = "a"; valeurFlottanteEnDoublePrcision = 23456.3456; fini = true;

Comme dans le cas des handles d'objets, il est possible d'effectuer la dclaration et l'initialisation sur la mme ligne :

int i = 12; char c = "a"; double valeurFlottanteEnDoublePrcision = 23456.3456; boolean fini = true;

Il faut noter que le compilateur peut parfois dduire du code une valeur d'initialisation, mais c'est assez rare. Considrez l'exemple suivant :
class Initialisation { public static void main(String[] args) { int b; if (true) { b = 5; } System.out.println(b); } }

108

LE DVELOPPEUR JAVA 2
Nous n'avons pas encore tudi l'instruction if, mais sachez simplement qu'elle prend un argument plac entre parenthses. Si cet argument est vrai, le bloc suivant est excut. Dans le cas contraire, il est ignor. Ici, si true est vrai, la variable b prend la valeur 5. Or, true est toujours vrai. (true signifie vrai.) Le compilateur est suffisamment intelligent pour s'en apercevoir. Ce code est donc compil sans erreur. En revanche, si vous modifiez le programme de la faon suivante :

class Initialisation { public static void main(String[] args) { int b; boolean a = true; if (a) { b = 5; } System.out.println(b); } }

le compilateur ne s'y retrouve plus et produit une erreur. Certains auteurs recommandent de toujours initialiser les variables au moment de leur dclaration afin d'viter les erreurs. Ce n'est pas, notre avis, un bon conseil. Il est vident qu'il faut initialiser les variables si leur valeur d'initialisation est connue. En revanche, si elle doit tre le rsultat d'un calcul, il est prfrable de ne pas les initialiser ( 0, par exemple, pour les valeurs numriques) avant que le calcul soit effectu. En effet, si pour une raison ou une autre, vous oubliez d'initialiser une variable qui n'a pas t initialise 0, l'erreur sera dtecte la compilation. En revanche, si vous initialisez la variable 0, le programme sera compil sans erreur. Il est galement tout fait possible qu'il ne produise pas d'erreur d'excution. Simplement, le programme risque de donner un rsultat incohrent (c'est un moindre mal) ou simplement faux. Ce type d'erreur peut parfaitement chapper votre vigilance. Si, par chance, vous effectuez les vrifications qu'il faut pour vous apercevoir qu'il y a une erreur, rien ne vous indiquera d'o elle provient.

CHAPITRE 4 LES PRIMITIVES ET LES HANDLES

109

Vous pouvez passer de longues heures de dbogage, parfois aprs que votre programme aura t distribu des millions d'exemplaires (on peut rver), alors qu'en ayant pris soin de ne pas initialiser la variable, l'erreur aurait t dtecte immdiatement.

Valeurs par dfaut des primitives


Nous venons de voir que les primitives devaient tre initialises. Cela semble indiquer qu'elles n'ont pas de valeur par dfaut. En fait, elles en reoivent une dans certains cas. Lorsque des primitives sont utilises comme membres d'une classe, et dans ce cas seulement, elles reoivent une valeur par dfaut au moment de leur dclaration. Pour le vrifier, vous pouvez compiler et excuter le programme suivant :
public class primitives2 { public static void main(String[] argv) { PrimitivesInitialiseesParDefaut pipd = new PrimitivesInitialiseesParDefaut(); System.out.println(pipd.i); System.out.println((int)pipd.c); System.out.println(pipd.valeurFlottanteEnDoublePrcision); System.out.println(pipd.fini); } } class PrimitivesInitialiseesParDefaut { int i; char c; double valeurFlottanteEnDoublePrcision; boolean fini; }

Vous pouvez remarquer trois choses intressantes dans ce programme. La premire est que, pour accder un membre d'un objet, nous utilisons le nom de cet objet, suivi d'un point et du nom du membre. Il arrive souvent

110

LE DVELOPPEUR JAVA 2
qu'un objet possde des membres qui sont eux-mmes des objets possdant des membres, etc. Il suffit alors d'ajouter la suite les noms des diffrents objets en les sparant l'aide d'un point pour accder un membre. Par exemple, si l'objet rectangle contient un membre appel dimensions qui est lui-mme un objet contenant les membres hauteur et largeur qui sont des primitives, on peut accder ces primitives en utilisant la syntaxe :
rectangle.dimensions.hauteur

En revanche, si l'objet rectangle contient directement deux membres de type primitives appels hauteur et largeur, on pourra y accder de la faon suivante :
rectangle.hauteur

Note : Dans ce type d'expression, les rfrences sont values de gauche


droite, c'est--dire que :
alpha.bta.gamma.delta

est quivalent :
((alpha.bta).gamma).delta

Il est parfaitement possible d'utiliser des parenthses pour modifier l'ordre d'valuation des rfrences. La deuxime chose qu'il est intressant de noter est l'utilisation de (int) la sixime ligne du programme. Nous reviendrons sous peu sur cette technique trs importante, qui sert ici transformer la valeur de type caractre en type numrique afin de pouvoir l'afficher. (Nous avons dit que le type char tait un type numrique, ce qui est vrai, mais il est affich par System.out.println sous la forme du caractre UNICODE correspondant

CHAPITRE 4 LES PRIMITIVES ET LES HANDLES

111

sa valeur. Le code 0 ne correspond pas un caractre affichable. Le rsultat obtenu n'est donc pas significatif. C'est pourquoi nous affichons ici le code du caractre et non le caractre lui-mme.) La troisime chose intressante dans ce programme est que nous n'avons pas fourni de constructeur pour la classe PrimitivesInitialiseesParDefaut. Ce n'est pas un problme, car dans ce cas, Java utilise un constructeur par dfaut, qui ne fait rien. (Il sagit en fait du constructeur sans argument de la classe parente, Object.) Vous pouvez constater que notre programme est compil sans erreur. Son excution produit le rsultat suivant :
0 0 0.0 false

Nos primitives ont donc bien t initialises par dfaut ! Toutes les primitives de type numrique utilises comme membres d'un objet sont initialises la valeur 0. Le type boolean est initialis la valeur false qui, rappelons-le, ne correspond aucune valeur numrique.

Diffrences entre les objets et les primitives


Nous allons maintenant pouvoir approfondir les diffrences qui existent entre les objets et les primitives. Nous utiliserons pour cela un programme manipulant des primitives de type int et des objets de type Entier. Entier est un enveloppeur, c'est--dire une classe que nous crerons pour envelopper une primitive dans un objet. Saisissez le programme suivant :
public class primitives3 { public static void main(String[] argv) { System.out.println("Primitives :"); int intA = 12; System.out.println(intA);

112
int intB = intA; System.out.println(intB); intA = 48; System.out.println(intA); System.out.println(intB); System.out.println("Objets :"); Entier entierA = new Entier(12); System.out.println(entierA); Entier entierB = entierA; System.out.println(entierB); entierA.valeur = 48; System.out.println(entierA); System.out.println(entierB); } } class Entier { int valeur; Entier(int v){ valeur = v; } public String toString(){ return ("" + valeur); } }

LE DVELOPPEUR JAVA 2

Compilez et excutez ce programme. Vous devez obtenir le rsultat suivant :


Primitives : 12 12 48 12

CHAPITRE 4 LES PRIMITIVES ET LES HANDLES


Objets : 12 12 48 48

113

Examinons tout d'abord la classe Entier. Celle-ci comporte une variable de type int. Il s'agit de la primitive envelopper. Elle comporte galement un constructeur, qui prend pour paramtre une valeur de type int et initialise le champ valeur l'aide de ce paramtre. Elle comporte enfin une mthode de type String qui ne prend pas de paramtres et retourne une reprsentation du champ valeur sous forme de chane de caractres, afin qu'il soit possible de l'afficher. La syntaxe utilise ici est un peu particulire. Elle consiste utiliser l'oprateur + avec une chane de caractres de longueur nulle d'un ct, et le champ valeur de l'autre, ce qui a pour consquence de forcer Java convertir valeur en chane de caractres et de l'ajouter la suite de la chane de longueur nulle. Le rsultat est donc une chane reprsentant valeur. Nous reviendrons en dtail sur cette opration dans la section consacre aux chanes de caractres. Venons-en maintenant la procdure main de la classe primitives3. Tout d'abord, nous affichons un message indiquant que nous traitons des primitives :
System.out.println("Primitives :");

puis nous crons une variable de type int, que nous initialisons avec la valeur littrale 12 :
int intA = 12;

Nous affichons immdiatement sa valeur la ligne suivante :


System.out.println(intA);

114
Le programme affiche donc 12.

LE DVELOPPEUR JAVA 2

Nous crons ensuite une nouvelle variable de type int et nous l'initialisons l'aide de la ligne :
int intB = intA;

ce qui attribue intB la valeur de intA. Nous affichons ensuite la valeur de intB :
System.out.println(intB);

et obtenons naturellement 12. Puis nous modifions la valeur de intA en lui attribuant une nouvelle valeur littrale, 48. Nous affichons ensuite intA et intB :
intA = 48; System.out.println(intA); System.out.println(intB);

Nous constatons que intA vaut bien maintenant 48 et que intB n'a pas chang et vaut toujours 12. Ensuite, nous faisons la mme chose avec des objets de type Entier. Nous affichons tout d'abord un message indiquant qu' partir de maintenant, nous traitons des objets :
System.out.println("Objets :");

puis nous crons un objet, instance de la classe Entier :


Entier entierA = new Entier(12);

CHAPITRE 4 LES PRIMITIVES ET LES HANDLES

115

Le handle entierA est dclar de type Entier, un nouvel objet est instanci par l'oprateur new et il est initialis par le constructeur Entier() en utilisant la valeur littrale 12 pour paramtre. Le champ valeur de cet objet contient donc la valeur entire 12. Nous le vrifions immdiatement la ligne suivante en affichant l'objet entierA :
System.out.println(entierA);

Lorsque l'on essaie d'afficher un objet, Java excute simplement la mthode toString() de cet objet et affiche le rsultat. Ici, le programme affiche donc 12. Nous crons ensuite un nouveau handle d'objet de type Entier et nous l'initialisons l'aide de la ligne :
Entier entierB = entierA;

Puis nous affichons l'objet entierB :


System.out.println(entierB);

ce qui affiche 12. la ligne suivante, nous modifions la valeur de entierA, puis nous affichons les valeurs de entierA et entierB.
entierA.valeur = 48; System.out.println(entierA); System.out.println(entierB);

Nous constatons alors que entierA a bien pris la valeur 48, mais que entierB a galement pris cette valeur.

116

LE DVELOPPEUR JAVA 2
Au point o nous en sommes, cela ne devrait pas vous surprendre. En effet, la diffrence entre objet et primitive prend toute sa signification dans les lignes :
int intB = intA;

et :
Entier entierB = entierA;

La premire signifie : Crer une nouvelle primitive de type int appele intB et l'initialiser avec la valeur de intA. alors que la deuxime signifie : Crer un nouveau handle de type Entier appel entierB et le faire pointer vers le mme objet que le handle entierA. Dans le premier cas, nous crons une nouvelle primitive, alors que dans le second, nous ne crons pas un nouvel objet, mais seulement un nouveau nom (handle) qui dsigne le mme objet. Puisqu'il n'y a qu'un seul objet, il ne peut avoir qu'une seule valeur. Par consquent, si nous changeons la valeur de l'objet vers lequel pointe le handle entierA, nous changeons aussi la valeur de l'objet point par entierB, puisquil s'agit du mme ! Pour simplifier, et bien que cela ne soit pas la ralit, vous pouvez considrer que le handle n'est qu'une tiquette pointant vers un objet qui peut avoir une valeur (c'est vrai), alors que la primitive est une valeur (en fait, c'est faux, mais cela peut tre considr comme vrai dans une certaine mesure du point de vue du programmeur Java). partir de maintenant, nous utiliserons de faon gnrique le terme de variable pour faire rfrence des primitives ou des handles d'objets. Vous devrez bien garder l'esprit le fait que deux variables diffrentes auront des valeurs diffrentes, mais que deux handles diffrents pourront ventuellement pointer vers le mme objet.

CHAPITRE 4 LES PRIMITIVES ET LES HANDLES

117

Les valeurs littrales


Nous avons vu au chapitre prcdent qu'il pouvait exister des objets anonymes, c'est--dire vers lesquels ne pointe aucun handle. Existe-t-il galement des primitives anonymes ? Pas exactement, mais l'quivalent (trs approximatif) : les valeurs littrales. Alors que les primitives sont cres et stockes en mmoire au moment de l'excution du programme (leur valeur n'est donc pas connue au moment de la compilation), les valeurs littrales sont crites en toutes lettres dans le code source du programme. Elles sont donc traduites en bytecode au moment de la compilation et stockes dans le fichier .class produit par le compilateur. Elles ne peuvent videmment tre utilises qu' l'endroit de leur cration, puisqu'il n'existe aucun moyen d'y faire rfrence. (Elles prsentent une diffrence fondamentale avec les objets anonymes qui, eux, n'existent que lors de l'excution du programme, et non lors de la compilation.) Des valeurs littrales correspondent tous les types de primitives. Pour les distinguer, on utilise cependant un procd totalement diffrent. Le Tableau 4.3 donne la liste des syntaxes utiliser. Primitive
char

Syntaxe 'x' '\--' o -- reprsente un code spcifique : \b arrire (backspace) \f saut de page (form feed) \n saut de ligne (new line) \r retour chariot (carriage return) \t tabulation horizontale (h tab) \\ \ (backslash) \' guillemet simple \" guillemet double \0oo caractre dont le code est oo en octal (Le 0 est facultatif.) \uxxxx caractre dont le code Unicode est xxxx (en hexadcimal)

Tableau 4.3 : Syntaxe des valeurs littrales.

118
Primitive
int long float double boolean

LE DVELOPPEUR JAVA 2

Syntaxe
5

(dcimal), 05 (octal), 0x5 (hexadcimal)

5L, 05L, 0x5L 5.5f 5.5

ou 5f ou 5.5E5f

ou 5.5d ou 5.5E5 ou 5.5E5d ou true

false

Tableau 4.3 (suite) : Syntaxe des valeurs littrales.

Note : Les valeurs flottantes ne peuvent tre exprimes qu'en dcimal.


Ces syntaxes ne prsentent un intrt que lorsqu'il y a une ambigut possible. Ainsi, si vous crivez :
long i = 55;

il n'y a aucune ambigut et il n'est pas utile d'ajouter le suffixe L. Par ailleurs, vous pouvez crire :
byte i = 5; short j = 8;

Bien que les valeurs littrales utilises ici soient de type int, cela ne pose pas de problme au compilateur, qui effectue automatiquement la conversion. Nous verrons plus loin par quel moyen. (Notez, au passage, que les littraux sont valus lors de la compilation, de faon viter au maximum les erreurs lors de l'excution.) Vous ne pouvez cependant pas crire :
byte i = 300;

CHAPITRE 4 LES PRIMITIVES ET LES HANDLES

119

car la valeur littrale utilise ici est suprieure la valeur maximale qui peut tre reprsente par le type byte. Vous pouvez crire :
float i = 5

car 5 est de type int et le compilateur est capable de le convertir automatiquement en type float. En revanche, vous ne pouvez pas crire :
float j = 5.5

car 5.5 est de type double. Dans ce cas, Java n'effectue pas automatiquement la conversion, pour une raison que nous verrons plus loin.

Le casting des primitives


Le casting (mot anglais qui signifie moulage), galement appel cast ou, parfois, transtypage, consiste effectuer une conversion d'un type vers un autre type. Le casting peut tre effectu dans deux conditions diffrentes :

Vers un type plus gnral. On parle alors de sur-casting ou de surtypage.

Vers un type plus particulier. On parle alors de sous-casting ou de


sous-typage. Dans le cas des primitives, le sur-casting consiste convertir vers un type dont la prcision est suprieure. C'est le cas, par exemple, de la conversion d'un byte en short, d'un int en long ou d'un float en double. On parlera en revanche de sous-casting lors de la conversion d'un type vers un autre de prcision infrieure, par exemple de double en float, de long en int ou de short en byte. A l'inverse du sur-casting, le sous-casting prsente un

120

LE DVELOPPEUR JAVA 2
risque de perte d'informations. C'est pourquoi Java est autoris effectuer de faon implicite un sur-casting, alors qu'un sous-casting doit tre demand explicitement par le programmeur. Le cas du type char est particulier. En effet, il s'agit d'un type numrique sur 16 bits non sign. C'est pourquoi il peut tre l'objet d'un casting vers un autre type numrique. Cependant, du fait qu'il est non sign, le casting d'un char en short constitue un sous-casting, bien que les deux valeurs soient reprsentes sur 16 bits. Il doit donc tre demand explicitement. Ainsi, vous pouvez crire :
int i = 'x';

mais pas :
short i = 'x';

Le casting explicite
Un casting explicite peut tre effectu simplement en faisant prcder la valeur par l'indication du nouveau type entre parenthses, par exemple :
1: 2: 3: 4: 5: float a = (float)5.5; short b = (short)'x'; double c = (double)5; float d = (float)5; byte f = (byte)300;

la premire ligne, la valeur littrale 5.5, de type double, est sous-caste (excusez le barbarisme !) vers le type float. Il n'y a pas de perte de donnes, car la valeur 5.5 peut tre valablement reprsente par un float. la ligne suivante, le caractre 'x' est sous-cast vers le type short. Il n'y pas de perte de donnes. Il n'y en a jamais avec les caractres ANSI utiliss

CHAPITRE 4 LES PRIMITIVES ET LES HANDLES

121

dans les pays occidentaux, car leurs codes sont reprsents sur 8 bits. Leur valeur est donc limite 255, alors que le type char peut reprsenter des valeurs de 0 65 535 et le type short de - 32 768 + 32 767. la troisime ligne, la valeur 5 (de type int) est sur-caste vers le type double. Ici, le casting explicite n'tait donc pas obligatoire. (On peut toujours effectuer un casting explicite, mme lorsqu'un casting implicite suffit.) la quatrime ligne, la valeur 5, de type int, est caste vers le type float. Ici non plus, un casting explicite n'tait pas obligatoire, mais pour une raison que nous allons dvelopper dans un instant. la cinquime ligne, la valeur de type int 300 est caste explicitement vers un byte. La valeur maximale que peut contenir le type byte tant de + 127, il y a perte de donnes. Aprs ce casting, si vous affichez la valeur de f, vous obtiendrez 44. La raison en est que le nombre 300, exprim en binaire, est tronqu gauche au huitime bit. De plus, le huitime bit indique le signe (en notation dite complment 2). Le rsultat, quoique calculable, est assez alatoire !

Casting d'une valeur entire en valeur flottante


Le cas du casting d'une valeur entire en valeur flottante est particulier. En effet, ce n'est pas la valeur maximale qui est prise en considration ici, mais la prcision. De faon surprenante, Java traite le problme un peu la lgre en autorisant les castings implicites d'une valeur entire vers une valeur flottante. Cela peut se comprendre du fait que la variation de valeur est limite la prcision de la reprsentation. (Il ne peut y avoir de surprise comme dans le cas du casting d'int en byte, o 128 devient - 128.) Cependant, il y a quand mme perte de donnes ! Aprs la ligne :
float i = 214748364794564L;

la variable i vaut :
2.14748365E14

122
soit :
214748365000000

LE DVELOPPEUR JAVA 2

ce qui constitue tout de mme une perte d'information. En revanche, l'ordre de grandeur est conserv.

Attention : Si vous utilisez une valeur littrale trop leve pour son
type, vous obtiendrez un message d'erreur Numeric overflow. C'est le cas, par exemple, si vous essayez de compiler un programme contenant la ligne :
short i = 2147483640;

car la valeur 2147483640 dpasse la valeur maximale qui peut tre reprsente par un short. Par ailleurs, la ligne suivante produira la mme erreur, bien que la valeur utilise ici soit parfaitement reprsentable par un long :
long i = 21474836409;

Pouvez-vous deviner pourquoi ? La rponse se trouve la fin de ce chapitre.

Casting d'une valeur flottante en valeur entire


Le casting d'une valeur flottante en valeur entire est excut par troncage (ou troncature) et non par arrondi. Pour obtenir une valeur arrondie l'entier le plus proche, il faut utiliser la mthode round() de la classe java.lang.Math.

Formation des identificateurs


Les identificateurs Java sont forms d'un nombre quelconque de caractres. Le premier caractre doit tre un Identifier Start (dbut d'identificateur). Les

CHAPITRE 4 LES PRIMITIVES ET LES HANDLES

123

autres caractres doivent faire partie des Identifier Parts. Les majuscules et les minuscules sont distingues. (Longueur, longueur et LONGUEUR sont trois identificateurs diffrents.) Vous ne pouvez pas utiliser comme identificateur un mot cl rserv de Java. (La liste des mots cls rservs est donne l'Annexe B.) Les caractres autoriss pour le dbut et la suite des identificateurs sont dcrits dans le Tableau 4.4. Codes (dcimal) 0-8 14-27 36 48-57 65-90 95 97-122 127-159 Caractres (en Times) Caractres de contrle Caractres de contrle $ 0-9 A-Z _ (trait de soulignement) a-z Dbut d'identificateur Non Non Oui Non Oui Oui Oui Non Corps d'identificateur Oui Oui Oui Oui Oui Oui Oui Oui

162-165 170 181 186 192-214

Oui Oui Oui Oui Oui

Oui Oui Oui Oui Oui

Tableau 4.4 : Les caractres autoriss dans les identificateurs.

124
Codes (dcimal) 216-246 Caractres (en Times)

LE DVELOPPEUR JAVA 2

Dbut Corps d'identificateur d'identificateur Oui

Oui Oui

248-255

Oui

Tableau 4.4 (suite) : Les caractres autoriss dans les identificateurs.

Note : Les ronds noirs () reprsentent des caractres non imprimables


dans la police Times.

Attention : Les caractres de code 0-8 et 14-27 sont souvent impossibles saisir dans les diteurs de texte. Si vous voulez vrifier s'ils sont utilisables, il peut tre ncessaire d'employer un programme spcial (diteur hexadcimal). De toute faon, cela ne prsente aucun intrt pratique !
En plus des rgles ci-dessus, il est conseill de respecter les directives suivantes :

vitez les caractres accentus dans les noms de classes. Les noms de

classes sont en effet utiliss de faon externe (en relation avec le systme d'exploitation), ce qui peut poser des problmes.

vitez de donner le mme nom des identificateurs d'lments diff-

rents, bien que cela soit autoris. Par exemple, vitez de donner une mthode le mme nom qu'une classe.

N'utilisez pas le caractre $, qui est employ automatiquement par Java

dans les noms de classes pour nommer les classes anonymes et les classe internes. (Au moins, ne l'utilisez pas dans les noms de classes.) Les classes internes et les classes anonymes seront tudies dans un prochain chapitre.

Note : Si vous voulez savoir si un caractre est un Indentifier Start ou un Identifier Part , vous pouvez utiliser les mthodes statiques isJavaIdentifierStart(char ch) et isJavaIdentifierPart(char ch) de la classe java.lang.Character.

CHAPITRE 4 LES PRIMITIVES ET LES HANDLES

125

Porte des identificateurs


Pour faire rfrence collectivement aux noms de primitives et aux handles d'objets, ainsi qu' tout ce qui permet d'identifier quelque chose (mthode, classe, etc.), on utilise le terme d'identificateur. premire vue, on peut considrer un identificateur comme le nom de l'lment qu'il identifie, mais on prfre le terme d'identificateur pour viter la confusion avec l'usage courant dans le langage naturel du mot nom. (Et en particulier, le fait que le mot nom voque l'unicit - on na souvent qu'un seul nom - alors que les objets peuvent avoir un nombre quelconque d'identificateurs.) Il est utile de s'intresser en dtail aux conditions de vie des identificateurs. La vie d'un objet se conoit souvent en termes de dure, ce qui semble pertinent. Un objet existe depuis le moment o il est cr, jusqu'au moment o le garbage collector le supprime. En revanche, ses identificateurs ont une vie tout fait indpendante. Ils peuvent tre crs avant ou aprs la cration de l'objet, et tre supprims avant ou aprs la suppression de l'objet. La vie d'un identificateur ne se conoit pas en termes de dure, mais en termes de visibilit dans le programme. Un identificateur est visible dans certaines parties du programme, et invisible dans d'autres. Lorsqu'il n'est pas visible, il peut tre masqu, ou ne pas exister. L'tendue de programme dans laquelle un identificateur existe est appele porte (scope en anglais). Le fait qu'il soit ou non visible est appel tout simplement visibilit. Nous avons vu prcdemment que la quasi-totalit d'un programme Java se trouve incluse dans une structure faite de blocs embots, dlimits par les caractres { et }. La porte d'un identificateur (du moins en ce qui concerne les primitives et les instances d'objets) s'tend de l'endroit o il est cr la fin du bloc dans lequel cette cration a eu lieu, en incluant tous les blocs imbriqus. Par exemple, dans le squelette de programme de la Figure 4.1, les zones de diffrentes nuances de gris indiquent la porte des identificateurs. Porte n'est pas synonyme de visibilit. En effet, considrez le programme suivant :

126
class X { int g = 0; { int h = 0; . . . } int i = 0; { int j = 0; . { int k = 0; . } } { int l = 0; . . } . . . }
Figure 4.1 : Porte des identificateurs.

LE DVELOPPEUR JAVA 2

public class Visibilite { static char i = 'x'; public static void main(String[] argv) { System.out.println(i); boolean i = false; System.out.println(i); test(); System.out.println(i); System.out.println(Visibilite.i); }

CHAPITRE 4 LES PRIMITIVES ET LES HANDLES


public static void test() { System.out.println(i); double i = 5.0; System.out.println(i); System.out.println(Visibilite.i); } }

127

Si vous compilez et excutez ce programme, vous devez obtenir le rsultat suivant :

x false x 5.0 x false x

L'identificateur char i et dclar static de faon que l'on puisse y accder en faisant rfrence la classe (sous la forme Visibilite.i) et non en faisant rfrence une instance de cette classe. La mthode test() est dclare static pour la mme raison. La Figure 4.2 met en vidence la porte et la visibilit des identificateurs dans ce programme. La porte de l'identificateur char i s'tend de la ligne 2 la ligne 17. Nous le vrifions aux lignes 4, 9, 12 et 15. La porte de l'identificateur boolean i s'tend de la ligne 5 la ligne 10. Nous le vrifions aux lignes 6 et 8. La porte de l'identificateur double i s'tend de la ligne 13 la ligne 16. Nous le vrifions la ligne 14.

128

LE DVELOPPEUR JAVA 2

l bo e i ol e ch an ar i i
1: public class Visibilite { 2: static char i = 'x'; 3: public static void main(String[] argv) { 4: System.out.println(i); 5: boolean i = false; 6: System.out.println(i); 7: test(); 8: System.out.println(i); 9: System.out.println(Visibilite.i); 10: } 11: public static void test() { 12: System.out.println(i); 13: double i = 5.0; 14: System.out.println(i); 15: System.out.println(Visibilite.i); 16: } 17: }
Porte Visibilit

Figure 4.2 : Porte et visibilit des identificateurs.

La visibilit de l'identificateur char i est suspendue lorsque celui-ci est masqu par l'identificateur boolean i, la ligne 5, et ce jusqu' la ligne 10. L'identificateur char i est de nouveau visible aux lignes 11 et 12, puis il est masqu encore une fois de la ligne 13 la ligne 16 par l'identificateur double i. La visibilit de l'identificateur boolean i est suspendue la ligne 11. En fait, la ligne 11 correspond l'appel de la mthode test(). La visibilit de l'identificateur boolean i est suspendue sur toute l'tendue de la mthode test(). La visibilit de l'identificateur double i s'tend sur toute sa porte. (Il n'est jamais masqu.)

Note 1 : Visibilit n'est pas synonyme d'accessibilit. Nous pouvons le constater aux lignes 9 et 15. Dans ces lignes, l'identificateur char i n'est pas visible, mais il est quand mme accessible en faisant rfrence la classe Visibilite. (Rappelons que cet identificateur est static, c'est--dire qu'il appartient la classe et non une instance de cette classe.) Nous reviendrons en dtail au chapitre suivant sur la notion d'accessibilit.

do

ub

CHAPITRE 4 LES PRIMITIVES ET LES HANDLES


Note 2 : Le masquage d'un identificateur n'est possible que :

129

Dans des mthodes diffrentes. Ici, l'identificateur char i appartient


la classe Visibilite, boolean i la mthode main() et double i la mthode test().

Dans des blocs diffrents, l'extrieur de toute mthode.


Il est possible de masquer un identificateur de la faon suivante :
char i = "x" { boolean i = false; }

condition que ce morceau de code n'appartienne pas une mthode. Ainsi, le programme suivant est compil sans erreur :
public class Visibilite2 { static char i = 'x'; { boolean i = false; } public static void main(String[] argv) { System.out.println(i); } }

et son excution affiche :


x

En revanche, la compilation du programme suivant :

130

LE DVELOPPEUR JAVA 2
public class Visibilite3 { public static void main(String[] argv) { char i = 'x'; { boolean i = false; } System.out.println(i); } }

produit le message d'erreur :


Visibilite3.java:5: Variable 'i' is already defined in this method. boolean i = false; ^ 1 error

Porte des identificateurs de mthodes


Contrairement aux handles et aux noms de primitives, la porte des identificateurs de mthodes s'tend la totalit du bloc qui les contient. Ainsi, dans la classe Visibilite, la dclaration de la mthode test() est place aprs son appel dans la mthode main(), ce qui ne produit pas d'erreur :
public class Visibilite { static char i = 'x'; public static void main(String[] argv) { System.out.println(i); boolean i = false; System.out.println(i); test(); System.out.println(i); System.out.println(Visibilite.i); }

CHAPITRE 4 LES PRIMITIVES ET LES HANDLES


public static void test() { System.out.println(i); double i = 5.0; System.out.println(i); System.out.println(Visibilite.i); } }

131

Cette criture et la suivante sont parfaitement quivalentes :


public class Visibilite { public static void test() { System.out.println(i); double i = 5.0; System.out.println(i); System.out.println(Visibilite.i); } static char i = 'x'; public static void main(String[] argv) { System.out.println(i); boolean i = false; System.out.println(i); test(); System.out.println(i); System.out.println(Visibilite.i); } }

Les objets n'ont pas de porte


Il faut bien garder l'esprit le fait que seuls les identificateurs ont une porte. Dans le cas des primitives, l'identificateur pouvant tre assimil l'objet identifi, cela ne fait pas beaucoup de diffrence pour le programmeur. En revanche, en ce qui concerne les objets, il en va diffremment.

132

LE DVELOPPEUR JAVA 2
Lorsqu'un handle est hors de porte, l'objet correspondant continue d'exister, et peut ventuellement tre accessible grce un autre handle.

Les chanes de caractres


En Java, les chanes de caractres sont des objets. Ce sont des instances de la classe String. Cependant, leur nature nous oblige en parler en mme temps que des primitives. Depuis les premiers langages de programmation, les chanes de caractres ont pos des problmes. En effet, s'il est facile de dfinir diffrents types numriques de format fixe, les chanes de caractres ne peuvent tre reprsentes dans un format fixe car leur longueur peut varier de 0 un nombre quelconque de caractres. Plusieurs approches sont possibles :

Obliger le programmeur dclarer la longueur de chaque chane. Il est


alors possible de rserver une zone de mmoire correspondant au nombre de caractres dclar. Le programmeur est ensuite libre de modifier comme il le souhaite le contenu d'une chane, du moment qu'il ne la fait pas dborder. En cas de dbordement, deux options sont possibles :

Les donnes sont tronques (avec ou sans message d'erreur). Les donnes dbordent librement, ce qui conduit gnralement
un plantage (au mieux, du programme, au pire, du systme). Cette mthode donne de bonnes performances, mais elle est peu souple, et dangereuse dans certains cas.

Utiliser des chanes de longueur dynamique. Les chanes sont cres


avec la longueur correspondant leur valeur d'initialisation. Si leur valeur est modifie, leur longueur est automatiquement adapte. Cette technique est trs souple, mais peu efficace en termes de performances.

CHAPITRE 4 LES PRIMITIVES ET LES HANDLES

133

Java utilise une approche particulire. Les chanes de caractres peuvent tre initialises une valeur quelconque. Leur longueur est choisie en fonction de leur valeur d'initialisation. En revanche, leur contenu ne peut plus tre modifi. Cette formule peut paratre restrictive, mais elle prsente plusieurs avantages. En effet, la longueur des chanes tant assure de ne jamais varier, leur utilisation est trs efficace en termes de performances. Par ailleurs, la restriction concernant l'immuabilit de leur contenu ne tient pas pour trois raisons :

Dans la plupart des programmes, de trs nombreuses chanes de ca-

ractres sont utilises, entre autres pour l'interface utilisateur, comme des constantes. (Java ne possde pas de constantes proprement parler, comme nous le verrons plus loin.) Les messages qui doivent tre affichs par le programme sont le plus souvent manipuls sous forme de chanes de caractres, et la plupart de ces messages ne changent jamais.

Java dispose d'une autre classe, appele StringBuffer, qui permet de


grer des chanes dynamiques.

Si le programmeur veut traiter des instances de la classe String comme

des chanes dynamiques, il le peut. Il y a simplement une petite prcaution prendre.

Les chanes de caractres existent aussi sous forme littrale. Pour utiliser une chane littrale, il suffit de la placer entre guillemets, comme dans l'exemple suivant :
System.out.println("Message afficher");

Attention : Vous possdez un PC, vous utilisez certainement un diteur


sous Windows pour crire vos programmes, alors que l'interprteur Java fonctionne dans une fentre DOS. L'diteur utilise donc le code ANSI alors que l'interprteur utilise le code ASCII tendu. Les caractres accentus que vous saisirez dans votre diteur ne seront donc probablement pas affichs de manire correcte. (Rappelons que le code ANSI est englob dans le code UNICODE utilis par Java pour reprsenter les caractres. Le code

134

LE DVELOPPEUR JAVA 2
ANSI constitue les 256 premiers caractres (codes 0 255) du code UNICODE. En revanche, le code ASCII ne comporte que 128 caractres (codes 0 127) identiques au code ANSI ; les codes 128 255 font partie des extensions qui peuvent diffrer selon les pays ou les environnements. Il est toutefois possible de slectionner une extension particulire grce aux pages de code du DOS.) Les chanes de caractres littrales peuvent contenir tous les caractres spciaux que nous avons dcrits propos du type char :
\b \f \n \r \t \\ \' \" \0oo \uxxxx

arrire (backspace) saut de page (form feed) saut de ligne (new line) retour chariot (carriage return) tabulation horizontale (h tab) \ (backslash) guillemet simple guillemet double caractre dont le code est oo en octal (le 0 est facultatif) caractre dont le code Unicode est xxxx (en hexadcimal)

Il est intressant d'adapter notre programme Primitive3 pour tester le comportement des chanes de caractres. Voici le programme modifi, que nous avons appel Chaines.java :
public class Chaines { public static void main(String[] argv) { String chaineA = new String("Chaine 1"); System.out.println(chaineA); String chaineB = chaineA; System.out.println(chaineB); chaineA = "Chaine 2"; System.out.println(chaineA); System.out.println(chaineB); } }

CHAPITRE 4 LES PRIMITIVES ET LES HANDLES


et voici le rsultat obtenu :
Chaine Chaine Chaine Chaine 1 1 2 1

135

Nous voyons que, bien qu'il s'agisse d'objets, les chanes se comportent comme des primitives. Cela est d au fait que les chanes ne peuvent tre modifies. Ainsi, la ligne :
chaineA = "Chaine 2";

ne modifie pas la chane pointe par le handle chaineA, mais cre une nouvelle chane et lui attribue ce handle. Si, ce moment, il n'existe pas d'autre handle pointant vers la chane d'origine, celle-ci devient inaccessible et disparatra de la mmoire au prochain dmarrage du garbage collector. Dans notre cas, le handle chaine2 pointe encore vers la chane et celle-ci continue donc d'exister normalement, en gardant sa valeur originale. la classe String. Voil pourquoi il est possible d'excuter une instruction comme celle ci-dessus. En fait, il ne s'agit pas d'affecter l'objet chaineA une nouvelle valeur comme on le ferait pour une primitive, mais de le faire pointer vers l'objet "Chaine 2". Chaque fois que vous placez une chane littrale dans votre programme, vous crez un objet instance de la classe String. Il s'agit, l encore, d'un objet anonyme, qui peut tre utilis immdiatement et devenir ensuite indisponible et futur client du garbage collector, comme dans l'instruction :
System.out.println("Message afficher");

Note : Les chanes littrales sont cres par Java sous forme d'instances de

ou se voir affect un handle, comme dans l'instruction :


String message = "Message afficher";

136

LE DVELOPPEUR JAVA 2
Dans notre programme Chaines, il tait donc tout fait inutile d'crire :
String chaineA = new String("Chaine 1");

ce qui entrane la cration de deux objets dont l'un, anonyme, est immdiatement abandonn. Il aurait t plus efficace d'crire directement :

String chaineA = "Chaine 1";

Constantes
Java ne comporte pas de constantes proprement parler. Il est cependant possible de simuler l'utilisation de constantes l'aide du mot cl final. Une variable dclare final ne peut plus tre modifie une fois qu'elle a t initialise. L'usage de constantes pour reprsenter des valeurs qui ne doivent pas tre modifies prsente deux avantages par rapport aux variables. Tout d'abord, c'est un bon moyen d'viter les erreurs. Si, par inadvertance, vous modifiez la valeur d'une primitive dclare final, le compilateur produira un message d'erreur. Cela vous vitera de futures erreurs d'excution pas toujours faciles dtecter. Par ailleurs, lorsque vous dclarez un lment final, le compilateur est mme d'optimiser le code compil afin d'amliorer sa vitesse d'excution.

Note : Il convient de remarquer que les variables dclares final peuvent tre initialises lors de l'excution, et non seulement lors de leur dclaration. Ainsi, elles ne sont constantes que pour une excution et peuvent prendre une valeur diffrente chaque excution du programme (contrairement ce qui tait le cas pour la premire version de Java, dans laquelle les variables finales devaient tre initialises lors de leur dclaration).

CHAPITRE 4 LES PRIMITIVES ET LES HANDLES

137

Utilisation de final avec des objets


L'utilisation de final n'est pas rserve aux primitives. Un handle d'objet peut parfaitement tre dclar final. Cependant, la contrainte ne s'applique alors qu'au handle, qui ne peut plus voir son affectation modifie. L'objet, lui, reste modifiable.

Accessibilit
Les variables, comme les objets, les mthodes et les classes, ont galement des accessibilits diffrentes. L'accessibilit dfinit dans quelles conditions on peut accder un lment ou, plus exactement, qui peut y accder. Un objet peut tre dclar de faon n'tre accessible que depuis la classe qui le contient. On utilise pour cela le mot cl private. Les autres modes d'accessibilit faisant appel des notions que nous n'avons pas encore tudies, nous les laisserons de ct pour l'instant.

Retour sur les variables statiques


Nous avons dfini au chapitre prcdent ce qu'taient les membres statiques en disant qu'ils appartenaient une classe et non une de ses instances. Nous allons maintenant revenir sur cette notion la lumire de ce que nous savons. Lorsque, dans la dfinition d'une classe, une primitive est dclare statique, il n'en existe qu'un seul exemplaire, quel que soit le nombre d'instances de cette classe cres (mme si ce nombre est 0). Pour prendre une analogie, considrons la dfinition d'une classe comme le plan d'une voiture. Le nombre de siges est une variable statique. En effet, il s'agit d'une caractristique du modle, et non de chaque exemplaire. Vous pouvez connatre la valeur de cette variable en regardant le plan. En revanche, la quantit d'essence restant dans le rservoir n'est pas une variable statique. Vous ne pouvez pas la mesurer sur le plan, bien que son existence ait un sens. En

138

LE DVELOPPEUR JAVA 2
effet, vous pouvez savoir, en consultant le plan, si vous pourrez interroger chaque exemplaire pour connatre la valeur de cette variable. Vous le pourrez si le plan indique que ce modle de voiture possde une jauge de carburant. La distinction variable de classe / variable d'instance devrait maintenant tre claire. Nous pouvons donc la compliquer un peu. Supposons que vous vouliez connatre la quantit d'essence restant dans le rservoir d'une voiture. Inutile de regarder sur le plan. Cette valeur ne concerne que les exemplaires, ou instances. En revanche, si vous voulez connatre le nombre de siges, vous pouvez consulter le plan, mais vous pouvez galement consulter un exemplaire quelconque. Nous avons vu qu'en Java, pour accder un membre d'un objet, il faut indiquer le nom de l'objet suivi du nom du membre, en sparant les deux l'aide d'un point. Considrons maintenant l'exemple suivant :

public class VariablesStatiques { public static void main(String[] argv) { Voiture maVoiture = new Voiture(); Voiture taVoiture = new Voiture(); System.out.println (maVoiture.puissanceMaxi); System.out.println (taVoiture.puissanceMaxi); System.out.println (Voiture.puissanceMaxi); Voiture.puissanceMaxi = 300; System.out.println (maVoiture.puissanceMaxi); System.out.println (taVoiture.puissanceMaxi); System.out.println (Voiture.puissanceMaxi); maVoiture.puissanceMaxi = 200; System.out.println (maVoiture.puissanceMaxi); System.out.println (taVoiture.puissanceMaxi); System.out.println (Voiture.puissanceMaxi); } }

CHAPITRE 4 LES PRIMITIVES ET LES HANDLES


class Voiture { static int puissanceMaxi = 250; }

139

Ce programme affiche le rsultat suivant :


250 250 250 300 300 300 200 200 200

Nous voyons ainsi que :

On peut accder un membre static par l'intermdiaire de la classe


ou d'une instance. Il est nanmoins conseill, pour optimiser la lisibilit des programmes, de s'en tenir l'accs par la classe.

Si on modifie la valeur d'une variable statique, elle est modifie pour


toutes les instances, mme celles cres avant la modification. Cela est impliqu par le fait que la variable n'existe qu' un seul exemplaire, partag par la classe et toutes les instances.

En modifiant une variable statique par l'intermdiaire d'une instance,


(ce qui n'amliore pas la lisibilit du programme), on obtient le mme rsultat qu'en le faisant par l'intermdiaire de la classe. Pour observer encore plus en dtail ce principe, on peut modifier le programme prcdent de la manire suivante :
public class VariablesStatiques2 { public static void main(String[] argv) {

140

LE DVELOPPEUR JAVA 2
Voiture maVoiture = new Voiture(); Voiture taVoiture = new Voiture(); System.out.print (maVoiture.moteur + " ");

System.out.println (maVoiture.moteur.puissanceMaxi); System.out.print (taVoiture.moteur + " "); System.out.println (taVoiture.moteur.puissanceMaxi); System.out.print (Voiture.moteur + " "); System.out.println (Voiture.moteur.puissanceMaxi); Voiture.moteur.puissanceMaxi = 300; System.out.print (Voiture.moteur + " "); System.out.println (Voiture.moteur.puissanceMaxi); System.out.print (taVoiture.moteur + " "); System.out.println (taVoiture.moteur.puissanceMaxi); System.out.print (Voiture.moteur + " "); System.out.println (Voiture.moteur.puissanceMaxi); maVoiture.moteur.puissanceMaxi = 200; System.out.print (maVoiture.moteur + " ");

System.out.println (maVoiture.moteur.puissanceMaxi); System.out.print (taVoiture.moteur + " "); System.out.println (taVoiture.moteur.puissanceMaxi); System.out.print (Voiture.moteur + " "); System.out.println (Voiture.moteur.puissanceMaxi); Moteur.puissanceMaxi = 150; System.out.print (maVoiture.moteur + " "); System.out.println (maVoiture.moteur.puissanceMaxi); System.out.print (taVoiture.moteur + " "); System.out.println (taVoiture.moteur.puissanceMaxi); System.out.print (Voiture.moteur + " "); System.out.println (Voiture.moteur.puissanceMaxi); System.out.println (maVoiture); System.out.println (taVoiture); } } class Voiture { static Moteur moteur = new Moteur(); }

CHAPITRE 4 LES PRIMITIVES ET LES HANDLES


class Moteur { static int puissanceMaxi = 250; }

141

Ce programme ne prsente d'autre intrt que de nous permettre de vrifier ce que nous avons appris jusqu'ici. Nous avons maintenant deux classes en plus de notre programme principal : Voiture et Moteur. Dans le programme principal, nous crons deux instances de Voiture : maVoiture et taVoiture. La classe Voiture possde cette fois un membre statique qui n'est plus une primitive, mais un objet. Cet objet possde luimme un membre statique qui est une primitive de type int.

Note : Aucune de ces classes ne possde de constructeur explicite. (Rappelons qu'un constructeur est une mthode qui porte le mme nom que la classe et qui est excute automatiquement lorsqu'une instance est cre.) Java utilise donc le constructeur par dfaut pour crer les instances de Voiture et de Moteur, c'est--dire le constructeur sans argument de la classe parente Object. Aprs avoir cr les deux instances de Voiture, nous affichons l'objet maVoiture.moteur :
System.out.print (maVoiture.moteur + " ");

Comme nous l'avons dit prcdemment, afficher un objet consiste simplement excuter sa mthode toString() et afficher le rsultat. En l'absence de mthode toString() dans la classe Moteur, Java affiche par dfaut (par un mcanisme qui sera expliqu en dtail au prochain chapitre) le nom de la classe et l'adresse en mmoire de l'objet. (Nous utilisons ici la mthode System.out.print() qui n'ajoute pas de fin de ligne, et nous ajoutons une chane littrale compose de trois espaces.) La ligne suivante affiche la valeur de la primitive puissanceMaxi du membre moteur de l'objet maVoiture.
System.out.println (maVoiture.moteur.puissanceMaxi);

142

LE DVELOPPEUR JAVA 2
Le programme fait ensuite la mme chose pour l'objet taVoiture ainsi que pour la classe Voiture. Les mmes oprations sont ensuite rptes aprs avoir effectu les oprations suivantes :
Voiture.moteur.puissanceMaxi = 300;

puis :
maVoiture.moteur.puissanceMaxi = 200;

et enfin :
Moteur.puissanceMaxi = 150;

La premire de ces oprations modifie l'instance moteur dans la classe Voiture. La deuxime modifie l'instance moteur dans l'instance maVoiture. Enfin, la troisime modifie directement la valeur de puissanceMaxi dans la classe Moteur. Enfin, la fin du programme, nous affichons les objets maVoiture et taVoiture :
System.out.println (maVoiture); System.out.println (taVoiture);

Si nous excutons ce programme, nous obtenons le rsultat suivant :


Moteur@10f8011b Moteur@10f8011b Moteur@10f8011b 250 250 250

CHAPITRE 4 LES PRIMITIVES ET LES HANDLES


Moteur@10f8011b Moteur@10f8011b Moteur@10f8011b Moteur@10f8011b Moteur@10f8011b Moteur@10f8011b Moteur@10f8011b Moteur@10f8011b Moteur@10f8011b Voiture@10f4011b Voiture@10f0011b 300 300 300 200 200 200 150 150 150

143

Nous voyons immdiatement qu'il n'existe, tous moments du droulement du programme, qu'un seul objet instance de la classe Moteur, l'adresse 10f8011b. En revanche, il existe bien deux instances diffrentes de la classe Voiture, l'une l'adresse 10f4011b et l'autre l'adresse 10f0011b.

Note : Toutes ces adresses changent chaque excution du programme et seront forcment diffrentes dans votre cas. Attention : Nous venons de voir qu'il est possible d'utiliser des rfrences diffrentes telles que :
maVoiture.moteur.puissanceMaxi taVoiture.moteur.puissanceMaxi Voiture.moteur.puissanceMaxi

En revanche, les rfrences suivantes sont impossibles :

maVoiture.Moteur.puissanceMaxi Voiture.Moteur.puissanceMaxi

En effet, Moteur fait rfrence une classe. Or, la notation utilisant le point comme sparateur indique une hirarchie d'inclusion. L'expression :

144
maVoiture.moteur.puissanceMaxi

LE DVELOPPEUR JAVA 2

signifie que l'objet maVoiture possde un membre appel moteur qui possde un membre appel puissanceMaxi. De la mme faon, la rfrence :
maVoiture.Moteur.puissanceMaxi

signifierait que l'objet maVoiture possde un membre Moteur qui possde un membre puissanceMaxi, ce qui est manifestement faux, puisque Moteur est une classe et que cette classe n'est pas un membre de l'objet maVoiture. De la mme faon, la rfrence :
Voiture.Moteur.puissanceMaxi

signifie que la classe Voiture possde un membre Moteur (qui est lui-mme une classe) qui possde un membre puissanceMaxi, ce qui ne correspond pas non plus la hirarchie cre par notre programme.

Note : Il faut distinguer les diffrents types de hirarchies. Ici, il s'agit d'une hirarchie base sur la possession d'un membre, ou composition. Cela n'a rien voir avec la hirarchie lie la gnalogie des classes, que nous tudierons au chapitre suivant, pas plus qu'avec celle de la propagation des vnements, qui fera l'objet d'une tude ultrieure. Le type de relation implique par la hirarchie dcrite ici est parfois appel a un (en anglais, has a) par opposition la relation mise en uvre dans la hirarchie gnalogique ou hritage, appele est un (en anglais is a).
Ainsi, on peut dire que les objets de la classe Voiture ont un membre de la classe Moteur, ce qui permet d'crire Voiture.moteur ou maVoiture.moteur mais ni les objets instances de la classe Voiture ni la classe Voiture ellemme ne contiennent la classe Moteur, ce qui interdit d'crire Voiture.Moteur ou maVoiture.Moteur. (Nous verrons au chapitre suivant qu'une classe peut, d'une certaine faon, contenir une autre classe, mais ce n'est pas le cas ici.) En revanche, l'criture suivante est possible :

CHAPITRE 4 LES PRIMITIVES ET LES HANDLES


Voiture.(Moteur.puissanceMaxi)

145

car (Moteur.puissanceMaxi) est une rfrence une primitive statique, alors que :
Voiture.Moteur.puissanceMaxi

est l'quivalent de :
(Voiture.Moteur).puissanceMaxi

Toutefois, il n'est pas possible d'crire :


maVoiture.(Moteur.puissanceMaxi);

ce qui produit une erreur de compilation.

Masquage des identificateurs de type static


Nous avons vu prcdemment que des identificateurs pouvaient tre masqus. Les identificateurs de type static peuvent tre masqus comme les autres. Examinez le programme suivant :
public class VariablesStatiques4 { public static void main(String[] argv) { Voiture maVoiture = new Voiture(350); System.out.println (maVoiture.puissance); } } class Voiture { static int puissance = 300;

146

LE DVELOPPEUR JAVA 2
Voiture (int puissance) { System.out.println (puissance); System.out.println (Voiture.puissance); } }

Si vous excutez ce programme, vous obtiendrez le rsultat suivant :


350 300 300

Ici, la classe Voiture est dote d'un constructeur. La premire ligne de ce constructeur affiche la valeur du paramtre puissance, pass lors de la cration de l'instance. Ce paramtre ayant masqu le membre statique qui porte le mme nom, comme on peut le voir sur la premire ligne de l'affichage. En revanche, le membre statique est toujours accessible en y faisant rfrence l'aide du nom de la classe, comme le montre la ligne suivante de l'affichage. Ds que l'on est hors de la porte du paramtre qui masquait le membre statique, celui-ci redevient disponible normalement comme le montre la troisime ligne de l'affichage, provoque par la ligne :
System.out.println (maVoiture.puissance);

de la mthode main(). Vous vous demandez peut-tre ce que donnerait cette ligne si elle tait excute l'intrieur du constructeur, comme dans l'exemple ci-dessous :
public class VariablesStatiques4 { public static void main(String[] argv) { Voiture maVoiture = new Voiture(350); System.out.println (maVoiture.puissance); } }

CHAPITRE 4 LES PRIMITIVES ET LES HANDLES


class Voiture { static int puissance = 300; Voiture (int puissance) { System.out.println (puissance); System.out.println (Voiture.puissance); // La ligne suivante produit une erreur : System.out.println (maVoiture.puissance); } }

147

Ce programme ne peut pas tre compil. En effet, il produit une erreur car la rfrence l'instance maVoiture est impossible puisque la construction de cet objet n'est pas termine. Pourtant, l'objet en question est parfaitement accessible. Seule sa rfrence l'aide du handle maVoiture ne peut tre rsolue. En revanche, vous pouvez y faire rfrence l'aide du mot cl this, qui sert dsigner l'objet dans lequel on se trouve. Ainsi modifi, le programme devient :
public class VariablesStatiques4 { public static void main(String[] argv) { Voiture maVoiture = new Voiture(350); System.out.println (maVoiture.puissance); } } class Voiture { static int puissance = 300; Voiture (int puissance) { System.out.println (puissance); System.out.println (Voiture.puissance); System.out.println (this.puissance); } }

et peut tre compil sans erreur. Son excution produit le rsultat suivant :

148
350 300 300 300

LE DVELOPPEUR JAVA 2

ce qui confirme le masquage du membre statique.

Note : Vous ne devez pas croire tout ce que l'on vous dit sur parole. Il
vaut toujours mieux vrifier par vous-mme. Pouvez-vous imaginer un moyen de vrifier que this fait bien rfrence l'objet maVoiture ? (Rponse la fin de ce chapitre.)

Java et les pointeurs


Un des sujets de controverse propos de Java est de savoir si ce langage possde des pointeurs. Certains auteurs ont crit que non, puisque le langage ne permet pas de manipuler le type pointeur, comme d'autres langages. Un pointeur est tout simplement une variable qui, au lieu de contenir une valeur, contient l'adresse en mmoire de cette valeur. Avant les langages orients objet, les pointeurs taient le seul outil qui permettait de crer efficacement des structures de donnes composites, comportant par exemple des chanes de caractres, des valeurs numriques, des valeurs boolennes, etc. Il suffisait au programmeur de rserver une partie de la mmoire pour y stocker ce qu'il voulait, et d'utiliser une variable pour pointer vers l'adresse de cette zone de mmoire. Un certain nombre d'oprateurs permettaient d'effectuer des oprations sur les pointeurs pour optimiser l'accs aux donnes. Les pointeurs sont considrs, juste titre, comme l'lment le plus dangereux de ce type de langage. Une simple erreur sur l'utilisation d'un pointeur permet au programme d'crire dans n'importe quelle partie de la mmoire et, dans certains environnements peu srs comme Windows, de planter tout le systme. Un langage tel que Java rend les pointeurs inutiles puisque l'on peut crer des objets de toutes natures pour reprsenter les donnes. Cependant, cer-

CHAPITRE 4 LES PRIMITIVES ET LES HANDLES

149

tains prtendent que Java possde des pointeurs (les handles) mais que le programmeur ne dispose pas des oprateurs pour manipuler ces pointeurs. Cet avis demande tre nuanc. En effet, qu'est-ce qu'un pointeur ? Si on appelle pointeur tout ce qui pointe vers quelque chose, alors l'identificateur d'une primitive (son nom) est aussi un pointeur, puisque ce nom pointe vers une zone de mmoire. Mais ici, le pointage est tout fait virtuel. L'identificateur n'est qu'un mot dans le programme. Il n'a pas d'existence propre, indpendante de la zone de mmoire correspondante. En revanche, un handle pointe galement vers une zone de mmoire, mais il a lui-mme une existence propre en mmoire. On pourrait donc dire qu'il s'agit d'un pointeur ? Pas tout fait. En effet, nul ne peut savoir vers quoi un handle pointe exactement. Virtuellement, pour le programmeur, il pointe vers l'objet auquel il a t affect. Mais rellement, lors de l'excution du programme par la JVM (Machine Virtuelle Java), bien malin qui peut dire vers quoi pointe le handle. Cela dpend de la faon dont la JVM est conue. Si l'on veut optimiser la JVM pour l'accs aux objets, il est prfrable que les handles pointent directement vers les objets. En revanche, si l'on veut optimiser la manipulation des objets, c'est une autre histoire. En effet, il est parfois ncessaire de dplacer les objets en mmoire. Si plusieurs handles pointent vers le mme objet, il faudra alors mettre jour tous les handles, ce qui peut tre long. Pour palier ce problme, certaines JVM (celle de Sun, en particulier) font pointer les handles vers un pointeur qui pointe lui-mme vers l'objet. Ainsi, si cinq handles pointent vers le mme objet et si l'objet est dplac, il suffit de modifier le pointeur intermdiaire, ce qui est plus rapide. Reste savoir si l'on peut encore dans ce cas considrer que les handles sont des pointeurs, mais cela reste un dbat purement terminologique.

Exercices
Pas d'exercice, ici, mais la rponse aux deux questions poses dans ce chapitre.

Premire question (page 112)


La premire question tait : Pourquoi la ligne suivante produit-elle une erreur de compilation :

150
long i = 21474836409;

LE DVELOPPEUR JAVA 2

alors que la valeur littrale 21474836409 est tout fait reprsentable par un long. Pour comprendre ce qui se passe ici, il faut se souvenir qu'en Java, les valeurs littrales entires sont, par dfaut, de type int. Ainsi, pour compiler la ligne ci-dessus, Java tente d'abord d'affecter la valeur littrale un int avant d'effectuer un casting vers un long. C'est l'affectation de la valeur un int qui produit l'erreur de compilation, car la valeur littrale est trop grande pour un int. Il aurait fallu crire :
long i = 21474836409L;

Deuxime question (page 138)


Pour vrifier que le mot cl this fait bien rfrence l'instance de Voiture en cours de cration, il faut, tout d'abord, considrer que, dans ce programme, il ne peut exister qu'un seul objet de ce type, puisque nous n'en crons qu'un seul. Il suffit donc de montrer que this fait rfrence un objet instance de la classe Voiture. Si c'est le cas, c'est forcment le ntre. Cela peut tre mis en vidence trs simplement en appelant la mthode System.out.println() avec this pour paramtre :
public class VariablesStatiques4 { public static void main(String[] argv) { Voiture maVoiture = new Voiture(350); System.out.println (maVoiture.puissance); } } class Voiture { static int puissance = 300; Voiture (int puissance) { System.out.println (puissance); System.out.println (Voiture.puissance);

CHAPITRE 4 LES PRIMITIVES ET LES HANDLES


System.out.println (this.puissance); System.out.println (this); } }

151

Le rsultat obtenu est le suivant :


350 300 300 Voiture@10f723ec 300

ce qui nous prouve que this fait rfrence un objet instance de la classe Voiture se trouvant l'adresse mmoire 10f723ec. (Cette adresse sera diffrente dans votre cas.)

Rsum
Dans ce chapitre, nous avons tudi deux lments fondamentaux du langage Java : les handles et les primitives. La discussion prsente ici peut parfois sembler couper les cheveux en quatre. Il est cependant ncessaire de matriser parfaitement ces notions avant de passer la suite. Dans le chapitre suivant, nous verrons comment il est possible de crer de nouvelles classes d'objets, ce qui constitue l'activit fondamentale d'un programmeur en Java.

Chapitre 5 : Crez vos propres classes

Crez vos propres classes


ANS LES EXEMPLES QUE NOUS AVONS UTILISS DANS LES chapitres prcdents, nous avons t amens crer des classes. Nous allons maintenant tudier cet aspect de la programmation en Java plus en dtail. Programmer en Java consiste uniquement crer des classes d'objets adaptes aux problmes rsoudre. Lorsque les classes ncessaires au traitement d'un problme ont t dtermines et dfinies, le dveloppement est termin. Le dveloppement d'une application Java peut se dcomposer en deux phases. La premire, que nous appellerons conception, consiste reprsenter l'univers du problme l'aide d'un ensemble de classes. La seconde, que nous appellerons implmentation, consiste construire ces classes. La

154

LE DVELOPPEUR JAVA 2
phase de conception est la plus importante car elle conditionne le bon droulement de la phase d'implmentation. Si la conception est mauvaise, aussi bonne que soit la qualit de l'implmentation, l'application sera peu efficace, voire ne fonctionnera pas. De plus, il faudra reprendre le problme la base pour amliorer les choses. Il est donc important de consacrer les ressources suffisantes cette phase. La premire chose faire pour dvelopper une application consiste donc faire la liste des classes qui seront ncessaires, ce qui consiste dcouper l'univers du problme en catgories d'objets (les classes), en dterminant leurs fonctionnalits, c'est--dire :

les tats qu'ils pourront prendre ; les messages qu'ils pourront recevoir et la faon dont ils y ragiront.
Cependant, avant de pouvoir concevoir un modle efficace, il est ncessaire de comprendre le systme hirarchique qui rgit les relations entre les classes Java.

Tout est objet (bis)


Comme nous l'avons dj dit, tous les lments que manipule Java sont des objets, l'exception des primitives. Cependant, les primitives peuvent tre traites comme des objets grce aux enveloppeurs. Tout objet Java est une instance d'une classe. De plus, chaque classe drive d'une classe de niveau suprieur, parfois appele classe parente. Cela est vrai pour toutes les classes, sauf une. Il s'agit de la classe Object, qui est l'anctre de toutes les classes. C'est l le premier point fondamental que vous ne devez jamais oublier. Le deuxime point tout aussi fondamental est que toute instance d'une classe est un objet du type correspondant, mais aussi du type de toutes ses classes anctres. C'est en fait ce que nous exprimons en disant que tout est objet. Si l'on reprend la hirarchie de la Figure 5.1, il apparat immdiatement qu'elle est incomplte. En effet, la

CHAPITRE 5 CREZ VOS PROPRES CLASSES

155

Animal

Chien

Chat

Canari

Teckel

Labrador

Caniche

Figure 5.1 : Dans cette hirarchie, il manque les ascendants de la classe Animal.

classe Animal est elle-mme drive de la classe Object, ou d'une autre classe descendant de la classe Object. La hirarchie dcrite ici est de type est un, contrairement la hirarchie a un tudie au chapitre prcdent. Un Teckel est un Chien, un Chien est un Animal, un Animal est un Object. On peut en dduire qu'un Teckel est un Animal, et qu'un Teckel est aussi un Object.

L'hritage
Lorsque nous disons qu'un Chien est un Animal (en insistant sur le fait qu'il s'agit de classes Java, et non d'objets rels) cela signifie entre autres qu'un Chien hrite de toutes les caractristiques d'un animal. De la mme faon, un Teckel hrite de toutes les caractristiques d'un Chien, et donc, par transitivit, d'un Animal. Dfinissons la classe Animal de la faon suivante :
Animal: vivant ge crier() vieillir() mourrir()

156

LE DVELOPPEUR JAVA 2
ce qui signifie qu'un animal possde un tat (vivant, qui peut tre vrai ou faux et ge, qui est une valeur numrique) et sait faire plusieurs choses (crier(), vieillir() et mourrir(), qui sont des mthodes). Nous pouvons en dduire immdiatement qu'un Caniche ou un Canari possdent les mmes caractristiques. Nous pouvons parfaitement imaginer que, pour un Animal, la caractristique vivant soit reprsente par une primitive de type boolean. Nous pouvons donc commencer crire notre classe de la faon suivante :
class Animal { boolean vivant; int ge; }

Les mthodes vieillir() et mourrir() peuvent tre crites trs simplement de la faon suivante :
class Animal { boolean vivant; int ge; void vieillir() { ++ge; } void mourrir() { vivant = false; } }

(La syntaxe ++ge n'a pas encore t tudie. Sachez que cela signifie simplement augmenter de 1 la valeur de ge.) En revanche, il est plus difficile de reprsenter la mthode crier(). En effet, nous savons que tout animal crie (dans l'univers de notre problme,

CHAPITRE 5 CREZ VOS PROPRES CLASSES

157

pas dans la ralit !), mais nous ne pouvons pas dterminer ce qu'est le cri d'un animal. Aussi, nous allons dfinir une mthode gnrique de la faon suivante :

class Animal { boolean vivant; int ge; void vieillir() { ++ge; } void mourrir() { vivant = false; } void crier() { } }

Cette mthode ne fait rien, mais sa prsence indique qu'un Animal est capable de ragir au message crier(). En l'absence de cette mthode, ce message produirait une erreur. Notre mthode n'est pas complte. En effet, si nous voulons crer une instance d'Animal, il faut que cette instance soit initialise. Cette initialisation peut comporter diverses tches, par exemple l'affichage d'un message indiquant la cration d'une nouvelle instance. Cela est laiss l'apprciation du programmeur, mais un certain nombre de tches doivent obligatoirement tre effectues. Il s'agit, par exemple, de l'initialisation des variables. Si une instance d'Animal est cre, il faut lui donner un ge et indiquer s'il est vivant ou mort. (Cela peut paratre bizarre mais, encore une fois, il s'agit de l'univers du problme et non de l'univers rel. Si nous crivons un programme de gestion de stock pour un revendeur d'animaux domestiques, il faut pouvoir faire entrer dans le stock un animal de n'importe quel ge.) Il serait possible d'initialiser la variable vivant de la faon suivante :

158
boolean vivant = true;

LE DVELOPPEUR JAVA 2

De cette faon, un animal cr est toujours vivant. Cependant, cette technique ne peut pas tre utilise pour l'ge, ni pour afficher un message. En rgle gnrale, et hormis le cas des primitives initialises lors de leur dclaration comme dans l'exemple ci-dessus, on utilise en Java deux techniques diffrentes pour initialiser les objets : les constructeurs et les initialiseurs.

Les constructeurs
Comme nous l'avons dj voqu, les constructeurs sont des mthodes particulires en ce qu'elles portent le mme nom que la classe laquelle elles appartiennent. Elles sont automatiquement excutes lors de la cration d'un objet. Nous pourrions crire pour notre classe Animal le constructeur suivant :

class Animal { boolean vivant; int ge; Animal (int a) { ge = a; vivant = true; } void vieillir() { ++ge; } void mourrir() { vivant = false; }

CHAPITRE 5 CREZ VOS PROPRES CLASSES


void crier() { } }

159

Dans ce cas, la cration d'un Animal se ferait l'aide de l'instruction suivante :

Animal nouvelAnimal = new Animal(3);

(Notez que, dans cet exemple, un animal cr est toujours vivant.) Le constructeur initialise l'objet au moyen du paramtre qui lui est pass. Ce paramtre est utilis pour initialiser la variable d'instance ge. Cela est possible parce que cette variable a une porte qui s'tend de sa dclaration jusqu' la fin du bloc, c'est--dire, dans ce cas, la fin de la classe. Par ailleurs, la visibilit de cette variable dans le constructeur est assure par le fait que nous avons appel le paramtre a et non ge. Cela nous permet d'viter le masquage de la variable d'instance ge. Si nous avions donn au paramtre le mme nom que celui de la variable d'instance, il aurait fallu accder celle-ci de la faon suivante :

Animal (int ge) { this.ge = ge; vivant = true; }

Comme il ne saute pas aux yeux qu'ge (le paramtre) et ge (la variable d'instance) sont deux primitives diffrentes, il vaut mieux leur donner des noms diffrents. Dans le cas de l'initialisation d'une variable d'instance l'aide d'un paramtre, on utilise souvent pour le nom du paramtre la premire (ou les premires) lettre(s) du nom de la variable d'instance. Si nous voulons qu'un message soit affich lors de la cration d'un Animal, nous pouvons ajouter au constructeur le code suivant :

160

LE DVELOPPEUR JAVA 2
Animal (int a) { ge = a; vivant = true; System.out.println("Un animal de " + a + " an(s) vient d'tre cr"); }

ou encore :

Animal (int a) { ge = a; vivant = true; System.out.println("Un animal de " + ge + " an(s) vient d'tre cr"); }

Note : De ces deux solutions, laquelle faut-il prfrer ? L'accs un


paramtre l'intrieur d'une mthode est normalement plus rapide que l'accs une variable d'instance. La diffrence de performance est variable selon la JVM utilise, mais elle est toujours faible. Au contraire, avec la JVM HotSpot, laccs la variable dinstance est mme plus rapide (jusqu 26 % selon nos tests). Par scurit (pour des raisons qui apparatront au Chapitre 16), si des calculs doivent tre effectus, il est toujours prfrable de les effectuer sur les paramtres et daffecter le rsultat aux variables d'instances, plutt que d'initialiser les variables d'instances puis effectuer les calculs sur elles. Nous pouvons maintenant crer les classes drives de la classe Animal. Nous commencerons par la classe Canari. Tout d'abord, nous indiquerons que cette classe drive de la classe Animal de la faon suivante :

class Canari extends Animal { }

CHAPITRE 5 CREZ VOS PROPRES CLASSES

161

Rfrence la classe parente


Notre nouvelle classe a besoin d'un constructeur. Ce constructeur prendra pour paramtre un entier indiquant l'ge du canari. Ce paramtre sera tout simplement pass au constructeur de la classe parente au moyen de l'identificateur super. La seule chose, dans notre modle, qui distingue un Canari d'un autre est son cri. Pour mettre en uvre cette diffrence, nous devons utiliser une technique appele method overriding, en anglais, que l'on peut traduire par redfinition de mthode. Dans les livres publis en franais, on trouve souvent le terme outrepasser une mthode. L'inconvnient de ce verbe est qu'il est difficile d'en driver un substantif (outrepassement ?). Nous prfrerons donc redfinition.
Animal

La redfinition des mthodes


Au titre de l'hritage, la classe Canari dispose de tous les membres de la classe Animal, et donc, en particulier, de ses variables d'instances et de ses mthodes. Toutefois, si une variable d'instance est initialise dans la classe parente, nous pouvons non seulement l'utiliser en consultant sa valeur, mais galement lui affecter une nouvelle valeur. De la mme faon, nous pouvons utiliser les mthodes de la classe parente, mais nous pouvons galement les redfinir. C'est ce que nous ferons pour la mthode crier() :
class Canari extends Animal { void crier() { System.out.println("Cui-cui !"); } }

Pour utiliser ces classes, nous les placerons, pour l'instant, dans le mme fichier que le programme principal, que nous appellerons Animaux. En voici le listing complet :

162

LE DVELOPPEUR JAVA 2
class Animaux { public static void main(String[] args) { Canari titi = new Canari(3); titi.vieillir(); titi.crier(); } } class Animal { boolean vivant; int ge; Animal (int a) { ge = a; vivant = true; System.out.println("Un animal de " + a + " an(s) vient d'tre cr"); } void vieillir() { ++ge; System.out.println("C'est l'anniversaire de cet animal."); System.out.println("Il a maintenant " + ge + " an(s)."); } void mourrir() { vivant = false; System.out.println("Cet animal est mort"); } void crier() { } } class Canari extends Animal { Canari(int a) { super(a); }

CHAPITRE 5 CREZ VOS PROPRES CLASSES


void crier() { System.out.println("Cui-cui !"); } }

163

Nous avons apport quelques modifications aux mthodes afin de les rendre plus attractives. Si vous compilez et excutez ce programme, vous obtenez le rsultat suivant :

Un animal de 3 an(s) vient d'tre cr. C'est l'anniversaire de cet animal. Il a maintenant 4 an(s). Cui-cui !

La classe Animaux possde uniquement une mthode main() qui cre un nouveau handle pour un objet de type Canari et lui affecte un nouvel objet de cette classe cr l'aide de l'oprateur new. Le paramtre 3 (de type int, comme nous l'avons vu au chapitre prcdent) est pass au constructeur de la classe Canari. Le constructeur de la classe Canari appelle simplement le constructeur de la classe parente (super()) en lui transmettant le paramtre reu. C'est l un point particulirement remarquable. En effet, c'est le constructeur de la classe Animal qui est excut, mais il construit bien un objet de la classe Canari. Les variables d'instances ge et vivant sont initialises, et le message correspondant est affich. La procdure main() appelle ensuite les mthodes vieillir() et crier(). La mthode vieillir() n'est pas redfinie dans la classe Canari. Elle y est cependant disponible en vertu de l'hritage. L'ge de titi est donc augment d'une unit, puis deux messages sont affichs. La mthode crier(), en revanche, est redfinie dans la classe Canari. C'est donc cette version qui est excute et affiche le cri du canari.

164

LE DVELOPPEUR JAVA 2

Surcharger les mthodes


Une des modifications que nous pourrions apporter notre programme concerne la possibilit de ne souhaiter l'anniversaire des animaux qu'une fois de temps en temps, mais pas tous les ans. (Ne me demandez pas pourquoi ! Disons que c'est une exigence du client pour lequel nous devons dvelopper ce programme.) Nous pourrions modifier la mthode vieillir() afin qu'elle prenne un paramtre indiquant le nombre d'annes ajouter l'ge. Cependant, nous voulons conserver la possibilit d'utiliser la mthode sans paramtre si le vieillissement est de 1. Pour cela, il nous suffit de crer dans la classe Animal une deuxime version de la mthode avec le mme nom :
class Animal { boolean vivant; int ge; Animal (int a) { ge = a; vivant = true; System.out.println("Un animal de " + a + " an(s) vient d'tre cr"); } void vieillir() { ++ge; System.out.println("C'est l'anniversaire de cet animal."); System.out.println("Il a maintenant " + ge + " an(s)."); } void vieillir(int a) { ge += a; System.out.println("C'est l'anniversaire de cet animal."); System.out.println("Il a maintenant " + ge + " an(s)."); }

CHAPITRE 5 CREZ VOS PROPRES CLASSES


void mourrir() { vivant = false; System.out.println("Cet animal est mort"); } void crier() { } }

165

(admettez, pour l'instant, que la syntaxe ge += a signifie ajouter la valeur de a ge.) Nous avons utilis ici une des fonctionnalits trs importantes du langage Java, qui consiste surcharger les mthodes. Surcharger une mthode consiste simplement la dfinir plusieurs fois avec le mme nom. Comment fait l'interprteur pour savoir quelle version il doit excuter ? Il utilise tout simplement pour cela la signature de la mthode.

Signature d'une mthode


La signature d'une mthode dsigne la liste de ses paramtres avec leur type. Ici, les deux mthodes ont des signatures diffrentes. L'une prend un paramtre de type int, l'autre ne prend aucun paramtre. Si la mthode vieillir() est appele avec un paramtre de type int ou sans paramtre, le compilateur sait quelle version il doit choisir. En revanche, un appel avec un paramtre d'un autre type, ou plusieurs paramtres, produit une erreur.

Note : Pour identifier les signatures des mthodes, Java utilise galement l'ordre des paramtres. Ainsi, les deux mthodes suivantes :
mthode(int a, long b) mthode(long b, int a)

ont des signatures diffrentes. En revanche, le nom des paramtres ne distingue pas les signatures. Une mme classe ne pourra donc contenir les deux mthodes suivantes :

166
mthode(int a, long b) mthode(int c, long d)

LE DVELOPPEUR JAVA 2

ce qui produirait une erreur de compilation. Notre programme complet modifi est donc maintenant le suivant :
class Animaux { public static void main(String[] args) { Canari titi = new Canari(3); titi.vieillir(); titi.vieillir(2); titi.crier(); } } class Animal { boolean vivant; int ge; Animal (int a) { ge = a; vivant = true; System.out.println("Un animal de " + a + " an(s) vient d'tre cr"); } void vieillir() { ++ge; System.out.println("C'est l'anniversaire de cet animal."); System.out.println("Il a maintenant " + ge + " an(s)"); } void vieillir(int a) { ge += a; System.out.println("C'est l'anniversaire de cet animal."); System.out.println("Il a maintenant " + ge + " an(s)"); }

CHAPITRE 5 CREZ VOS PROPRES CLASSES


void mourrir() { vivant = false; System.out.println("Cet animal est mort"); } void crier() { } } class Canari extends Animal { Canari(int a) { super(a); } void crier() { System.out.println("Cui-cui !"); } }

167

Si nous excutons ce programme, nous obtenons le rsultat suivant :


Un animal de 3 an(s) vient d'tre cr. C'est l'anniversaire de cet animal. Il a maintenant 4 an(s). C'est l'anniversaire de cet animal. Il a maintenant 6 an(s). Cui-cui !

Nous voyons que Java a bien excut chaque fois la mthode correspondant la signature utilise.

Optimisation
Il apparat immdiatement que notre programme n'est pas du tout optimis du point de vue de la maintenance. Si nous devons un jour modifier les messages affichs, il nous faudra effectuer deux fois la modification. En

168

LE DVELOPPEUR JAVA 2
effet, chaque version de la mthode vieillir() comporte deux lignes identiques, susceptibles d'tre modifies. Il y a plusieurs faons de rsoudre le problme. L'une pourrait tre de crer une mthode spare pour l'affichage de l'ge. Elle conviendra si cette opration est rellement distincte de l'augmentation de l'ge. Cela pourrait en effet tre le cas pour l'affichage. La classe Animal pourrait alors tre rcrite de la faon suivante :
class Animal { boolean vivant; int ge; Animal (int a) { ge = a; vivant = true; System.out.println("Un animal de " + a + " an(s) vient d'tre cr"); } void vieillir() { ++ge; afficheAge(); } void vieillir(int a) { ge += a; afficheAge(); } void afficheAge() { System.out.println("C'est l'anniversaire de cet animal."); System.out.println("Il a maintenant " + ge + " an(s)"); } void mourrir() { vivant = false; System.out.println("Cet animal est mort"); }

CHAPITRE 5 CREZ VOS PROPRES CLASSES


void crier() { } }

169

En revanche, imaginons que nous voulions vrifier si l'ge ne dpasse pas une valeur maximale, ce qui pourrait tre ralis de la faon suivante :
class Animal { boolean vivant; int ge; int geMaxi = 10; Animal (int a) { ge = a; vivant = true; System.out.println("Un animal de " + a + " an(s) vient d'tre cr"); } void vieillir() { ++ge; afficheAge(); if (ge > geMaxi) { mourrir(); } } void vieillir(int a) { ge += a; afficheAge(); if (ge > geMaxi) { mourrir(); } }

170

LE DVELOPPEUR JAVA 2
void afficheAge() { System.out.println("C'est l'anniversaire de cet animal."); System.out.println("Il a maintenant " + ge + " an(s)"); } void mourrir() { vivant = false; System.out.println("Cet animal est mort"); } void crier() { } }

Nous retrouvons cette fois encore des lignes de code dupliques. (Admettez, pour l'instant, que ces lignes signifient si la valeur de ge est suprieure la valeur de geMaxi, excuter la mthode mourrir().) Nous n'avons aucune raison d'ajouter ici une nouvelle mthode pour tester l'ge, car celui-ci n'est test que lorsque l'ge est modifi. Nous allons donc utiliser une autre approche :
class Animal { boolean vivant; int ge; int geMaxi = 10; Animal (int a) { ge = a; vivant = true; System.out.println("Un animal de " + a + " an(s) vient d'tre cr"); } void vieillir() { vieillir(1); }

CHAPITRE 5 CREZ VOS PROPRES CLASSES

171

void vieillir(int a) { ge += a; if (ge > geMaxi) { mourrir(); System.out.println("C'est l'anniversaire de cet animal."); System.out.println("Il a maintenant " + ge + " an(s)"); } } void mourrir() { vivant = false; System.out.println("Cet animal est mort"); } void crier() { } }

Cette fois, il n'y a pas de lignes dupliques. L'intgralit du traitement est effectue en un seul endroit. La mthode qui gre le cas particulier appelle simplement la mthode gnrale en lui fournissant la valeur particulire du paramtre. Cette approche doit tre employe chaque fois que possible pour amliorer la lisibilit du code. (Ici, nous avons rintgr l'affichage dans la mthode vieillir(). Bien sr, l'opportunit de cette modification dpend de ce que vous voulez obtenir. Nous aurions tout aussi bien pu conserver la mthode afficheAge().)

Surcharger les constructeurs


Les constructeurs peuvent galement tre surchargs. Notre classe Animal possde un constructeur qui prend pour paramtre une valeur de type int. Cependant, si la plupart des instances sont cres avec la mme valeur initiale, nous pouvons souhaiter utiliser un constructeur sans paramtre. Supposons, par exemple, que la plupart des instances soient cres avec 1 pour valeur initiale de ge. Nous pouvons alors rcrire la classe Animal de la faon suivante :

172

LE DVELOPPEUR JAVA 2
class Animal { boolean vivant; int ge; int geMaxi = 10; Animal() { ge = 1; vivant = true; System.out.println ("Un animal de 1 an(s) vient d'tre cr"); } Animal(int a) { ge = a; vivant = true; System.out.println("Un animal de " + a + " an(s) vient d'tre cr"); } void vieillir() { vieillir(1); } void vieillir(int a) { ge += a; if (ge > geMaxi) { mourrir(); System.out.println("C'est l'anniversaire de cet animal."); System.out.println("Il a maintenant " + ge + " an(s)"); } } void mourrir() { vivant = false; System.out.println("Cet animal est mort"); } void crier() { } }

CHAPITRE 5 CREZ VOS PROPRES CLASSES

173

Ici, les deux constructeurs ont des signatures diffrentes. Le constructeur sans paramtre traite les cas o l'ge vaut 1 la cration de l'instance. Une nouvelle instance peut donc tre cre sans indiquer l'ge, de la faon suivante :
Animal milou = new Animal();

Cependant, nous avons le mme problme d'optimisation de la lisibilit du code que dans le cas des mthodes. Bien sr, la solution est maintenant vidente. Il suffit de modifier le constructeur traitant la valeur particulire (1) pour lui faire appeler le constructeur traitant le cas gnral. Dans le cas des mthodes, il suffisait d'appeler la mthode par son nom, avec le ou les paramtres correspondant sa signature. Dans le cas d'un constructeur, l'quivalent pourrait tre :
Animal() { Animal(1); }

Ce type de rfrence n'est pas possible. Pour obtenir le rsultat souhait, vous devez utiliser le mot cl this, de la faon suivante :
Animal() { this(1); }

La raison pour laquelle vous ne pouvez pas utiliser la premire syntaxe n'est pas vidente priori. Pouvez-vous essayer de l'imaginer ? En fait, la rponse tient au fait que les constructeurs ne sont pas des mthodes. Par consquent, une invocation de type :
Animal();

174

LE DVELOPPEUR JAVA 2
appelle une mthode et non un constructeur. Or, notre programme ne contient pas de mthode de ce nom. Cependant, il est tout fait possible d'en crer une. Une classe peut parfaitement (et c'est bien dommage !) possder une mthode portant le mme nom que son constructeur. Bien entendu, il est impratif d'viter cela, principalement en raison des risques de confusion entre la mthode et le constructeur. Par ailleurs, il est prfrable de respecter l'usage consistant faire commencer le nom des mthodes par une minuscule. Les constructeurs, ayant le mme nom que leur classe, commenceront par une majuscule. De plus, un autre usage consiste utiliser des noms (du langage naturel) pour les classes, et donc les constructeurs, et des verbes pour les mthodes. En effet, les classes correspondent des objets et les mthodes des actions. La signification d'une mthode nomme vieillir() est assez vidente. En revanche, que fait une mthode nomme Animal()?

Les constructeurs par dfaut


Notre programme ainsi modifi ne fonctionne plus car nous avons introduit dans la classe Canari un bug que le compilateur n'est pas capable de dtecter. Examinez le listing complet et essayez de le trouver :

class Animaux { public static void main(String[] args) { Canari titi = new Canari(3); titi.vieillir(); titi.vieillir(2); titi.crier(); } } class Animal { boolean vivant; int ge; int geMaxi = 10;

CHAPITRE 5 CREZ VOS PROPRES CLASSES


Animal() { this(1); }

175

Animal (int a) { ge = a; vivant = true; System.out.println("Un animal de " + a + " an(s) vient d'tre cr"); } void vieillir() { vieillir(1); } void vieillir(int a) { ge += a; afficheAge(); if (ge > geMaxi) { mourrir(); } } void afficheAge() { System.out.println("C'est l'anniversaire de cet animal."); System.out.println("Il a maintenant " + ge + " an(s)"); } void mourrir() { vivant = false; System.out.println("Cet animal est mort"); } void crier() { } } class Canari extends Animal { Canari(int a) { super(a); }

176
void crier() { System.out.println("Cui-cui !"); } }

LE DVELOPPEUR JAVA 2

Vous avez trouv ? Si vous avez essay de compiler le programme et de l'excuter, vous avez pu constater que tout fonctionne correctement. Cependant, modifiez la troisime ligne de la faon suivante :
Canari titi = new Canari();

La compilation n'est plus possible. Cette situation est dlicate et potentiellement dangereuse. En effet, le bug se trouve dans la classe Canari. Nous l'y avons introduit en modifiant la classe parente Animal. De plus, il n'apparat pas si l'on compile ces deux classes sparment, ou mme ensemble, sans le programme principal (la classe Animaux). La raison pour laquelle le programme ne fonctionne pas apparat clairement lorsque vous compilez la version ainsi modifie. En effet, la tentative de compilation produit le message d'erreur suivant :
Animaux.java:3: No Constructor matching Canari() found in class Canari. Canari titi = new Canari(); ^ 1 error

En effet, la classe parente Animal comporte bien un constructeur sans paramtre, mais la classe Canari n'en comporte pas ! Modifiez maintenant la classe Canari en supprimant simplement son constructeur, de la faon suivante :
class Canari extends Animal { void crier() { System.out.println("Cui-cui !"); } }

CHAPITRE 5 CREZ VOS PROPRES CLASSES

177

Miracle ! Le programme est maintenant compil sans erreur ! Demi-miracle seulement car, si vous tentez maintenant de crer une instance de Canari avec un paramtre, vous obtenez de nouveau un message d'erreur ! Que s'est-il pass ? Pour que notre programme fonctionne, il faut que la classe Canari possde un constructeur dont la signature corresponde la syntaxe que nous utilisons pour crer une instance. Lorsque nous supprimons le constructeur de cette classe, Java fournit automatiquement un constructeur par dfaut. Le constructeur par dfaut est le suivant :

NomDeLaClasse() {}

c'est--dire qu'il ne comporte aucun paramtre et qu'il ne fait rien... en apparence ! En fait, lors de la cration d'une instance d'une classe, Java appelle automatiquement le constructeur de la classe parente. Cependant cet appel se fait sans passer le ou les paramtres correspondants. Nous pouvons le vrifier en modifiant le programme de la faon suivante :
class Animaux { public static void main(String[] args) { Canari titi = new Canari(3); titi.vieillir(); titi.vieillir(2); titi.crier(); } } class Animal { boolean vivant; int ge; int geMaxi = 10;

Animal() { this(1); }

178

LE DVELOPPEUR JAVA 2
Animal (int a) { ge = a; vivant = true; System.out.println("Un animal de " + a + " an(s) vient d'tre cr"); } void vieillir() { vieillir(1); } void vieillir(int a) { ge += a; afficheAge(); if (ge > geMaxi) { mourrir(); } } void afficheAge() { System.out.println("C'est l'anniversaire de cet animal."); System.out.println("Il a maintenant " + ge + " an(s)"); } void mourrir() { vivant = false; System.out.println("Cet animal est mort"); } void crier() { } } class Canari extends Animal { Canari(int a) { } void crier() { System.out.println("Cui-cui !"); } }

CHAPITRE 5 CREZ VOS PROPRES CLASSES


(Nous avons supprim la troisime ligne de la classe Canari.)

179

Ce programme est compil sans erreurs, et produit le rsultat suivant :


Un animal de 1 an(s) vient d'tre cr. C'est l'anniversaire de cet animal. Il a maintenant 2 an(s). C'est l'anniversaire de cet animal. Il a maintenant 4 an(s). Cui-cui !

ce qui n'est videmment pas le rsultat escompt. Ce type de bug est le plus dangereux. Ici, dans un programme de soixante-dix lignes, il ne saute dj pas aux yeux, alors imaginez le cas d'un programme de plusieurs dizaines de milliers de lignes ! La principale difficult est de localiser l'erreur. Toutes les classes fonctionnent, mais le rsultat obtenu est correct pour certaines valeurs, et pas pour d'autres. La raison de ce comportement est que, lorsqu'un constructeur ne commence pas par un appel d'un autre constructeur au moyen de :
this(arguments); // Autre constructeur de la mme classe

ou :
super(arguments); // constructeur de la classe parente

Java invoque automatiquement le constructeur sans argument de la classe parente. Ce qui est quivalent :
super();

Note : Bien entendu, cela n'est pas valable pour la classe Object, qui n'a pas de classe parente.

180

LE DVELOPPEUR JAVA 2
Retenez donc cette rgle fondamentale, dont la connaissance vous vitera bien des ennuis : Tout constructeur dont la dfinition ne commence pas explicitement par this( ou super( commence implicitement par super(). Cette rgle a un corollaire encore plus important : Si vous voulez invoquer explicitement un autre constructeur l'aide de this ou super, vous devez le faire sur la premire ligne de la dfinition. Dans le cas contraire, l'invocation implicite aura dj eu lieu. Il est un autre point important que vous ne devez pas oublier. En l'absence de tout constructeur, Java fournit un constructeur par dfaut sans argument. Cependant, si un constructeur avec argument(s) existe, Java ne fournit pas de constructeur par dfaut sans argument. Nous pouvons maintenant modifier le programme pour que tout fonctionne correctement.

class Animaux { public static void main(String[] args) { Canari titi = new Canari(3); titi.vieillir(); titi.vieillir(2); titi.crier(); } } class Animal { boolean vivant; int ge; int geMaxi = 10; Animal() { this(1); }

CHAPITRE 5 CREZ VOS PROPRES CLASSES

181

Animal (int a) { ge = a; vivant = true; System.out.println("Un animal de " + a + " an(s) vient d'tre cr"); } void vieillir() { vieillir(1); } void vieillir(int a) { ge += a; afficheAge(); if (ge > geMaxi) { mourrir(); } } void afficheAge() { System.out.println("C'est l'anniversaire de cet animal."); System.out.println("Il a maintenant " + ge + " an(s)"); } void mourrir() { vivant = false; System.out.println("Cet animal est mort"); } void crier() { } } class Canari extends Animal { Canari() { } Canari(int a) { super(a); }

182
void crier() { System.out.println("Cui-cui !"); } }

LE DVELOPPEUR JAVA 2

Note : Nous verrons dans un prochain chapitre que Java permet le traitement des erreurs d'excution au moyen des exceptions. Les exceptions sont des objets Java qui sont lancs en cas d'erreur et peuvent tre attraps l'aide d'une structure particulire. Cela permet au programmeur de traiter des erreurs sans que le programme soit interrompu. Par exemple, si le programme tente d'ouvrir un fichier inexistant, le programme peut afficher un message d'erreur pour demander l'utilisateur d'y remdier, au lieu de se terminer brutalement. Les instructions donnant lieu au traitement d'erreurs doivent tre insres dans un bloc commenant par :
try {

Par consquent, aucun traitement d'erreur n'est possible dans un constructeur invoqu l'aide de this ou super. Une autre consquence de la faon dont fonctionnent les constructeurs est que le constructeur de la classe parente est toujours excut avant celui de la classe instancie. De cette faon, on devrait pouvoir tre assur que si une classe initialise des variables de faon dpendante de variables de la classe parente, il n'y aura aucun risque d'utiliser des variables non encore initialises. En fait, il existe plusieurs moyens de contourner cette scurit, en particulier en invoquant dans un constructeur des mthodes faisant appel des variables d'une classe drive. Bien sr, nous viterons ce type de manipulation.

Les initialiseurs
Les constructeurs ne sont pas le seul moyen d'initialiser les objets. En fait, il existe en Java trois lments pouvant servir l'initialisation : les construc-

CHAPITRE 5 CREZ VOS PROPRES CLASSES

183

teurs, que nous venons d'tudier, les initialiseurs de variables d'instances, que nous avons dj utiliss sans les tudier explicitement, et les initialiseurs d'instances.

Les initialiseurs de variables d'instances


Nous avons vu que nous pouvions dclarer une variable de la faon suivante :
int a;

Nous crons ainsi une variable de type int dont le nom est a. Cette variable a-t-elle une valeur ? Comme nous l'avons dj dit, cela dpend. Si cette dclaration se trouve dans une mthode, la variable n'a pas de valeur. Toute tentative d'y faire rfrence produit une erreur de compilation. En revanche, s'il s'agit d'une variable d'instance, c'est--dire si cette dclaration se trouve en dehors de toute mthode, Java l'initialise automatiquement avec une valeur par dfaut, comme nous l'avons vu au Chapitre 4. Nous pouvons cependant initialiser nous-mmes les variables de la faon suivante :
int a = int b = float c boolean float e 5; a * 7; = (b - a) / 3; d = (a < b); = (b != 0) ? (float)a / b : 0;

Sur chaque ligne, on trouve gauche du premier signe = la dclaration d'une variable, et droite un initialiseur de variable. Il peut s'agir de la forme la plus simple (une valeur littrale, comme la premire ligne) ou d'une formule plus complexe, comme celle de la cinquime ligne, qui comporte un test logique, une division et un casting d'un int en float. (La signification de cette expression sera tudie dans un prochain chapitre. Si vous tes curieux, sachez que sa valeur est a/b si b est diffrent de 0 et 0 dans le cas contraire.)

184

LE DVELOPPEUR JAVA 2
Les initialiseurs de variables permettent d'effectuer des oprations d'une certaine complexit, mais celle-ci est tout de mme limite. En effet, ils doivent tenir sur une seule ligne. Pour effecteur des initialisations plus complexes, nous savons que nous pouvons excuter celles-ci dans un constructeur. Une autre possibilit consiste utiliser un initialiseur d'instance.

Les initialiseurs d'instances


Un initialiseur d'instance est tout simplement un bloc plac, comme les variables d'instances, l'extrieur de toute mthode ou constructeur. Par exemple, nous pouvons initialiser la variable e de l'exemple prcdent de la faon suivante :
class Initialiseurs { public static void main(String[] args) { Init init = new Init(); System.out.println("a = " + init.a); System.out.println("b = " + init.b); System.out.println("c = " + init.c); System.out.println("d = " + init.d); System.out.println("e = " + init.e); } } class Init { int a = 5; int b = a * 7; float c = (b - a) / 3; boolean d = (a < b); float e; { if (b != 0) { e = (float)a/b; } } }

CHAPITRE 5 CREZ VOS PROPRES CLASSES

185

(Nous avons intgr ces initialisations une classe de faon pouvoir compiler le programme sans erreurs.) Ici, la classe Init ne fait rien d'autre qu'initialiser ses variables. La variable e est initialise la mme valeur que dans l'exemple prcdent. En fait, le cas o b vaut 0 n'est pas trait car, s'agissant d'une variable d'instance, e est d'abord initialise 0. Si b est gal 0, aucune autre initialisation de e n'est alors ncessaire. Les initialiseurs d'instances permettent d'effectuer des initialisations plus complexes que les initialiseurs de variables. Ils offrent en outre l'avantage par rapport aux constructeurs d'tre beaucoup plus rapides. Invoquer un constructeur est une opration plus longue, surtout si la classe a une ascendance complexe. Dans ce cas, en effet, les constructeurs de tous les anctres doivent tre invoqus. De plus, les initialiseurs permettent d'utiliser les exceptions pour traiter les conditions d'erreurs. Un autre avantage des initialiseurs est qu'ils permettent d'effectuer un traitement quelle que soit la signature du constructeur utilis. Il est ainsi possible de placer le code d'initialisation commun tous les constructeurs dans un initialiseur et de ne traiter dans les diffrents constructeurs que les oprations spcifiques. Un dernier avantage des initialiseurs est qu'ils permettent d'effectuer des initialisations dans des classes qui ne peuvent pas comporter de constructeurs. En effet, tout comme il est possible de crer des objets anonymes, qui sont utiliss seulement au moment de leur cration, Java permet de crer au vol des classes anonymes. Les constructeurs devant porter le mme nom que leur classe, les classes anonymes ne peuvent pas comporter de constructeurs. (Nous reviendrons plus loin sur les classes anonymes, lorsque nous aurons introduit les classes internes.) Les initialiseurs comportent cependant des limitations. Il n'est pas possible de leur passer des paramtres comme dans le cas des constructeurs. De plus, ils sont excuts avant les constructeurs et ne peuvent donc utiliser les paramtres de ceux-ci.

186
Ordre d'initialisation

LE DVELOPPEUR JAVA 2

L'excution des initialiseurs a lieu dans l'ordre dans lequel ils apparaissent dans le code. Normalement, il n'est pas possible de faire rfrence une variable qui n'a pas encore t initialise. Par exemple, le code suivant :
class OrdreInitialisation { public static void main(String[] args) { Init init = new Init(); System.out.println("a = " + init.a); System.out.println("b = " + init.b); } } class Init { int b = a; int a = 5; }

provoque une erreur de compilation. Le compilateur voit qu'il s'agit d'une rfrence en avant et refuse d'excuter l'initialisation. Cependant, il est possible, dans certains cas, d'arriver au mme rsultat sans provoquer d'erreur de compilation. Le programme suivant, par exemple, est compil sans erreur :
class OrdreInitialisation { public static void main(String[] args) { Init init = new Init(); System.out.println("a = " + init.a); System.out.println("b = " + init.b); } } class Init { int b = calcul(); int a = 5;

CHAPITRE 5 CREZ VOS PROPRES CLASSES


int calcul() { return a; } }

187

Cependant, son excution produit le rsultat suivant :


a = 5 b = 0

En effet, Java effectue d'abord toutes les dclarations avant d'effectuer les initialisations. En d'autres termes, lorsque le programme comporte les instructions :
int a = 5; int b = 8;

Java effectue d'abord la dclaration de la variable a, c'est--dire qu'il rserve l'espace mmoire ncessaire pour le type de donne dclar (ici un int) et met en place l'identificateur correspondant (a). A ce moment, la zone de mmoire considre contient une valeur quelconque. Laisser cette valeur telle quelle pourrait conduire, dans certains cas, un bug. Pour viter cela, Java dispose de deux techniques. La premire consiste empcher le programme d'tre excut, en produisant une erreur de compilation. Cette technique est utilise pour les variables qui ne sont pas des variables d'instances. La seconde technique consiste initialiser immdiatement cette zone de mmoire. C'est ce que fait Java pour les variables d'instances, et ce mme si la dclaration est suivie d'un initialiseur. Toutes les variables d'instances sont donc ainsi cres et initialises leurs valeurs par dfaut avant que la premire initialisation explicite soit excute. En d'autres termes, les deux lignes ci-dessus sont quivalentes :
int a; a = 0;

188
int b = a = b = b; 0; 5; 8;

LE DVELOPPEUR JAVA 2

L'exemple prcdent tait donc quivalent :


class Init { int b; b = 0; int a; a = 0; b = calcul(); a = 5; int calcul() { return a; } }

On voit immdiatement que la variable a existe et a bien une valeur au moment o la mthode calcul() est appele pour affecter une valeur b. Cette valeur est 0. Le compilateur n'a pas t capable de dtecter la rfrence en avant, mais Java parvient quand mme viter un problme grave grce l'initialisation systmatique et immdiate des variables d'instances. Ce programme ne produira pas d'erreur, mais ne s'excutera pas correctement, en ce sens que la variable b ne sera pas initialise la valeur escompte. Cependant, du fait que b aura pris la valeur par dfaut 0, le dysfonctionnement sera plus facile localiser que si elle avait pris une valeur alatoire en fonction du contenu antrieur de la zone de mmoire concerne.

Mthodes : les valeurs de retour


Jusqu'ici, nous avons utilis des mthodes et des constructeurs en prcisant qu'il s'agissait de deux lments diffrents. Une des diffrences est que les

CHAPITRE 5 CREZ VOS PROPRES CLASSES

189

mthodes peuvent avoir une valeur de retour. La valeur de retour d'une mthode est la valeur fournie par cette mthode lorsqu'elle retourne, c'est-dire lorsqu'elle se termine. Le type de la valeur de retour d'une mthode est indiqu avant son nom dans sa dclaration. Les mthodes que nous avons employes jusqu'ici possdaient presque toutes une valeur de retour de type void, ce qui nquivaut aucune valeur de retour. De plus, ces mthodes retournaient toutes la fin du code. Lorsque ces deux conditions sont runies, il n'est pas obligatoire que la mthode retourne explicitement. En d'autres termes, la mthode :
void crier() { System.out.println("Cui-cui !"); }

devrait tre crite :


void crier() { System.out.println("Cui-cui !"); return; }

mais l'utilisation de return est facultative car, d'une part, le code est termin et, d'autre part, il n'y a aucune valeur retourner. Dans l'exemple suivant :
void crier() { System.out.println("Cui-cui !"); return; System.out.println("Aie !"); }

le deuxime message ne sera jamais affich car la mthode retourne explicitement la troisime ligne.

190

LE DVELOPPEUR JAVA 2
L'instruction return est souvent utilise pour un retour conditionnel, du type :
si (condition) { return; } suite du traitement

Dans ce cas, si la condition est remplie, la mthode retourne (c'est--dire s'arrte). Dans le cas contraire, le traitement continue. L'autre fonction de return est de renvoyer la valeur de retour, comme dans la mthode suivante :
int calcul() { return a; }

Ici, la mthode ne fait rien. Elle retourne simplement la valeur d'une variable. En gnral, les mthodes qui retournent des valeurs effectuent un certain nombre de calculs avant de retourner. Par exemple, la mthode suivante calcule le carr d'un nombre :
long carr(int i) { return i * i; }

Tout comme les constructeurs, les mthodes peuvent tre surcharges. Ainsi, une mthode levant une valeur une puissance pourra tre surcharge de la faon suivante :
double puissance(float i) { return i * i; }

CHAPITRE 5 CREZ VOS PROPRES CLASSES


double puissance(float i, int j) double rsultat = 1; for (int k = 1; k <= j; k++) { rsultat *= i; } return rsultat; }

191

Dans cet exemple, la mthode puissance() retourne son premier argument lev la puissance de son second argument si elle est appele avec un float et un int. Si elle est appele avec pour seul argument un float, elle retourne cette valeur au carr. La valeur de retour est un double dans les deux cas. Notez que si vous appelez la mthode puissance() avec un int et un float, le compilateur protestera qu'il ne trouve pas de version de la mthode puissance() correspondant cette signature. Parfois, il peut tre intressant, si vous crivez une mthode devant tre utilise par d'autres programmeurs, de la surcharger avec des versions utilisant les mmes arguments dans un ordre diffrent. Par exemple, la mthode suivante prend pour argument une chane de caractres et l'affiche une fois :
void affiche(String s) { System.out.println (s); }

Vous pouvez la surcharger avec la version suivante, qui affiche plusieurs fois la chane :
void affiche(String s) { System.out.println (s); } void affiche(String s, int i) { for (int j = 0; j < i; j++) { System.out.println (s); } }

192

LE DVELOPPEUR JAVA 2
Si la mthode est appele avec une chane pour argument, la chane est affiche une fois. Si elle est appele avec une chane et un int (dans cet ordre), la chane est affiche le nombre de fois indiqu par la valeur entire. Si vous voulez que l'utilisateur de la mthode n'ait pas mmoriser l'ordre des paramtres, il vous suffit de surcharger encore une fois la mthode de la faon suivante :
void affiche(String s) { System.out.println (s); } void affiche(String s, int i) { for (int j = 0; j < i; j++) { System.out.println (s); } } void affiche(int i, String s) { for (int j = 0; j < i; j++) { System.out.println (s); } }

La mthode pourra alors tre appele des diffrentes faons suivantes :


affiche("chane 1"); affiche("chane 2", 3); affiche(6, "chane 3");

Surcharger une mthode avec une mthode de type diffrent


Pour surcharger une mthode, il n'est pas obligatoire d'employer une mthode du mme type. Il est tout fait possible de surcharger une mthode de type int (par exemple) l'aide d'une mthode de type float.

CHAPITRE 5 CREZ VOS PROPRES CLASSES

193

Dans l'exemple suivant, nous crons une classe CalculCarre qui contient la mthode carr(), calculant le carr d'une valeur numrique. La premire version est de type int et prend un paramtre de type short. La deuxime est de type float et prend un paramtre de type int. La troisime est de type double et prend un paramtre de type long.
class Surcharge { public static void main(String[] args) { short a = 32767; int b = 2147483; long c = 2147483647214748364L; System.out.println(CalculCarre.carr(a)); System.out.println(CalculCarre.carr(b)); System.out.println(CalculCarre.carr(c)); } } class CalculCarre { static int carr(short i) { System.out.println("int carr(short i)"); return i * i; } static float carr(int i) { System.out.println("Version float carr(int i)"); return i * i; } static double carr(long i) { System.out.println("Version double carr(long i)"); return i * i; } }

Chaque version affiche un message afin de montrer quelle version est excute.

194
Note : La classe est nomme
CalculCarre,

LE DVELOPPEUR JAVA 2
sans accent, afin d'viter les erreurs de compilation avec certains systmes dexploitations. Les noms de variables, de mthodes et de classes peuvent tre accentus en Java. Il est toutefois prudent dviter les caractres accentus dans les noms de classes car les fichiers qui les contiennent doivent imprativement porter le mme nom.

Attention : Cet exemple ne sert qu' dmontrer la possibilit de surcharge par des mthodes de types diffrents. Si vous avez besoin d'une mthode pour lever une valeur quelconque au carr, ne suivez surtout pas ce modle !

Distinction des mthodes surcharges par le type uniquement


Dans l'exemple prcdent, les mthodes surcharges diffrent par leur type et par leur signature. Vous vous demandez peut-tre s'il est possible de distinguer les mthodes surcharges uniquement par leur type, comme dans l'exemple suivant :

class Surcharge2 { public static void main(String[] args) { short a = 5; int b = Calcul.calculer(a); long c = Calcul.calculer(a); System.out.println(a); System.out.println(b); System.out.println(c); } } class Calcul { static int calculer(int i) { return i * 2; }

CHAPITRE 5 CREZ VOS PROPRES CLASSES


static long calculer(int i) { return i * i; } }

195

La rponse est non. En effet, ici, Java pourrait dterminer quelle mthode doit tre employe en fonction du type utilis pour l'appel de la mthode. Ainsi, la ligne :
int b = Calcul.calculer(a);

la mthode :
static int calculer(int i) { return i * 2; }

serait utilise puisque son rsultat est affect un int. Cependant, cela entrerait en conflit avec le fait qu'il est tout fait possible d'crire et de compiler sans erreur le progamme suivant :

class Surcharge2 { public static void main(String[] args) { short a = 5; int b = Calcul.calculer(a); long c = Calcul.calculer(a); System.out.println(a); System.out.println(b); System.out.println(c); } } class Calcul { static int calculer(int i) {

196
return i * 2; } }

LE DVELOPPEUR JAVA 2

Dans ce cas, la ligne :


long c = Calcul.calculer(a);

appelle la mthode calculer(), qui renvoie un int, mais le rsultat est affect un long. Il s'agit, comme nous l'avons indiqu dans un prcdent chapitre, d'un casting implicite. Par ailleurs, dans notre classe Surcharge, nous avions effectu des appels de mthodes sans affecter les rsultats des variables types, comme dans l'exemple :
System.out.println(CalculCarre.carr(a));

Ici, il serait impossible au compilateur de slectionner une mthode sur la base du type utilis pour stocker le rsultat, puisque le rsultat n'est pas stock. En fait, comme vous pouvez le supposer, la mthode System.out.println() est surcharge pour traiter des arguments de toutes natures. C'est donc la valeur de retour fournie par CalculCarre.carre(a) qui indique Java quelle version de System.out.println() doit tre utilise, et non l'inverse.

Le retour
Le retour d'une mthode ne consiste pas seulement fournir une valeur de retour. Lorsqu'une mthode retourne, son traitement est termin. Une mthode retourne dans deux conditions :

Son code a t excut en entier. L'instruction return a t excute.

CHAPITRE 5 CREZ VOS PROPRES CLASSES


Ainsi, la mthode suivante :
void affiche(String s, int i) { for (int j = 0; j < i; j++) { System.out.println(s); } System.out.println("C'est fini !"); }

197

affiche i fois la chane s, puis le message "C'est fini !", et retourne, exactement comme si on avait crit :
void affiche(String s, int i) { for (int j = 0; j < i; j++) { System.out.println(s); } System.out.println("C'est fini !"); return; }

En revanche, la mthode suivante n'affichera jamais le message C'est fini ! :


void affiche(String s, int i) { for (int j = 0; j < i; j++) { System.out.println (s); } return; System.out.println("C'est fini !"); }

car l'instruction return est excute et fait retourner la mthode avant que sa dernire ligne soit excute. Dans l'exemple suivant, la chane s n'est affiche qu'une seule fois :

198
void affiche(String s, int i) { for (int j = 0; j < i; j++) { System.out.println (s); return; } System.out.println("C'est fini !"); }

LE DVELOPPEUR JAVA 2

car l'instruction return est excute immdiatement aprs le premier affichage de la chane, ce qui a pour effet de faire retourner la mthode immdiatement.

Rsum
Dans ce chapitre, nous avons commenc tudier la faon dont les classes sont construites en drivant les unes des autres. Nous nous sommes intresss en particulier aux mcanismes servant contrler la cration des instances de classes (les initialiseurs et les constructeurs) et nous avons vu en quoi ils sont fondamentalement diffrents des mthodes. La diffrence entre constructeurs et mthodes est particulirement importante, car la faon dont ils sont construits offre bien des similitudes. Cependant, du point de vue du programmeur Java, ce sont des lments trs diffrents, comme l'attestent les particularits suivantes :

Les constructeurs n'ont pas de type. Les constructeurs ne retournent pas. Les constructeurs ne sont pas hrits par les classes drives. Lorsqu'un constructeur est excut, les constructeurs des classes parentes le sont galement.

Une mthode peut porter le mme nom qu'un constructeur (ce qui est
toutefois formellement dconseill).

CHAPITRE 5 CREZ VOS PROPRES CLASSES

199

Les constructeurs et les initialiseurs sont des lments trs importants car ils dterminent la faon dont les objets Java commencent leur existence. Nous verrons dans un prochain chapitre que Java propose galement divers moyens de contrler comment les objets finissent leur existence, ce qui est un problme tout aussi important.

Exercice
titre d'exercice, examinez le programme suivant et dterminez :

S'il est correctement crit ; S'il peut tre compil sans erreur.
Dans la ngative, comment pouvez-vous corriger ce programme sans modifier la classe Animal ?
class Animaux2 { public static void main(String[] args) { Canari titi = new Canari(3); titi.vieillir(); titi.vieillir(2); titi.crier(); } } class Animal { boolean vivant; int ge; int geMaxi = 10; Animal (int a) { ge = a; vivant = true; System.out.println("Un animal de " + a + " an(s) vient d'tre cr"); }

200
void vieillir() { vieillir(1); } void vieillir(int a) { ge += a; afficheAge(); if (ge > geMaxi) { mourrir(); } }

LE DVELOPPEUR JAVA 2

void afficheAge() { System.out.println("C'est l'anniversaire de cet animal."); System.out.println("Il a maintenant " + ge + " an(s)"); } void mourrir() { vivant = false; System.out.println("Cet animal est mort"); } void crier() { } } class Canari extends Animal { Canari(int a) { ge = a + 2; vivant = true; System.out.println("Un canari de " + ge + " an(s) vient d'tre cr"); } void crier() { System.out.println("Cui-cui !"); } }

CHAPITRE 5 CREZ VOS PROPRES CLASSES

201

Une fois que vous avez rpondu aux questions, essayez de compiler ce programme. Vous obtiendrez un message d'erreur vous indiquant qu'aucun constructeur sans argument n'a t trouv pour la classe Animal, et ce, bien qu' aucun moment le programme ne tente de crer une instance d'animal sans argument. La raison de ce problme vient de ce que le constructeur de la classe Canari ne commence ni par this ni par super. Java invoque donc implicitement le constructeur sans argument de la classe parente Animal. Or, cette classe n'en possde pas. De plus, elle possde un constructeur avec argument(s), ce qui empche Java de crer un constructeur par dfaut. Vous ne pouvez pas corriger ce programme sans modifier la classe Animal. En effet, vous pouvez modifier le constructeur de la classe Canari de la faon suivante :
Canari(int a) { super(a + 2); System.out.println("Un canari de " + ge + " an(s) vient d'tre cr"); }

Toutefois, dans ce cas, vous obtiendrez l'affichage de deux messages : celui affich par le constructeur de la classe Animal :
Un animal de 5 an(s) vient d'tre cr.

et celui affich par le constructeur de la classe Canari :


Un canari de 5 an(s) vient d'tre cr.

Un bon compromis pourrait tre :


Canari(int a) { super(a + 2); System.out.println("C'est un canari."); }

202

LE DVELOPPEUR JAVA 2
ce qui permettrait d'obtenir le rsultat suivant :
Un animal de 5 an(s) vient d'tre cr. C'est un canari.

Chapitre 6 : Les oprateurs

Les oprateurs

E LANGAGE JAVA PEUT TRE DCOMPOS EN TROIS PARTIES : LA syntaxe, qui regroupe l'ensemble des mots cls, les oprateurs, ainsi que les rgles gouvernant leur utilisation, la smantique des classes et des objets, qui regroupe tous les aspects des rgles concernant l'organisation des classes et leur manipulation, ainsi que celle des objets, et les packages standard, qui ne font pas vraiment partie intgrante du langage, mais sans lesquels celui-ci serait trs peu efficace, puisqu'il faudrait au programmeur crer toutes les classes dont il a besoin pour raliser une application. Si l'on examine le problme d'un peu plus prs, on s'aperoit rapidement qu'un certain nombre de classes sont indispensables au programmeur Java. En effet, un programme Java ne tourne pas (encore) dans un environnement 100 % Java. Il faut donc grer l'interaction des applications crites en Java avec l'environnement (l'ordinateur sur lequel l'application est utilise et son systme d'exploitation.) Ainsi, pour afficher une chane de caractres l'cran, l'application doit dialoguer avec le systme. Ce type de

204

LE DVELOPPEUR JAVA 2
dialogue peut tre pris en charge par l'interprteur de bytecode. Cependant, le nombre de cas diffrents traiter est tel qu'il est beaucoup plus rentable d'utiliser des classes toutes faites, dont certaines font ventuellement appel directement au systme, par l'intermdiaire de mthodes dites natives. Java est ainsi fourni avec un ensemble de classes regroupes en packages, selon leur fonction. Les packages en gnral feront l'objet d'un prochain chapitre. Certains packages, comme ceux concernant l'interface utilisateur (les boutons, les menus, les fentres, etc.) feront l'objet d'une tude approfondie dans les chapitres suivants. L'tude de la smantique des classes et des objets a dj t entame dans les chapitres prcdents, et nous y reviendrons car il reste beaucoup dire. C'est dans ce domaine que s'exprime l'essence mme des langages orients objets en gnral, et de Java en particulier. La syntaxe est un sujet beaucoup moins passionnant, mais dont l'tude est tout de mme indispensable. Nous l'avons dj employe sans l'expliquer en dtail (on ne peut pas crire une ligne de Java sans utiliser la syntaxe du langage). Nous allons maintenant essayer d'en faire le tour. C'est un sujet beaucoup moins intressant et plus rbarbatif que ce que nous avons tudi jusqu'ici, mais il n'y a malheureusement pas moyen d'y chapper.

Java et les autres langages


On prtend souvent que Java drive de tel ou tel langage. Du point de vue smantique, on peut clairement tablir une filiation avec des langages objets tels que C++ ou Smalltalk. En ce qui concerne la syntaxe, Java ressemble beaucoup de langages. En effet, la grande majorit des langages utilisent la mme syntaxe. Quelques instructions peuvent manquer certains langages, certains raccourcis d'criture peuvent tre possibles avec les uns et pas avec les autres, mais ces diffrences sont trs superficielles. (Il est tout de mme des langages plus exotiques que d'autres en ce qui concerne la syntaxe, comme Forth ou PostScript, qui utilisent la mtaphore de la pile.) La syntaxe de Java est clairement calque sur celle de C, et donc galement de C++. Cependant, ne vous inquitez pas si vous ne connaissez pas ces langages. La syntaxe des langages de programmation est une chose extr-

CHAPITRE 6 LES OPRATEURS

205

mement facile matriser. Celle de Java, en tout cas, est d'une grande simplicit, compare aux concepts mis en jeu par les aspects smantiques du langage. Les oprateurs Java s'utilisent pratiquement de la mme faon que dans la vie courante, du moins pour les oprateurs deux oprandes, qui utilisent la notation dite infixe, dans laquelle un oprateur est plac entre les oprandes. C'est la notation que vous avez apprise l'cole.

L'oprateur d'affectation
Java utilise pour l'affectation le signe =, ce qui est une vieille habitude en programmation. Pour tre vieille, cette habitude n'en est pas moins dplorable car elle entrane un risque de confusion certain entre l'affectation :
x = 3

et la condition :
if x = 3

Cette confusion n'existe toutefois pas en Java, car la syntaxe de la condition a t remplace par :
if (x == 3)

(Les parenthses sont obligatoires en Java.) Souvenez-vous donc simplement qu'en Java :
x = 3;

206

LE DVELOPPEUR JAVA 2
est une affectation, qui signifie donner l'lment se trouvant gauche, la valeur de l'lment se trouvant droite. Notez que, dans le cas des primitives, l'affectation n'entrane pas une galit au sens courant. L'galit au sens courant est plutt une identit. Ici, nous avons gauche un conteneur qui reoit la valeur d'un autre conteneur. Ainsi :
y = 3 * 2; x = y;

signifie, pour la premire ligne, mettre dans y le rsultat de l'opration 3 * 2 et, pour la deuxime ligne, copier dans x la valeur de y. Les deux conteneurs x et y existent simultanment de faon indpendante. Ils ont chacun un contenu diffrent, dont les valeurs sont momentanment gales. Dans le cas des objets, la situation est diffrente. Dans l'exemple suivant :
x = new Animal(); y = x;

la premire ligne signifie tablir une relation entre le handle x et le rsultat de l'instruction place droite du signe gal, c'est--dire le nouvel objet cr. En revanche, la deuxime ligne signifie tablir une relation entre le handle y et l'lment en relation avec le handle x. Dans ce cas, aucun objet nouveau n'est cr. Le contenu (c'est un abus de langage) de x est non seulement gal mais identique celui de y (c'est le mme !).

Raccourci
Java permet galement une criture raccourcie consistant effectuer plusieurs affectations sur une mme ligne, de la faon suivante :
x = y = z = w = v + 5;

CHAPITRE 6 LES OPRATEURS

207

Bien entendu, il faut que toutes les variables aient t dclares, et que l'lment le plus droite soit une expression valuable. Dans cet exemple, il faut par consquent que les variables v, w, x, y, et z aient t dclares et que la variable v ait t initialise. Cette criture est incompatible avec l'initialisation effectue sur la mme ligne que la dclaration. En revanche, dans ce cas, deux critures raccourcies sont possibles :
int x = 5, y = 5, z = 5;

ou :
int x, y, z; x = y = z = 5;

Bien sr, la deuxime criture n'est possible que si les variables sont initialises avec la mme valeur, alors que la premire peut tre employe quelles que soient les valeurs d'initialisation.

Les oprateurs arithmtiques deux oprandes


Note : Nous n'utiliserons pas, comme certains auteurs, les termes d'oprateurs binaires et unaires pour dsigner les oprateurs deux oprandes et ceux un oprande, de faon viter la confusion avec les oprateurs d'arithmtique binaire (manipulant des bits). Les oprateurs arithmtiques deux oprandes sont les suivants : + * addition soustraction multiplication

208
/ % division modulo (reste de la division)

LE DVELOPPEUR JAVA 2

Il faut noter que la division d'entiers produit un rsultat tronqu, et non arrondi. La raison cela est la cohrence des oprateurs appliqus aux entiers. En effet, la cohrence impose que pour toutes valeurs de a et b (sauf b = 0) : ((a / b) * b) + (a % b) = a En d'autres termes, si quotient est le rsultat et reste le reste de la division de dividende par diviseur, on doit avoir : (quotient * diviseur) + reste = dividende Si le quotient tait arrondi, cette formule ne se vrifierait que dans 50 % des cas. Java dispose cependant de moyens permettant d'effectuer des divisions avec des rsultats arrondis. Notez qu'il n'existe pas en Java d'oprateur d'exponentiation. Pour effectuer une exponentiation, vous devez utiliser la fonction pow(double a, double b) de la classe java.lang.Math.

Les oprateurs deux oprandes et le sur-casting automatique


Java effectue automatiquement un sur-casting sur les donnes utilises dans les expressions contenant des oprateurs deux oprandes. Cela peut sembler normal dans un cas comme le suivant :
byte x = (byte)127; System.out.println(x + 300);

Dans ce cas, Java sur-caste automatiquement la valeur x (du type byte) en type int. Le rsultat produit est de type int. En revanche, cela parat moins naturel dans le cas suivant :

CHAPITRE 6 LES OPRATEURS


byte x = (byte)12; byte y = (byte)13; System.out.println(x + y);

209

Ici, bien que les deux arguments soient de type byte et que le rsultat ne dpasse pas les limites du type byte, Java effectue tout de mme un surcasting. Le rsultat x + y est en effet de type int. Pour le prouver, nous utiliserons le programme suivant :
class SurCast { public static void main(String[] args) byte x = (byte)12; byte y = (byte)13; afficheType(x + y); } static void afficheType (byte x) { System.out.print("L'argument est de } static void afficheType (short x) { System.out.print("L'argument est de } static void afficheType (char x) { System.out.print("L'argument est de } static void afficheType (int x) { System.out.print("L'argument est de } static void afficheType (long x) { System.out.print("L'argument est de } static void afficheType (float x) { System.out.print("L'argument est de } static void afficheType (double x) { System.out.print("L'argument est de } }

type byte.");

type short.");

type char.");

type int.");

type long.");

type float.");

type double.");

210
Ce programme affiche :
L'argument est de type int.

LE DVELOPPEUR JAVA 2

Vous pouvez utiliser ce programme pour tester tous les cas. Vous constaterez que les oprations mettant en jeu des donnes de type byte, char, short ou int produisent un rsultat de type int. Si un des oprandes est d'un autre type numrique, le rsultat sera du type ayant la priorit la plus leve, dans l'ordre suivant : Priorit la plus faible : int
long float double

Priorit la plus leve :

En revanche, Java ne tient pas compte du dbordement qui peut rsulter de l'opration. Par exemple, le programme suivant :
class Debordement { public static void main(String[] args) { int x = 2147483645; int y = 2147483645; System.out.println(x + y); } }

affiche gaillardement le rsultat suivant :


-6

sans provoquer le moindre message d'erreur ! Ce comportement, outre les risques de bug difficile dceler, induit un autre problme. Vous ne pouvez pas crire :

CHAPITRE 6 LES OPRATEURS


byte x = (byte)12; x = x * 2;

211

car le rsultat de x * 2 est un int et vous ne pouvez pas affecter un int un byte sans effectuer un casting explicite, de la faon suivante :
byte x = (byte)12; x = (byte)(x * 2);

Notez qu'ici, les parenthses sont obligatoires autour de x * 2, car les oprateurs de casting ont la priorit sur les oprateurs arithmtiques. De ce fait :
(byte)x * 2

quivaut :
((byte)x) * 2

ce qui ne produit videmment pas le rsultat escompt. Vous trouvez peut-tre tout cela bien gnant. Malheureusement, cela n'est pas tout, comme nous allons le voir dans la section suivante.

Les raccourcis
Java propose un raccourci permettant de condenser l'utilisation d'un oprateur binaire et de l'oprateur d'affectation. En effet, on utilise souvent ces deux types d'oprateurs sur une mme ligne, de la faon suivante :
x = x + 4; z = z * y; v = v % w;

212

LE DVELOPPEUR JAVA 2
Chaque fois qu'une ligne commence par une affectation du type :
x = x oprateur oprande;

vous pouvez la remplacer par :


x oprateur= oprande;

soit, dans notre exemple prcdent :


x += 4; z *= y; v %= w;

On ne peut pas dire que la lisibilit en soit grandement amliore ! Alors, o est l'avantage ? Cela fait plus srieux, car cela ressemble du C. Des tests portant sur 10 millions d'oprations font apparatre des rsultats ingaux en matire de performances, par exemple un gain de 0,006 % pour l'addition, rien du tout pour la multiplication. Autant dire qu'en ce qui nous concerne, la diffrence n'est pas du tout significative. Vous choisirez donc l'une ou l'autre notation en fonction de vos gots. Vous devrez toutefois tenir compte d'un aspect important concernant la fiabilit des programmes. En effet, les combinaisons oprateurs + affectation prsentent l'inconvnient (ou l'avantage, c'est selon) de procder un sous-casting implicite sans en avertir le moins du monde le programmeur. Souvenez-vous que le code suivant :
byte x = (byte)12; x = x * 2;

produit un message d'erreur puisque l'on tente d'affecter le rsultat de l'opration x * 2, de type int, un byte. En revanche, le code suivant est compil sans erreur :

CHAPITRE 6 LES OPRATEURS


byte x = (byte)12; x *= 2;

213

Ici, cela ne pose pas de problme. En revanche, avec d'autres valeurs, il n'en est pas de mme. Le code suivant :
byte x = (byte)120; x *= 2;

ne produit pas d'erreur non plus. Nous sommes pourtant en prsence d'un dbordement, comme dans le cas mis en vidence prcdemment avec le type int. Pour rsumer, Java ne traite pas les problmes de dbordement. Il se contente de rduire les risques en transformant les byte, char et short en int. Malheureusement, cela est doublement insuffisant, d'une part parce que les dbordements d'int, de long, de float et de double ne sont pas contrls, d'autre part parce que les raccourcis court-circuitent le mcanisme de casting automatique. Il revient donc au programmeur de traiter lui-mme les risques de dbordement.

Les oprateurs un oprande


Java possde plusieurs types d'oprateurs un oprande. Les plus simples sont les oprateurs + et -. Ils sont toujours prfixs. L'oprateur + ne fait rien. L'oprateur - change le signe de son oprande.

Attention : L'utilisation d'espaces pour sparer les oprateurs des oprandes n'est pas obligatoire. Cependant, l'absence d'espaces augmente le risque de confusion. Ainsi la notation suivante :
x=-5; x-=4;

214
est moins facile lire que :
x = -5; x -= 4;

LE DVELOPPEUR JAVA 2

Ici, un bon usage des espaces amliore la lisibilit. Pas autant, toutefois, que l'utilisation de la notation suivante :
x = -5; x = x - 4;

Java possde galement des oprateurs d'auto-incrmentation et d'autodcrmentation. Ils sont particulirement utiles pour la ralisation de boucles de comptage. Il en existe deux versions : prfixe et postfixe.
x++; ++x; x--; --x;

et ++x ont tous deux pour effet d'augmenter la valeur de x de une unit, tout comme x-- et --x ont l'effet inverse, c'est--dire qu'ils diminuent la valeur de x d'une unit. La diffrence entre la version prfixe et la version postfixe n'apparat que lorsque l'opration est associe une affectation ou une utilisation du rsultat. Considrez l'exemple suivant :
class Incr { public static void main(String[] args) { int x = 5; int y = x++; System.out.println("x = " + x);

x++

CHAPITRE 6 LES OPRATEURS


System.out.println("y = " + y); y = ++x; System.out.println("x = " + x); System.out.println("y = " + y); } }

215

Ce programme affiche le rsultat suivant :

x y x y

= = = =

6 5 7 7

On voit que la ligne y = x++ affecte y la valeur de x puis incrmente x. y vaut donc 5 (la valeur de x) et x voit sa valeur augmente de 1 et vaut donc 6. En revanche, la ligne y = ++x augmente d'abord la valeur de x (qui vaut donc maintenant 7) puis affecte cette valeur y. Le mme rsultat est obtenu lorsque la valeur est fournie une mthode au lieu d'tre affecte une variable. Par exemple, le programme suivant :

class Decr { public static void main(String[] args) { int x = 5; System.out.println(x); System.out.println(x--); System.out.println(x); System.out.println(--x); System.out.println(x); } }

produit le rsultat suivant :

216
5 5 4 3 3

LE DVELOPPEUR JAVA 2

ce qui met en vidence le fait que dans l'instruction System.out. println(x--), x est pass la mthode System.out.println() avant d'tre dcrment, alors que dans l'instruction System.out. println(--x), il lui est pass aprs.

Note : Les oprateurs un oprande ne produisent pas de sur-casting automatique, l'inverse des oprateurs deux oprandes.

Les oprateurs relationnels


Les oprateurs relationnels permettent de tester une relation entre deux oprandes et fournissent un rsultat de type boolean, dont la valeur peut tre true (vrai) ou false (faux). Java dispose des oprateurs relationnels suivants :
== < > <= >= !=

quivalent plus petit que plus grand que plus petit ou gal plus grand ou gal non quivalent

Les notions de plus petit que ou plus grand que s'appliquent aux valeurs numriques. Les notions d'quivalence et de non-quivalence s'appliquent toutes les primitives ainsi qu'aux handles d'objets. Il faut noter (et ne jamais oublier) que l'quivalence applique aux handles d'objets concerne les handles, et non les objets eux-mmes. Deux handles

CHAPITRE 6 LES OPRATEURS

217

sont quivalents s'ils pointent vers le mme objet. Il ne s'agit donc pas d'objets gaux, mais d'un seul objet, ce qui correspond la dfinition de l'identit, et non de lgalit. Considrez, par exemple, l'exemple suivant :

class Egal1 { public static void main(String[] args) { Integer a = new Integer(100); Integer b = new Integer(100); System.out.println(a == b); } }

Ce programme affiche :

false

Bien que les deux objets de type Integer contiennent la mme valeur, ils ne sont pas identiques, car il s'agit bien de deux objets diffrents. Pour que la valeur de la relation b == c soit true, il faudrait qu'il s'agisse d'un seul et mme objet, comme dans l'exemple suivant :

class Egal2 { public static void main(String[] args) { Integer a = new Integer(100); Integer b = a; System.out.println(a == b); } }

La Figure 6.1 montre clairement la diffrence. Pour tester l'galit de la valeur entire contenue dans les objets de type Integer, il faut utiliser la mthode equals(), de la faon suivante :

218

LE DVELOPPEUR JAVA 2

Programme Egal1 : a != b

Integer a = new Integer(100); a Integer b = new Integer(100); b


handles

100 100
objets

Programme Egal2 : a == b

Integer a = new Integer(100); a Integer b = a; b


handles

100

objets

Figure 6.1 : Loprateur == appliqu aux objets reprsente lidentit et non lgalit.

class Egal3 { public static void main(String[] args) { Integer a = new Integer(100); Integer b = new Integer(100); System.out.println(a.equals(b)); } }

Ce programme affiche :
true

Ici, nous avons utilis la mthode equals() de l'objet a. Nous aurions tout aussi bien pu utiliser celle de l'objet b de la faon suivante :
System.out.println(b.equals(a));

CHAPITRE 6 LES OPRATEURS

219

Vous pouvez assimiler cela au fait qu'il est quivalent d'crire a == b ou b== a. Ce n'est pourtant pas la mme chose. Lorsque vous crivez a == b, l'galit est value par un mcanisme construit dans l'interprteur Java. C'est exactement le mme mcanisme qui est utilis pour valuer b == a. Dans le cas de la mthode equals(), il s'agit d'une version diffrente dans chaque cas. A premire vue, cela ne change rien, car la mthode equals() de l'objet a est identique celle de l'objet b. Mais cela n'est pas toujours le cas, comme nous allons le voir plus loin. En fait, la mthode equals() n'est pas propre aux objets de type Integer. Elle appartient la classe Object. Tous les objets Java tant implicitement drivs de cette classe, ils hritent tous de cette mthode, dont voici la dfinition :

public boolean equals(Object obj) { return (this == obj); }

Cette mthode retourne true uniquement si son argument est un handle qui pointe vers le mme objet que celui depuis lequel la mthode est appele. Cela signifie qu'a priori, pour tous les objets de la classe Object, ces quatre expressions sont quivalentes :

a == b b == a a.equals(b) b.equals(a)

La mthode equals() ne nous est donc gure utile. En fait, elle est conue pour tre redfinie dans les classes drives qui ncessitent un autre type d'galit. C'est le cas des classes telles que Integer, Long, Float, etc. Toutes ces classes redfinissent la mthode equals(). Si vous redfinissez cette mthode dans une de vos classes, vous tes suppos respecter les indications suivantes :

220
tourner la valeur true.

LE DVELOPPEUR JAVA 2

La mthode doit tre rflexive, c'est--dire que x.equals(x) doit re Elle doit tre symtrique, c'est--dire que x.equals(y) doit retourner la
mme valeur que y.equals(x).

Elle doit tre transitive, c'est--dire que si x.equals(y) et y.equals(z)

retournent la valeur true, alors x.equals(z) doit retourner la valeur true. de x et y, x.equals(y) doit toujours retourner la mme valeur.

Elle doit tre cohrente, c'est--dire que, quelles que soient les valeurs Quelle que soit x, x.equals(null) doit retourner la valeur false.
La classe Integer redfinit la mthode equals() de la faon suivante :
public boolean equals(Object obj) { if ((obj != null) && (obj instanceof Integer)) { return value == ((Integer)obj).intValue(); } return false; }

ce qui respecte bien les critres dfinis. Cependant, vous pouvez parfaitement redfinir cette mthode dans vos classes. En particulier, vous pouvez redfinir cette mthode afin qu'elle renvoie true pour des objets de classes diffrentes, comme dans l'exemple suivant :
class MyEquals { public static void main(String[] args) { MyInteger a = new MyInteger(100); MyLong b = new MyLong(100); System.out.println(a.equals(b)); System.out.println(b.equals(a)); } }

CHAPITRE 6 LES OPRATEURS


class MyInteger { int value; MyInteger(int i) { value = i; } public int value() { return value; } public long longValue() { return (long)value; } public boolean equals(Object obj) { System.out.print("Classe MyInt "); if (obj != null) { if (obj instanceof MyInteger) return value() == ((MyInteger)obj).longValue(); else if (obj instanceof MyLong) return value() == ((MyLong)obj).value(); } return false; } } class MyLong { long value; MyLong(long l) { value = l; } public long value() { return value; } public boolean equals(Object obj) { System.out.print("Classe MyLong "); if (obj != null) {

221

222

LE DVELOPPEUR JAVA 2
if (obj instanceof MyInteger) return value == ((MyInteger)obj).longValue(); else if (obj instanceof MyLong) return value == ((MyLong)obj).value(); } return false; } }

Dans ce programme, nous crons deux classes nommes MyInteger et MyLong. Il s'agit d'enveloppeurs pour des valeurs de type int et long. Chacune de ces classes comporte une mthode equals() qui respecte parfaitement les critres dfinis plus haut. Ces mthodes permettent de tester l'galit des valeurs enveloppes, bien qu'elles soient de types diffrents. (Dans la pratique, il existe des moyens beaucoup plus simples d'arriver au mme rsultat.) Malgr le respect des critres (qui ne concernent que la valeur de retour), les mthodes peuvent avoir des effets de bord diffrents (symboliss ici par la prsence des instructions System.out.print("Classe MyLong"); et System.out.print("Classe MyInteger ");. Ce programme affiche le rsultat suivant :
Classe MyInt true Classe MyLong true

Pour viter ce type de problme, il est prfrable, bien que cela ne figure pas parmi les recommandations officielles, de ne pas implmenter de mthodes equals() fonctionnant sur des objets de types diffrents. On pourra alors crer une classe intermdiaire parente des classes tester et implmenter la mthode equals() dans la classe parente, par exemple de la faon suivante :
class MyEquals2 { public static void main(String[] args) { MyInteger a = new MyInteger(100); MyLong b = new MyLong(100); System.out.println(a.equals(b));

CHAPITRE 6 LES OPRATEURS


System.out.println(b.equals(a)); } }

223

class MyNumber { public boolean equals(Object obj) { if (obj != null) { if (obj instanceof MyInteger) { if (this instanceof MyInteger) return ((MyInteger)this).value() == ((MyInteger)obj).value(); if (this instanceof MyLong) return ((MyLong)this).value() == ((MyInteger)obj).longValue(); } else if (obj instanceof MyLong) { if (this instanceof MyInteger) return ((MyInteger)this).longValue() == ((MyLong)obj).value(); if (this instanceof MyLong) return ((MyLong)this).value() == ((MyLong)obj).value(); } } return false; } } class MyInteger extends MyNumber { int value; MyInteger(int i) { value = i; } public int value() { return value; } public long longValue() {

224
return (long)value; } } class MyLong extends MyNumber { long value; MyLong(long l) { value = l; } public long value() { return value; } }

LE DVELOPPEUR JAVA 2

Dans ce programme, la mthode equals() est fournie par la classe parente MyNumber. Cela assure que ce sera bien toujours la mme mthode qui sera appele, quel que soit l'ordre des paramtres. Bien entendu, il est toujours possible de faire en sorte que les effets de bord soient diffrents dans les deux cas, mais les risques sont tout de mme beaucoup plus limits. (Il faudrait quasiment le faire exprs !) De plus, le risque majeur avec la premire approche est de modifier une des mthodes en oubliant l'existence de l'autre. Il est toujours prfrable de grouper en un seul endroit le code correspondant une mme opration. Faire en sorte que les valuations de x.equals(y) et y.equals(x) reposent sur deux mthodes diffrentes est la meilleure faon de s'attirer des ennuis futurs.

Note 1 : Nous sommes bien conscients que nous avons utilis dans ces
exemples des notions que nous n'avons pas encore prsentes. Nous nous en excusons, mais il est vraiment difficile de faire autrement. Tout ce qui concerne les instructions conditionnelles if{} else{} sera trait dans le prochain chapitre. L'oprateur instanceof est prsent la fin de ce chapitre. Les techniques de cast ont dj t voques. Elles sont employes ici d'une faon systmatique trs courante en Java. Nous y reviendrons.

Note 2 : Pour ne pas ajouter aux inconvnients dcrits dans la note 1, nous avons volontairement omis d'employer un certain nombre de techniques qui devraient l'tre dans ce type d'exemple. Ne considrez donc pas

CHAPITRE 6 LES OPRATEURS

225

ce programme comme une version dfinitive reprsentant la faon optimale de traiter le problme.

Les oprateurs logiques


Java dispose des oprateurs logiques qui produisent un rsultat de type boolean, pouvant prendre les valeurs true ou false. Il s'agit des oprateurs suivants :
&& || !

Et Ou Non

(deux oprandes) (deux oprandes) (un seul oprande)

On reprsente couramment le fonctionnement de ces oprateurs sous la forme de tables de vrit (Figure 6.2) Il faut noter une particularit dans la manire dont Java value les expressions contenant des oprateurs logiques : afin d'amliorer les performances, l'valuation cesse ds que le rsultat peut tre dtermin. Ainsi, dans l'exemple suivant :
int x = 5; int y = 10; boolean z = (x > 10) && (y == 15);

&&

true

false

||

true

false

true

true false false false

true

true true true false

true

false true

false

false

false

Figure 6.2 : Tables de vrit des oprateurs logiques.

226

LE DVELOPPEUR JAVA 2
Java commence par valuer le premier terme de l'expression, x > 10. Sa valeur est false, puisque x vaut 5. Cette expression faisant partie d'une autre expression avec l'oprateur &&, un simple coup d'il la table de vrit de cet oprateur nous montre que l'expression globale sera fausse, quelle que soit la valeur de y==15. Il est donc inutile d'valuer la suite de l'expression. De la mme faon, dans l'exemple suivant :
int x = 5; int y = 10; boolean z = (x < 10) || (y == 15);

il est inutile d'valuer y == 15 puisque l'valuation de x < 10 donne true, ce qui, d'aprs la table de vrit de l'oprateur ||, implique que l'expression globale vaudra true dans tous les cas. Dans ces exemples, cela ne prte pas consquence. En revanche, dans l'exemple suivant :
int x = 5; int y = 10; boolean z = (x < 10) || (y == calcul(x));

la mthode calcul ne sera pas excute. Cela peut avoir ou non une importance, selon que cette mthode a ou non des effets de bord utiles. En effet, les mthodes Java peuvent servir de fonctions (qui retournent une valeur), de procdures (qui ne retournent pas de valeur mais excutent un traitement), ou des deux la fois.

Note : Une fonction pure n'existe pas. Toutes les fonctions modifient
l'environnement, au moins de faon passagre. Idalement, une fonction pure devrait laisser l'environnement tel qu'elle l'a trouv. Ce n'est pratiquement jamais le cas en Java. En effet, si une fonction cre des objets, elle n'a aucun moyen de les dtruire avant de retourner. Par ailleurs, les arguments passs aux fonctions sont le plus souvent des handles pointant vers des objets. Toute modification effectue sur un de ses arguments objets par une

CHAPITRE 6 LES OPRATEURS

227

mthode persiste aprs que la mthode a retourn. Toutes les mthodes Java ont donc des effets de bord, dsirs ou non. Dans l'exemple prcdent, si vous comptez que la mthode sera appele chaque valuation de l'expression, vous risquez d'avoir des surprises ! Le problme ne se pose pas seulement avec les mthodes, mais galement avec certains oprateurs :
boolean z = (x < 10) || (y == x++); System.out.println(x);

Dans cet exemple (on suppose que x et y ont t dclares et initialises ailleurs), Java affichera 5 si x est plus petit que 10 et 6 dans le cas contraire ! Vous devez faire trs attention ce point. Si vous voulez tre certain que la totalit de l'expression sera value systmatiquement, vous pouvez utiliser une astuce consistant employer les oprateurs d'arithmtique binaire, qui sont dcrits dans la prochaine section. Notez que cette particularit des oprateurs logiques peut galement tre employe dans le sens oppos. Considrez, par exemple, la ligne suivante :
boolean marche = true; String ligne; . . . while (marche && (ligne = entre.readLine()) != null) { . traitement . }

Dans cet exemple, entre est un objet de type InputStream, cest--dire une source de donnes. Il peut sagir par exemple dun fichier local ou

228

LE DVELOPPEUR JAVA 2
accessible partir dun serveur. (Ce sujet sera trait aux Chapitres 13 et 20.) La variable ligne est ici de type chane de caractres et reoit une nouvelle ligne de texte chaque fois que la mthode entre.readline() est appele. Si la fin du fichier est atteinte, la variable ligne prend la valeur null. Dans notre exemple, un traitement est effectu tant que la variable marche vaut true et que la fin du fichier nest pas atteinte. Si la variable marche prend la valeur false (ce qui est ralis par une autre partie du programme affichant, par exemple, un bouton marche/arrt sur lequel lutilisateur peut cliquer), le traitement est interrompu. Toutefois, lvaluation de la condition :
(marche && (ligne = entre.readLine()) != null)

sarrte ds que marche est value la valeur false. De ce fait, la lecture du fichier est galement interrompue. Si nous inversons les termes de lexpression logique de la faon suivante :
(((ligne = entre.readLine()) != null) && marche)

une ligne est lue avant que marche soit value. Ici, la diffrence nest que dune ligne. Il est facile de modifier le reste du programme pour tenir compte de lun ou lautre cas. Mais considrez maintenant lexemple suivant, dans lequel la boucle est effectue en permanence afin que le traitement continue si la variable marche reprend la valeur true :
boolean marche = true; String ligne; while (true) { if (marche && (ligne = entre.readLine()) != null) { . traitement . } }

CHAPITRE 6 LES OPRATEURS

229

Ici, la boucle externe (while) sexcute indfiniment (une condition de sortie tant teste lintrieur de la boucle). Si marche prend la valeur false, la boucle continue de sexcuter mais le traitement contenu dans le bloc if{} est ignor. De plus, linstruction ligne = entre.readLine() nest pas excute en raison du court-circuit provoqu par loprateur &&. Lorsque la variable marche prend de nouveau la valeur true, le traitement peut reprendre normalement. En revanche, si lon inverse encore une fois les deux oprandes de loprateur &&, on se trouve ne prsence dun gros bug :
while (true) { if (((ligne = entre.readLine()) != null) && marche) { . traitement . } }

Ici, le traitement contenu dans le bloc if{} nest toujours pas excut, mais linstruction ligne = entre.readLine() lest ! Le rsultat est que le programme continue de lire les donnes sans les traiter, jusqu puisement de celles-ci, ce qui nest probablement pas leffet escompt.

Les oprateurs d'arithmtique binaire


Les oprateurs d'arithmtique binaire agissent au niveau des bits de donnes, sans tenir compte de ce qu'ils reprsentent. La prsence de ces oprateurs en Java est curieuse, non pas parce qu'ils ne sont pas utiles ; ils sont mme pratiquement indispensables pour certains traitements si l'on veut obtenir des performances correctes. Ce qui est trange, c'est l'absence d'lments fondamentalement complmentaires que sont les types de donnes non signs. Toutes les donnes sont reprsentes en mmoire par des sries de bits, gnralement groups par multiples de 8. L'lment fondamental est donc l'octet, qui comporte 8 bits. Java dispose bien d'un type de

230

LE DVELOPPEUR JAVA 2
donnes correspondant, mais il est sign, c'est--dire qu'un de ses bits (le petit dernier au fond gauche) reprsente le signe (positif ou ngatif). Bien sr, il est possible de ne pas en tenir compte lorsque l'on effectue des manipulations binaires, mais cela empche d'effectuer facilement des conversions qui simplifient l'criture. Nous examinerons tout d'abord les oprateurs disponibles avant de revenir sur ce problme. Les oprateurs binaires disponibles en Java sont les suivants :
& | ^ ~ << >> >>>

Et Ou Ou exclusif Non Dcalage gauche

(deux oprandes) (deux oprandess) (deux oprandes) (un oprande) (deux oprandes)

Dcalage droite avec extension du signe (deux oprandes) Dcalage droite sans extension du signe (deux oprandes)

L'oprateur ~ inverse la valeur de chacun des bits de son oprande unique. Les oprateurs &, |, et ^ prennent deux oprandes et produisent un rsultat, en fonction des tables de la Figure 6.3. Ces tables donnent les rsultats pour chaque bit des oprandes. Ces oprateurs peuvent tre associs l'oprateur d'affectation = pour produire les versions suivantes :

& 1 0 1 0
1 0 0 0

| 1 0 1 0
1 1 1 0

^ 1 0 1 0
0 1 1 0

~ 1 0
0 1

Figure 6.3 : Talbes de vrit des oprateurs binaires.

CHAPITRE 6 LES OPRATEURS


&= |= ^= <<= >>= >>>=

231

avec lesquelles le rsultat de l'opration est affect l'oprande de gauche. Dans ce cas, un sous-casting est automatiquement effectu, avec les risques de perte de donnes que nous avons dj dcrits pour les oprateurs arithmtiques. Les oprateurs de dcalage dplacent les bits du premier oprande vers la gauche ou vers la droite d'un nombre de rangs correspondant au second oprande, comme le montre la Figure 6.4. Les bits qui sortent gauche ou droite sont perdus. Les bits qui rentrent droite, dans le cas d'un dcalage gauche (voir exemple x, page suivante) valent toujours 0. On parle alors parfois d'extension de 0. Dans le cas d'un dcalage droite sans extension de signe, les bits qui entrent gauche valent toujours 0 (exemples v et w). Dans le cas d'un dcalage droite avec extension de signe, les bits qui entrent gauche ont la mme valeur que le bit le plus gauche (exemples y et z). Comme nous l'avons dit prcdemment, il est utile de disposer de types de donnes non signs pour l'arithmtique binaire. En effet, le dcalage gauche de 1 bit correspond la multiplication par deux et le dcalage droite la division entire par deux. On utilise frquemment la notation hexadcimale pour reprsenter les valeurs binaires, car la conversion est trs aise (avec un peu d'habitude, elle se fait mentalement). Mais si l'on opre un dcalage sur une valeur signe, il arrive frquemment que le signe change chaque dcalage. Il devient difficile de faire correspondre la reprsentation binaire et la reprsentation dcimale ou hexadcimale. Par exemple, pour un dcalage de 1 bit, la valeur :

232

LE DVELOPPEUR JAVA 2

x 0 0 1 1 0 1 1 1 x << 3 1 0 1 1 1 0 0 0 y 0 0 1 1 0 1 1 1 y >> 2 0 0 0 0 1 1 0 1 z 1 0 1 1 1 0 0 1 z >> 2 1 1 1 0 1 1 1 0 v 0 1 0 1 0 1 1 0 00 v >>> 2 0 0 0 1 0 1 0 1 w 1 1 0 1 0 1 1 0 00 w >>> 2 0 0 1 1 0 1 0 1 10 10

0 01

000

0 0

11

11

01

Figure 6.4 : Exemples des diffrents types de dcalage.

CHAPITRE 6 LES OPRATEURS


01010101 (85 en dcimal) devient : 10101010 (-41 en dcimal) Alors qu'en reprsentation non signe, nous aurions : 01010101 (85 en dcimal) devient : 10101010 (170 en dcimal)

233

Cependant, Java tourne la difficult d'une autre faon. En fait, tous les dcalages sont effectus sur 4 octets, c'est--dire que si vous dcalez une valeur de type byte ou short, Java effectue d'abord un casting pour la convertir en int. Le rsultat de l'opration est un int. (Dans la figure, tous les dcalages sont reprsents sur 8 bits pour des raisons d'espace disponible !) Un dcalage sur un long produit un long. Un dcalage sur un char produit un char.

Note : Vous pouvez effectuer des oprations binaires sur les types byte,
short, int, long,

et char (qui est le seul type numrique non sign de Java). Les oprateurs &, | et ^ peuvent galement tre appliqus aux valeurs de type boolean (qui sont des valeurs 1 bit), mais pas l'oprateur ~ (ni videmment, les dcalages !). Il rsulte de ce que nous venons de dire que Java refusera de compiler le programme suivant :
class Decalage { public static void main(String[] args) { short a = 5; short b = a << 1; }

234

LE DVELOPPEUR JAVA 2
car a << 1 produit un int. Il faut donc un casting explicite pour affecter le rsultat un short :
class Decalage { public static void main(String[] args) { short a = 5; short b = (short)(a << 1); }

Une source d'erreur frquente est l'oubli des parenthses autour de l'expression a << 1. En effet, si vous crivez :
short b = (short)a << 1;

Java effectue un casting de a en short (ce qu'il est dj), puis en int pour effectuer l'opration. Lorsque Java effectue un dcalage sur un int (directement ou aprs un cast d'un byte ou d'un short), le dcalage est limit 31 bits, c'est--dire le nombre de bits correspondant un int. De la mme faon, un dcalage effectu sur un long est limit 63 bits, soit le nombre de bits d'un long. Dans les deux cas, il s'agit du nombre de bits en excluant le bit de signe. Que se passe-t-il si vous tentez d'effectuer un dcalage d'un nombre de bits suprieur ces valeurs ? Le programme suivant permet de le savoir :
class Decalage2 { public static void main(String[] args) { int x = 150; System.out.println(x); System.out.println(x >>> 1); System.out.println(x >>> 33); System.out.println(x >>> 97); System.out.println(x >>> -31); System.out.println(x >>> -95); } }

CHAPITRE 6 LES OPRATEURS


Ce programme produit le rsultat suivant :

235

150 75 75 75 75 75

Nous constatons qu'un dcalage d'un int avec un oprande gal 33, 97, -95 ou -31 produit le mme rsultat qu'un dcalage d'un bit. Pour comprendre pourquoi, il faut considrer la faon dont les nombres signs sont reprsents. Java utilise la notation en complment 2. Avec cette notation, un nombre ngatif est reprsent en inversant tous ses bits (ce qui produit le complment 1) puis en ajoutant 1. Ce qui peut s'exprimer de la faon suivante : -x = ~x + 1 et peut tre vrifi l'aide du programme :
class Essai { public static void main(String[] args) { byte x = 66; System.out.println(x); System.out.println(~x + 1); } }

qui affiche :
66 -66

236

LE DVELOPPEUR JAVA 2
Les valeurs dcimales 1, 33, 97, -31 et -95 sont donc reprsentes respectivement par les valeurs binaires suivantes :
1 33 97 -31 -95 0000 0000 0000 0000 0000 0000 1111 1111 1111 1111 0000 0000 0000 0000 0000 0000 1111 1111 1111 1111 0000 0000 0000 0000 0000 0000 1111 1111 1111 1111 0000 0001 0010 0001 0110 0001 1110 0001 1100 0001

Ces cinq valeurs ont en commun leurs cinq bits de poids faible, c'est--dire placs le plus droite. Pour effectuer un dcalage d'un int, Java ne tient compte que de ces cinq bits. Utiliser une valeur suprieure produit un rsultat tout fait prvisible, mais souvent imprvu, ce qui peut parfaitement servir de dfinition au mot bug ! Lorsqu'il s'agit du dcalage d'un long, Java prend en compte les six bits les plus droite, ce qui limite le dcalage 63 bits. (Avec 6 bits, on peut exprimer de faon non signe des valeurs comprises entre 0 et 63.) Notez au passage que si Java ne fournit pas au programmeur des types non signs, ils sont toutefois utiliss par le langage, ce qui est d'autant plus frustrant.

Consquences du sur-casting automatique surl'extension de zro


Nous savons maintenant comment fonctionne rellement le dcalage. Nous savons galement que les valeurs de type char, byte ou short sont surcastes en int avant que le dcalage soit effectu. Le sur-casting n'introduit en principe aucune perte de donnes. Par consquent, le mme dcalage appliqu un char, un byte, un short ou un int, tous initialiss la mme valeur, devrait produire le mme rsultat. Nous pouvons le vrifier l'aide du programme suivant :

CHAPITRE 6 LES OPRATEURS


class Probleme { public static void main(String[] args) { byte w = (byte)66; short x = (short)66; char y = (char)66; int z = 66; for (int i = 0; i < 3; i++) { System.out.print( (w >>> i) + "\t" + (x >>> i) + "\t" + (y >>> i) + "\t" + (z >>> i) + "\n"); } } }

237

Nous n'avons pas encore tudi l'instruction for, mais sachez que, dans ce programme, elle a pour effet d'excuter le contenu du bloc suivant (l'instruction System.out.print) une fois pour chaque valeur de i comprise entre 0 (inclus) et 3 (exclu). L'instruction System.out.print a t coupe en plusieurs lignes pour des questions de mise en page. (Il est possible de la couper n'importe o sauf au milieu d'un mot ou entre les guillemets.) Les quatre variables subissent donc successivement un dcalage de 0, 1 et 2 bits. Le rsultat obtenu est le suivant :

66 33 16

66 33 16

66 33 16

66 33 16

Pour l'instant, tout va bien. Essayons la mme chose avec des valeurs ngatives, en remplaant les lignes 3, 4, 5 et 6 du programme par :

byte w = (byte)-66; short y = (short)-66; int z = -66;

238

LE DVELOPPEUR JAVA 2
et en modifiant l'instruction d'affichage de la faon suivante :
System.out.print( (w >>> i) + "\t" + (x >>> i) + "\t" + (z >>> i) + "\n");

(Le type char tant non sign, lui attribuer une valeur ngative n'a pas de sens, bien que cela soit parfaitement possible.) Nous obtenons maintenant le rsultat suivant :
-66 -66 2147483615 1073741807 -66 2147483615 1073741807

2147483615 1073741807

Tout semble continuer d'aller parfaitement. Vraiment ? Pourtant, on peut dceler ici la trace d'un problme. Essayez de le trouver vous-mme. Cela saute aux yeux, non ? Le problme est li au sur-casting. L'extension de 0 se produit dans le bit de poids fort, c'est--dire le bit le plus gauche. Dans un int, il s'agit du 32e bit en commenant par la droite. Cependant, dans un short, le bit de signe est le 16e, et dans un byte, il s'agit du 8e. Java devrait en tenir compte lors des sous-castings. En fait, Java se contente de tronquer les bits excdentaires. En raison de l'utilisation de la notation en complment deux, cela fonctionne parfaitement, dans la plupart des cas. En revanche, si le bit de signe est modifi avant le sous-casting, cela peut entraner un problme. C'est le cas de l'extension de 0, qui, applique un nombre ngatif, le transforme en nombre positif. Exemple : 11111111 11111111 11111111 10111110 aprs dcalage avec extension de 0 : 01111111 11111111 11111111 11011111 = 2 147 483 615 = - 66

CHAPITRE 6 LES OPRATEURS

239

Le rsultat est correct dans le cas d'un int, mais pas dans celui d'un short ou d'un byte sur-casts. En effet, le dcalage avec extension de 0 d'un byte de la mme valeur est reprsent ci-dessous : 10111110 = - 66

aprs dcalage : 01011111 = 95

Dans le cas d'un byte sur-cast, le processus complet est : 10111110 = - 66

aprs sur-casting : 11111111 11111111 11111111 10111110 aprs dcalage : 01111111 11111111 11111111 11011111 aprs sous-casting : 11011111 = -33 = 2 147 483 615 = - 66

L'extension de 0 ne s'est pas produite au bon endroit. Nous pouvons mettre cela en vidence dans la pratique en modifiant le programme prcdent de la faon suivante :

240

LE DVELOPPEUR JAVA 2
class Probleme2 { public static void main(String[] args) { byte w = (byte)-66; short x = (short)-66; int z = -66; System.out.print(w + "\t" + x + "\t" + z + "\n"); for (int i = 1; i < 3; i++) { w >>>= 1; x >>>= 1; z >>>= 1; System.out.print(w + "\t" + x + "\t" + z + "\n"); } } }

Ce programme affiche le rsultat suivant :


-66 -33 -17 -66 -33 -17 -66 2147483815 1073741807

Cette particularit fait dire certains que le dcalage droite avec extension de 0 comporte un bug. Ce n'est pas le cas. On est plutt ici en prsence d'une idiosyncrasie : l'incompatibilit du dcalage tel qu'il est implment avec le sous-casting. Le bug n'est ni dans le dcalage, ni dans le souscasting. Il est induit par l'utilisation du sur-casting automatique. Moralit, pour l'arithmtique binaire, il vaut mieux utiliser le type int.

Utilisation des oprateurs d'arithmtique binaire avec desvaleurs logiques


Nous avons vu que les oprateurs &, | et ^ peuvent tre employs avec des valeurs logiques, qui sont des valeurs sur 1 bit. L'intrt de cette possibilit est que, s'agissant d'oprateurs arithmtiques, ils sont toujours valus. Comme nous l'avons dit prcdemment, dans l'exemple suivant :

CHAPITRE 6 LES OPRATEURS


int x = 5; int y = 10; boolean z = (x < 10) || (y == calcul(x));

241

la mthode calcul(x) n'est jamais excute en raison du court-circuit provoqu par Java : le premier terme de l'expression tant vrai, Java peut dterminer que l'expression entire est vraie sans valuer le second terme. Si vous voulez forcer Java valuer les deux termes dans tous les cas, il vous suffit de remplacer l'oprateur logique || par l'oprateur d'arithmtique binaire | :
int x = 5; int y = 10; boolean z = (x < 10) | (y == calcul(x));

Dans ce cas, les deux termes seront valus quelle que soit la valeur du premier. De mme, dans l'exemple suivant :
boolean z = (x < 10) && (y == x++);

la valeur de x sera augmente de 1 seulement si x est plus petit que 10, alors que dans l'expression ci-aprs :
boolean z = (x < 10) & (y == x++);

elle le sera quoi qu'il arrive.

Utilisation de masques binaires


L'arithmtique binaire est souvent mise en uvre travers la cration et l'utilisation de masques. Supposons, par exemple, que vous souhaitiez crire un programme de test de connaissances comportant vingt questions. Chaque fois que l'utilisateur rpond une question, le programme dtermine si

242

LE DVELOPPEUR JAVA 2
la rponse est juste ou fausse. Vous souhaitez stocker dans le programme une valeur indiquant si la rponse est juste ou fausse. Vous pouvez procder de plusieurs faons. L'une consiste utiliser une srie de vingt valeurs de type boolean qui vaudront, par exemple, true si la rponse est juste et false dans le cas contraire. Ces valeurs pourront tre places, par exemple, dans un tableau. (Les tableaux seront tudis dans un prochain chapitre.) Cette faon de procder n'est pas trs performante, car la manipulation de tableau est une technique plutt lourde. Une autre faon, plus efficace, consiste employer une valeur de 20 bits et de mettre chaque bit 1 ou 0 selon que la rponse est juste ou fausse. Cette technique peut tre mise en uvre en employant des oprateurs arithmtiques. En effet, en partant d'une valeur de 0, mettre le bit de rang n 1 quivaut ajouter 2n la valeur. (Pour que cela fonctionne, il faut, comme c'est l'usage, compter les rangs de 0 19 et non de 1 20.) Cette technique prsente cependant un inconvnient certain : elle fonctionne parfaitement si le bit considr vaut 0, mais produit un dbordement sur le bit suivant s'il vaut dj 1, ce qui peut tre le cas si l'utilisateur a la possibilit de revenir en arrire pour modifier ses rponses. La prise en compte de cette possibilit avec les moyens de l'arithmtique dcimale devient complexe. Les oprateurs d'arithmtique binaire permettent de raliser cela trs facilement. En effet, pour mettre 1 la valeur d'un bit quelle que soit sa valeur d'origine, il suffit d'effectuer l'opration OU avec une valeur dont le bit de rang considr soit 1 et tous les autres 0. Par exemple, pour mettre 1 le bit de rang n (en commenant par la droite) d'une valeur quelconque x sans changer les autres bits, on utilisera la formule suivante : x = x | 2n Pour faire l'opration inverse, c'est--dire mettre le bit de rang n 0, ont utilisera la formule : x = x & (~2n) en remarquant que le masque utiliser est l'inverse binaire du prcdent.

CHAPITRE 6 LES OPRATEURS

243

L'oprateur trois oprandes ?:


Java dispose d'un oprateur un peu particulier permettant de tester une condition et d'utiliser une expression diffrente pour fournir le rsultat, selon que cette condition est vraie ou fausse. La syntaxe de cet oprateur est la suivante : condition ? expression_si_vrai : expression_si_faux Voici un exemple de son utilisation :
x = y < 5 ? 4 * y : 2 * y;

Dans cet exemple, x prend une valeur gale 4 * Y si y est plus petit que 5 et 2 * y dans le cas contraire. Le mme rsultat peut tre obtenu avec l'instruction conditionnelle if...else (que nous tudierons bientt) de la faon suivante :
if (y < 5) x = 4 * y; else x = 2 * y;

ce qui est beaucoup plus lisible. Notez que seule l'expression utilise pour le rsultat est value. Ainsi, dans l'exemple suivant :
int x = y < 5 ? y++ : y--;

Dans tous les cas, x prend la valeur de y. En revanche, si y est plus petit que 5, sa valeur est augmente de 1. Dans le cas contraire, sa valeur est diminue de 1. (x prend la valeur de y avant l'augmentation ou la diminution en raison de l'utilisation de la version postfixe des oprateurs ++ et --.) Exemples :

244
int y = 5; int x = y < 5 ? y++ : y--; System.out.println(x); System.out.println(y);

LE DVELOPPEUR JAVA 2

Dans ce cas, le programme affiche :


5 4

En revanche, les lignes :


int y = 4; int x = y < 5 ? y++ : y--; System.out.println(x); System.out.println(y);

affichent :
4 5

Les oprateurs de casting


Les oprateurs de casting existent pour toutes les primitives comme pour tous les objets. Cependant, tous les castings ne sont pas autoriss. Nous reviendrons sur le cas des objets. En ce qui concerne les primitives, les castings sont autoriss entre tous les types sauf les boolean. Bien entendu, certains castings peuvent conduire des pertes de donnes, mais cela reste sous la responsabilit du programmeur. Java ne se proccupe que des castings implicites. Les oprateurs de castings sont simplement les noms des types de destination placs entre parenthses avant la valeur caster. Exemple :

CHAPITRE 6 LES OPRATEURS


int x = 5; short y = (short)x;

245

Un casting d'un type d'entier vers un type d'entier de taille infrieure est effectu par simple troncature de la partie de poids fort (partie gauche). S'agissant de types signs utilisant la reprsentation en complment 2, le rsultat est cohrent dans la mesure o :

La valeur sous-caste provient d'une valeur sur-caste partir du mme


type.

Aucune manipulation n'a t faite sur les bits se trouvant gauche du


bit de signe d'origine entre le moment o la valeur a t sur-caste et le moment ou elle est sous-caste. Par exemple, si un byte (7 bits de donnes plus un bit de signe) est surcast en short (15 bits de donnes et un bit de signe), puis sous-cast en byte, le rsultat sera cohrent si les bits 8 15 n'ont pas t modifis. Dans le cas contraire, le rsultat sera incohrent (et non pas seulement tronqu). Exemple :
byte x = (byte)127; short y = (short)x; y++; System.out.println((byte)y);

Ce programme affiche le rsultat suivant :


-128

oprateur n'effectue pas un cast automatique en int.

Note 1 : Nous avons utilis ici l'oprateur ++ pour incrmenter y car cet Note 2 : Les casts s'appliquent aux valeurs et non aux variables. Lorsqu'une variable est dclare avec un type, il n'est plus possible d'en changer car une variable ne peut tre redfinie dans sa zone de visibilit. En

246

LE DVELOPPEUR JAVA 2
revanche, il est possible de masquer une variable avec une autre d'un autre type car les deux variables coexistent dans ce cas.

Les oprateurs de casting et les valeurs littrales


Les oprateurs de casting peuvent tre employs avec des valeurs littrales. Pour une explication dtaille, reportez-vous au Chapitre 4. Rappelons seulement ici que Java effectue automatiquement un sous-casting de int en short, char ou byte lorsque cela est ncessaire, condition que l'opration ne prsente pas de risques de perte de donnes. Dans le cas contraire, un sous-casting explicite est ncessaire. En d'autres termes, l'expression :
byte i = 127;

est correcte bien que 127 soit un int. En revanche, l'expression suivante produit une erreur de compilation :
byte i = 128;

car la valeur maximale qui peut tre reprsente par un byte est 127.

L'oprateur new
L'oprateur new permet d'instancier une classe, c'est--dire de crer un objet instance de cette classe. Nous l'avons tudi dans le prcdent chapitre. Nous ne le mentionnons ici que parce qu'il occupe une place dans l'ordre des priorits des oprateurs.

L'oprateur instanceof
L'oprateur instanceof produit une valeur de type boolean. Il prend deux oprandes, dont l'un ( gauche) est un handle et l'autre ( droite) une

CHAPITRE 6 LES OPRATEURS

247

classe, renvoie true si l'identificateur pointe vers un objet de la classe indique, et false dans le cas contraire :
int x = 5; String y = "y"; boolean a = x instanceof String; boolean b = y instanceof String;

Dans cet exemple, a vaut false et b vaut true.

Note : Il existe galement une version dynamique de l'oprateur


instanceof,

sous la forme d'une mthode de la classe Class. Nous reviendrons sur ce sujet dans un prochain chapitre.

L'oprateur instanceof ne permet pas de tester le type d'une primitive. Il ne peut mme pas dterminer si un identificateur correspond une primitive ou un objet, car son utilisation avec une primitive produit une erreur de compilation. Dterminer le type d'une primitive est cependant assez facile en utilisant la redfinition de mthode, comme nous le verrons dans un exemple la fin du chapitre suivant.

Priorit des oprateurs


Dans une expression, les oprateurs sont valus dans un certain ordre, selon leur priorit. Les oprateurs de priorit haute sont valus avant ceux de priorit basse. Lorsque plusieurs oprateurs d'un mme niveau de priorit sont prsents, ils sont valus dans l'ordre propre ce niveau de priorit (de droite gauche ou de gauche droite). Les parenthses, les appels de mthodes ou les crochets ou le connecteur . peuvent modifier l'ordre d'valuation. Le Tableau 6.1 montre l'ordre d'valuation des oprateurs Java.

248

LE DVELOPPEUR JAVA 2

Priorit la plus haute Oprateurs appel de mthode ( )


! ~ ++ -+ [ ] .

Ordre d'valuation de gauche droite de droite gauche de gauche droite


-

(un seul oprande) - (un seul oprande) (casting) new


/ %

* +

(deux oprandes)
>> <= != > >>>

(deux oprandes)

de gauche droite de gauche droite

<< < = & ^ | && || ? : = |=

>= instanceof

de gauche droite de gauche droite de gauche droite de gauche droite de gauche droite de gauche droite de gauche droite de gauche droite

+= ^=

-= <<=

*= >>=

/= >>>=

%=

&=

de droite gauche

Priorit la plus basse


Tableau 6.1 : Priorit des oprateurs.

Les parenthses peuvent tre utilises pour modifier l'ordre d'valuation de certains oprateurs. Cependant il est des cas o cela est impossible. Par exemple :

CHAPITRE 6 LES OPRATEURS


x *= y += 20;

249

est quivalent :
x *= (y += 20);

mais il est impossible d'crire :


(x *= y) += 20;

Pour obtenir le rsultat souhait (multiplier x par y, ajouter 3 et placer le rsultat dans x), il faut crire :
x *= y; x += 20;

ou encore, dans certains cas :


x *= y + 20 / x;

Pourquoi dans certains cas ? Si cela ne vous parat pas vident, essayez l'exemple suivant :
class MonCalcul { public static void main(String[] args) { double x = 5; double y = 10; x *= y + 20 / x; System.out.println(x); } }

Cela semble fonctionner. Essayez maintenant :

250

LE DVELOPPEUR JAVA 2
class MonCalcul { public static void main(String[] args) { double x = 3; double y = 10; x *= y + 20 / x; System.out.println(x); } }

Cela semble fonctionner aussi. Essayez ensuite :


class MonCalcul { public static void main(String[] args) { int x = 5; int y = 10; x *= y + 20 / x; System.out.println(x); } }

Tout va bien. Est-ce la peine de faire un essai supplmentaire ? Peut-tre, comme le montre l'exemple suivant :
class MonCalcul { public static void main(String[] args) { int x = 3; int y = 10; x *= y + 20 / x; System.out.println(x); } }

Cette fois, cela ne fonctionne plus du tout. Que s'est-il pass ? L'expression :
x *= y + 20 / x;

est quivalente, en tenant compte de l'ordre de priorit des oprateurs, de :

CHAPITRE 6 LES OPRATEURS


x = x * (y + (3 / x));

251

ce qui, pour nous, semble quivalent :


x = (x * y) + (x * (20 / x))

soit :
x = (x * y) + 20

ce qui est bien le rsultat souhait. Eh bien, il n'en est rien ! En effet, Java ne simplifie pas ce type d'expression mais la calcule btement. Au passage, 20/ x produit ventuellement un rsultat tronqu, si x n'est pas un diviseur de 20, en raison du casting du rsultat en int. Si vous pensez que l'utilisation de primitives de type float ou double rsout le problme, sachez qu'il n'en est rien. Pour le prouver, excutez le programme suivant :
class Precision { public static void main(String[] args) { double x = 9.00000000000001; double y = 10; double z = 8; boolean a; a = ((x * y + z) == (x *= (y+z/x))); System.out.println(a); } }

Ce programme affiche :
false

252

LE DVELOPPEUR JAVA 2
alors que, d'un point de vue mathmatique, les deux expressions sont gales. En fait, le rsultat de la premire est 98,00000000000011 alors que celui de la seconde est 98,0000000000001. Bien sr, l'erreur numrique est ngligeable dans pratiquement tous les cas. En revanche, elle peut prendre une importance considrable si vous effectuez, comme ici, un test d'galit.

Rsum
Dans ce chapitre, nous avons tudi en dtail les oprateurs de Java. Nous avons en particulier mis en lumire quelques piges lis au casting automatique effectu par Java lors des oprations, ainsi que quelques problmes concernant les consquences du manque de prcision des calculs. Le sujet tait un peu ardu. Aussi, il n'y aura pas d'exercice pour cette fois. Profitezen pour vous reposer avant d'aborder le chapitre suivant, qui sera consacr l'tude des structures de contrle.

Chapitre 7 : Les structures de contrle

Les structures de contrle

ANS CE CHAPITRE, NOUS ALLONS TUDIER LES DIFFRENTES structures qui permettent de contrler le droulement des programmes. Contrairement d'autres langages, ces structures ne permettent pas de contrler intgralement l'excution. En effet, Java est bas sur un modle contrl par des vnements. La particularit de ce type de langage peut tre dcrite simplement par un exemple. Supposons que le programme doive ragir la frappe d'une certaine touche. Avec les langages d'anciennes gnrations, le programmeur doit raliser une boucle dans laquelle le programme teste le clavier pour savoir si une touche a t frappe. Tant qu'aucune touche n'est frappe, la boucle s'excute indfiniment. Au contraire, avec un langage bas sur la gestion d'vnements, le programme n'a pas se proccuper du clavier. Un gestionnaire d'vnements

254

LE DVELOPPEUR JAVA 2
prviendra un objet de l'application que l'vnement frappe d'une touche s'est produit. Le programme pourra alors lire le clavier pour savoir si la touche frappe est celle laquelle (ou une de celles auxquelles) il doit ragir. Dans un modle diffrent, le message envoy l'objet pourra contenir galement l'indication de la touche frappe, ce qui permettra l'objet destinataire du message de savoir immdiatement s'il doit ragir ou non. Java est un langage bas sur la gestion des vnements. Cela dit, tout ne peut pas se ramener la gestion vnements et l'envoi de messages entre objets. Des traitements sont effectus l'intrieur des objets. Pour ces traitements, nous avons besoin de structures de contrle identiques celles que l'on trouve dans les langages traditionnels.

La squence
La structure la plus simple est la squence. Une squence est simplement compose d'une ou de plusieurs instructions qui se suivent et sont excutes les unes aprs les autres, dans l'ordre dans lequel elles figurent dans le programme. Cela peut paratre trivial, mais il s'agit de la structure la plus frquemment rencontre. En Java, grce l'oprateur trois oprandes ?:, il est possible d'effectuer des traitements relativement complexes uniquement l'aide de squences. Cependant, il s'agit l d'un exercice de style. Cette pratique n'est pas conseiller car ce type de programme devient vite totalement illisible. En fait, on peut traiter pratiquement n'importe quel problme l'aide de l'oprateur ?:, de squences et d'appels de mthodes, condition que la longueur des squences ne soit pas limite. Cela dit, a ne nous avance pas beaucoup !

Le branchement par appel de mthode


Une faon de dtourner le flux dexcution d'un programme (opration parfois appele branchement) consiste appeler une mthode. Nous avons

CHAPITRE 7 LES STRUCTURES DE CONTRLE

255

vu dans les chapitres prcdents qu'une mthode peut tre utilise comme une fonction, c'est--dire pour la valeur qu'elle retourne, ou comme une procdure, pour la modification qu'elle apporte son environnement, ou encore pour les deux la fois. Cette caractristique est souvent mise profit pour dtourner l'excution d'une squence. Dans l'exemple suivant :

1 class Control1 { 2 public static void main(String[] args) { 3 int j = 10; 4 j = printDec(j); 5 j = printDec(j); 6 j = printDec(j); 7 j = printDec(j); 8 j = printDec(j); 9 j = printDec(j); 10 } 11 12 static int printDec(int i) { 13 System.out.println(i--); 14 return i; 15 } 16 }

le flux d'excution se droule de la faon suivante :

ligne ligne ligne ligne ligne ligne ligne ligne ligne ligne ligne

3 4 13 14 5 13 14 6 13 14 7

256
ligne ligne ligne ligne ligne ligne ligne ligne 13 14 8 13 14 9 13 14

LE DVELOPPEUR JAVA 2

Une mthode utilise comme procdure permet donc de contrler le cours de l'excution du programme. Par exemple, l'aide de l'oprateur ?:, il est possible de modifier ce programme afin qu'il dcompte la valeur de j jusqu' 0 :

class Control2 { public static void main(String[] args) { int j = 10; j = (j == 0)?0:printDec(j); j = (j == 0)?0:printDec(j); j = (j == 0)?0:printDec(j); j = (j == 0)?0:printDec(j); j = (j == 0)?0:printDec(j); j = (j == 0)?0:printDec(j); j = (j == 0)?0:printDec(j); j = (j == 0)?0:printDec(j); j = (j == 0)?0:printDec(j); } static int printDec(int i) { System.out.println(i--); return i; } }

Nous avons volontairement recopi la ligne contenant l'oprateur ?: plus de fois que ncessaire pour montrer que le programme fonctionne correc-

CHAPITRE 7 LES STRUCTURES DE CONTRLE

257

tement. Vous objecterez peut-tre qu'il ne fonctionne que pour des valeurs de j infrieures 9. Qu' cela ne tienne. Il suffit de multiplier volont la ligne en question pour que le programme fonctionne pour n'importe quelle valeur. (Aucun langage ne pouvant traiter des valeurs infinies, il est possible de traiter tous les cas.) Ici, il suffit de recopier la ligne 2 147 483 647 fois (la plus grande valeur positive pouvant tre reprsente par un int) pour pouvoir traiter toutes les valeurs de j. Bien sr, ce n'est pas trs efficace, mais c'est possible !

L'instruction de branchement return


Le retour d'une mthode se fait l'instruction return si elle est prsente, ou la fin du bloc dans le cas contraire. Cependant, l'instruction return peut tre utilise n'importe o dans le corps de la mthode. Celle-ci peut d'ailleurs contenir plusieurs instructions return qui seront excutes en fonction de la ralisation de diffrentes conditions testes au moyen d'instructions telles que if, que nous tudierons dans la section suivante. L'instruction return doit toujours tre suivie d'une valeur correspondant au type de la mthode, sauf si le type est void. Cette valeur est appele valeur de retour de la mthode.

L'instruction conditionnelle if
Un programme ne peut rien faire s'il n'est pas capable de prendre des dcisions, c'est--dire d'effectuer ou non un traitement en fonction de l'valuation d'une expression logique. En plus de l'oprateur ?:, Java dispose, comme la plupart des langages de programmation, de l'instruction if. L'instruction if prend pour paramtre une expression de type boolean place entre parenthses. Cette expression est value et produit un rsultat valant true ou false. Si le rsultat est true, l'instruction ou le bloc suivant est excut. Si le rsultat est false, l'instruction ou le bloc suivant est ignor. La syntaxe de l'instruction if peut donc tre dcrite de la faon suivante :

258
if (expression) instruction;

LE DVELOPPEUR JAVA 2

ou :
if (expression) { instruction1; instruction2; . . . instructinon; }

L'expression boolenne doit toujours tre place entre parenthses. Si elle est suivie d'une seule instruction, celle-ci peut tre place sur la mme ligne ou sur la ligne suivante. (En Java, les sauts de ligne ont presque toujours valeur de simples sparateurs.) Cependant, si vous prfrez la version sur deux lignes :

if (expression) instruction;

il faut faire attention de ne pas mettre un point-virgule aprs l'expression.

Note : Lorsqu'il y a une seule instruction, il est parfaitement possible


d'utiliser tout de mme un bloc, ce qui est souvent plus lisible. Ainsi, les deux versions suivantes sont quivalentes la prcdente :

if (expression) { instruction; } if (expression) {instruction;}

CHAPITRE 7 LES STRUCTURES DE CONTRLE

259

Attention : N'oubliez pas qu'en Java, contrairement l'usage avec d'autres


langages, le point-virgule est obligatoire mme en fin de bloc (avant le caractre }). Quoi qu'il arrive, l'expression logique est toujours value en respectant les rgles de Java. Ainsi, dans l'exemple suivant :

int i = 0; if (i++ > 0) { i += 2; }

la variable i est initialise 0. Lorsque l'expression logique de la deuxime ligne est value, i vaut encore 0. L'expression vaut donc false et l'instruction i += 2 n'est donc pas excute. En revanche, la post-incrmentation de i (i++) est effectue lors de l'valuation de l'expression logique. A la fin du programme, i vaut donc 1.

Attention : Une source d'erreur trs frquente consiste utiliser l'oprateur d'affectation dans l'expression logique :

if (i = 0) { i += 2; }

La forme correcte est :

if (i == 0) { i += 2; }

Heureusement, et contrairement ce qui se passe avec d'autres langages, le compilateur Java signale immdiatement cette erreur.

260

LE DVELOPPEUR JAVA 2

L'instruction conditionnelle else


Comme nous l'avons dit prcdemment, pratiquement tous les problmes de programmation peuvent tre traits en utilisant une instruction de branchement (par exemple l'appel de mthode) et une instruction conditionnelle. Les premiers langages de programmation ne comportaient d'ailleurs que l'instruction if et une instruction de branchement beaucoup plus sommaire que l'appel de mthode. Cependant, il est utile de disposer d'instructions plus sophistiques de faon rendre les programmes plus lisibles. Une des structures que l'on rencontre le plus souvent en programmation est du type :

si(expression logique) bloc d'instructions sinon autre bloc d'instructions

Cette structure peut tre ralise l'aide de deux instructions if utilisant des expressions logiques contraires, par exemple :

if (i == 0) { instruction1; } if (i != 0) { instruction2; }

L'instruction else permet de simplifier cette criture sous la forme :


if (i == 0) { instruction1; }

CHAPITRE 7 LES STRUCTURES DE CONTRLE


else { instruction2; }

261

Ces deux critures ne sont toutefois pas du tout quivalentes. En effet, l'instruction else n'value pas l'expression contraire de celle de l'instruction if. Elle utilise simplement le rsultat de l'valuation de l'expression de l'instruction if pour excuter son bloc d'instruction si ce rsultat est faux. La diffrence apparatra plus clairement dans l'exemple suivant. Avec if else :
int i = if (i < i += } else { i += } 6; 10) { 5;

10;

Avec deux instructions if :


int i = 4; if (i < 10) { i += 5; } if (i >= 10) { i += 10; }

Dans le premier cas, l'expression i < 10 est value. Elle vaut true. Le premier bloc est donc excut et le bloc suivant l'instruction else est ignor. la fin du programme, i vaut 11. Dans le second cas, l'expression est teste de la mme faon. Le bloc suivant l'instruction if est donc excut. Le problme vient de ce que la variable teste est modifie avant la deuxime instruction if. Lorsque celle-ci est excute, la valeur de l'expression logique i >= 10 est de nouveau true. Le second bloc est donc galement excut. A la fin du programme, i vaut donc 21.

262

LE DVELOPPEUR JAVA 2

Les instructions conditionnelles et les oprateurs ++ et -Vous pouvez parfaitement utiliser les oprateurs d'auto-incrmentation et d'auto-dcrmentation dans les expressions logiques, bien que cela soit une source de confusion. Ainsi, dans l'exemple suivant :
int i = if (i++ i += } else { i -= } 4; > 4) { 2;

2;

le bloc else est excut alors que dans celui-ci :


int i = if (++i i += } else { i -= } 4; > 4) { 2;

2;

c'est le bloc if qui l'est.

Les instructions conditionnelles imbriques


Dans certains langages, l'imbrication d'instructions if else conduit des structures de hirarchie complexes telles que :
if (expression1) { bloc1; }

CHAPITRE 7 LES STRUCTURES DE CONTRLE


else { if (expression2) { bloc2; } else { if (expression3) { bloc3; } else { if (expression4) { bloc 4; } else { bloc 5; } } } }

263

Java permet d'crire ce type de structure plus simplement sous la forme :

if (expression1) { bloc1; } else if (expression2) { bloc2; } else if (expression3) { bloc3; } else if (expression4) { bloc 4; } else { bloc5; }

264

LE DVELOPPEUR JAVA 2
De cette faon, tous les blocs se trouvent sur le mme niveau de structure, ce qui est plus lisible et vite d'avoir compter les accolades fermantes.

Attention : En fait, il ne s'agit pas d'une faon diffrente d'utiliser les instructions else et if, mais d'une instruction particulire, qui s'crit en deux mots en Java, alors qu'il serait plus logique de lui donner un autre nom, comme dans de nombreux autres langages (par exemple elseif).
De toute faon, ces deux structures ne sont pas du tout quivalentes comme le montre l'exemple suivant, impossible exprimer avec la structure else if :
if (i == 0) { System.out.println("0"); } else { if (i == 1) { System.out.println("1"); } else { if (i == 2) { System.out.println("2"); } else { if (i == 3) { System.out.println("3"); } else { System.out.println("plus grand que 3"); } } System.out.println("Il suffit de 2."); } }

Dans cet exemple, un bloc diffrent est excut pour chaque valeur de i comprise entre 1 et 4, et un bloc diffrent pour les valeurs suprieures 4. De plus, un bloc spcial doit tre excut pour les valeurs suprieures 2.

CHAPITRE 7 LES STRUCTURES DE CONTRLE

265

Seule la structure ci-dessus permet de raliser cela sans dupliquer le bloc, ici reprsent par l'instruction :
System.out.println("Il suffit de 2.");

La difficult consiste ne pas se tromper en plaant ce bloc dans la hirarchie. Pour raliser la mme chose avec la structure else if, il faut dupliquer le bloc de la faon suivante :
if (i == 0) { System.out.println("0"); } else if (i == 1) { System.out.println("1"); } else if (i == 2) { System.out.println("2"); System.out.println("Il suffit de 2."); } else if (i == 3) { System.out.println("3"); System.out.println("Il suffit de 2."); } else { System.out.println("plus grand que 3"); System.out.println("Il suffit de 2."); }

ce qui est contraire un des principes de la programmation efficace qui veut que le code soit dupliqu le moins possible. (La raison en est vidente : si vous voulez modifier les messages de votre programme, par exemple pour les traduire, vous avez trois modifications faire au lieu d'une.) Une autre faon de procder serait la suivante :
if (i == 0) { System.out.println("0"); }

266

LE DVELOPPEUR JAVA 2
else if (i == 1) { System.out.println("1"); } else if (i == 2) { System.out.println("2"); } else if (i == 3) { System.out.println("3"); } else { System.out.println("plus grand que 3"); } if (i >= 2) { System.out.println("Il suffit de 2."); }

Cependant, le code est dupliqu ici aussi. En effet, le test i >= 2 est une autre faon d'crire que l'un des trois derniers tests est vrai. (On suppose pour les besoins de l'exemple que i a une valeur entire positive ou nulle.) Comme vous le voyez, il faut parfois choisir entre l'lgance et la lisibilit.

Note : Il est tout fait possible d'utiliser l'instruction if else avec une clause if vide. Cela permet d'indiquer une condition au lieu de son contraire, ce qui est parfois plus parlant. Ainsi, vous pouvez crire :
if (i == 5); else System.out.println(i);

ce qui est quivalent :


if (i != 5) System.out.println(i);

Ici, l'avantage ne parat pas vident. En revanche, en cas d'utilisation d'expressions logiques complexes, il peut tre avantageux, du point de vue de la lisibilit, de ne pas utiliser leur ngation.

CHAPITRE 7 LES STRUCTURES DE CONTRLE

267

La boucle for
La boucle for est une structure employe pour excuter un bloc d'instructions un nombre de fois en principe connu l'avance. Elle utilise la syntaxe suivante :
for (initialisation; test; incrmentation) { instructions; }

Exemple :
int i = 0; for (i = 2; i < 10;i++) { System.out.println("Vive Java !"); }

Lors de la premire excution de la boucle, i se voit affecter la valeur 2. L'expression logique i < 10 est ensuite teste. Sa valeur tant true, la boucle est excute et le programme affiche la chane de caractres "Vive Java!". La partie incrmentation est alors excute. L'expression logique est de nouveau value, et le processus continue jusqu' ce que la valeur de cette expression soit false. A ce moment, l'excution se poursuit par la ligne suivant le bloc d'instruction de la boucle for.

Note : la sortie de la boucle, la valeur de la variable servant d'indice (ici, i)


est donc diffrente de la dernire valeur utilise dans la boucle. Dans l'exemple dessus, la chane de caractres sera affiche 8 fois, pour les valeurs de i allant de 2 (valeur initiale) 9. A la sortie de la boucle, i vaudra 10 (premire valeur pour laquelle l'expression logique i < 10 vaut false.

L'initialisation
La partie initialisation consiste en l'initialisation d'une ou de plusieurs variables, par exemple :

268
for (i = 1; ....

LE DVELOPPEUR JAVA 2

ou encore :
for (i = 1, j = 5, k = 12;....

Notez qu'avec ces syntaxes, la ou les variables doivent avoir t dclares pralablement. En contrepartie, il est possible d'utiliser des variables de types diffrents, par exemple :
int i; long j; char k; for (i = 1, j = 5, k = 'a';....

En revanche, il est possible d'utiliser la syntaxe suivante pour dclarer et initialiser les variables simultanment :
for (int i = 1, j = 5, k = 12;...

mais, dans ce cas, toutes les variables doivent tre du mme type. Si vous utilisez une variable qui a t pralablement dclare et initialise, vous n'tes pas oblig de l'initialiser une seconde fois. Il suffit de laisser vide la partie initialisation. Le point-virgule doit cependant tre prsent :
int i = 0; for (;...

Le test
La partie test est compose d'une expression logique qui est value et prend la valeur true ou false. Si la valeur est true, le bloc d'instructions suivant est excut. Dans le cas contraire, la boucle est termine et l'excution se poursuit la ligne suivant le bloc.

CHAPITRE 7 LES STRUCTURES DE CONTRLE

269

La boucle for ne peut comporter qu'une seule expression logique. En revanche, celle-ci peut comprendre plusieurs expressions combines l'aide des oprateurs logiques, par exemple :
int i; long j; char k; for (i = 0, j = 2, k = 'a'; i <= 10 || j <= 12 || k <= 'x';...

L'expression logique utilise pour le test peut galement comporter des oprateurs d'auto-incrmentation ou d'auto-dcrmentation, bien que cela ne soit pas du tout conseill. En effet, cela augmente considrablement les risques d'erreur, alors qu'il est pratiquement toujours possible de l'viter. videmment, la diffrence entre pr et post-incrmentation/dcrmentation ajoute encore la confusion. Avouez qu'il n'est pas vident de comprendre immdiatement ce que fait le programme suivant :
int i; long j; char k; for (i = 0, j = 2, k = 'a'; --i <= i++, j System.out.println("i : " + System.out.println("j : " + System.out.println("k : " + }

10 || j++ <= 12 || k-- <= 'x'; += 2, k += 3) { i); j); k);

La rponse est simple : il plante l'interprteur Java ! En fait, cette boucle ne trouve aucune condition de sortie car i est pr-dcrment dans le test et vaut donc -1 la premire itration. L'expression logique est donc vraie puisque son premier terme (--i<=10) est vrai. En raison du court-circuit, le reste de l'expression n'est pas valu. La boucle est donc excute une premire fois, puis i est incrment et vaut donc 0, j se voit augment de 2 et k de 3. Lorsque le test est effectu la fois suivante, i prend de nouveau la valeur -1 et tout recommence. Il s'agit donc d'une boucle infi-

270

LE DVELOPPEUR JAVA 2
nie. Par ailleurs, chaque boucle, k est augment de 3. Lorsque la valeur de k dpasse 56 319, l'interprteur Java se plante ! (Bon, d'accord, il est rare d'avoir imprimer des caractres ayant un code aussi lev, mais c'est tout de mme bon savoir. Ce bug existe depuis la version 1.0 jusqu' la version 1.2 bta 3 sous les environnements Solaris, Windows 95 et Windows NT. Il est possible qu'il soit corrig dans les versions ultrieures.)

Note 1 : Pour raliser une boucle infinie, il est plus simple d'utiliser true
comme expression logique. Ainsi, dans l'exemple :
for (byte i = 0; true; i++) { System.out.println(i); }

la boucle est effectue indfiniment puisque l'expression logique value vaut toujours true ! reur. Le dbordement de la valeur de i lorsque celle-ci dpasse 127 lui fait prendre automatiquement la valeur -128 et la boucle continue de s'excuter.

Note 2 : Remarquez que ce programme ne produit pas de message d'er-

Note 3 : Si votre programme entre dans une boucle infinie, vous pouvez
l'arrter en tapant les touches CTRL + C. (Du moins s'il s'agit d'une application. S'il s'agit d'une applet, il sera ncessaire de fermer le document qui la contient.)

L'incrmentation
Incrmentation est utilis ici dans un sens gnrique signifiant modification du ou des indices. L'incrmentation est l'opration la plus frquente, mais vous pourrez effectuer toutes les oprations que vous souhaitez. Cette partie peut d'ailleurs comporter un nombre quelconque d'instructions, spares par des virgules. Elle peut, en particulier, ne comporter aucune instruction. (C'est la seule des trois parties qui puisse tre vide.)

CHAPITRE 7 LES STRUCTURES DE CONTRLE

271

Les instructions prsentes dans cette partie peuvent tre utilises pour leurs effets sur les variables indices, ou pour tout autre effet, par exemple :

for (int i = 1; i<10; i++, System.out.println(i)) { . . . . }

Notez l'absence de points-virgules la fin des instructions.

Le bloc d'instructions
Les instructions excutes par la boucle sont places dans un bloc dlimit par les caractres { et }. Si une seule instruction doit tre excute, l'utilisation d'un bloc est facultative. Ainsi, les quatre formes suivantes sont quivalentes :

for (int i = 1; i<10; i++) { System.out.println(i); } for (int i = 1; i<10; i++) {System.out.println(i);} for (int i = 1; i<10; i++) System.out.println(i); for (int i = 1; i<10; i++) System.out.println(i);

La premire est toutefois la plus lisible et nous vous conseillons de l'utiliser systmatiquement (surtout si vous tes un programmeur professionnel pay la ligne de code).

272

LE DVELOPPEUR JAVA 2 Modification des indices l'intrieur de la boucle


Il est tout fait possible de modifier les indices utiliss pour contrler la boucle l'intrieur de celle-ci. Ainsi, le programme suivant fonctionne parfaitement mais ne s'arrte jamais :
for (int i = 1; i<10; i++) { System.out.println(i); i--; }

La manipulation des indices l'intrieur de la boucle est fortement dconseille si vous souhaitez simplifier la maintenance de votre programme. Comme vous pouvez le voir, la syntaxe des boucles for est trs peu contraignante. Le revers de la mdaille est qu'il est possible d'utiliser de nombreuses astuces qui rendent la maintenance des programmes hasardeuse. Considrez, par exemple, une boucle affichant les valeurs entires comprises entre 1 et une valeur quelconque x. Voici trois faons d'arriver ce rsultat :
for (int i = 1; i <= x; i++) System.out.println(i); for (int i = 0; i++ < x;) System.out.println(i); for (int i = 1; i <= x;) System.out.println(i++);

On peut trouver d'autres faons encore plus obscures. Inutile de prciser que nous vous conseillons fortement de vous en tenir la premire.

Imbrication des boucles for


Les boucles for peuvent tre imbriques. Par exemple, si vous souhaitez initialiser avec la valeur z les lments d'un tableau d'entiers de dimensions x par y (nous tudierons les tableaux dans un prochain chapitre), vous pouvez utiliser deux boucles for imbriques, de la faon suivante :

CHAPITRE 7 LES STRUCTURES DE CONTRLE


int z = 5; int x = 8; int y = 10; for (int i = 0; i < x; i++) { for (int j = 0; j < y; j++) { tableau[i][j] = z; } }

273

Type des indices


On peut se poser la question de savoir quel type de donnes il est prfrable d'utiliser pour les indices des boucles. Il peut sembler, en effet, qu'il soit plus efficace d'utiliser le type byte pour les indices dont la valeur ne dpasse pas 127, et le type short pour les indices limits 32 767. On peut mme utiliser le type char, qui permet de compter jusqu' 65 535 avec seulement 16 bits. En fait, il n'en est rien. Les boucles fonctionnent beaucoup plus rapidement avec des indices de type int qu'avec tout autre type. Cela est d au fait que Java convertit les byte, short et char en int avant d'effectuer des calculs, puis effectue la conversion inverse une fois le calcul effectu. Pour le vrifier, vous pouvez utiliser le programme suivant :
class TestFor { public static void main(String[] args) { int h = 0; long t = System.currentTimeMillis(); for (byte i = 0; i < 127;i++) { for (byte j = 0; j < 127;j++) { for (byte k = 0; k < 127;k++) { for (byte l = 0; l < 127;l++) { h = 2; } } } }

274
System.out.println(t); } }

LE DVELOPPEUR JAVA 2
t = System.currentTimeMillis() - t;

qui affiche le temps mis pour effectuer 260 144 661 fois l'affectation de la valeur littrale 2 la variable h. Ce programme affiche :

68770

Si vous utilisez des indices de type int :

for (int i = 0; i < 127;i++) { for (int j = 0; j < 127;j++) { for (int k = 0; k < 127;k++) { for (int l = 0; l < 127;l++) {

le programme affiche :

43500

soit un gain de 36,7 % en performance.

Note : Si vous essayez ce programme, vous obtiendrez forcment des


rsultats diffrents. La vitesse d'excution du programme dpend de nombreux facteurs, comme la puissance du processeur ou son occupation par d'autres tches. Ce qui compte ici, c'est le rapport entre les deux valeurs. Notez que l'on peut constater ici une grande amlioration dans les castings de byte depuis la version 1.1 de Java. En effet, avec celle-ci, l'cart en performance tait de 45,5 %.

CHAPITRE 7 LES STRUCTURES DE CONTRLE

275

Porte des indices


La porte des indices est un problme ne pas ngliger. Celle-ci dpend du choix que vous ferez d'initialiser les indices dans la boucle ou hors de celleci. En effet, les boucles for prsentent une exception la rgle qui veut que les variables aient une porte limite au bloc dans lequel elles sont dclares. En effet, les variables d'indices dclares dans la partie initialisation de l'instruction for ont une porte limite au bloc excut par la boucle. En d'autres termes, le programme suivant fonctionne sans problme :
int i; for (i = 0; i < 10; i++) { System.out.println(i); } System.out.println(i);

alors que celui-ci produit une erreur de compilation :


for (int i = 0; i < 10; i++) { System.out.println(i); } System.out.println(i);

car la variable i a une porte limite au bloc d'instructions excut par la boucle. Elle n'existe donc plus lorsque la boucle est termine. Dans une prochaine section, nous tudierons un moyen permettant de sortir d'une boucle de faon anticipe, lorsqu'une condition est remplie, l'aide de l'instruction break. Dans ce cas, il peut tre ncessaire de connatre la valeur de l'indice au moment de la sortie. La faon politiquement correcte de procder consiste utiliser une autre variable et lui affecter la valeur de l'indice au moment de la sortie. Par exemple :
int x = 0; for (int i = 0; i < 10; i++) {

276
if (tableau[i] = 5) { x = i; break; } System.out.println(x);

LE DVELOPPEUR JAVA 2

Ce programme parcourt les 10 lments d'un tableau pour trouver le premier (s'il existe) dont la valeur est 5. Si cet lment est trouv, la boucle est interrompue au moyen de l'instruction break et l'excution continue la ligne suivant le bloc d'instructions de la boucle. La valeur de l'indice a t pralablement affecte la variable x, dclare avant la boucle, et peut donc tre utilise aprs celle-ci.

Note : la premire ligne du programme, la variable x est dclare et initialise. Cette initialisation est obligatoire (dans la mesure o x n'est pas une variable d'instance) car le compilateur ne peut pas tre sr que x sera initialise avant la dernire ligne et refuse donc de compiler le programme. Nous pouvons en tre srs car nous voyons que les paramtres de la boucle for garantissent qu'elle sera excute au moins une fois. Le compilateur, lui, n'est pas assez intelligent pour le comprendre. Il est toutefois prudent puisque le message d'erreur affich est :
Variable x may not have been iniatilized.

(Il se pourrait que la variable x ne soit pas initialise.) Une autre solution consiste utiliser directement l'indice de la boucle, en le dclarant avant celle-ci, de la faon suivante :
int i; for (i = 0; i < 10; i++) { if (tableau[i] = 5) { break; } System.out.println(i);

CHAPITRE 7 LES STRUCTURES DE CONTRLE

277

Cette mthode n'est pas conseille. En effet, il est toujours prfrable de rserver les indices pour leur fonction primaire, qui est de compter les itrations de la boucle. Une utilisation l'extrieur de la boucle peut conduire des problmes difficiles dtecter. Comparez, par exemple, les deux programmes suivants :

class For2 { public static void main(String[] args) { int x = 0; for (i = 5; i < 10; i = i + 2) { x = i; } System.out.println(x); } }

et :

class For1 { public static void main(String[] args) { int i; for (i = 5; i < 10; i = i + 2) { } System.out.println(i); } }

Le premier programme, construit de la manire recommande, affiche la valeur de l'indice lors de la dernire itration de la boucle, c'est--dire 9. Le deuxime programme, pour sa part, affiche la valeur qui a provoqu la sortie de la boucle, c'est--dire 11. Cela parat vident parce que les indices sont initialiss 5 et incrment de 2 chaque boucle. En revanche si, comme c'est souvent le cas, on utilise une valeur initiale de 0 et un incrment de 1, on arrive une situation confuse. Le premier programme :

278

LE DVELOPPEUR JAVA 2
class For2 { public static void main(String[] args) { int x = 0; for (i = 0; i < 10; i++) { x = i; } System.out.println(x); } }

affiche 9, ce qui est clairement la valeur de l'indice lors de la dernire itration de la boucle, alors que le deuxime :
class For1 { public static void main(String[] args) { int i; for (i = 0; i < 10; i++) { } System.out.println(i); } }

affiche 10, ce qui peut facilement tre confondu avec la valeur de la limite ou, plus grave, avec le nombre d'itrations, qui se trouve, mais c'est un cas particulier, tre aussi gal 10.

Sortie d'une boucle par return


Nous avons dj voqu l'utilisation de l'instruction return pour modifier le cours de l'excution du programme. Lorsqu'une boucle for est employe dans une mthode, il est possible de sortir de la boucle au moyen de l'instruction return, comme dans l'exemple suivant :
for (int i = 0; i < 10; i++) { if (tableau[i] = 5) {

CHAPITRE 7 LES STRUCTURES DE CONTRLE


return i; }

279

Ce programme parcourt les 10 lments d'un tableau pour trouver le premier (s'il existe) dont la valeur est 5. Si cet lment est trouv, la mthode retourne l'indice de l'lment correspondant. Notez qu'ici, il est parfaitement sr d'utiliser la variable indice avec l'instruction return car, en Java, les primitives sont passes par valeur et non par rfrence, ce qui signifie que ce n'est pas une rfrence la variable i qui est retourne, mais uniquement sa valeur.

Branchement au moyen des instructionsbreaketcontinue


Comme nous l'avons vu prcdemment, il est possible d'interrompre une boucle au moyen de l'instruction break. Celle-ci a deux effets distincts :

Interruption de l'itration en cours. Passage l'instruction suivant la boucle.


L'effet de l'instruction break est illustr par la Figure 7.1.

int x = 10; for (int i = 0; i < 10; i++) { x--; if (x == 5) break; } System.out.println(x);
Figure 7.1 : Sortie de boucle au moyen de linstruction break.

280
Note : L'instruction
break

LE DVELOPPEUR JAVA 2
ne modifie pas la valeur de la variable indice (ici i), contrairement l'exemple suivant (fortement dconseill), qui simule le prcdent :

int x = 10; for (int i = 0; i < 10; i++) { x--; if (x == 5) i = 10; } System.out.println(x);

L'instruction continue permet galement de sortir d'une boucle. Elle a les deux effets suivants :

Interruption de l'itration en cours. Retour au dbut de la boucle avec excution de la partie incrmentation.
En d'autres termes, l'instruction continue interrompt l'itration en cours et passe directement l'itration suivante. Son fonctionnement est illustr par la Figure 7.2. Ce programme a pour effet d'afficher toutes les valeurs de l'indice, de 0 9, l'exception de 5. Il peut tre simul de deux faons :

for (int i = 0; i < 10; i++) { if (i == 5) continue; System.out.println(i); }


Figure 7.2 : Utilisation de linstruction continue.

CHAPITRE 7 LES STRUCTURES DE CONTRLE


for (int i = 0; i < 10; i++) { if (i == 5) i++; System.out.println(i); }

281

ou :
for (int i = 0; i < 10; i++) { if (i == 5); else { System.out.println(i); } }

La premire faon, comme toutes les manipulations de l'indice dans la boucle, est fortement dconseille.

Utilisation de break et continue avec des tiquettes


Dans le cas de boucles imbriques que nous avons tudi prcdemment, les instructions break et continue n'agissent qu'au niveau de la boucle interne. Ainsi, dans l'exemple suivant :
int z = 5; int x = 8; int y = 10; for (int i = 0; i < x; i++) { for (int j = 0; j < y; j++) { if (tableau[i][j] != 0) continue; tableau[i][j] = z; } }

si l'lment de tableau a une valeur non nulle, l'excution se poursuit la prochaine itration de la boucle interne. Si vous voulez que l'excution se

282

LE DVELOPPEUR JAVA 2
poursuive la prochaine itration de la boucle externe, vous devez utiliser une tiquette. Les tiquettes sont des identificateurs Java se terminant par le signe :. Pour obtenir l'effet recherch dans l'exemple prcdent, vous pouvez crire :
int z = 5; int x = 8; int y = 10; etiquette: for (int i = 0; i < x; i++) { for (int j = 0; j < y; j++) { if (tableau[i][j] != 0) continue etiquette; tableau[i][j] = z; } }

Il ne doit jamais y avoir d'instruction entre l'tiquette et la boucle concerne. Dans le cas contraire, Java est incapable de trouver l'tiquette lorsqu'il rencontre l'instruction continue ou break. Par ailleurs, il est impossible d'interrompre une itration de boucle extrieure pour effectuer un branchement vers une tiquette de boucle intrieure. Le branchement se fait toujours en remontant vers la surface.

L'instruction while
Il arrive frquemment qu'une boucle doive tre excute tant qu'une condition est ou n'est pas remplie, sans que cette condition porte sur une forme d'indice. L'instruction while est prvue pour le cas o la condition doit tre teste avant toute excution. Le programme suivant recherche le premier lment non nul d'un tableau de valeur de type int.
int i = 0, x = 0; while (x == 0) { x = tableau[i++]; }

CHAPITRE 7 LES STRUCTURES DE CONTRLE

283

L'instruction while est utile lorsque le nombre maximal d'itrations n'est pas connu l'avance, et lorsque la condition de sortie est indpendante de celui-ci. Notez qu'ici, ce nombre pourrait tre connu en dterminant la longueur du tableau. Nous pourrions donc utiliser une boucle for. (Il serait mme plus prudent de le faire, car la mthode employe ici risque de nous faire dpasser les limites du tableau si aucun lment satisfaisant la condition n'est trouv.) Cependant, la condition de sortie tant dtermine par la valeur de l'lment de tableau, il nous faudrait utiliser galement une instruction break pour sortir de la boucle :
int x = 0; int y = tableau.length; for (int i = 0; i < y; i++) { x = tableau[i]; if (x != 0) break; }

Notez que tableau.length() donne le nombre d'lments du tableau. Les lments tant numrots partir de 0, nous utilisons l'oprateur < et non <=. Tout cela sera plus clair lorsque nous tudierons les tableaux. Ce code est beaucoup moins compact que la version prcdente. (En revanche, il ne risque pas de provoquer d'erreur si on dpasse les limites du tableau !) Pour ce qui est des performances, le temps d'excution est suprieur de plus de 57 %. Notez que l'on peut amliorer la situation en utilisant la forme suivante :
int x = 0, y = tableau.length; for (int i = 0; i < y;i++) { if (tableau[i] != 0) { x = tableau[i]; break; } }

284

LE DVELOPPEUR JAVA 2
Le temps d'excution est alors suprieur de seulement 12 % celui de la version avec while. Notez qu'il est possible de rduire cet cart 0 en utilisant la forme suivante :
int x = 0, i = 0, y = tableau.length; for (; i < y; i++) { if (tableau[i] == 0) continue; x = tableau[i]; break; }

Le moins qu'on puisse dire est que l'on ne gagne pas en lisibilit ! Il existe une solution qui permet d'allier lisibilit, scurit et performance. Il suffit de prvoir un tableau dont le dernier lment satisfait toujours la condition. De cette faon, on ne risque pas de dpasser les limites du tableau. Il suffit de tester l'indice la sortie de la boucle pour savoir si l'lment trouv est le dernier du tableau :

int[] tableau = new int[101]; tableau[100] = 1; . . . // initialisation des lments du tableau . . int i = 0, x = 0; while (x == 0) { x = tableau[i++]; } if (i < 100) {... // Aucune valeur trouve }

CHAPITRE 7 LES STRUCTURES DE CONTRLE

285

Un autre possibilit consiste utiliser une valeur particulire pour l'indicateur de fin de tableau, par exemple une valeur ngative si on sait que toutes les valeurs seront positives ou nulles :
int[] tableau = new int[100]; tableau[100] = -1; . . . // initialisation des lments du tableau . . int i = 0, x = 0; while (x == 0) { x = tableau[i++]; } if (x < 0) {... // Aucune valeur trouve }

Dans ces deux cas, le test permettant de savoir si on a atteint les limites du tableau n'est effectu qu'une seule fois la fin du traitement, ce qui n'a qu'une influence ngligeable sur les performances. (Tout cela sera videmment plus clair lorsque nous aurons tudi les tableaux !) L'instruction do...while correspond au cas o la condition teste dpend de l'issue d'un traitement. En d'autres termes, la boucle doit tre effectue au moins une fois pour que le test puisse avoir lieu. Par exemple, si vous comptez les occurrences d'un caractre dans une chane, l'algorithme sera (en supposant qu'une recherche qui aboutit est signale par la valeur true de la variable de type boolean trouv) :
do rechercher le caractre; if (trouv) augmenter le compte d'une unit; while (trouv);

286

LE DVELOPPEUR JAVA 2
Dans un cas comme celui-ci, il serait impossible d'effectuer le test au dbut de la boucle, puisque la valeur teste dpend de l'excution de la boucle. Il est parfois possible de choisir presque indiffremment la forme while ou la forme do...while. Ainsi, notre exemple prcdent peut tre rcrit de la faon suivante :
int i = 0; int x; do { x = tableau[i++]; } while (x == 0);

(Attention de ne pas oublier le point-virgule aprs le test.) La diffrence entre les deux boucles apparat clairement ici : avec la version do...while, il n'est pas ncessaire que la variable x soit initialise avant la boucle puisque le test a lieu la fin de celle-ci et que la variable reoit une valeur l'intrieur de la boucle. Les instructions break et continue peuvent naturellement tre utilises avec les boucles while et do...while comme avec les boucles for. L'instruction break interrompt l'itration en cours et fait continuer l'excution aprs la fin du bloc dans le cas de l'instruction while, et aprs le test dans le cas de do...while. L'instruction continue prsente une petite particularit. En effet, elle interrompt l'itration en cours et fait continuer l'excution au dbut de la boucle dans le cas de do...while, ou juste avant le test dans le cas de while. Cependant, si continue est utilise avant la partie de la boucle qui modifie la condition tester, le programme tombe dans une boucle infinie, comme dans l'exemple suivant :
class TestWhile { public static void main(String[] args) { int i = 0;

CHAPITRE 7 LES STRUCTURES DE CONTRLE


do { if (i == 10) continue; System.out.println(i++); } while (i <= 20); } }

287

Ce programme est suppos afficher les valeurs de i de 0 20, l'exception de 10. Cependant, lorsque continue est excute, i n'a pas t incrment. La solution consiste traiter la modification de la condition avant d'excuter continue :
class TestWhile2 { public static void main(String[] args) { int i = 0; do { if (i == 10) { i++; continue; } System.out.println(i++); } while (i <= 20); } }

Il peut venir certains l'ide de traiter la modification de la condition en mme temps que le test dterminant l'excution de l'instruction continue :
class TestWhile3 { public static void main(String[] args) { int i = 0; do { if (++i == 10) continue; System.out.println(i); }

288
while (i <= 20) ; } }

LE DVELOPPEUR JAVA 2

Cependant, ce programme ne donne pas le mme rsultat. En effet, il imprime les valeurs de 1 21 et non de 0 20. Le rsultat correct serait obtenu en initialisant i -1 et en remplaant le test par i<=21, mais le programme deviendrait difficile lire !

L'instruction switch
Nous avons vu que l'instruction if...else if permet de traiter plusieurs cas sous la forme :
if (condition1) { } else if (condition2) { } else if (condition3) { } etc.

Il arrive souvent que les diffrentes conditions correspondent diffrentes valeurs d'une variable. Si cette variable (appele slecteur) est entire, Java permet d'utiliser l'instruction switch, dont la syntaxe est la suivante :
switch(variable) { case valeur1: instructions1; case valeur2: instructions2; case valeur3: instructions2; . . case valeurN: instructionsN; default: instructions; }

CHAPITRE 7 LES STRUCTURES DE CONTRLE


Le fonctionnement de cette structure est le suivant :

289

La valeur de la variable est compare avec valeur1. En cas d'ingalit,


la variable est compare avec valeur2, et ainsi de suite jusqu' ce qu'une galit soit trouve.

Lorsqu'une galit est trouve, le bloc d'instructions correspondant est


excut. Tous les blocs d'instructions suivants sont ensuite excuts. Il est trs important de bien raliser cette particularit. En effet, il pourrait sembler naturel que seul le bloc correspondant la condition d'galit soit excut. Il n'en est rien. (Ici, bloc dsigne toutes les instructions comprises entre deux instructions case, que celles-ci soient ou non encadres par les symboles { et }.)
default:

Si aucune galit n'est trouve, le bloc correspondant l'tiquette


est excut. Par exemple le programme suivant :
class TestSwitch { public static void main(String[] args) { int i = 6; switch (i) { case 1: System.out.println("1"); case 2: System.out.println("2"); case 3: System.out.println("3"); case 4: System.out.println("4"); case 5: System.out.println("5"); case 6: System.out.println("6"); case 7: System.out.println("7"); case 8: System.out.println("8"); case 9: System.out.println("9"); default: System.out.println("Autre"); } } }

affiche :

290
6 7 8 9 Autre

LE DVELOPPEUR JAVA 2

Le droulement du programme peut tre reprsent comme indiqu par la Figure 7.3 Si vous souhaitez que seul le bloc d'instructions correspondant la valeur de la variable slecteur soit excut, il suffit de terminer le bloc par une instruction break. L'exemple prcdent devient :
class TestSwitch2 { public static void main(String[] args) {

class TestSwitch { public static void main(String[] args) { int i = 6; switch (i) { case 1: System.out.println("1"); case 2: System.out.println("2"); case 3: System.out.println("3"); case 4: System.out.println("4"); case 5: System.out.println("5"); case 6: System.out.println("6"); case 7: System.out.println("7"); case 8: System.out.println("8"); case 9: System.out.println("9"); default: System.out.println("Autre"); } } }

Figure 7.3 : Utilisation de switch sans break.

CHAPITRE 7 LES STRUCTURES DE CONTRLE


int i = 6; switch (i) { case 1: System.out.println("1"); break; case 2: System.out.println("2"); break; case 3: System.out.println("3"); break; case 4: System.out.println("4"); break; case 5: System.out.println("5"); break; case 6: System.out.println("6"); break; case 7: System.out.println("7"); break; case 8: System.out.println("8"); break; case 9: System.out.println("9"); break; default: System.out.println("Autre"); } } }

291

Ce programme n'affiche plus que :


6

Son droulement est reprsent sur la Figure 7.4.

292

LE DVELOPPEUR JAVA 2

class TestSwitch2 { public static void main(String[] args) { int i = 6; switch (i) { case 1: System.out.println("1"); break; case 2: System.out.println("2"); break; case 3: System.out.println("2"); break; case 4: System.out.println("2"); break; case 5: System.out.println("2"); break; case 6: System.out.println("2"); break; case 7: System.out.println("2"); break; case 8: System.out.println("2"); break; case 9: System.out.println("2"); break; default: System.out.println("2"); break; } } }

Figure 7.4 : Utilisation de switch avec break.

Il n'est pas du tout ncessaire que les valeurs de la variable slecteur soient places dans l'ordre croissant. Ainsi le programme suivant fonctionne parfaitement :

CHAPITRE 7 LES STRUCTURES DE CONTRLE


class TestSwitch3 { public static void main(String[] args) { int i = 3; switch (i) { case 1: System.out.println("1"); break; case 4: System.out.println("4"); break; default: System.out.println("Autre"); break; case 3: System.out.println("3"); break; case 2: System.out.println("2"); } } }

293

Notez cependant que si un seul bloc doit tre excut pour chaque valeur du slecteur, seul le dernier bloc peut tre dispens de l'instruction break. Par ailleurs, un bloc d'instruction peut tre vide, ou comporter seulement une instruction break si aucun traitement ne doit tre effectu pour la valeur correspondante. De plus, le bloc default: est facultatif. Toutes ces particularits peuvent tre mises profit pour crer un exemple convertissant les minuscules en majuscules (nous supposerons qu'il existe un morceau de programme par ailleurs pour vrifier si les valeurs soumises correspondent bien des caractres) :

class Conversion { public static void main(String[] args) { System.out.println(conv('a'));

294
System.out.println(conv('')); System.out.println(conv('')); System.out.println(conv('c')); System.out.println(conv('e')); System.out.println(conv('')); System.out.println(conv('')); System.out.println(conv('o')); } static char conv (char c) { switch (c) { case '': case '': case '': return 'A'; case '': case '': case '': case '': return 'E'; case '': case '': return 'I'; case '': case '': return 'O'; case '': case '': case '': return 'U'; case '': return 'C'; default: return (char)(c - 32); } } }

LE DVELOPPEUR JAVA 2

Ce programme affiche :

CHAPITRE 7 LES STRUCTURES DE CONTRLE


A I C C E E O O

295

Notez qu'ici, nous n'avons pas utilis l'instruction break car l'instruction return permet d'obtenir le mme rsultat. L'instruction switch voit son intrt limit par le fait qu'il n'est possible de tester que des valeurs numriques entires, et non des expressions logiques comme dans la plupart des autres langages possdant ce type d'instruction. (Ici, les caractres peuvent tre tests car le type char est en fait un type numrique entier non sign sur 16 bits.) Si vous devez tester des expressions logiques, vous devrez vous contenter d'une succession d'instructions if et else if.

L'instruction synchronized
L'instruction synchronized est une sorte d'instruction conditionnelle lie l'utilisation d'une fonction particulire de Java appele multithreading, qui permet un programme d'excuter plusieurs processus simultanment. L'instruction synchronized prend pour paramtre une rfrence d'objet (handle ou expression) et excute le bloc de code suivant seulement si l'objet a pu tre verrouill afin d'en interdire l'accs aux autres processus. Sa syntaxe est la suivante :
synchronized (objet) { bloc d'instructions; }

Nous reviendrons sur ce sujet au cours des prochains chapitres.

296

LE DVELOPPEUR JAVA 2

Rsum
Dans ce chapitre, nous avons tudi la syntaxe des instructions Java permettant de contrler le flux d'excution du programme. Cette partie plutt rbarbative de l'apprentissage tait cependant ncessaire la suite de notre tude. Le prochain chapitre sera consacr un sujet tout fait diffrent : les conditions d'accessibilit aux lments de Java.

Chapitre 8 : Laccessibilit

Laccessibilit

ANS LES CHAPITRES PRCDENTS, TOUS LES IDENTIFICATEURS que nous avons utiliss taient accessibles pratiquement sans restriction. Les variables pouvaient tre lues et modifies sans contraintes. Les classes pouvaient tre instancies volont. En usage normal, cette situation est dangereuse. Par exemple, si vous concevez une classe pour la mettre la disposition de programmeurs, il est souhaitable que vous puissiez exercer un certain contrle sur la faon dont ceux-ci pourront l'utiliser. Vous pourrez, en particulier, limiter l'accs certaines variables membres, autoriser ou interdire l'instanciation, limiter l'utilisation de certaines mthodes, restreindre les possibilits de cration de nouvelles classes par extension de la vtre ou, au contraire, n'autoriser que ce type d'utilisation. Un des avantages essentiels de la restriction d'accs certains lments est que cette technique permet de se rserver la possibilit de modifier le

298

LE DVELOPPEUR JAVA 2
fonctionnement d'un programme sans que cela change quoi que se soit pour l'utilisateur. Par exemple, supposons que nous crions une classe reprsentant une voiture et possdant une variable membre statique capacit reprsentant la capacit du rservoir d'essence et une variable d'instance carburant reprsentant la quantit de carburant se trouvant dans le rservoir. Il est clair que nous ne devons pas autoriser l'utilisateur modifier la valeur de capacit. En revanche, il pourrait sembler naturel de l'autoriser lire cette valeur, ainsi qu' lire et modifier la valeur de carburant. Cependant, si nous le laissons faire le plein en modifiant la valeur de carburant, rien ne nous permet de nous assurer qu'il ne va pas faire dborder le rservoir en affectant cette variable une valeur suprieure celle de capacit. Par ailleurs, si nous dcidons un jour d'amliorer notre voiture en la dotant d'un double rservoir, nous nous trouverons dans une situation trs dlicate. En effet, tous les utilisateurs de notre classe devront modifier leurs programmes pour tenir compte de ce qui est, pour nous, une amlioration, mais risque d'tre peru par eux comme une lourde contrainte. La solution consiste dfinir trs prcisment la faon dont les utilisateurs peuvent interfrer avec notre classe. Par exemple, nous pourrons dfinir deux mthodes, capacit() et carburant(), permettant respectivement de connatre les valeurs de capacit et de carburant, ainsi que de modifier cette dernire valeur. Dans la premire version de la classe (un seul rservoir), la mthode capacit() retournera la valeur de la variable capacit. La mthode carburant() sans argument retournera la valeur de carburant alors que la version surcharge carburant(floatc) ajoutera la valeur de c carburant tout en limitant le total la valeur de capacit et en retournant capacit - (carburant + c), c'est--dire une valeur reprsentant le volume restant libre dans le rservoir si la valeur est positive, et la quantit de carburant exdentaire dans le cas contraire. De cette faon, si nous modifions la classe pour crer une version double rservoir, il nous suffira de modifier les mthodes pour en tenir compte. Du point de vue de l'utilisateur, rien n'aura chang. Cette possibilit de cacher la mcanique interne est appele encapsulation. Dans d'autres langages, l'ensemble des lments accessibles l'utilisateur est parfois appel interface. Cependant, en Java, ce terme signifie tout fait autre chose. Les mthodes qui permettent de lire les donnes contenues dans les objets sont appeles accesseurs (accessors), alors que celles qui permettent de les

CHAPITRE 8 LACCESSIBILIT

299

modifier sont appeles mutateurs (mutators). Une mthode peut, bien sr, modifier et lire des donnes, mais cela est normalement dconseill si votre classe doit tre utilise par d'autres programmeurs. L'usage est mme de faire commencer le nom des accesseurs par get et celui des mutateurs par set. Bien sr, avec des noms de mthodes en franais, cela peut paratre bizarre. Ainsi, dans l'exemple prcdent, la mthode (statique) permettant de connatre la capacit du rservoir serait nomme getCapacit(), celle permettant de savoir combien d'essence il reste getCarburant() et celle permettant de faire le plein setCarburant(). Normalement, cette dernire mthode ne devrait pas renvoyer une valeur mais, en cas de dbordement, placer la quantit excdentaire dans une variable et renvoyer une condition d'erreur au moyen d'un mcanisme que nous n'avons pas encore tudi. crire des applications en Java consiste crer des classes (au moins une). Une classe peut contenir des primitives, des blocs d'instructions, des constructeurs, des mthodes, des objets (nous avons dj utilis toutes ces possibilits), ainsi que d'autres classes et d'autres lments tels que des interfaces. (Les interfaces seront prsentes plus loin. Les classes incluses dans d'autres classes feront l'objet d'un prochain chapitre.) Lorsque nous crons un objet en instanciant une classe, ou lorsque nous utilisons un membre statique d'une autre classe (mthode ou variable), nous devons disposer d'un moyen d'y faire rfrence. Jusqu'ici, nous avons utilis sans trop y faire attention deux faons d'accder des classes. La premire faon consistait crer dans notre application la classe principale (celle comportant la mthode main) ainsi que les autres classes dont nous avions besoin. Par exemple, au Chapitre 3, nous avons cr le programme suivant :

public class Test { public static void main(String[] argv) { new Employe(); } } class Employe { int matricule; static int nombre;

300
Employe() { matricule = ++nombre; afficherMatricule(); } void afficherMatricule() { System.out.println(matricule); } }

LE DVELOPPEUR JAVA 2

(Notez que nous avons ici ajout une ligne.) Ce programme, stock dans le fichier Test.java, contient la dfinition de deux classes : Test (la classe principale contenant la mthode main) et Employe. Lorsque ce programme a t compil, le compilateur a cr deux fichiers dans le dossier o se trouvait le fichier source Test.java : Test.class et Employe.class. Pour utiliser la classe Employe afin d'en crer une instance (ce qui est fait dans la mthode main de la classe Test) l'aide de l'instruction :

new Employe();

tait-il ncessaire que la classe Employe soit dfinie dans le mme fichier que la classe Test ? Oui et non. Non, car le fichier Employe.class aurait trs bien pu tre cr la suite de la compilation d'un fichier source indpendant, Employe.java. Cependant, dans ce cas, la mthode main de la classe Test aurait d disposer de deux lments pour l'utiliser :

Son adresse, c'est--dire l'endroit o se trouve la classe, sur le disque


dur ou sur un serveur reli un rseau.

Une autorisation d'accs.


Placer le code source de la classe Employe dans le mme fichier que le code source de la classe qui l'utilise (Test) revient :

CHAPITRE 8 LACCESSIBILIT

301

Crer le fichier Employe.class dans le mme dossier que le fichier


Test.class

Donner la classe Test l'autorisation d'utiliser la classe Employe.


Si vous compilez sparment les deux classes, vous pourrez facilement remplir la premire condition. En revanche, la deuxime ne sera pas remplie par dfaut. Il vous faudra donc utiliser un modificateur d'accs pour donner explicitement une autorisation. Une troisime condition doit tre remplie pour que la classe Test puisse utiliser la classe Employe : il faut que le type d'utilisation souhait (ici l'instanciation) soit possible. Pour rsumer, les trois critres permettant d'utiliser une classe sont Qui, Quoi, O. Il faut donc :

Que l'utilisateur soit autoris (Qui). Que le type d'utilisation souhait soit autoris (Quoi). Que l'adresse de la classe soit connue (O).
Nous commencerons notre tude par le troisime critre : O.

Les packages (O)


Dans l'exemple de programme prcdent, la mthode afficherMatricule utilise une classe appele System :
void afficherMatricule() { System.out.println(matricule); }

Cette classe n'est pas instancie. Il n'y a donc pas de tentative de cration d'un nouvel objet instance de la classe System. En revanche, la deuxime ligne du code ci-dessus invoque la mthode println du membre out de la

302

LE DVELOPPEUR JAVA 2
classe System, ce que nous pouvons vrifier en consultant la documentation de Java comme nous avons appris le faire au Chapitre 2. La figure cidessous montre la page correspondant cette classe.

Figure 8.1 : La documentation de la classe System.

CHAPITRE 8 LACCESSIBILIT

303

La documentation nous informe galement de l'endroit o se trouve cette classe : le chemin d'accs complet la classe S y s t e m est java.lang.System . Comme nous l'avons dj dit au Chapitre 2, java et lang sont des dossiers. java.lang.System est donc un chemin d'accs, qui prsente la particularit, par rapport aux chemins d'accs de Windows, d'utiliser le point comme sparateur de niveaux au lieu de la barre oblique inverse. En effet, les sparateurs de niveaux varient selon les systmes (\ sous Windows, / sous Unix) et peuvent mme parfois tre configurs au gr de l'utilisateur. Pour que les programmes Java puissent tre excuts sur toutes les plates-formes sans modification, Java utilise son propre sparateur et effectue automatiquement la conversion lorsque c'est ncessaire. L'utilisation du point n'entre pas en conflit avec celle qui est faite par Windows car les noms de classes ne comportent pas d'extension en Java. (En revanche, les fichiers Windows qui les contiennent portent l'extension .class.) Le dossier qui contient des classes est appel package. La classe System fait donc partie du package java.lang. Les chemins d'accs Windows commencent au poste de travail, mais il est possible de n'en spcifier qu'une partie. On parle alors de chemin d'accs relatif. Si vous vous trouvez dans le rpertoire c:\programmation\java, et que vous vouliez accder au fichier c:\programmation\java\util\dates\vacances.java, vous pouvez le faire en indiquant simplement util\dates\vacances.java. Windows reconstitue le chemin d'accs complet en mettant bout bout le chemin d'accs courant (c:\programmation\java) et le chemin d'accs relatif (util\dates\vacances.java). De plus, vous pouvez galement spcifier des chemins d'accs par dfaut, grce la commande path, gnralement utilise au dmarrage d'une session DOS. (Voir Chapitre 1.) Ainsi, si vous avez utilis la commande :

path c:\windows;c:\windows\system;c:\jdk1.2\bin

et si vous vous trouvez dans le rpertoire c:\programmation\java, Windows cherchera le fichier util\dates\vacances.java en utilisant successivement les chemins d'accs suivants :

304

LE DVELOPPEUR JAVA 2
c:\programmation\java\util\dates\vacances.java c:\windows\util\dates\vacances.java c:\windows\system\util\dates\vacances.java c:\jdk1.2\bin\util\dates\vacances.java

Java dispose d'un mcanisme quivalent pour la recherche des classes. Java recherche les classes en priorit :

Dans le rpertoire courant, c'est--dire celui o se trouve la classe


appelante, si la variable d'environnement classpath n'est pas dfinie ; si celle-ci est dfinie.

Dans les chemins spcifis par la variable d'environnement classpath


Sous Windows, la variable d'environnement classpath peut tre dfinie l'aide de la commande :
set classpath=.;c:\programmation\java;c:\appli\java\util

Sous Unix, la syntaxe utiliser est :


setenv classpath .:/home/programmation/java;/home/appli/java/util

Les diffrents chemins d'accs sont spars par le caractre : sous Unix et ; sous Windows. Notez l'utilisation du point comme premier chemin d'accs. Le point dsigne le rpertoire courant. En effet, il faut se souvenir que le rpertoire courant est utilis par dfaut uniquement si la variable classpath n'est pas dfinie.

Note : Si vous souhaitez supprimer toute dfinition de la variable d'environnement, utilisez la syntaxe :
set classpath=

CHAPITRE 8 LACCESSIBILIT

305

Chemins d'accs et packages


Tout cela signifie-t-il que package = chemin d'accs ? Pas du tout ! Un package est une unit logique regroupant un certain nombre de classes, au gr de leur concepteur. (Les raisons qui conduisent placer des classes dans un package commun seront abordes dans une prochaine section.) Une classe peut tre dfinie sans indiquer quel package elle appartient. Elle est alors considre comme faisant partie du package par dfaut. Celui-ci ne correspond aucun rpertoire particulier. Ses classes peuvent tre places n'importe o, du moment que le chemin d'accs par dfaut ou un de ceux dfinis par la variable d'environnement classpath permet de les atteindre. On voit donc que cette variable d'environnement spcifie un chemin d'accs aux fichiers contenant les classes, et non les packages les contenant. La syntaxe est similaire, mais les deux concepts sont lgrement diffrents.

L'instruction package
Si vous souhaitez qu'une classe que vous avez cre appartienne un package particulier, vous devez le spcifier explicitement au moyen de l'instruction package, suivie du nom du package. Cette instruction doit tre la premire du fichier. Elle concerne toutes les classes dfinies dans ce fichier. L'indication du package ne suffit pas. En effet, les fichiers .class rsultant de la compilation ne sont pas placs automatiquement dans le rpertoire correspondant. En revanche, leur appartenance un package est code dans le fichier compil, qui ne peut plus tre utilis autrement que depuis le chemin d'accs correspondant. En d'autres termes, si vous compilez les deux fichiers suivants :
package monpackage; public class Employe { int matricule; static int nombre; Employe() { matricule = ++nombre; afficherMatricule(); }

306
void afficherMatricule() { System.out.println(matricule); } }

LE DVELOPPEUR JAVA 2

et :
package monpackage; public class Test { public static void main(String[] argv) { new monpackage.Employe(); } }

tout se passera bien pour le premier, mais vous obtiendrez un message d'erreur la compilation du second :
Test.java:4: cannot resolve symbol symbol : class Employe location: package monpackage new monpackage.Employe(); ^ 1 error

Cela est tout simplement d au fait que, la classe Employe tant dclare appartenir au package monpackage, le fichier Employe.class doit se trouver dans le rpertoire correspondant, alors qu'il se trouve pour l'instant dans le rpertoire courant, tout comme le fichier Test.class. Pour que la classe Test puisse tre compile, il suffit de dplacer le fichier Employe.class dans un rpertoire appel monpackage, que vous devez crer pour l'occasion. O a ? Dans un des rpertoires accessibles par dfaut, c'est--dire dans le rpertoire courant si la variable d'environnement n'est pas dfinie, ou dans un des rpertoires dfinis par cette variable dans le cas contraire.

CHAPITRE 8 LACCESSIBILIT
Note 1 : Dans cet exemple, nous avons inclus la ligne :
package monpackage;

307

au dbut du fichier Test.java. Cette instruction est obligatoire pour une raison que nous tudierons dans une prochaine section, mais qui n'a rien voir, cette fois, avec l'emplacement du fichier rsultant Test.class.

Note 2 : Contrairement ce qui se passait lorsque les deux classes


taient dfinies dans le mme fichier, o l'ordre d'apparition des classes tait indiffrent, il faut ici compiler d'abord la classe Employe puis la classe Test. Si vous procdez en ordre inverse, vous risquez deux types de problmes :

S'il s'agit de la premire compilation, vous obtiendrez un message d'erreur car le compilateur ne pourra trouver la classe Employe dont il a besoin.

Si la classe Employe a dj t compile et place dans le rpertoire

correct, le compilateur utilisera la version existante. Cela peut ne poser aucun problme si la classe Test n'a pas t modifie. Dans le cas contraire, une erreur peut se produire. Pour lviter, le compilateur recompile automatiquement toutes les classes rfrences dans le ou les fichiers sources indiqus sur la ligne de commande et qui ont t modifies depuis leur dernire compilation. (Il est possible de compiler plusieurs fichiers en mme temps en plaant leurs noms les uns la suite des autres la fin de la ligne de commande.) Si les classes recompiles de cette faon font elles-mmes appel d'autres classes, celles-ci sont vrifies automatiquement. (La vrification est rcursive.) La vrification est effectue en tenant compte de la date de modification des fichiers. Toutefois, cette mthode nest pas absolument sre. Il est possible de demander une vrification plus pousse en utilisant loption -Xdepend :

javac -Xdepend Test.java

308

LE DVELOPPEUR JAVA 2
Si le fichier source d'une classe n'est pas trouv, la vrification n'est pas effectue. Le compilateur ne produit dans ce cas aucun message d'erreur ou d'avertissement.

Placement automatique des fichiers .class dans les rpertoires correspondant aux packages
Plutt que de dplacer manuellement les fichiers .class, vous pouvez demander au compilateur de le faire pour vous, grce l'option de compilation -d rpertoire. Lorsque cette option est utilise, le compilateur place les fichiers .class sans indication de package dans le rpertoire indiqu, et cre automatiquement les sous-rpertoires ncessaires pour stocker les classes appartenant un package. Pour indiquer le rpertoire courant, vous devez taper un point. Par exemple :

javac -d . Test.java

compile le fichier Test.java ainsi que le fichier Employe.java si celui-ci porte une date de dernire modification postrieure la date de cration du fichier Employe.class, et place les fichiers Test.class et ventuellement Employe.class dans le sous-rpertoire monpackage du rpertoire courant. Si ce sous-rpertoire n'existe pas, il est automatiquement cr.

Note 1 : L'ordre des options du compilateur n'a pas d'importance, mais


les noms des fichiers compiler doivent tre placs en dernier.

Note 2 : Le compilateur cherche le fichier

dans le sousrpertoire monpackage afin de dterminer s'il est ou non jour. Il ne tient pas compte d'un ventuel fichier Employe.class se trouvant ailleurs (dans le rpertoire courant, par exemple).

Employe.class

doivent se trouver dans le mme rpertoire que les fichiers .class, ce qui rduit beaucoup l'intrt de l'option -d puisque le compilateur place par dfaut les fichiers .class dans le mme rpertoire que les fichiers sources.

Note 3 : Pour vrifier si les classes utilises sont jour, les fichiers sources

CHAPITRE 8 LACCESSIBILIT

309

Excution du programme
Le programme Test se trouvant dans le package monpackage, il ne peut plus tre excut de la manire habituelle. En effet, nous avons vu que le fichier .class correspondant doit se trouver dans un sous-rpertoire portant le mme nom que le package. Si nous nous plaons dans le sous-rpertoire monpackage et essayons dexcuter le programme Test laide de la commande :
java Test

nous obtenons le message derreur suivant :


C:\Java\monpackage>java Test Exception in thread "main" java.lang.NoClassDefFoundError: Test (wrong name: monpackage/Test) at java.lang.ClassLoader.defineClass0(Native Method) at java.lang.ClassLoader.defineClass(Unknown Source) at java.security.SecureClassLoader.defineClass(Unknown Source) at java.net.URLClassLoader.defineClass(Unknown Source) at java.net.URLClassLoader.access$100(Unknown Source) at java.net.URLClassLoader$1.run(Unknown Source) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(Unknown Source) at java.lang.ClassLoader.loadClass(Unknown Source) at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source) at java.lang.ClassLoader.loadClass(Unknown Source) at java.lang.ClassLoader.loadClassInternal(Unknown Source)

Ce message nous indique tout simplement que le fichier Test.class contient une classe qui ne porte pas le nom correct. Il sagit en effet du nom complet (fully qualified name). Le nom donn pour le fichier est Test alors que le nom complet est monpackage.Test. Ds lors, la procdure suivre pour excuter le programme est vidente. Il suffit de se placer dans le rpertoire parent du rpertoire monpackage et de taper la commande :
java monpackage.Test

310

LE DVELOPPEUR JAVA 2

Chemin d'accs par dfaut pour les packages


Par dfaut, c'est--dire en l'absence de toute dfinition de la variable classpath, deux chemins d'accs sont utiliss pour rechercher les packages :

Le rpertoire courant (pour toutes les classes). Le chemin d'accs des classes standard Java.
Il est important de noter que les classes sont recherches en utilisant ces deux possibilits dans l'ordre ci-dessus. Il est donc possible d'intercepter les appels des classes Java en crant des classes de mme nom. Par exemple, si vous crez une classe System contenant un objet out possdant une mthode println() et si vous placez cette classe dans le rpertoire courant, l'instruction :
System.out.println("message");

fera rfrence votre classe et non la classe standard. En revanche, vous pourrez toujours faire rfrence la classe standard en utilisant l'instruction :
java.lang.System.out.println("message");

(sauf si vous avez dfini votre classe System dans un package java.lang que vous avez cr dans le rpertoire courant, mais l, vous cherchez vraiment les ennuis !) Si la variable d'environnement classpath est dfinie, Java utilise les chemins d'accs suivants pour trouver les packages :

Le(s) rpertoire(s) indiqu(s) dans la variable (pour toutes les classes). Le chemin d'accs des classes standard Java.
Il est donc l aussi possible d'intercepter les appels aux classes standard Java. Par ailleurs, notez que le rpertoire courant ne figure plus implicite-

CHAPITRE 8 LACCESSIBILIT

311

ment dans la liste des chemins d'accs utiliss. Il est donc d'usage de commencer la dfinition de classpath par une rfrence au rpertoire courant :
set classpath=.;<chemin d'accs>;<chemin d'accs>...

L'instruction import
Dans un programme, il peut tre pnible de systmatiquement faire rfrence une classe en indiquant explicitement le package auquel elle appartient. Ainsi, si votre programme cre une instance de la classe Button, vous devez utiliser la syntaxe :

java.awt.Button bouton = new java.awt.Button();

ce qui est long et fastidieux. Une autre faon de procder consiste rendre la classe Button disponible dans votre programme au moyen de l'instruction :
import java.awt.Button;

Cette instruction n'a pas pour effet de charger la classe Button, mais simplement de la rendre disponible de faon plus simple. Vous pouvez alors y faire rfrence sous la forme :

Button bouton = new Button();

Si vous voulez importer toutes les classes d'un package, vous pouvez utiliser la syntaxe :
import java.awt.*;

312

LE DVELOPPEUR JAVA 2
(Encore une fois, importer a ici un sens virtuel. Rien n'est rellement import par cette instruction. Vous pouvez utiliser autant d'instructions import que vous le souhaitez sans diminuer en quoi que ce soit les performances de votre programme, ni augmenter sa taille.) Ainsi, toutes les classes du package java.awt seront accessibles sans qu'il soit besoin de spcifier leur chemin d'accs. En revanche, vous ne pouvez pas importer plusieurs packages la fois. L'instruction :
import java.awt.*;

fait rfrence toutes les classes du package java.awt et non tous les packages dont le nom commence par java.awt. Une confusion est toutefois possible car il existe onze packages dont le nom commence par java.awt parmi les packages standard de Java. Il est difficile de comprendre pourquoi les concepteurs de Java ont choisi d'entretenir cette confusion. Rappelons donc la signification des rfrences suivantes :
java.awt.Button

Ici, le nom le plus droite est un nom de classe, ce qui est reconnaissable au fait qu'il commence par une majuscule. (Ce n'est toutefois qu'une convention, et non une obligation.) Tout ce qui prcde le nom de classe est donc un nom de package : il s'agit du package java.awt.
java.awt.event.ActionEvent

ActionEvent est un nom de classe. (Il commence par une majuscule.) Le nom du package est donc ici java.awt.event.

java.awt.*

L'astrisque ne peut reprsenter que des noms de classes. Ce qui prcde est donc un nom de package. Il s'agit ici du package java.awt.

CHAPITRE 8 LACCESSIBILIT

313

Il est parfaitement possible que deux packages comportent des classes de mme nom. Dans ce cas, vous ne pouvez importer qu'une seule de ces classes. Si vous importez explicitement les deux classes, par exemple :

import monpackage.Test; import ancienpackage.Test;

le compilateur produit un message d'erreur. En revanche, il est possible d'importer implicitement les deux classes. Dans ce cas, le rsultat varie selon les conditions. Dans le cas le plus gnral :

import monpackage.*; import ancienpackage.*;

le compilateur ne produit aucun message d'erreur tant que votre programme n'utilise pas une classe existant dans les deux packages. Dans le cas contraire, il produit un message d'erreur peu explicite :

Class Test not found in type declaration.

qui pourrait faire croire que la classe n'a pas t trouve, alors que le problme vient au contraire de ce que le compilateur en a trouv deux. Dans le cas o une des deux importations est explicite, celle-ci l'emporte :

import monpackage.Test; import ancienpackage.*;

Ici, la classe Test du package monpackage sera utilise car elle est importe explicitement, contrairement la classe Test du package ancienpackage, qui est importe implicitement l'aide du caractre gnrique *.

314

LE DVELOPPEUR JAVA 2
Attention : D'autres situations d'importation implicite peuvent se produire, causant des erreurs dont l'origine n'est pas toujours vidente. Ainsi, si le rpertoire courant contient un fichier Test.class, nous nous trouvons en situation d'importation implicite de cette classe. Aussi, si notre programme commence par :

import monpackage.Test;

la classe Test du package monpackage est utilise car l'importation explicite l'emporte. En revanche, si nous utilisons l'instruction :

import monpackage.*;

il s'agit d'une importation implicite. Devant les deux possibilits d'importation implicite (la classe Test du rpertoire courant et celle du package monpackage), Java choisit le rpertoire courant. Si les deux versions du fichier Test.class sont diffrentes, cela peut poser des problmes. Le cas est frquent si vous avez d'abord dvelopp une version du programme dans laquelle les diffrentes classes taient dfinies dans le mme fichier. Le compilateur a donc cr un fichier Test.class dans le rpertoire courant, mais vous n'y avez pas prt attention, n'ayant jamais cr de fichier Test.java. Lorsque vous crez ensuite une version de la classe Test dans le package monpackage, vous placez le fichier source dans le rpertoire monpackage, afin que Java puisse le recompiler la demande lors de la compilation du programme principal. Cependant, Java trouve alors le fichier Test.class du rpertoire courant. En l'absence d'un fichier Test.java, il ne peut dterminer si le fichier .class est jour et l'utilise donc tel quel. Une autre situation intrigante peut se produire si vous oubliez que Java recompile automatiquement les classes non jour lorsqu'il en a la possibilit. Si le rpertoire courant contient un fichier Test.java mais pas de fichier Test.class, et si ce fichier contient l'instruction :
package monpackage;

CHAPITRE 8 LACCESSIBILIT

315

ce qui est somme toute assez normal, Java le recompile automatiquement. S'il se trouve que vous n'avez pas utilis l'option -d du compilateur (puisque vous tes en train de compiler le programme principal), le fichier Test.class est cr dans le rpertoire courant. C'est donc lui qui est utilis en priorit. Si le fichier Test.java n'tait pas jour, le fonctionnement du programme ne sera pas correct. Trouver l'origine de l'erreur ne sera pas toujours vident ! En conclusion, si votre programme ne semble pas fonctionner correctement, et si vous souponnez qu'une ancienne version d'une classe est utilise, ou si vous obtenez un message d'erreur du type :

Error: File: xxxxx does not contain type yyyyy as expected

ou :
Class zzzz not found in type declaration

vrifiez les importations implicites et explicites. En particulier, dans le premier message, xxxxx est probablement le nom du fichier (.class ou .java) non jour se trouvant dans le rpertoire courant.

Packages accessibles par dfaut


Deux packages sont accessibles par dfaut. Ce sont, par ordre de priorit :

Le package par dfaut, qui contient toutes les classes pour lesquelles aucun package n'a t indiqu, et qui se trouve dans un des rpertoires accessibles par dfaut. Le package java.lang, qui contient un certain nombre de classes d'usage gnral comme System, que nous avons dj utilise frquemment, ou Math, qui contient des mthodes permettant d'effectuer des calculs mathmatiques.

316

LE DVELOPPEUR JAVA 2
Attention : Nous avons vu que la spcification d'un chemin d'accs

pour les classes l'aide de la variable d'environnement classpath rend le rpertoire courant inaccessible par dfaut (obligeant ajouter explicitement une rfrence ce rpertoire dans la variable). En revanche, le chemin d'accs au package java.lang, ainsi qu'aux autres packages standard de Java n'est pas affect par la dfinition de classpath. Il est galement possible d'indiquer un chemin d'accs pour les classes au moyen de l'option -classpath du compilateur. Mais, dans ce cas, tous les chemins d'accs par dfaut sont supprims, y compris ceux des packages standard. Vous devez donc indiquer explicitement en paramtre de l'option -classpath tous les chemins d'accs que vous souhaitez utiliser, plus le chemin d'accs au rpertoire courant si ncessaire, plus le chemin d'accs aux packages standard (en les sparant l'aide de points-virgules). Pour cela, il faut connatre le chemin d'accs aux packages standard, ce qui ncessite d'aborder le cas particulier des fichiers .jar.

Les fichiers .jar


Les fichiers .jar sont des fichiers compresss, comme les fichiers .zip, selon un algorithme particulier devenu un standard de fait dans le monde des PC. Ils sont parfois appels fichiers d'archives ou, plus simplement, archives. Ces fichiers sont produits par des outils de compression tels que Pkzip (sous DOS) ou Winzip (sous Windows), ou encore par jar.exe. Les fichiers .jar peuvent contenir une multitude de fichiers compresss avec l'indication de leur chemin d'accs. Par exemple, si vous placez vos fichiers dans un rpertoire programmes contenant les sous-rpertoires appli et util, vous pouvez recrer la mme arborescence l'intrieur d'un fichier .zip ou .jar. Vous pouvez donc spcifier un chemin d'accs un fichier compress en indiquant le chemin d'accs au fichier .jar suivi du chemin d'accs au fichier compress l'intrieur de l'archive. Les packages standard de Java sont organiss de cette manire, dans un fichier nomm rt.jar plac dans le sous-rpertoire lib du rpertoire o est install le J2SDK. Dans le cas d'une installation standard de Java 2 sur le disque C:, le chemin d'accs complet la classe System est donc :
c:\jdk1.3\jre\lib\rt.jar\java\lang\System

CHAPITRE 8 LACCESSIBILIT

317

(Ce chemin d'accs est thorique, car le systme d'exploitation est incapable de l'utiliser.) La rfrence la classe System peut tre limite :
java.lang.System

parce que Java connat le chemin d'accs par dfaut aux packages standard.

Cration de vos propres fichiers .jar ou .zip


Vous pouvez, vous aussi, placer vos packages dans des fichiers .jar ou .zip. Le principal intrt n'est pas de gagner de l'espace, mais d'amliorer la transportabilit ( ne pas confondre avec la portabilit !) de vos packages. Il est plus simple de n'avoir qu'un seul fichier transporter ou archiver. De plus, il est beaucoup plus rare d'effacer, de dplacer ou de remplacer par erreur un fichier dans une archive que dans un rpertoire. La maintenance des versions successives de votre package est ainsi facilite. Pour crer un fichier .zip incluant l'arborescence des rpertoires l'aide de Winzip, il suffit d'ouvrir cette application, et de faire glisser un un les rpertoires contenant vos packages dans la fentre affiche. Lorsque vous faites glisser le premier rpertoire, Winzip affiche une fentre pour vous demander le nom du fichier .zip crer. La bote de dialogue contient galement deux options importantes :

Recurse folders permet d'inclure dans l'archive les sous-rpertoires ventuels de celui que vous faites glisser.

Save extra folder info permet d'ajouter les fichiers l'archive en incluant l'arborescence des rpertoires. La premire option doit tre active. Dans le cas contraire, seuls les fichiers du premier niveau sont archivs. Pour la deuxime option, c'est un peu plus complexe. Cette option ajoute le chemin d'accs depuis la racine du disque dur. Si le rpertoire contenant vos packages se trouve dans la racine du disque dur, elle doit donc tre active. En revanche, si, comme c'est

318

LE DVELOPPEUR JAVA 2
souvent le cas, il se trouve plus profond dans l'arborescence de votre disque, vous devez :

Ne pas activer cette option. Archiver le niveau suprieur celui de vos packages. En effet, le premier niveau d'arborescence n'est pas conserv. Exemples :

1. Vous avez cr trois packages package1, package2 et package3 correspondant aux rpertoires c:\package1, c:\package2 et c:\package3. Vous devez alors activer l'option Save extra folder info et faire glisser les trois rpertoires dans la fentre de Winzip.

2. Vous avez cr trois packages package1, package2 et package3 corres-

pondant aux rpertoires c:\programmes\util\package1, c:\programmes\util\package2 et c:\programmes\util\package3. Vous devez alors dsactiver l'option Save extra folder info et faire glisser le rpertoire c:\programmes\util dans la fentre de Winzip. Bien sr, ce rpertoire ne doit rien contenir d'autre que vos packages. (Bien que cela ne pose pas de problme que le fichier .zip contienne galement les photos de la communion de votre petit-neveu.)

et package3 (c:\programmes\util tant le chemin d'accs). Si vous avez cr les packages util.package1, util.package2 et util.package3, c'est le rpertoire c:\programmes qu'il faut faire glisser dans la fentre de Winzip (et qui ne doit rien contenir d'autre que vos packages).

Note 1 : La procdure ci-dessus n'est valable que si vos packages sont


package1, package2

Note 2 : L'archivage utilisant le format .jar sera dcrit au Chapitre 20.

Comment nommer vos packages ?


Vous pouvez nommer vos packages comme vous le souhaitez tant que vous en tes le seul utilisateur. En revanche, si vous concevez des packages pour d'autres programmeurs, vous devez choisir des noms en vous assurant qu'ils n'entreront jamais en conflit avec ceux des autres packages qu'ils

CHAPITRE 8 LACCESSIBILIT

319

seraient susceptibles d'utiliser. Si tous vos clients sont des membres de votre entreprise, vous pouvez peut-tre contrler les packages qu'ils utilisent ; mais si vous destinez votre production une diffusion externe, il faut respecter une convention permettant d'assurer l'unicit des noms de packages. Cette convention consiste faire commencer les noms de vos packages par votre nom de domaine Internet en commenant par la fin. Ainsi, si votre domaine Internet est volga.fr, tous vos packages auront un nom commenant par fr.volga. La suite du nom est libre. Si vous souhaitez personnaliser vos packages, il vous suffit d'ajouter le pseudonyme que vous utilisez pour votre adresse Email. Si votre adresse est anatole@volga.fr, tous vos packages commenceront par fr.volga.anatole. Comme vous tes assur que votre adresse Email est unique au monde, vous tes sr du mme coup que vos packages pourront tre utiliss partout sans conflit.

Note : Si vous n'avez pas de domaine Internet, vous pouvez utiliser tout de mme cette convention. (Il suffit d'une adresse Email.) Cependant, il n'est pas sr que des noms de packages commenant par fr.wanadoo.marcel.durand. soient du meilleur effet ! (Si vos packages sont destins une diffusion publique, il n'est mme pas certain que votre fournisseur d'accs Internet voie d'un trs bon il l'utilisation de son nom, mais normalement, vous devriez dans ce cas avoir votre propre domaine.)

Ce qui peut tre fait (Quoi)


Nous avons maintenant fait le tour de la question O ? Pour qu'une classe puisse tre utilise (directement ou par l'intermdiaire d'un de ses membres), il faut non seulement tre capable de la trouver, mais aussi qu'elle soit adapte l'usage que l'on veut en faire. Une classe peut servir plusieurs choses :

Crer des objets, en tant instancie. Crer de nouvelles classes, en tant tendue. On peut utiliser directement ses membres statiques (sans qu'elle soit
instancie.)

320

LE DVELOPPEUR JAVA 2

On peut utiliser les membres de ses instances.


(On peut trouver d'autres utilisations, mais nous nous contenterons de celles-l pour l'instant). Les classes peuvent contenir des primitives, des objets, des classes, des mthodes, des constructeurs, et d'autres choses encore que nous n'avons pas tudies, comme les finaliseurs. Java permet d'imposer tous ces lments des restrictions d'utilisation. Il ne s'agit pas proprement parler d'un problme d'accs, mais, pour l'utilisateur, le rsultat est du mme ordre. C'est pourquoi nous traitons cet aspect dans le chapitre consacr l'accessibilit. Java dispose de mots cls permettant de modifier la faon dont les lments peuvent tre utiliss.

static
Un lment dclar static appartient une classe et non ses instances. Dans l'exemple cit au dbut de ce chapitre, la primitive capacit aurait t dclare static. Les objets instancis partir d'une classe ne possdent pas les lments de cette classe qui ont t dclars static. Un seul lment existe pour la classe et il est partag par toutes les instances. Cependant, cela ne limite pas l'accessibilit, mais conditionne simplement le rsultat obtenu lors des accs.

Les variables static


Si la classe Voiture contient la primitive statique capacit et la primitive non statique carburant, et si nous instancions cette classe pour crer l'objet maVoiture, cet objet possde sa propre variable carburant, laquelle il est possible d'accder grce la syntaxe :

maVoiture.carburant

CHAPITRE 8 LACCESSIBILIT

321

(Nous verrons que ce type d'accs est le plus souvent rendu impossible par une modification des droits d'accs. Considrons pour l'instant qu'il n'existe aucune restriction de droit.) L'objet maVoiture ne possde pas de variable capacit. Normalement, caappartient la classe Voiture, et il est possible d'y accder en utilisant la syntaxe :
pacit

Voiture.capacit

Cependant, Java nous permet galement d'utiliser la syntaxe :

maVoiture.capacit

Il faut bien comprendre que les deux expressions font rfrence la mme variable. Ainsi le code suivant :
maVoiture.capacit = 120; System.out.println(Voiture.capacit);

affichera 120, valeur qui vient d'tre affecte la variable statique en y accdant l'aide du nom de l'instance. (Encore une fois, ce type de manipulation est normalement impossible dans un programme correctement crit, l'impossibilit ne venant pas de Java, mais des droits d'accs.) En revanche, il sera impossible d'utiliser la rfrence suivante :
Voiture.carburant

En effet, la quantit de carburant prsente dans le rservoir est une caractristique d'une instance et n'a aucun sens applique la classe. Une tentative d'utilisation de cette rfrence produit le message d'erreur :

322

LE DVELOPPEUR JAVA 2
Can't make a static reference to nonstatic variable carburant in class Voiture

Si la classe Voiture n'a pas t instancie, ses variables statiques ne sont videmment accessibles qu'avec le nom de la classe. Il existe cependant un cas dans lequel il est possible de faire rfrence une variable static sans utiliser le nom de la classe ni le nom d'une instance : lors de la dfinition mme de la classe. (Aucun nom d'instance ne peut videmment tre employ ce moment.) Considrez l'exemple suivant :
public class Automobile { public static void main(String[] argv) { Voiture maVoiture = new Voiture(80); System.out.println (Voiture.capacit); } } class Voiture { static int capacit; int carburant = 0; Voiture (int c) { capacit = c; } }

Dans le constructeur de la classe Voiture, nous utilisons la rfrence capacit sans indiquer le nom de la classe. Cet exemple peut paratre trange car la variable statique capacit est susceptible d'tre modifie chaque fois qu'un objet est instanci. Cela ne pose aucun problme. Tel que l'exemple est crit, elle peut mme tre modifie tout moment en lui affectant une valeur quelconque. Ainsi, si nous ajoutons la ligne :
public class Automobile { public static void main(String[] argv) {

CHAPITRE 8 LACCESSIBILIT
Voiture maVoiture = new Voiture(80); maVoiture.capacit = 90; System.out.println (Voiture.capacit);

323

le programme affichera 90 au lieu de 80. Ce qu'il faut comprendre, c'est que capacit sera modifie mme pour les instances cres avant, puisqu'il n'en existe qu'un seul exemplaire partag par toutes les instances. (Cet exemple est un peu tir par les cheveux, mais ce n'est qu'un exemple !)

Note : N'oubliez pas que Java initialise automatiquement les variables membres. Nous y reviendrons dans trs peu de temps.
Les primitives ne sont pas les seuls lments pouvoir tre dclars statiques. Il en est exactement de mme pour les objets et les mthodes.

Les mthodes static


Les mthodes peuvent galement tre statiques. Supposons que nous souhaitions crire un accesseur pour la variable capacit. Nous pouvons le faire de la faon suivante :
public class Automobile { public static void main(String[] argv) { Voiture maVoiture = new Voiture(80); System.out.println(maVoiture.getCapacit()); } } class Voiture { static int capacit; int carburant = 0; Voiture (int c) { capacit = c; }

324
static int getCapacit() { return capacit; } }

LE DVELOPPEUR JAVA 2

La mthode getCapacit() peut tre dclare static car elle ne fait rfrence qu' des membres static (en l'occurrence la variable capacit). Ce n'est pas une obligation. Le programme fonctionne aussi si la mthode n'est pas dclare static. Comme dans le cas des variables, les mthodes static peuvent tre rfrences l'aide du nom de la classe ou du nom de l'instance. On peut utiliser le nom de la mthode seul, uniquement dans la dfinition de la classe. Il est important de noter que les mthodes static ne peuvent normalement pas faire rfrence aux mthodes ou aux variables non static de la classe car aucune rfrence implicite une instance ne leur est passe. (La rfrence this ne peut pas tre employe dans une mthode static .) La seule faon de contourner cette limitation consiste passer explicitement la rfrence linstance lors de lappel de la mthode. Mais dans ce cas, la mthode doit avoir t dfinie avec le paramtre correspondant.

Initialisation des variables static


Nous savons dj que les variables non static sont initialises lorsqu'une instance est cre. Qu'en est-il des variables static ? Elles sont tout simplement initialises lorsque c'est ncessaire, la premire fois que la classe est charge. Si nous reprenons l'exemple prcdent en enlevant la ligne contenant la cration de l'instance maVoiture, que se passe-t-il ?
public class Automobile { public static void main(String[] argv) { System.out.println (maVoiture.getCapacit()); . .

CHAPITRE 8 LACCESSIBILIT

325

Le compilateur indique une erreur car l'appel de la mthode getCapacit() est ralis travers une instance qui n'existe pas. En revanche, il est toujours possible d'y faire rfrence au moyen du nom de la classe :
System.out.println(Voiture.getCapacit());

Dans ce cas, la mthode getCapacit() retourne 0. En effet, la variable static capacit est initialise ds la premire rfrence la classe. La dfinition de la classe ne comporte pas d'initialisation de la variable. Java l'initialise automatiquement 0 car il s'agit d'une variable membre de la classe. Comment faire si nous voulons initialiser nous-mmes la variable ? Pour l'initialiser l'aide d'une valeur littrale, il suffit de le faire au moment de la dclaration :
static int capacit = 80;

En revanche, si l'initialisation est plus complexe, nous pouvons la mener bien dans le constructeur, mais cela prsente deux inconvnients :

L'initialisation n'aura pas lieu avant qu'une instance soit cre. Elle aura lieu de nouveau chaque fois qu'une instance sera cre.
Pour pallier ces inconvnients, nous devons nous souvenir des initialiseurs d'instances que nous avons dcrits au Chapitre 5. Les initialiseurs d'instances sont des blocs de codes qui sont excuts chaque fois qu'une instance est cre. Leur utilit ne paraissait pas vidente alors, tant limite aux classes dpourvues de constructeurs (les classes anonymes, que nous tudierons dans un prochain chapitre) et certains cas particuliers. Tout comme il existe des initialiseurs d'instances, nous disposons galement d'initialiseurs statiques.

Les initialiseurs statiques


Un initialiseur statique est semblable un initialiseur d'instance, mais il est prcd du mot cl static. Par exemple :

326

LE DVELOPPEUR JAVA 2
public class Automobile { public static void main(String[] argv) { System.out.println (Voiture.getCapacit()); Voiture maVoiture = new Voiture(); System.out.println (Voiture.getCapacit()); } } class Voiture { static int capacit; static { capacit = 80; System.out.println ("La variable statique capacit vient d'tre initialise"); } int carburant = 0; Voiture () { } static int getCapacit() { return capacit; } }

Si vous compilez et excutez ce programme, vous obtiendrez l'affichage suivant :


La variable statique capacit vient d'tre initialise 80 80

En revanche, si vous supprimez la ligne imprime en italique, le programme affiche :


La variable statique capacit vient d'tre initialise 80

CHAPITRE 8 LACCESSIBILIT

327

L'initialiseur statique est excut au premier chargement de la classe, que ce soit pour utiliser un membre statique ou pour l'instancier.

Note 1 : Les membres statiques (ici la variable

capacit) doivent tre dclars avant l'initialiseur et non dedans car, sinon, leur porte serait limite l'initialiseur lui-mme.

Note 2 : Vous pouvez placer autant d'initialiseurs statiques que vous le


souhaitez, n'importe quels endroits de la dfinition de la classe. Ils seront tous excuts au premier chargement de celle-ci, dans l'ordre dans lequel ils apparaissent.

final
Nous venons d'tudier les restrictions qui pouvaient tre apportes l'utilisation d'un lment en fonction de son appartenance une instance ou une classe. D'autres caractristiques peuvent influencer les conditions d'accs. De nombreux langages de programmation, par exemple, font la diffrence entre les donnes dont la valeur peut tre modifie (les variables) et celles dont la valeur est fixe (les constantes). Java ne dispose pas de constantes. Ce n'est pas une limitation, car Java dispose d'un mcanisme beaucoup plus puissant qui permet non seulement d'utiliser une forme de constantes, mais galement d'appliquer ce concept d'autres lments comme les mthodes ou les classes.

Les variables final


Une variable dclare final ne peut plus voir sa valeur modifie. Elle remplit alors le rle d'une constante dans d'autres langages. Une variable final est le plus souvent utilise pour encoder des valeurs constantes. Par exemple, si vous voulez utiliser dans votre programme la valeur de Pi, il est peu probable que vous soyez amen modifier cette valeur. Vous pouvez donc l'attribuer une variable dclare final :
final float pi = 3.14;

328

LE DVELOPPEUR JAVA 2
(Ce n'est qu'un exemple. Java met votre disposition une valeur de Pi beaucoup plus prcise que cela !) Dclarer une variable final offre deux avantages. Le premier concerne la scurit. En effet, le compilateur refusera toute affectation ultrieure d'une valeur la variable. De cette faon, toute tentative de modification se traduira par un message d'erreur :
Can't assign a value to a final variable.

De cette faon, si votre programme tente de modifier la valeur de Pi, l'erreur sera dtecte la compilation. Le deuxime avantage concerne l'optimisation du programme. Sachant que la valeur en question ne sera jamais modifie, le compilateur est mme de produire un code plus efficace. En particulier, il peut effectuer lors de la compilation certains calculs qui, avec des variables non statiques, devraient tre effectus au moment de l'excution du programme. Ainsi, lorsque le compilateur rencontre la ligne :
int x = a + 2;

o a est une variable normale, il ne peut effectuer le calcul et doit donc remplacer cette ligne de code source par le code objet quivalent. L'opration sera donc effectue au moment de l'excution du programme. En revanche, si a est une variable final, le compilateur sait que sa valeur ne sera jamais modifie. Il peut donc effectuer immdiatement le calcul et affecter la valeur correcte x.

Les variables final non initialises


Il existe en fait deux types de constantes dans un programme : celles dont la valeur est connue avant l'excution du programme, et celles dont la valeur n'est connue qu'au moment de l'excution. Ces deux types se prtent des optimisations diffrentes. En effet, le calcul au moment de la

CHAPITRE 8 LACCESSIBILIT

329

compilation n'est possible que si la valeur des constantes est connue. Un autre type d'optimisation consiste, par exemple, stocker la valeur dans une zone de la mmoire o elle pourra tre lue plus rapidement, mais o, en revanche, sa modification prendrait plus de temps. Ce type d'optimisation est tout fait possible avec des constantes dont la valeur n'est connue qu'au moment de l'excution. Java permet de simuler ce type de constantes en utilisant des variables final non initialises. Ce type de variable peut tre initialis ultrieurement. Une fois l'initialisation effectue, la valeur ne peut plus tre modifie. Une variable final non initialise est simplement dclare de la faon suivante :
final int a;

videmment, cette variable ne pourra pas tre utilise avant d'tre initialise. Le cas le plus frquent d'utilisation de ce type de variable est celui des paramtres de mthodes. Ceux-ci peuvent tre dclars final afin que le compilateur soit en mesure d'optimiser le code. Pour autant, leur valeur ne peut pas tre connue au moment de la compilation. Vous pouvez cependant parfaitement crire :
int calcul(final int i, final int j) { corps de la mthode... }

Les variables i et j ne seront initialises que lors de l'appel de la mthode. Leur valeur ne pourra pas tre modifie l'intrieur de celle-ci.

Les mthodes final


Les mthodes peuvent galement tre dclares final, ce qui restreint leur accs d'une tout autre faon. En effet, les mthodes final ne peuvent pas tre redfinies dans les classes drives. Vous devez donc utiliser ce mot cl lorsque vous voulez tre sr qu'une mthode d'instance aura bien le fonctionnement dtermin dans la classe parente. (S'il s'agit d'une mthode static, il n'est pas ncessaire de la dclarer final car les mthodes static ne peuvent jamais tre redfinies.)

330

LE DVELOPPEUR JAVA 2
Les mthodes final permettent galement au compilateur d'effectuer certaines optimisations qui acclrent l'excution du code. Pour dclarer une mthode final, il suffit de placer ce mot cl dans sa dclaration, de la faon suivante :

final int calcul(int i, in j) {

Notez que le fait que la mthode soit dclare final n'a rien voir avec le fait que ses arguments le soient ou non. L'intrt des mthodes final est li une caractristique des objets Java que nous n'avons pas encore tudie : le polymorphisme. Les objets Java peuvent tre manipuls en tant que ce qu'ils sont (une instance d'une classe) ou comme s'ils taient une instance d'une classe parente. Ainsi, dans l'exemple suivant :

public class Animaux3 { public static void main(String[] argv) { Animal milou = new Chien(); Animal titi = new Canari(); milou.crier(); titi.crier(); } } class Animal { void crier() { } } class Canari extends Animal { void crier() { System.out.println("Cui-cui !"); } }

CHAPITRE 8 LACCESSIBILIT
class Chien extends Animal { void crier() { System.out.println("Ouah-Ouah !"); } }

331

les deux objets milou et titi sont crs en tant qu'objets de type Animal. Lorsque nous utilisons la syntaxe milou.crier(), Java est oblig d'effectuer une recherche dynamique (c'est--dire au moment de l'excution du programme) pour savoir quelle version de la mthode doit tre utilise. Si la version du type correspondant au type de l'objet tait utilise, le programme n'afficherait rien, puisque cette version ne fait rien. Au contraire, Java trouve que l'objet de type Animal rfrenc par le handle milou est en ralit une instance de Chien, et la version correspondante de la mthode est excute. Cette recherche dynamique est effectue systmatiquement pour tous les appels de mthode. L'utilisation dune mthode final indique Java qu'il n'est pas ncessaire d'effectuer une recherche dynamique, puisque la mthode ne peut pas avoir t redfinie. L'excution du programme est donc optimise. (Nous reviendrons dans un prochain chapitre sur les diffrents aspects du polymorphisme.)

Les classes final


Une classe peut galement tre dclare final, dans un but de scurit ou d'optimisation. Une classe final ne peut pas tre tendue pour crer des sous-classes. Par consquent, ses mthodes ne peuvent pas tre redfinies et leur accs peut donc se faire directement, sans recherche dynamique. Une classe final ne peut tre clone. Le clonage est une technique de cration d'objets qui sera tudie dans un prochain chapitre.

synchronized
Le mot cl synchronized modifie galement les conditions d'accs aux classes et aux objets. Il concerne uniquement les programmes utilisant plusieurs threads, c'est--dire plusieurs processus se droulant simultanment.

332

LE DVELOPPEUR JAVA 2
Java permet de raliser facilement ce genre de programme. Par exemple, vous pouvez concevoir un programme dans lequel des objets d'un tableau sont modifis frquemment. De temps en temps, le tableau doit tre tri pour amliorer les conditions d'accs. Cependant, les objets ne doivent pas tre modifis pendant que le tri s'excute. Il faut donc s'assurer que la mthode permettant de modifier un lment du tableau ne pourra pas accder celui-ci lorsque le tri est actif. Avec un langage traditionnel, nous serions obligs d'interdire tout accs au tableau pendant le tri. Java permet d'tre plus slectif. Une des faons de procder consiste utiliser pour la modification des lments du tableau une mthode dclare synchronized. De cette faon, la mthode ne pourra tre excute que si elle a pu s'assurer l'exclusivit de l'utilisation de l'objet. L'objet sera donc verrouill pendant la modification d'un lment. Cette approche est plus efficace pour l'utilisateur que le verrouillage pendant le tri car le tri peut tre une opration longue. De cette faon, les lments peuvent toujours tre consults pendant cette opration. Pour dclarer une mthode synchronized, il suffit de faire prcder sa dclaration du mot cl :

synchronized void trier () {

Une classe peut galement tre dclare synchronized. Dans ce cas, toutes ses mthodes le sont. Les mthodes d'instances ne peuvent s'excuter que si l'objet auquel elles appartiennent a pu tre verrouill. Les mthodes statiques ne sont excutables qu'aprs verrouillage de la classe. Il est galement possible de verrouiller un objet de l'extrieur, l'aide de la syntaxe suivante :

synchronized (objet) {bloc d'instructions}

est un handle d'objet ou toute expression dont le rsultat de l'valuation est un objet. Le bloc d'instructions n'est excut que si l'objet a pu tre verrouill. Pendant toute la dure de l'excution du bloc d'instructions, l'objet est inaccessible pour les autres threads du programme. (Les threads seront tudis en dtail dans un prochain chapitre.)

objet

CHAPITRE 8 LACCESSIBILIT

333

Note : Si le bloc est rduit une seule instruction, les accolades ne sont
pas ncessaires.

native
Une mthode peut galement tre dclare native, ce qui a des consquences importantes sur la faon de l'utiliser. En effet, une mthode native n'est pas crite en Java, mais dans un autre langage. Les mthodes native ne sont donc pas portables d'un environnement un autre. Les mthodes native n'ont pas de dfinition. Leur dclaration doit tre suivie d'un point-virgule. Nous n'tudierons pas les mthodes native dans ce livre.

transient
Le mot cl transient s'applique aux variables d'instances (primitives et objets). Il indique que la variable correspondante est transitoire et que sa valeur ne doit pas tre conserve lors des oprations de srialisation. La srialisation sera tudie dans un prochain chapitre. Sachez simplement pour l'instant que cette opration permet d'enregistrer sur disque un objet Java afin de le conserver pour une session ultrieure, ou de l'envoyer travers un rseau. Lors de la srialisation, tous les champs de l'objet sont sauvegards l'exception de ceux dclars transient.

volatile
Le mot cl volatile s'applique aux variables pour indiquer qu'elles ne doivent pas tre l'objet d'optimisation. En effet, le compilateur effectue certaines manipulations pour acclrer le traitement. Par exemple, certaines variables peuvent tre recopies dans une zone de mmoire dont l'accs est plus rapide. Si, pendant ce temps, un autre processus modifie la valeur de la variable, la version utilise risque de ne pas tre jour. Il est donc possible d'empcher cette optimisation pas mesure de scurit. videmment, cela ne concerne que les programmes utilisant plusieurs processus simultans. Nous y reviendrons dans un prochain chapitre.

334
abstract

LE DVELOPPEUR JAVA 2

Le mot cl abstract peut tre employ pour qualifier une classe ou une mthode. Une mthode dclare abstract ne peut pas tre excute. En fait, elle n'a pas d'existence relle. Sa dclaration indique simplement que les classes drives doivent la redfinir. Ainsi, dans l'exemple Animaux3 des pages prcdentes, la mthode crier() de la classe Animal aurait pu tre dclare abstract :
abstract class Animal { abstract void crier(); }

ce qui signifie que tout animal doit tre capable de crier, mais que le cri d'un animal est une notion abstraite. Qui, en effet, peut dire quel est le cri d'un animal ? La question n'a pas de sens. Nous savons simplement qu'un animal a un cri. C'est exactement la signification du mot cl abstract. La mthode ainsi dfinie indique qu'une sous-classe devra dfinir la mthode de faon concrte. Les mthodes abstract prsentent les particularits suivantes :

Une

classe qui contient une mthode abstract doit tre dclare abstract elle-mme.

Une classe abstract ne peut pas tre instancie. Une classe peut tre dclare abstract, mme si elle ne comporte pas
de mthodes abstract.

Pour pouvoir tre instancie, une sous-classe d'une classe abstract


doit redfinir toutes les mthodes abstract de la classe parente.

Si une des mthodes n'est pas redfinie de faon concrte, la sous-

classe est elle-mme abstract et doit tre dclare explicitement comme telle.

CHAPITRE 8 LACCESSIBILIT

335

Les mthodes abstract n'ont pas de dfinition. Leur dclaration doit


tre suivie d'un point-virgule. Notre programme prcdent rcrit en utilisant une classe abstract devient donc :

public class Animaux3 { public static void main(String[] argv) { Animal milou = new Chien(); Animal titi = new Canari(); milou.crier(); titi.crier(); } } abstract class Animal { abstract void crier(); } class Canari extends Animal { void crier() { System.out.println("Cui-cui !"); } } class Chien extends Animal { void crier() { System.out.println("Ouah-Ouah !"); } }

De cette faon, il n'est plus possible de crer un animal en instanciant la classe Animal. En revanche, grce la dfinition de la mthode abstract crier() dans la classe Animal, il est possible de faire crier un animal sans savoir s'il s'agit d'un Chien ou d'un Canari.

336

LE DVELOPPEUR JAVA 2
Note : Vous vous demandez peut-tre quelle est l'utilit de tout cela. En
Canari.

effet, il aurait t plus simple de dfinir milou de type Chien et titi de type Vous avez en partie raison, mais nous avons voulu simuler ici certains traitements dpendant de l'utilisation de structures que nous n'avons pas encore tudies. Vous verrez dans un prochain chapitre que Java traite parfois les objets comme des instances des classes parentes.

Les interfaces
Une classe peut contenir des mthodes abstract et des mthodes non abstract. Cependant, il existe une catgorie particulire de classes qui ne contient que des mthodes abstract. Il s'agit des interfaces. Les interfaces sont toujours abstract, sans qu'il soit ncessaire de l'indiquer explicitement. De la mme faon, il n'est pas ncessaire de dclarer leurs mthodes abstract. Les interfaces obissent par ailleurs certaines rgles supplmentaires. Elles ne peuvent contenir que des variables static et final. Elles peuvent tre tendues comme les autres classes, avec une diffrence majeure : une interface peut driver de plusieurs autres interfaces. En revanche, une classe ne peut pas driver uniquement d'une ou de plusieurs interfaces. Une classe drive toujours d'une autre classe et peut driver, en plus, d'une ou de plusieurs interfaces. Les interfaces seront tudies en dtail au chapitre suivant.

Qui peut le faire (Qui)


Maintenant que nous avons tudi tous les lments qui permettent de contrler l'emplacement (O) des lments et ce qui peut tre fait avec (Quoi), nous allons nous intresser aux autorisations d'accs (Qui). En Java, il existe quatre catgories d'autorisations d'accs, spcifies par les modificateurs suivants :

private

CHAPITRE 8 LACCESSIBILIT

337

protected public
La quatrime catgorie correspond l'absence de modificateur. C'est donc l'autorisation par dfaut, que nous appellerons package (en italique pour indiquer qu'il ne s'agit pas d'un vrai mot cl).

Note : Dans certains livres, vous trouverez cette catgorie sous le nom de
friendly,

par rfrence au langage C++.

public
Les classes, les interfaces, les variables (primitives ou objets) et les mthodes peuvent tre dclares public. Les lments public peuvent tre utiliss par n'importe qui sans restriction. (Du moins sans restriction d'autorisation, car les restrictions d'usage tudies dans les sections prcdentes s'appliquent.) Certains lments doivent imprativement tre public. Ainsi, la classe contenant la mthode main() (votre programme principal) doit tre public. Les mthodes d'une interface doivent, lorsqu'elles sont redfinies de manire concrte, tre dclares public.

Note : Un fichier contenant un programme Java ne peut contenir qu'une seule dfinition de classe dclare public De plus, le fichier doit porter le mme nom que la classe, avec l'extension .java.
Les accesseurs et les mutateurs sont le plus souvent dclars public alors que l'accs direct aux variables est plus restreint. Cela permet de contrler la faon dont les variables sont modifies.

protected
Les membres d'une classe peuvent tre dclars protected. Dans ce cas, l'accs en est rserv aux mthodes des classes appartenant au mme package, aux classes drives de ces classes, ainsi qu'aux classes appartenant aux mmes packages que les classes drives. Cette autorisation s'applique

338

LE DVELOPPEUR JAVA 2
uniquement aux membres de classes, c'est--dire aux variables (primitives et objets), aux mthodes et aux classes internes. (Les classes internes seront tudies dans un prochain chapitre.) Les classes qui ne sont pas membres d'une autre classe ne peuvent pas tre dclares protected.

package
L'autorisation par dfaut, que nous appelons package, s'applique aux classes, interfaces, variables et mthodes. Les lments qui disposent de cette autorisation sont accessibles toutes les mthodes des classes du mme package. Les classes drives ne peuvent donc y accder que si elles ont t explicitement dclares dans le mme package.

Note : Rappelons que les classes n'appartenant pas explicitement un


package appartiennent automatiquement au package par dfaut. Toute classe sans indication de package dispose donc de l'autorisation d'accs toutes les classes se trouvant dans le mme cas.

private
L'autorisation private est la plus restrictive. Elle s'applique aux membres d'une classe (variables, mthodes et classes internes). Les lments dclars private ne sont accessibles que depuis la classe qui les contient. Ce type d'autorisation est souvent employ pour les variables qui ne doivent tre modifies ou lues qu' l'aide d'un mutateur ou d'un accesseur. Les mutateurs et les accesseurs, de leur ct, sont dclars public afin que tout le monde puisse utiliser la classe. Ainsi, l'exemple dcrit au dbut de ce chapitre peut s'crire de la faon suivante :
public class Automobile { public static void main(String[] argv) { Voiture maVoiture = new Voiture(); maVoiture.setCarburant(30); Voiture taVoiture = new Voiture(); taVoiture.setCarburant(90); } }

CHAPITRE 8 LACCESSIBILIT
class Voiture { private static int capacit = 80; private int carburant = 0; Voiture() { } public static int getCapacit() { return capacit; }

339

public void setCarburant(final int c) { final int maxi = capacit - carburant; if (c <= maxi) { carburant += c; System.out.println ("Le remplissage a t effectu sans problme."); } else { carburant = capacit; System.out.println ((c - maxi) + " litre(s) de carburant ont dbord."); } } public int getCarburant() { return carburant; } }

De cette faon, tout le monde peut faire le plein ou consulter la jauge d'essence, mais vous tes assur que le rservoir ne contiendra jamais plus que sa capacit. (Pour tre complet, il faudrait traiter les autres cas possibles d'erreur, comme l'utilisation de valeurs ngatives.)

Autorisations d'accs aux constructeurs


Les constructeurs peuvent galement tre affects d'une autorisation d'accs. Un usage frquent de cette possibilit consiste, comme pour les variables, contrler leur utilisation. Supposons que vous criviez un programme

340

LE DVELOPPEUR JAVA 2
de simulation d'un levage de lapins. Il pourrait s'articuler autour des classes suivantes :

public class Elevage { public static void main(String[] argv) { Lapin lapin1 = Lapin.crerLapin(); } } class Lapin { private static int nombre = 0; private static int maxi = 50; private boolean vivant = true; private Lapin() { System.out.println("Un lapin vient de natre."); System.out.println("Vous possdez " + (++nombre) + " lapin(s)."); } public void passeALaCasserole() { if (vivant) { vivant = false; System.out.println("Un lapin vient de passer la casserole."); System.out.println("Vous possdez " + (--nombre) + " lapin(s)."); } else { System.out.println("Ce lapin a dj t mang !"); } } public static Lapin crerLapin() { if (nombre < maxi) { return new Lapin(); } else { System.out.println("Elevage surpeupl. Naissance impossible."); return null; } } }

CHAPITRE 8 LACCESSIBILIT

341

Le constructeur tant dclar private, il est impossible de crer un nouveau lapin, sauf depuis la classe Lapin elle-mme. C'est ce que fait la mthode public crerLapin(), aprs avoir vrifi que le nombre de lapins permet une nouvelle naissance. La mthode retourne alors un objet instance de la classe Lapin cr l'aide de l'oprateur new. Dans le cas contraire, la mthode retourne null.

Attention : Ce programme n'est pas une illustration de la faon dont le


problme devrait tre trait. Il sert juste montrer comment on peut tirer parti d'un constructeur priv pour effectuer certains traitements avant la cration d'un objet afin, par exemple, de dterminer si cet objet doit tre cr ou non. En effet, il est toujours possible d'effectuer des traitements avant que le constructeur soit appel, au moyen d'un initialiseur. Cependant, lorsque ces traitements sont effectus, la cration de l'objet est dj commence. Ici, du fait de l'utilisation d'une mthode static, les traitements ncessaires peuvent tre effectus avant que la cration ne soit entame. Il est donc possible de dcider si l'objet doit tre cr ou non. Cette technique est galement employe pour crer des banques d'objets. Une banque d'objet (object pool en anglais) permet d'optimiser certains traitements. Cette technique consiste conserver les objets qui ne sont plus utiliss pour les affecter de nouveau lorsque cela est demand, au lieu de crer de nouvelles instances. Le fonctionnement s'tablit de la faon suivante :

Un lment demande une instance d'un objet en faisant appel une


mthode du mme type que la mthode crerLapin() de l'exemple cidessus.

Le programme cherche si un objet est disponible. Si un objet est trouv, il est rinitialis, et un handle vers cet objet est
retourn.

Si aucun objet n'est disponible, un nouvel objet est cr et un handle


vers cet objet est retourn. Idalement, la rinitialisation peut tre effectue lorsque les objets sont librs, afin qu'ils soient plus rapidement disponibles. De plus, un proces-

342

LE DVELOPPEUR JAVA 2
sus peut contrler en permanence le nombre d'objets libres, pour en supprimer s'il y en a trop, ou en crer quelques-uns d'avance si le stock est trop bas. Une version simplifie de cette technique est employe pour s'assurer qu'un objet n'existe qu' un seul exemplaire.

public class DemoUnique { public static void main(String[] args) { Unique s1 = Unique.crer(5); System.out.println(s1.getValeur()); Unique s2 = Unique.crer(10); System.out.println(s1.getValeur()); } } final class Unique { private int i; static Unique s0 = new private Unique() { }

Unique();

public static Unique crer(final int x) { s0.setValeur(x); return s0; } public int getValeur() { return i; } public void setValeur(final int x) { i = x; } }

Dans ce programme, un objet de type Unique est cr la premire fois que la classe est utilise. Lors des utilisations suivantes, le mme objet est r-

CHAPITRE 8 LACCESSIBILIT

343

initialis et renvoy comme s'il s'agissait d'un objet neuf. Le constructeur ne fait rien, mais il est indispensable. En effet, s'il n'existait pas, il ne pourrait pas tre dclar private. Une instance pourrait alors tre cre l'aide de l'oprateur new. Si vous excutez ce programme, vous constaterez que les handles s1 et s2 pointent en fait vers le mme objet. Pour valuer le gain en performances, vous pouvez compiler et excuter les deux versions suivantes :
import java.util.*; public class DemoNonUnique { public static void main(String[] args) { long t = System.currentTimeMillis(); for (int i = 0; i < 10000000; i++) { NonUnique s1 = new NonUnique(i); } t = System.currentTimeMillis() - t; System.out.println(t); Runtime rt = Runtime.getRuntime(); System.out.println(rt.totalMemory()); System.out.println(rt.freeMemory()); } } final class NonUnique { private int i; NonUnique(final int x) { i = x; } public int getValeur() { return i; } public void setValeur(final int x) { i = x; } }

344
et :

LE DVELOPPEUR JAVA 2

import java.util.*; public class DemoUnique { public static void main(String[] args) { long t = System.currentTimeMillis(); for (int i = 0; i < 10000000; i++) { Unique s1 = Unique.crer(i); } t = System.currentTimeMillis() - t; System.out.println(t); Runtime rt = Runtime.getRuntime(); System.out.println(rt.totalMemory()); System.out.println(rt.freeMemory()); } } final class Unique { private int i; static Unique s0 = new Unique(); private Unique() { } public static Unique crer(final int x) { s0.setValeur(x); return s0; } public int getValeur() { return i; } public void setValeur(final int x) { i = x; } }

La premire fonctionne normalement et cre dix millions d'objets de type Non Unique. La deuxime version ne cre qu'un seul objet et l'initialise dix

CHAPITRE 8 LACCESSIBILIT

345

millions de fois. Le premier programme s'excute en 13,680 secondes et le deuxime en 7,850 secondes, soit un gain de 42 %. En ce qui concerne la mmoire ncessaire, la deuxime version fait apparatre un gain de 56 %.

Note : Ces deux programmes utilisent une instance de la classe


java.util.Runtime

pour obtenir des informations concernant la mmoire disponible. Pour plus de renseignements sur les mthodes disponibles dans cette classe, reportez-vous la documentation en ligne de Java.

Rsum
Vous connaissez maintenant les principaux modificateurs qui peuvent tre utiliss avec les classes, les mthodes et les variables Java. Nous allons pouvoir aborder la partie intressante de notre tude. Le prochain chapitre sera consacr au polymorphisme, qui est un des aspects les plus fascinants de Java.

346

LE DVELOPPEUR JAVA 2

Chapitre 9 : Le polymorphisme

Le polymorphisme
OUS AVONS TUDI JUSQU'ICI DES ASPECTS DE JAVA ASSEZ conventionnels. Bien sr, la facult de crer de nouvelles classes en tendant les classes existantes est un mcanisme puissant, mais en fin de compte assez trivial. Il ne fait que reproduire la faon dont nous structurons notre environnement. Cependant, si nous y rflchissons un peu, nous nous apercevrons rapidement qu'il n'existe pas une seule faon de structurer l'environnement. Chaque sujet structure son environnement en fonction du problme qu'il a rsoudre. Pour un enfant de 8 ans, un vlo est un objet du mme type qu'un ours en peluche ou un ballon de football. Exprim en Java, nous dirions que c'est une instance de la classe Jouet. Pour un adulte, un vlo pourra tre un objet du mme type qu'une voiture ou une moto, c'est--dire une instance de la classe Vhicule. Pour un industriel fabriquant galement des brouettes, des chelles ou des tagres mtalliques, un vlo sera simplement une instance de la classe Produit.

348

LE DVELOPPEUR JAVA 2
Cependant, une structure diffrente n'implique pas forcment un sujet diffrent. Le programmeur pourra tre amen traiter des problmes de produits, de vhicules ou de jouets, et devra donc utiliser une structure diffrente pour chaque problme. Il s'apercevra rapidement qu'il aura intrt s'affranchir de sa propre faon de structurer son environnement pour s'attacher celle correspondant le mieux au problme traiter. Pour autant, il constatera bientt qu'au sein d'un mme problme, il peut exister simultanment plusieurs structures. Il ne s'agit pas alors de choisir la structure la plus adapte, mais de concevoir le programme de telle sorte que ces structures coexistent de manire efficace. Nous avons certainement tous appris l'cole qu'il tait impossible d'additionner des pommes et des oranges. Cette affirmation dnote une troitesse d'esprit qui choque gnralement ceux qui elle est assne, jusqu' ce qu'ils finissent par se faire une raison et acceptent l'ide qu'un problme ne peut tre trait qu' travers une structure unique rigide. Pourtant, un enfant de cinq ans est mme de comprendre que :

3 pommes + 5 oranges = 8 fruits

Bien sr, cette faon d'crire est un peu cavalire. Nous prciserons bientt dans quelles conditions et par quel mcanisme ce type de manipulation peut tre mis en uvre en Java. Un autre problme peut surgir lorsqu'un objet parat pouvoir appartenir une structure ou une autre suivant l'angle sous lequel on aborde le problme. Par exemple, il parat naturel de dfinir une classe Cheval comme drivant de la classe Animaux. Cependant, il peut exister galement une classe MoyenDeTransport, qui serait ventuellement galement candidate. Un objet peut-il tre la fois un animal et un moyen de transport ? Le bon sens nous indique que oui, tout comme nous savons qu'un objet peut tre la fois une pomme et un fruit. Pour autant, la relation n'est pas la mme dans les deux cas. Dans ce chapitre, nous tudierons les diffrents aspects de cette possibilit qu'ont les objets d'appartenir plusieurs catgories la fois, et que l'on nomme polymorphisme.

CHAPITRE 9 LE POLYMORHISME

349

Le sur-casting des objets


Une faon de dcrire l'exemple consistant additionner des pommes et des oranges serait d'imaginer que nous disons pommes et oranges mais que nous manipulons en fait des fruits. Nous pourrions crire alors la formule correcte :
3 fruits (qui se trouvent tre des pommes) + 5 fruits (qui se trouvent tre des oranges) --------------------------------------------= 8 fruits

Cette faon de voir les choses implique que les pommes et les oranges soient transforms en fruits pralablement l'tablissement du problme. Cette transformation est appele sur-casting, bien que cela n'ait rien voir avec le sur-casting des primitives. Avec la syntaxe de Java, nous cririons quelque chose comme :
3 (fruits)pommes + 5 (fruits)oranges --------------------------------------------= 8 fruits

Une autre faon de voir les choses consiste poser le problme de la faon suivante :
3 pommes + 5 oranges --------------------------------------------= ?

puis laisser Java se poser la question : Peut-on effectuer l'opration ainsi ? La rponse tant non, celui-ci devrait effectuer automatiquement le surcasting ncessaire pour produire le rsultat.

350

LE DVELOPPEUR JAVA 2
Ici, les deux approches semblent fonctionner de manire identique. Cependant, dans un programme, on ne sait pas toujours l'avance quel type d'objets on va rencontrer. La premire approche oblige effectuer le surcasting avant que l'opration soit pose. Avec la deuxime approche, le sur-casting peut tre effectu la demande. C'est exactement ce qui se passe en Java. Un objet peut tre considr comme appartenant sa classe ou une classe parente selon le besoin, et cela de faon dynamique. En d'autres termes, le lien entre une instance et une classe n'est pas unique et statique. Au contraire, il est tabli de faon dynamique, au moment o l'objet est utilis. C'est l la premire manifestation du polymorphisme.

Retour sur l'initialisation


Pour qu'un objet puisse tre considr comme une instance de la classe Pomme ou une instance de la classe Fruit, une condition est ncessaire : il doit tre rellement une instance de chacune de ces classes. Il faut donc que, lors de l'initialisation, un objet de chaque classe soit cr. Nous pouvons le vrifier au moyen du programme suivant :
public class Polymorphisme { public static void main(String[] argv) { Pomme pomme = new Pomme(85); Orange orange = new Orange(107); pse(orange); } static void pse(Fruit f) { int p = f.poids; System.out.println("Ce fruit pese " + p + " grammes."); } } abstract class Fruit { int poids; Fruit() { System.out.println("Creation d'un Fruit."); } }

CHAPITRE 9 LE POLYMORHISME
class Pomme extends Fruit { Pomme(int p) { poids = p; System.out.println("Creation d'une Pomme."); } } class Orange extends Fruit { Orange(int p) { poids = p; System.out.println("Creation d'une Orange."); } }

351

Ce programme dfinit une classe Fruit qui contient un constructeur sans argument et un champ poids de type int. La classe Fruit est abstract car on ne souhaite pas qu'il soit possible de crer un fruit sans indiquer de quel type de fruit il s'agit. Le programme dfinit galement deux classes Pomme et Orange qui tendent la classe Fruit et possdent un constructeur prenant un argument de type int dont la valeur sert initialiser le champ poids. Il est intressant de noter que les classes Pomme et Orange ne possdent pas de champs poids. La classe Polymorphisme, qui est la classe principale du programme, contient une mthode main et une mthode pse(Fruit f). Celle-ci prend pour argument un objet de type Fruit et affiche son poids. Dans la mthode main, nous crons une instance de Pomme, puis une instance d'Orange. Enfin, nous appelons la mthode pse avec pour argument l'objet orange. Si nous excutons ce programme, nous obtenons le rsultat suivant :

Cration Cration Cration Cration Ce fruit

d'un Fruit. d'une Pomme. d'un Fruit. d'une Orange. pese 107 grammes

352

LE DVELOPPEUR JAVA 2
Que se passe-t-il ? Nous nous apercevons qu'avant de crer une Pomme, le programme cre un Fruit, comme le montre l'excution du constructeur de cette classe. La mme chose se passe lorsque nous crons une Orange. Lorsque le constructeur de la classe Pomme est excut, le champ poids prend la valeur du paramtre pass. Ce champ correspond la variable d'instance poids de la classe Fruit. La partie la plus intressante du programme se trouve la dernire ligne de la mthode main. Celle-ci appelle la mthode pse avec un argument apparemment de type Orange :
pse(orange);

Or, il n'existe qu'une seule version de la mthode pse et elle prend un argument de type Fruit :
static void pse(Fruit f) {

D'aprs ce que nous savons de la faon dont Java gre les mthodes, cela devrait provoquer un message d'erreur du type :
Incompatible type for method. Can't convert Orange to Fruit.

Pourtant, tout se passe sans problme. En effet, l'objet pass la mthode est certes de type Orange, mais galement de type Fruit. L'objet point par le handle orange peut donc tre affect sans problme au paramtre f, qui est dclar de type Fruit. Le fait d'tablir le lien entre l'objet et la classe parente (Fruit) est un sur-casting. Le sur-casting est effectu automatiquement par Java lorsque cela est ncessaire.

Le sur-casting
L'objet point par le handle orange n'est en rien modifi par le sur-casting. A l'appel de la mthode pse, l'objet point par le handle orange est pass

CHAPITRE 9 LE POLYMORHISME

353

la mthode. Le handle f est alors point sur cet objet. f tant dclar de type Fruit, Java vrifie si l'objet correspond bien ce type, ce qui est le cas. Le handle orange continue de pointer vers l'objet orange, qui est toujours de type Orange et Fruit. (Incidemment, cet objet est galement de type Object, comme tous les objets Java.) L'avantage de cette faon de procder est que nous n'avons besoin que d'une seule mthode pse. Sans le sur-casting, il nous faudrait crire une mthode pour peser les oranges et une autre pour peser les pommes. En somme, tout cela est parfaitement naturel et correspond une fois de plus la volont de crer un langage permettant d'exprimer la solution avec les termes du problme. Lorsque vous pesez des pommes ou des oranges, vous n'utilisez pas un pse-pommes ou un pse-oranges ; ventuellement un pse-fruits, mais plus probablement un psepetits-objets. Ce faisant, vous effectuez un sur-casting de pomme et orange en petit-objet. Le sur-casting permet ici d'effectuer l'inverse de ce que permet la surcharge de mthodes. Il aurait t tout fait possible d'crire le programme de la faon suivante :
public class Polymorphisme2 { public static void main(String[] argv) { Pomme pomme = new Pomme(85); Orange orange = new Orange(107); pse(orange); } static void pse(Pomme f) { int p = f.poids; System.out.println("Ce fruit pese " + p + " grammes."); } static void pse(Orange f) { int p = f.poids; System.out.println("Ce fruit pese " + p + " grammes."); } }

354

LE DVELOPPEUR JAVA 2
Cette faon de faire nous aurait simplement obligs crer et maintenir deux mthodes utilisant le mme code, ce qui est contraire aux rgles de la programmation efficace. En revanche, elle aurait un intrt incontestable si nous souhaitions effectuer un traitement diffrent en fonction du type de Fruit, par exemple afficher le type. Nous pourrions alors modifier le programme de la faon suivante :

public class Polymorphisme3 { public static void main(String[] argv) { Pomme pomme = new Pomme(85); Orange orange = new Orange(107); pse(orange); } static void pse(Pomme f) { int p = f.poids; System.out.println("Cette pomme pese " + p + " grammes."); } static void pse(Orange f) { int p = f.poids; System.out.println("Cette orange pese " + p + " grammes."); } }

Le rsultat correspond incontestablement ce que nous souhaitons, mais les rgles de la programmation efficaces ne sont toutefois pas compltement respectes. En effet, une partie du traitement (la premire ligne) est encore commune aux deux mthodes. Bien entendu, dans un cas aussi simple, nous ne nous poserions pas toutes ces questions et nous nous satisferions sans problme d'une si petite entorse aux rgles. Il est cependant utile, d'un point de vue pdagogique, de considrer la manire lgante de traiter le problme. Celle-ci consiste mettre la partie commune du traitement dans une troisime mthode et appeler cette mthode dans chacune des mthodes spcifiques, ce qui pourrait tre ralis de la faon suivante :

CHAPITRE 9 LE POLYMORHISME
public class Polymorphisme4 { public static void main(String[] argv) { Pomme pomme = new Pomme(85); Orange orange = new Orange(107); pse(orange); } static void pse(Pomme f) { String p = getPoids(f); System.out.println("Cette pomme pese " + p); } static void pse(Orange f) { String p = getPoids(f); System.out.println("Cette orange pese " + p); } static String getPoids(Fruit f) { return f.poids + " grammes"; } }

355

Nous avons ici dplac dans la mthode getPoids non seulement la lecture du poids, mais galement la transformation de celui-ci en une chane de caractres compose de la valeur du poids suivie de l'unit. A priori, le programme semble plus complexe. Cependant, si vous dcidez d'exprimer le poids en kilogrammes, vous n'avez qu'une seule ligne modifier. Le listing suivant montre le programme complet affichant le poids en kilogrammes :

public class Polymorphisme5 { public static void main(String[] argv) { Pomme pomme = new Pomme(85); Orange orange = new Orange(107); pse(orange); }

356

LE DVELOPPEUR JAVA 2
static void pse(Pomme f) { String p = getPoids(f); System.out.println("Cette pomme pese " + p); } static void pse(Orange f) { String p = getPoids(f); System.out.println("Cette orange pese " + p); } static String getPoids(Fruit f) { return (float)f.poids / 1000 + " kilogramme(s)"; } }

abstract class Fruit { int poids; Fruit() { System.out.println("Creation d'un Fruit."); } }

class Pomme extends Fruit { Pomme(int p) { poids = p; System.out.println("Creation d'une Pomme."); } }

class Orange extends Fruit { Orange(int p) { poids = p; System.out.println("Creation d'une Orange."); } }

CHAPITRE 9 LE POLYMORHISME

357

Le sur-casting explicite
Comme dans le cas des primitives (et bien qu'il s'agisse d'une opration tout fait diffrente), il existe des sur-castings implicites et explicites. Cependant, la distinction n'est pas toujours aussi vidente. En effet, il ne faut pas perdre de vue que les handles servent manipuler des objets mais ne sont pas des objets. Le sur-casting le plus explicite est celui utilisant l'oprateur de sur-casting, constitu du type vers lequel on souhaite effectuer le sur-casting, plac entre parenthses avant la rfrence l'objet. Dans notre programme prcdent, nous pourrions utiliser ce type de sur-casting de la faon suivante :
public class Polymorphisme6 { public static void main(String[] argv) { Pomme pomme = new Pomme(85); Orange orange = new Orange(107); pse(orange); } static void pse(Pomme f) { String p = getPoids((Fruit)f); System.out.println("Cette pomme pese " + p); } static void pse(Orange f) { String p = getPoids((Fruit)f); System.out.println("Cette orange pese " + p); } static String getPoids(Fruit f) { return (float)f.poids / 1000 + " kilogramme(s)"; } }

Ici, f est sur-cast explicitement en Fruit avant d'tre pass la mthode getPoids. Cette opration est tout fait inutile.

358

LE DVELOPPEUR JAVA 2
Un sur-casting un tout petit peu moins explicite est celui obtenu en affectant un objet un handle de type diffrent. Par exemple :

Fruit pomme = new Pomme(85);

ou, en version longue :

Fruit pomme; pomme = new Pomme(85);

Ici, le handle pomme est dclar de type Fruit ; la ligne suivante, un objet de type Pomme est cr et affect au handle pomme. Rptons encore une fois que ni le handle ni l'objet ne sont modifis. Les handles ne peuvent jamais tre redfinis dans le courant de leur existence. (En revanche, dans certains cas, par exemple lintrieur dune mthode, on peut dfinir un handle de mme nom et d'un type identique ou diffrent.) Les objets, de leur ct, ne sont pas modifis par le sur-casting. Seule la nature du lien qui les lie aux handles change en fonction de la nature des handles. Le sur-casting li au passage des paramtres, comme dans notre exemple, est exactement de ce type. Il n'est pas moins explicite. Il est simplement un peu moins lisible.

Le sur-casting implicite
Le sur-casting est implicite lorsque aucun handle n'est l pour indiquer qu'il a lieu. Ainsi, par exemple, nous verrons dans un prochain chapitre que les tableaux, en Java, ne peuvent contenir que des rfrences d'objets. L'affectation d'un handle d'objet un tableau provoque donc implicitement un sur-casting vers le type Object. Nous verrons ce que cela implique pour l'utilisation des tableaux.

CHAPITRE 9 LE POLYMORHISME

359

Le sous-casting
Dans notre exemple prcdent, nous avons vu que, dans la mthode getPoids, le handle d'objet manipul est de type Fruit. L'objet, lui, est toujours de type Pomme ou Orange. Existe-t-il un moyen pour retrouver le type spcifique de l'objet ? En d'autres termes, est-il possible d'effectuer d'abord le traitement gnral, et ensuite seulement le traitement particulier ? Reprenons la premire version de notre programme :
public class Polymorphisme7 { public static void main(String[] argv) { Pomme pomme = new Pomme(85); Orange orange = new Orange(107); pse(orange); } static void pse(Fruit f) { int p = f.poids; System.out.println("Ce fruit pese " + p + " grammes."); } }

abstract class Fruit { int poids; Fruit() { System.out.println("Creation d'un Fruit."); } }

class Pomme extends Fruit { Pomme(int p) { poids = p; System.out.println("Creation d'une Pomme."); } }

360
class Orange extends Fruit { Orange(int p) { poids = p;

LE DVELOPPEUR JAVA 2

System.out.println("Creation d'une Orange."); } }

Nous souhaiterions que la mthode pse affiche, aprs le poids, un message indiquant s'il s'agit d'une Pomme ou d'une Orange. Nous pourrions modifier le programme de la faon suivante :

public class Polymorphisme8 { public static void main(String[] argv) { Pomme pomme = new Pomme(85); Orange orange = new Orange(107); pse(orange); } static void pse(Fruit f) { int p = f.poids; System.out.println("Ce fruit pese " + p + " grammes."); quoi(f); } static void quoi(Pomme p) { System.out.println("C'est une Pomme."); } static void quoi(Orange o) { System.out.println("C'est une Orange."); } }

Malheureusement, ce programme provoque une erreur de compilation :

CHAPITRE 9 LE POLYMORHISME

361

Incompatible type for method. Explicit cast needed to convert Fruit to Orange.

En effet, Java n'a aucun moyen de savoir comment effectuer le sous-casting. Celui-ci doit donc tre effectu explicitement, ce qui n'est pas possible ici directement.

Le late binding
Notre problme peut tout de mme tre rsolu grce au fait que Java utilise une technique particulire pour dterminer quelle mthode doit tre appele. Dans la plupart des langages, lorsque le compilateur rencontre un appel de mthode (ou de fonction ou procdure, selon la terminologie employe), il doit tre mme de savoir exactement de quelle mthode il s'agit. Le lien entre l'appel et la mthode est alors tabli au moment de la compilation. Cette technique est appele early binding, que l'on pourrait traduire par liaison prcoce. Java utilise cette technique pour les appels de mthodes dclares final. Elle a l'avantage de permettre certaines optimisations. En revanche, pour les mthodes qui ne sont pas final, Java utilise la technique du late binding (liaison tardive). Dans ce cas, le compilateur n'tablit le lien entre l'appel et la mthode qu'au moment de l'excution du programme. Ce lien est tabli avec la version la plus spcifique de la mthode. Dans notre cas, il nous suffit d'utiliser une mthode pour chaque classe (Pomme et Orange) ainsi que pour la classe parente Fruit, au lieu d'une mthode statique dans la classe principale (Polymorphisme). Ainsi, la mthode la plus spcifique (celle de la classe Pomme ou Orange, selon le cas) sera utilise chaque fois que la mthode sera appele avec un argument de type Fruit. Il nous faut donc modifier le programme de la faon suivante :
public class Polymorphisme9 { public static void main(String[] argv) { Pomme pomme = new Pomme(85); Orange orange = new Orange(107); pse(pomme); }

362

LE DVELOPPEUR JAVA 2
static void pse(Fruit f) { int p = f.poids; System.out.println("Ce fruit pese " + p + " grammes."); f.quoi(); } } class Fruit { int poids; Fruit() { System.out.println("Creation d'un Fruit."); } void quoi() {} } class Pomme extends Fruit { Pomme(int p) { poids = p; System.out.println("Creation d'une Pomme."); } void quoi() { System.out.println("C'est une Pomme."); } } class Orange extends Fruit { Orange(int p) { poids = p; System.out.println("Creation d'une Orange."); } void quoi() { System.out.println("C'est une Orange."); } }

CHAPITRE 9 LE POLYMORHISME

363

A la fin de la mthode pse, nous appelons la mthode quoi de l'objet f. Or, l'objet f, tout en tant une instance de Fruit, est galement une instance de Pomme. Java lie donc l'appel de la mthode avec la mthode correspondant au type le plus spcifique de l'objet. La mthode existant dans la classe Fruit ainsi que dans la classe Pomme, c'est cette dernire version qui est utilise, ce qui nous permet d'atteindre le but recherch.
abstract de la classe Fruit uniquement pour montrer que le late binding est bien la cause de ce fonctionnement. De la mme faon, nous n'avons pas dclar abstract la mthode quoi de la classe Fruit pour qu'aucune interfrence ne puisse tre suspecte. Dans un cas pratique, la classe Fruit et la mthode quoi de cette classe seraient dclares abstract (ou tout du moins la mthode quoi, ce qui rend automatiquement la classe Fruit abstract galement) :

Note : Nous avons supprim la dclaration

abstract class Fruit { int poids; Fruit() { System.out.println("Creation d'un Fruit."); } abstract void quoi(Fruit f); }

Certains programmes tirent parti de la technique du late binding sans que cela apparaissent au premier coup d'il. Examinez, par exemple, le programme suivant :

public class Dressage1 { public static void main(String[] argv) { Chien milou = new Chien(); Cheval tornado = new Cheval(); Dinosaure denver = new Dinosaure(); avancer(milou); avancer(tornado); avancer(denver); arrter(milou);

364
arrter(tornado); arrter(denver); }

LE DVELOPPEUR JAVA 2

static void avancer(Animal a) { if (a.avance) System.out.println("Ce " + a + " est deja en marche."); else { a.crier(); a.avance = true; System.out.println("Ce " + a + " est maintenant en marche."); } } static void arrter(Animal a) { if (a.avance) { a.avance = false; System.out.println("Ce " + a + " est maintenant arrete."); } else System.out.println("Ce " + a + " est deja arrete."); } } abstract class Animal { boolean avance; void crier() { System.out.println("Le cri de cet animal est inconnu."); } } class Chien extends Animal { public void crier() { System.out.println("Ouah-Ouah !"); } public String toString() { return "chien"; } }

CHAPITRE 9 LE POLYMORHISME
class Cheval extends Animal { public void crier() { System.out.println("hiiiii !"); } public String toString() { return "cheval"; } } class Dinosaure extends Animal { public String toString() { return "dinosaure"; } }

365

Dans ce programme, nous dfinissons une classe Animal contenant une variable d'instance avance de type boolean qui vaut true si l'animal est en marche et false s'il est arrt, une mthode crier, et deux mthodes static, avancer et arrter. La mthode avancer prend pour argument un objet de type Animal et teste la variable avance de cet objet pour savoir si l'animal est dj en mouvement. Si c'est le cas, un message est affich pour l'indiquer. Dans le cas contraire, l'animal pousse un cri et se met en mouvement. La mthode crier est appele ici sans argument. Ce n'est donc pas la nature de l'argument qui dtermine quelle mthode va tre appele. L'appel est de la forme :

a.crier();

C'est donc la mthode crier de l'objet a qui est appele. Comme dans l'exemple prcdent, le compilateur ne peut pas savoir quel est le type spcifique de l'objet a. Tout ce qu'il sait est qu'il s'agit d'un Animal. Cette fois encore, le lien est tabli au moment de l'excution. Lorsque la mthode a t redfinie dans la classe spcifique de l'objet, comme dans le cas de Chien ou Cheval, c'est

366

LE DVELOPPEUR JAVA 2
cette version qui est appele. Dans le cas contraire (celui de la classe Dinosaure), la version gnrique de la classe Animal est appele. Dans ce programme, le late binding est mis profit d'une autre faon. Examinez la ligne suivante, extraite de la mthode avance.
System.out.println("Ce " + a + " est maintenant en marche.");

a est l'argument pass la mthode avancer(). Il s'agit d'un objet de type Animal. Que se passe-t-il lorsque l'on essaie d'afficher un objet comme s'il s'agissait d'une chane de caractres ? La mthode toString() de cet objet est appele. La mthode toString() est dfinie dans la classe Object, dont

drivent toutes les classes Java. La ligne ci-dessus est donc quivalente :

System.out.println("Ce " + (Object)a.toString() + " est maintenant en marche.");

Nous sommes alors exactement ramens la situation prcdente. La mthode de la classe la plus spcifique est lie l'appel de mthode au moment de l'excution du programme. a est :

un Object, un Animal, un Chien, un Cheval ou un Dinosaure.


C'est donc dans la classe la plus spcifique (Chien, Cheval ou Dinosaure) que Java cherche d'abord la mthode toString(). Comme nous avons redfini cette mthode dans chacune de ces classes, c'est la version redfinie qui est lie l'appel de mthode. Dans cet exemple, nous voyons qu'il n'est pas ncessaire que la mthode existe dans la classe de l'objet. Il suffit qu'elle existe dans une classe parente pour que la version redfinie dans une classe drive soit appele.

CHAPITRE 9 LE POLYMORHISME

367

en ce qui concerne la dclaration de la classe Animal. En effet, la mthode de cette classe ne peut tre dclare abstract puisqu'elle possde une dfinition. Cette dfinition doit tre utilise pour toutes les classes drives qui ne redfinissent pas la mthode crier . Pour empcher l'instanciation de la classe, il est donc ncessaire dans ce cas de dclarer la classe elle-mme abstract. Il est d'ailleurs prudent de toujours dclarer explicitement une classe abstract plutt que de se reposer sur le fait qu'elle contient des mthodes abstract. En effet, si la dfinition de la classe est longue, la prsence de ces mthodes peut ne pas tre vidente, ce qui complique la maintenance en rduisant la lisibilit. Par ailleurs, vous pouvez tre amen supprimer une mthode abstract d'une classe. S'il s'agissait de la seule mthode de ce type, votre classe devient de ce fait non abstract, ventuellement sans que vous y pensiez, ce qui peut tre une source d'erreurs futures.
crier

Note : Dans cet exemple, nous avons utilis une technique particulire

Les interfaces
Une autre forme de polymorphisme consiste driver une classe de plusieurs autres classes. Certains langages permettent cela sans restriction. Java ne l'autorise pas, mais permet d'aboutir au mme rsultat en utilisant les interfaces. Une interface est une sorte de classe qui ne contient que des mthodes abstract et des variables static et final. Une interface peut tre cre de la mme faon qu'une classe, en remplaant, dans sa dclaration, le mot class par le mot interface. Comme une classe, elle peut tre dclare public ( condition d'tre enregistre dans un fichier portant le mme nom). Elle peut tre attribue un package. Si elle ne l'est pas, elle fera partie du package par dfaut. Une interface peut tre tendue au moyen du mot cl extends. Dans ce cas, vous obtiendrez automatiquement une nouvelle interface. Pour driver une classe d'une interface, vous devez utiliser le mot cl implements. Celui-ci doit tre suivi de la liste des interfaces dont la classe drive, spares par

368

LE DVELOPPEUR JAVA 2
des virgules. En effet, une classe peut driver de plusieurs interfaces au moyen du mot cl implements. Les mthodes d'une interface sont toujours public. Il est important de noter que l'implmentation des mthodes d'une interface dans la classe drive sera toujours public. En effet, Java ne permet pas de restreindre l'accs d'une mthode hrite.

Utiliser les interfaces pour grer des constantes


Supposons que vous criviez un programme produisant de la musique l'aide d'un quipement MIDI. Chaque note (correspondant une frquence) est reprsente par une valeur numrique. Cette valeur est la mme pour tous les instruments MIDI. Cependant, le cas de la batterie est particulier. Une batterie dispose de plusieurs instruments (caisse claire, grosse caisse, tom aigu, tom mdium, tom basse, charley, crash, ride, etc.) Chacun de ces instruments n'met qu'une seule note. (Cette affirmation ferait hurler les batteurs !) On utilise donc une valeur de note pour chaque instrument. Cependant, les botes rythmes produisant les sons de batterie n'utilisent pas toutes la mme correspondance entre une note et un instrument. Pour une marque de bote rythmes, la caisse claire correspondra la note 63 alors que pour une autre, ce sera la note 84. Il vous faut donc grer un ensemble de constantes correspondant chaque marque de bote rythmes. Vous pouvez dfinir ces constantes dans vos classes, de la faon suivante :
class MaClasse extends XXX { final static int CAISSE_CLAIRE = 48, GROSSE_CAISSE = 57, CHARLEY_PIED = 87, CHARLEY_MAIN = 89, TOM_BASSE = 66, . . . etc. }

CHAPITRE 9 LE POLYMORHISME

369

Si plusieurs classes de votre programme utilisent ces valeurs, vous devrez les coder dans chacune de ces classes. Qu'arrivera-t-il si vous voulez adapter votre programme une nouvelle marque de bote rythmes ? Vous devrez modifier toutes les classes qui utilisent ces donnes. Une autre solution consiste placer ces donnes dans une interface :

interface Yamaha { final static int CAISSE_CLAIRE GROSSE_CAISSE CHARLEY_PIED CHARLEY_MAIN TOM_BASSE etc. }

= = = = =

48, 57, 87, 89, 66,

class maClasse extends XXX implements Yamaha { etc. }

Tout ce que vous avez faire maintenant pour adapter votre programme une nouvelle bote rythmes est de crer une nouvelle interface pour celleci et de modifier la dclaration des classes qui l'utilisent. Si plusieurs classes sont dans ce cas, vous pouvez vous simplifier la vie encore un peu plus en utilisant une structure comme celle-ci :

interface Yamaha { final static int CAISSE_CLAIRE GROSSE_CAISSE CHARLEY_PIED CHARLEY_MAIN TOM_BASSE etc. }

= = = = =

48, 57, 87, 89, 66,

370
interface Roland { final static int CAISSE_CLAIRE GROSSE_CAISSE CHARLEY_PIED CHARLEY_MAIN TOM_BASSE etc. }

LE DVELOPPEUR JAVA 2

= = = = =

68, 69, 72, 74, 80,

interface BAR extends Yamaha {} class maClasse extends XXX implements BAR { etc.

Ici, nous avons cr une interface intermdiaire. De cette faon, toutes les classes qui utilisent ces donnes implmentent l'interface BAR. Pour passer d'une bote rythmes une autre, il suffit d'apporter une seule modification la classe BAR. Les classes Java tant charges dynamiquement en fonction des besoins, il suffit de remplacer le fichier contenant la classe BAR pour que le programme fonctionne avec le nouveau matriel. On ne peut rver d'une maintenance plus facile !

Note : Nous avons ici dclar les constantes explicitement final et static, ce qui n'est pas ncessaire car les variables d'une interface sont toujours final et static. Par ailleurs, nous avons respect une convention qui consiste crire les noms des variables final et static en majuscules, en sparant les mots l'aide du caractre _. Dans d'autres langages, on crit ainsi les constantes globales, c'est--dire celles qui sont disponibles dans la totalit du programme. En Java, les variables final et static sont ce qui se rapproche le plus des constantes globales.

Un embryon d'hritage multiple


Grce aux interfaces, une classe Java peut hriter des constantes de plusieurs autres classes. Si une classe ne peut tendre qu'une seule classe

CHAPITRE 9 LE POLYMORHISME

371

parente, elle peut en revanche implmenter un nombre quelconque d'interfaces. Pour continuer notre programme de musique MIDI, nous pourrions ajouter sur le mme principe des interfaces permettant de grer diffrents synthtiseurs. Les synthtiseurs utilisent des numros de programmes pour slectionner les diffrentes sonorits. Ici encore, chaque marque a son propre arrangement.
interface T1 { final static int PIANO . . GUITARE etc. } interface EX7 { final static int PIANO . . ACCOUSTIC_BASS etc. }

= 1,

= 22,

= 1,

= 22,

interface Synthtiseur extends T1 {} class maClasse extends XXX implements BAR, Synthtiseur { etc. }

Le polymorphisme et les interfaces


De la mme faon qu'un objet est la fois une instance de sa classe et de toutes ses classes parentes, un objet sera valablement reconnu comme une instance d'une quelconque de ses interfaces. Le programme suivant en montre un exemple :

372

LE DVELOPPEUR JAVA 2
public class Midi2 { public static void main(String[] argv) { Sequence maSequence = new Sequence(); System.out.println(maSequence instanceof System.out.println(maSequence instanceof System.out.println(maSequence instanceof System.out.println(maSequence instanceof System.out.println(maSequence instanceof System.out.println(maSequence instanceof } } interface Yamaha { final static int CAISSE_CLAIRE GROSSE_CAISSE CHARLEY_PIED CHARLEY_MAIN TOM_BASSE } interface Roland { final static int CAISSE_CLAIRE GROSSE_CAISSE CHARLEY_PIED CHARLEY_MAIN TOM_BASSE } interface T1 { final static int PIANO = 1, GUITARE = 22; // etc. } interface EX7 { final static int PIANO = 1, ACCOUSTIC_BASS = 22; // etc. }

Sequence); Object); Synthetiseur); BAR); Roland); T1);

= = = = =

48, 57, 87, 89, 66;

= = = = =

68, 69, 72, 74, 80;

CHAPITRE 9 LE POLYMORHISME
interface BAR extends Roland {} interface Synthetiseur extends T1 {} class Sequence implements Synthetiseur, BAR { }

373

Ce programme affiche :

true true true true true true

montrant que l'objet maSequence est une instance de Sequence, d'Object (la classe Sequence tend automatiquement la classe Object), de BAR, de Synthetiseur, de Roland et de T1. Nous aurions pu crire notre programme diffremment, en remplaant les trois dernires lignes par :

interface Materiel extends Roland, T1 {} class Sequence implements Materiel { }

car une interface peut tendre un nombre quelconque d'interfaces. Pour rsumer, un objet est une instance de :

sa classe, toutes les classes parentes de sa classe,

374

LE DVELOPPEUR JAVA 2

toutes les interfaces qu'il implmente, toutes les interfaces parentes des interfaces qu'il implmente, toutes les interfaces qu'implmentent les classes parentes de sa classe, toutes les interfaces parentes des prcdentes.
La Figure 9.1 rsume la situation.

Interface62

Interface61

Interface52

Interface6 Interface51

ClasseC

Interface5 Interface42

Interface4

Interface41

Interface32

ClasseB

Interface3 Interface22

Interface31

Interface2

Interface21

Interface12

ClasseA

Interface1

Interface11

implements

extends

Figure 9.1 : Une instance de la classe ClasseA est galement instance de toutes les autres classes et interfaces.

CHAPITRE 9 LE POLYMORHISME

375

Dans cette structure, tout objet instance de la classe ClasseA est galement instance (au sens de l'oprateur instanceof) de toutes les classes et interfaces reprsentes. Cette particularit peut tre employe, comme dans notre exemple prcdent, pour permettre une classe d'hriter des constantes de plusieurs autres classes, mais galement pour servir simplement de marqueur, l'aide d'instructions conditionnelles du type :

if (objet instanceof ClasseOuInterface1) traitement1; else if (objet instanceof ClasseOuInterface2) traitement 2; else if (objet instanceof ClasseOuInterface3) traitement 3; etc

Nous verrons mme dans un prochain chapitre qu'il est possible d'obtenir directement la classe d'un objet pendant l'excution d'un programme.

Attention : Ce genre de structure pose un problme potentiel : vous


devez vous assurer que deux classes ou interfaces situes dans des branches diffrentes de la hirarchie ne possdent pas deux constantes de mme nom. Dans le cas contraire, la rfrence sera ambigu et le compilateur affichera un message d'erreur. Par exemple, le schma de la Figure 9.2 comporte trois ambiguts : les variables a1, c1 et d1 sont en effet dfinies plusieurs fois dans des branches diffrentes. Dans une mme branche, la variable la plus basse dans la hirarchie masque les variables de mme nom situes plus haut. En revanche, le compilateur n'est pas mme de rsoudre les cas o les variables sont dfinies dans des branches diffrentes.

Pas de vrai hritage multiple


Malheureusement, excuter un traitement ou un autre selon le type d'un objet en l'interrogeant pour savoir de quoi il est l'instance n'est pas une

376

LE DVELOPPEUR JAVA 2

Interface62

Interface61 int d1 = 1; Interface6

Interface52

Interface51 int c1 = 1

ClasseC

Interface5 Interface42

Interface4

Interface41

Interface32

ClasseB int b1=0;

Interface3 int b3=0 Interface22

Interface31 int b3 = 1;

Interface2 int a1 = 1,b2=0; ClasseA int b1 = 1, b2=1; Interface1 int a1 = 0;

Interface21

Interface12 int d1 = 0 Interface11 int c1 = 0;

implements

extends

Figure 9.2 : Les variables a1, c1 et d1 sont dfinies plusieurs fois dans des branches diffrentes de la hirarchie, ce qui cre des ambiguts.

faon trs lgante de programmer. Il est indiscutablement prfrable, chaque fois que cela est possible, de laisser agir le polymorphisme. Ainsi dans l'exemple de la Figure 9.3, si la mthode A est appele pour un objet instance de la classe B, la mthode de cette classe sera excute. En revanche, si la mthode A est appele pour un objet de la classe C, c'est la mthode A de la classe A qui sera excute.

CHAPITRE 9 LE POLYMORHISME

377

ClasseA mthode A

ClasseB extends ClasseA mthode A

ClasseC extends ClasseA

Figure 9.3 : Lorsque la mthode A est appele sur un objet de la classe ClasseC, la mthode de la classe ClasseA est excute.

ClasseA

ClasseB extends ClasseA

ClasseC extends ClasseA

mthodeA(ClasseA a){} mthodeA(ClasseB b){}

Figure 9.4 : Effets du polymorphisme des arguments des mthodes.

Cette structure est tout fait impossible avec les interfaces. En effet, les interfaces ne peuvent pas comporter de dfinition de mthodes, mais seulement des dclarations. Par consquent, une classe n'hrite pas des mthodes des interfaces qu'elle implmente, mais uniquement de l'obligation de les dfinir elle-mme. Le polymorphisme est mis en uvre d'une autre manire dans la structure reprsente sur la Figure 9.4.

378

LE DVELOPPEUR JAVA 2
Dans cet exemple, si la mthode A (qui peut tre dfinie n'importe o) reoit un objet instance de la classe A, la premire version est excute. Si elle reoit un objet instance de la classe B, la deuxime version est excute. Enfin, si elle reoit un objet instance de la classe C, alors qu'il n'existe pas de version spcifique de la mthode pour ce type d'objet, la premire version est excute car un objet de la classe C est aussi instance de la classe A. Cette exploitation du polymorphisme est tout fait possible avec les interfaces, condition qu'il n'y ait pas d'ambigut. Par exemple, la structure reprsente sur la Figure 9.5 est impossible. En effet, la premire et la troisime version de la mthode A sont ambigus car leurs signatures ne se rsolvent pas de faons distinctes si la mthode est appele avec pour paramtre un objet de type ClasseC.

Quand utiliser les interfaces ?


La plupart des livres sur Java mettent en avant la possibilit de dclarer des mthodes abstraites comme un intrt majeur des interfaces. On peut se

ClasseA

Interface1

ClasseB extends ClasseA

ClasseC extends ClasseA implements Interface1

mthodeA(ClasseA a){} mthodeA(ClasseB b){} mthodeA(Interface1 i){}

Figure 9.5 : La premire et la troisime version de la mthode sont ambiges si largument est une instance de ClasseC.

CHAPITRE 9 LE POLYMORHISME

379

demander, dans ce cas, quels avantages ont les interfaces sur les classes abstraites ? Le fait de pouvoir implmenter plusieurs interfaces n'est pas pertinent dans ce cas, puisque cela ne fait qu'augmenter le nombre de mthodes que doivent dfinir les classes drives, sans leur permettre d'hriter de quoi que ce soit d'autre, en dehors des constantes. Le fait d'obliger les classes qui les implmentent dfinir les mthodes dclares dans les interfaces est prsent comme une contrainte bnfique pour l'organisation des programmes. Cet argument est fallacieux, d'autant qu'il arrive souvent d'implmenter une interface sans avoir besoin de dfinir toutes ses mthodes. Il faudra pourtant le faire, car cela est obligatoire pour que la classe ne soit pas abstraite. Certains lments de la bibliothque de Java sont fournis sous forme d'interfaces. La gestion des fentres, par exemple, ncessite l'implmentation de l'interface WindowListener, ce qui oblige dfinir 7 mthodes, mme si vous n'avez besoin que d'une seule dentre elles. Pour chapper cette contrainte, Java fournit par ailleurs une classe appele WindowAdapter qui implmente l'interface WindowListener en dfinissant les 7 mthodes avec une dfinition vide. Cela permet au programmeur d'tendre la classe WindowAdapter au lieu d'implmenter l'interface WindowListener, et de n'avoir ainsi redfinir que les mthodes qui l'intressent. En fait, pour comprendre la ncessit des interfaces, il faut savoir comment circule l'information entre les classes Java. Supposons que vous souhaitiez crire une application comportant une fentre avec un bouton. Lorsque l'utilisateur clique sur ce bouton, un texte est affich dans une zone prvue cet effet. En Java, un clic sur un bouton est un vnement. Cet vnement dclenche l'envoi d'un message, qui est reu par les objets qui ont la capacit de recevoir ces messages. Le problme est que tous les objets sont susceptibles d'avoir besoin un jour ou l'autre de recevoir tous les messages. Le nombre de messages diffrents tant trs important, il faudrait que tous les objets soient des rcepteurs pour tous les messages potentiels, ce qui serait trs lourd. Java est donc organis d'une autre faon. Pour qu'un message du type clic sur un bouton soit reu par un objet, il faut que celui-ci soit du type receveur_de_clic. En fait, le vritable type est couteur d'action, ou ActionListener. Si ActionListener tait une classe, tous les objets susceptibles de ragir au clic d'un bouton devraient tre drivs de cette classe, ce qui ne serait pas du tout pratique. Voil pourquoi les concepteurs de Java ont prfr raliser ActionListener sous la forme d'une interface, de faon

380

LE DVELOPPEUR JAVA 2
que cette fonctionnalit puisse tre ajoute n'importe quel objet, quelle que soit sa classe. Bien entendu, il serait totalement inutile que ActionListener dfinisse la mthode excute en cas de clic, car il peut s'agir de n'importe quoi, au gr des besoins du programmeur. Il a paru cependant logique de rendre obligatoire la prsence de cette dfinition dans les classes drives, de faon s'assurer qu'un message envoy un objet implmentant ActionListener ne finira pas dans le vide. Le rsultat est que l'on voit fleurir ce type de construction dans les programmes Java. Tout objet peut tre un ActionListener (ou un listener de n'importe quoi d'autre), et les programmeurs ne s'en privent pas, alors qu'il existe d'autres faons beaucoup plus logiques de traiter le problme, comme nous le verrons dans un prochain chapitre. Disons qu'il vaut mieux utiliser des objets dont la seule fonction soit de traiter les messages d'vnements, plutt que d'intgrer cette fonctionnalit dans des objets dont ce n'est pas la destination. Il suffit que ces objets spcialiss se trouve l'intrieur des objets qui ont besoin de cette fonction. Par exemple, le programme suivant applique la premire mthode :
class MaClasse extends AutreClasse implements ActionListener { dfinition de MaClasse void actionPerformed(...) { dfinition du traitement en cas de clic } }

La mthode actionPerformed est dclare dans l'interface ActionListener et doit donc tre dfinie dans notre classe. Sa dfinition est videmment tout fait spcifique cette classe. L'exemple suivant applique la deuxime mthode :
class MaClasse extends AutreClasse { . . dfinition de MaClasse . . }

CHAPITRE 9 LE POLYMORHISME
class MonListener extends ActionListener void actionPerformed(...) { dfinition du traitement en cas de clic } }

381

Bien entendu, la cible du message envoy en cas de clic doit tre dfinie comme tant une instance de MaClasse dans le premier cas, et une instance de MonListener dans le second. Le seul problme li la seconde approche est la ncessit de faire communiquer la classe MonListener avec la classe MaClasse, car il est vident que le traitement effectu dans la premire concerne exclusivement la seconde. Nous verrons dans un prochain chapitre que ce problme se rsout trs simplement en plaant la classe MonListener l'intrieur de la classe MaClasse.

Hritage et composition
Les sections prcdentes peuvent laisser penser que Java est un langage plus pauvre que ceux qui autorisent l'hritage multiple. On peut dbattre longtemps de cette question. En fait, les deux points de vue opposs peuvent tre dfendus :

Les interfaces ne sont qu'un ple succdan de l'hritage multiple. Les interfaces ne sont pas indispensables. Tout ce qui peut tre ralis
avec elles peut l'tre sans elles. Il est vrai que les langages autorisant l'hritage multiple permettent des constructions plus sophistiques. Le prix payer est la diminution de la lisibilit et de la maintenabilit des programmes. L'hritage multiple est une fonctionnalit un peu plus puissante mais beaucoup plus complexe matriser. Les concepteurs de Java ont pens que le jeu n'en valait pas la chandelle. D'un autre ct, on peut trs facilement se passer de l'hritage multiple, tout comme on peut facilement se passer des interfaces. Il serait videmment

382

LE DVELOPPEUR JAVA 2
stupide de s'en passer lorsqu'elles apportent un avantage vident. Il faut cependant se mfier de la tendance consistant faire reposer toute la structure d'une application sur l'hritage. L'hritage est certes une fonctionnalit premire des langages objets, mais au mme titre que la composition. Chaque fois que vous devez rsoudre un problme qui semblerait mriter l'utilisation de l'hritage multiple, vous devez vous demander si la composition n'est pas une solution plus approprie. En d'autres termes, si vous pensez qu'un objet devrait tre la fois un X et un Y, demandez-vous si vous ne pouvez pas rsoudre le problme avec un objet X possdant un Y (ou un Y possdant un X). C'est l'approche que nous avons dcrite dans la section prcdente. Plutt que de considrer que notre classe devait tre un ActionListener, nous avons dcid qu'elle possderait un ActionListener. Dans le cas des constantes, la question est encore plus simple. En effet, s'agissant de variables final et static, la relation implique par l'interface (de type est un) ne concerne que l'accs aux variables, et non les variables elles-mmes. Si nous reprenons l'exemple des botes rythmes, nous pouvons obtenir exactement la mme fonctionnalit sans utiliser les interfaces. Pour le montrer, nous avons inclus les deux versions de la classe dans un programme permettant de mettre en vidence la diffrence d'utilisation. Version avec interfaces :
public class BAR1 { public static void main(String[] argv) { MaClasse maClasse = new MaClasse(); maClasse.joueCaisseClaire(); } } interface Yamaha { final static int CAISSE_CLAIRE GROSSE_CAISSE CHARLEY_PIED CHARLEY_MAIN TOM_BASSE }

= = = = =

48, 57, 87, 89, 66;

CHAPITRE 9 LE POLYMORHISME
interface Roland { final static int CAISSE_CLAIRE GROSSE_CAISSE CHARLEY_PIED CHARLEY_MAIN TOM_BASSE }

383

= = = = =

68, 69, 72, 74, 80;

interface BAR extends Yamaha {} class MaClasse implements BAR { void joueCaisseClaire(){ System.out.println(CAISSE_CLAIRE); } }

Version sans interface :


public class BAR2 { public static void main(String[] argv) { MaClasse maClasse = new MaClasse(); maClasse.joueCaisseClaire(); } } class Yamaha { final static int CAISSE_CLAIRE GROSSE_CAISSE CHARLEY_PIED CHARLEY_MAIN TOM_BASSE } class Roland { final static int CAISSE_CLAIRE

= = = = =

48, 57, 87, 89, 66;

= 68,

384
GROSSE_CAISSE CHARLEY_PIED CHARLEY_MAIN TOM_BASSE } class BAR extends Yamaha {} class MaClasse { = = = = 69, 72, 74, 80;

LE DVELOPPEUR JAVA 2

void joueCaisseClaire() { System.out.println(BAR.CAISSE_CLAIRE); } }

La seule diffrence importante est dans la dernire ligne du programme et concerne la faon d'accder aux variables. Il est en effet indispensable d'indiquer dans quelle classe elles se trouvent, puisqu'elles ne se trouvent plus dans la classe qui les utilise. En revanche, la deuxime version permettrait de dfinir la classe BAR l'intrieur de la classe MaClasse (comme nous apprendrons le faire dans un prochain chapitre), ce que ne permet pas la version utilisant les interfaces. Un autre avantage de cette approche est qu'elle offre une plus grande souplesse. En effet, nous avons choisi ici d'utiliser une classe intermdiaire, de faon que le changement d'implmentation soit transparent pour l'utilisateur de la classe MaClasse. C'est la dfinition de la classe BAR qui dtermine le matriel utilis. Cependant, il est galement possible d'utiliser directement les classes correspondant chaque matriel, et ainsi de choisir l'une ou l'autre de faon dynamique, au moment de l'excution du programme :
void joueCaisseClaire() { if(yamaha) System.out.println(Yamaha.CAISSE_CLAIRE); else System.out.println(Roland.CAISSE_CLAIRE); }

CHAPITRE 9 LE POLYMORHISME

385

Rsum
Dans ce chapitre, nous avons prsent les diffrents aspects d'un lment essentiel des langages orients objets, et de Java en particulier : le polymorphisme, qui permet un objet d'appartenir plusieurs types la fois. Nous avons vu qu'il existait deux formes diffrentes de polymorphisme : un polymorphisme hirarchique, qui fait qu'un objet instance d'une classe est automatiquement instance des classes parentes, et un polymorphisme non hirarchique, qui permet qu'un objet soit considr comme une instance d'un nombre quelconque d'interfaces. Dans le prochain chapitre, nous nous changerons les ides en tudiant les structures qui permettent de stocker des collections de donnes. Nous verrons alors comment le polymorphisme est mis en uvre par certaines de ces structures.

Chapitre 10 : Les tableaux et les collections

Les tableaux et les collections

10

ANS LES CHAPITRES PRCDENTS, NOUS AVONS APPRIS CRER des objets et des primitives pour stocker des donnes. Chacun de ces lments tait considr individuellement. Cependant, il arrive frquemment que des lments doivent tre traits collectivement. Dans la vie courante, les exemples ne manquent pas. Le personnel d'une entreprise est un ensemble d'employs. Un mot est un ensemble de caractres. Une phrase est un ensemble de mots. Un polygone peut tre dcrit comme un ensemble de sommets. Les rsultats d'un examen peuvent tre un ensemble de couples lve, note. Tous ces ensembles ne sont pas de mme nature. Certains sont ordonns : l'ensemble des mots d'une phrase ne constitue cette phrase que s'ils sont

388

LE DVELOPPEUR JAVA 2
parcourus dans un ordre prcis. D'autres ne le sont pas : l'ensemble des articles en stock ne correspond aucun ordre fondamental. Certains sont ordonns de faon particulire : les sommets d'un polygone dcrivent ce polygone s'ils sont ordonns, mais cet ordre est double. (Selon certains points de vue, les polygones dfinis par le parcours d'un ensemble de sommets dans un sens ou dans l'autre peuvent tre quivalents.) Certains ensembles contiennent des objets de nature identique (les sommets d'un polygone) alors que d'autres contiennent des objets de diffrentes natures (les articles d'un stock). Certains ensembles ont un nombre d'lments fixe alors que d'autres peuvent crotre indfiniment. Java dispose de plusieurs structures pour traiter ces diffrents cas.

Les tableaux
Les tableaux Java sont des structures pouvant contenir un nombre fixe d'lments de mme nature. Il peut s'agir d'objets ou de primitives. Chaque lment est accessible grce un indice correspondant sa position dans le tableau. Les tableaux Java sont des objets, mme lorsqu'ils contiennent des primitives.

Dclaration
Les tableaux doivent tre dclars comme tous les objets. Java dispose de deux syntaxes quivalentes pour la dclaration des tableaux, ce qui peut troubler les nophytes :
int[] x;

ou :
int x[];

L'utilisation de l'une ou l'autre de ces syntaxes est une affaire de got personnel. Nous avons dj affirm notre prfrence pour la premire, qui

CHAPITRE 10 LES TABLEAUX ET LES COLLECTIONS

389

exprime clairement que le handle de droite x sera de type int[], c'est-dire un tableau contenant des entiers, alors que le second signifie que le handle x correspond un tableau, et qu'il contiendra des lments du type indiqu gauche (int). Cependant, pour un utilisateur non averti, ces deux formes peuvent sembler signifier :
x x

est de type int[] ; le type x est un tableau contenant des int. est un tableau ; ce tableau est de type int.

Or, la deuxime affirmation est manifestement fausse. Il est donc plus lisible d'utiliser la premire. Souvenez-vous qu'une dclaration ne concerne qu'un handle et non l'objet correspondant. L'opration consiste simplement crer le handle et indiquer qu'il pourra tre utilis pour pointer vers un objet de type tableau de int. Aucun tableau n'est cr ce stade.

Initialisation
L'initialisation est l'opration dont le rsultat est la cration d'un objet tableau. Les tableaux Java sont de taille fixe. L'initialisation devra donc indiquer la taille du tableau. La syntaxe employer est la suivante :
x = new int[dimension];

Dimension est le nombre d'lments que le tableau pourra contenir. L'initialisation cre un tableau contenant le nombre d'entres indiqu, par exemple :
int[] x; x = new int[5];

390

LE DVELOPPEUR JAVA 2
La premire ligne cre un handle x qui pourra pointer vers un objet de type tableau de int. La deuxime ligne cre un tableau de 5 entres et fait pointer x vers celui-ci. Les entres du tableau seront numrotes de 0 4. A ce stade, le tableau ne contient rien. En effet, les oprations ci-dessus ont cr des rfrences cinq int, x[0] x[4], un peu comme si nous avions crit :

int x[0], x[1], x[2], x[3], x[4];

Comme les autres objets, les tableaux peuvent tre dclars et initialiss sur la mme ligne :

int[] x = new int[5];

Initialisation automatique
Si nous crivons le programme suivant, nous devrions, d'aprs ce que nous avons appris jusqu'ici, obtenir une erreur de compilation :

class TestArray { public static void main(String[] args) { int[] x; x = new int[5]; System.out.println(x[4]); } }

car x[4] n'a pas t explicitement initialise. Pourtant, ce programme est compil sans erreur et produit le rsultat suivant :
0

CHAPITRE 10 LES TABLEAUX ET LES COLLECTIONS

391

De la mme faon, une version de ce programme utilisant un tableau d'objets :


class TestArray2 { public static void main(String[] args) { Integer[] x; x = new Integer[5]; System.out.println(x[4]); } }

est compile sans erreur et son excution affiche :


null

Cela nous montre que Java initialise automatiquement les lments des tableaux, avec la valeur 0 pour les primitives entires (byte, short, int et long), 0.0 pour les primitives flottantes (float et double), false pour les boolean et null pour les objets.

Les tableaux littraux


De la mme faon qu'il existe des valeurs littrales correspondant chaque type de primitives, il existe galement des tableaux littraux. L'expression suivante est un tableau de cinq lments de type int :
{1, 2, 3, 4, 5}

Cependant, leur usage est trs limit. Ils ne peuvent en effet tre employs que pour initialiser un tableau au moment de sa dclaration, en utilisant la syntaxe :
int[] x = {1, 2, 3, 4, 5};

392

LE DVELOPPEUR JAVA 2
Dans ce cas, le handle de tableau x est cr et affect au type tableau de int, puis un nouveau tableau est cr comportant 5 lments, et enfin, les valeurs 1, 2, 3, 4 et 5 sont affectes aux lments x[0], x[1], x[2], x[3] et x[4]. Les tableaux littraux peuvent contenir des variables, comme dans l'exemple suivant :
int a = 1, b = 2, c = 3, d = 4, e = 5; int[] y = {a, b, c, d, e};

Ils peuvent mme contenir des variables de types diffrents :


byte a = 1; short b = 2; char c = 3; int d = 4; long e = 5; long[] y = {a, b, c, d, e};

Cela n'est cependant possible que grce la particularit suivante : comme avec les oprateurs, tous les lments d'un tableau littral sont sur-casts vers le type suprieur. Ici, bien que chaque variable soit d'un type diffrent, tous les lments du tableau littral {a, b, c, d, e} sont des long, ayant subi un sur-casting implicite vers le type de l'lment le plus long. Les tableaux littraux peuvent contenir des objets anonymes, comme le montre l'exemple suivant :
class TestArray3 { public static void main(String[] args) { A[] a = {new A(), new A(), new A(),}; } } class A {}

CHAPITRE 10 LES TABLEAUX ET LES COLLECTIONS

393

Les objets du tableau littral sont crs de faon anonyme. Ils ne sont pas perdus pour autant puisqu'ils sont immdiatement affects aux lments du tableau a.

Les tableaux de primitives sont des objets


Il est important de raliser que la diffrence fondamentale existant entre les primitives et les objets existe galement entre les primitives et les tableaux de primitives. Les tableaux sont des objets. Lorsque vous manipulez un tableau, vous ne manipulez rellement qu'un handle vers ce tableau. Le programme suivant met cela en vidence :
class TestArray4 { public static void main(String[] args) { int[] x = {1, 2, 3, 4, 5}; int[] y = {10, 11, 12}; x = y; x[2] = 100; System.out.println(y[2]); } }

la cinquime ligne de ce programme, nous affectons la valeur de y x. Cela parat choquant si l'on considre que x et y sont des tableaux de longueurs diffrentes. En fait, cela ne l'est pas du tout, car il s'agit simplement de dtacher le handle x du tableau vers lequel il pointe pour le faire pointer vers le mme tableau que le handle y. La seule chose ncessaire pour que cela soit possible est que les deux handles soient de mme type. la ligne 6 du programme, nous modifions la valeur de l'lment d'indice 2 du tableau x. Comme nous le voyons la ligne suivante, cette modification concerne galement le tableau point par y, puisqu'il s'agit du mme.

Le sur-casting implicite des tableaux


Tout comme les autres objets et les primitives, les tableaux peuvent tre sur-casts implicitement. Le programme suivant en fait la dmonstration :

394

LE DVELOPPEUR JAVA 2
class TestArray5 { public static void main(String[] args) { A[] a = {new A(), new A(), new A()}; B[] b = new B[] {new B(), new B()}; a = b; } } class A {} class B extends A {}

Ici, a, qui et un handle de type A[], peut pointer vers le mme objet que b, qui est un handle de type B[], en raison du polymorphisme. Nous sommes en prsence d'un sur-casting implicite. Nous constatons donc que la relation entre les types A[] et B[] est la mme qu'entre les types A et B. Il n'aurait pas t possible d'crire l'affectation inverse b=a. En revanche, ce type de sur-casting est impossible avec les primitives. En effet, la relation entre les types int et short n'a rien voir avec le polymorphisme. Un short peut tre converti en un int, mais un short n'est pas un int. Le programme suivant :
class TestArray6 { public static void main(String[] args) { int[] x = {1, 2, 3, 4, 5}; short[] y = {10, 11, 12}; x = y; x[2] = 100; System.out.println(y[2]); } }

ne peut donc tre compil sans produire l'erreur :


Incompatible type for =. Can't convert short[] to int[].

CHAPITRE 10 LES TABLEAUX ET LES COLLECTIONS

395

diffrentes pour l'initialisation des tableaux a et b. La deuxime n'apporte rien de plus sous cette forme. Cependant, nous aurions pu crire :
A[] b = new B[] {new B(), new B()};

Note : Nous avons utilis dans le programme TestArray5 deux syntaxes

ce qui est une nouvelle application du polymorphisme. En effet, cela consiste tout simplement prendre un tableau initialis comme instance d'une classe et l'affecter un handle de la classe parente. En revanche, la ligne :
B[] b = new A[] {new A(), new A()};

produit un message d'erreur, car il s'agit d'un sous-casting, qui doit donc tre effectu explicitement. Le sur-casting dcrit plus haut est lgrement diffrent du cas suivant :
A[] b = new A[] {new B(), new B()};

Dans ce cas, le sur-casting a lieu au niveau des lments du tableau et non au niveau du tableau lui-mme. Cette application du polymorphisme est souvent mise en uvre pour crer des tableaux contenant des objets de types diffrents. Un tableau ne peut en effet contenir que des objets de mme type, mais il peut s'agir du type d'une classe parente. Le programme suivant en montre un exemple :
class MesAnimaux { public static void main(String[] args) { Animal[] menagerie = new Animal[3]; menagerie[0] = new Chien(); menagerie[1] = new Chat(); menagerie[2] = new Canari(); menagerie[0].afficheType();

396
menagerie[1].afficheType(); menagerie[2].afficheType(); } } abstract class Animal { public void afficheType() { System.out.println("Animal"); } } class Chien extends Animal { public void afficheType() { System.out.println("Chien"); } } class Chat extends Animal { public void afficheType() { System.out.println("Chat"); } } class Canari extends Animal { public void afficheType() { System.out.println("Canari"); } }

LE DVELOPPEUR JAVA 2

Ce programme stocke trois objets de type Chien, Chat et Canari dans un tableau dclar de classe Animal. La suite du programme utilise les mthodes afficheType (dfinies dans chaque classe) pour afficher une chane de caractres spcifique chaque type.

Note : Les mthodes afficheType ne peuvent pas tre statiques. En effet, une fois les objets sur-casts en Animal pour tre placs dans le tableau, un appel une mthode statique utilisant leur nom d'instance entrane l'excution de la mthode statique de la classe vers laquelle ils ont t sur-casts.

CHAPITRE 10 LES TABLEAUX ET LES COLLECTIONS

397

Pour la mme raison, l'utilisation du polymorphisme est impossible (pour l'instant) pour afficher le type. En effet, le programme suivant :
class MesAnimaux2 { public static void main(String[] args) { Animal[] menagerie = new Animal[3]; menagerie[0] = new Chien(); menagerie[1] = new Chat(); menagerie[2] = new Canari(); afficheType(menagerie[0]); afficheType(menagerie[1]); afficheType(menagerie[2]); } void afficheType(Chien c) { System.out.println("Chien"); } void afficheType(Chat c) { System.out.println("Chat"); } void afficheType(Canari c) { System.out.println("Canari"); } } abstract class Animal { } class Chien extends Animal { } class Chat extends Animal { } class Canari extends Animal { }

398
produit trois erreurs de compilation :

LE DVELOPPEUR JAVA 2

Incompatible type for method. Explicit cast needed to convert Animal to Canari.

Il est intressant de noter qu'il s'agit trois fois de la mme erreur, ce qui montre que Java est apparemment incapable de retrouver le type d'origine des objets sur-casts. Nous verrons dans un prochain chapitre comment contourner cette difficult.

Les tableaux d'objets sont des tableaux de handles


Tout comme les handles d'objets ne sont pas des objets, les tableaux d'objets ne contiennent pas des objets, mais des handles d'objets. Nous pouvons mettre cela en (relative) vidence l'aide du programme suivant :

class TestArray7 { public static void main(String[] args) { A a1 = new A(1); A a2 = new A(2); A a3 = new A(3); A a4 = new A(4); A a5 = new A(5); A[] x = {a1, a2, a3}; A[] y = {a4, a5}; y[0] = x[0]; a1.setValeur(10); System.out.println(x[0].getValeur()); System.out.println(y[0].getValeur()); } } class A { int valeur;

CHAPITRE 10 LES TABLEAUX ET LES COLLECTIONS


A(int v){ valeur = v; } void setValeur(int v) { valeur = v; } int getValeur() { return valeur; } }

399

Ce programme cre deux tableaux x et y, tous deux de type A[]. L'lment 0 du tableau x contient le handle a1, pointant vers un objet de type A dont le champ valeur vaut 1. L'lment 0 du tableau y contient le handle a4, pointant vers un objet de type A dont le champ valeur vaut 4. La ligne :
y[0] = x[0];

fait pointer l'lment 0 de y vers le mme objet que l'lment 0 de x, c'est-dire l'objet point par a1. Lorsque, la ligne suivante, nous modifions le champ valeur de a1, x[0] et y[0] sont galement modifis. L'opration inverse donne un rsultat quivalent :
class TestArray8 { public static void main(String[] args) { A a1 = new A(1); A a2 = new A(2); A a3 = new A(3); A[] x = {a1, a2, a3}; A[0].setValeur(10); System.out.println(a1.getValeur()); } }

400
class A { int valeur; A(int v){ valeur = v; } void setValeur(int v) { valeur = v; } int getValeur() { return valeur; } }

LE DVELOPPEUR JAVA 2

Ici, lorsque nous modifions la valeur de A[0], a1 est galement modifi. Encore une fois, rptons que les tableaux permettent de stocker et de manipuler des handles et des primitives, mais en aucun cas des objets. La situation de l'exemple TestArray7 peut tre reprsente graphiquement de comme indiqu sur la Figure 10.1.

La taille des tableaux


La taille des tableaux est fixe. Une fois initialise, elle ne peut plus tre modifie. Cependant, il n'est pas ncessaire qu'elle soit connue au moment de la compilation. Un tableau peut parfaitement tre initialis de faon dynamique, pendant l'excution du programme. Dans l'exemple suivant, nous crons un tableau dont le nombre d'lments est dtermin par la variable x, dont la valeur est calcule de manire alatoire :
import java.util.*; class TestArray9 { public static void main(String[] args) { int x = Math.abs((new Random()).nextInt()) % 10; int[] y;

CHAPITRE 10 LES TABLEAUX ET LES COLLECTIONS

401

Modification de la valeur de a1

handles:

x
La valeur de x[0] est modifie Objet tableau 0 1 2

a1 a2 a3 a4 a5

y
La valeur de y[0] est modifie Objet tableau 0 1

Objet Instance de A valeur = 10

Objet Instance de A valeur = 2

Objet Instance de A valeur = 4

Objet Instance de A valeur = 3

Objet Instance de A valeur = 5

Figure 10.1 : Structure de lexemple TestArray7.

y = new int[x]; System.out.println(x); } }

La quatrime ligne du programme est un peu obscure, mais finalement assez simple comprendre. Une instance de la classe java.util.Random est cre. Il s'agit d'un gnrateur de nombres alatoires. La mthode nextInt de cet objet est appele. Elle renvoie un int quelconque. Cette valeur est fournie comme argument de la mthode abs de la classe java.lang.Math pour obtenir une valeur toujours positive. Le rsultat est modul par 10, ce qui nous donne un entier compris entre 0 (inclus) et 10 (exclu). Une fois le tableau cr, la valeur de x est affiche afin de vrifier qu'elle est diffrente chaque excution. Il est donc vident que la taille du tableau ne peut pas tre connue lors de la compilation.

402

LE DVELOPPEUR JAVA 2
Ici, nous connaissons la taille du tableau, puisqu'il s'agit de la valeur de x. Nous aurions pu cependant crire le programme sans utiliser la variable x, de la faon suivante :
import java.util.*; class TestArray10 { public static void main(String[] args) { int[] y; y = new int[Math.abs((new Random()).nextInt()) % 10]; } }

Comment connatre, alors, la taille du tableau ? Tous les tableaux possdent un champ length qui peut tre interrog pour connatre leur nombre d'lments. Il nous suffit d'ajouter une ligne notre programme pour afficher la taille du tableau :
System.out.println(y.length);

Note : Remarquez au passage que cet exemple met en vidence le fait


qu'il est parfaitement possible de crer un tableau de taille nulle. Le plus difficile est de trouver quelque chose faire avec ! Le champ length des tableaux est souvent utilis pour initialiser ses lments l'aide d'une boucle for. En effet, Java initialise les lments des tableaux contenant des objets la valeur null, correspondant un handle pointant vers rien du tout. Ce type de handle peut parfaitement tre manipul, mais si vous tentez d'accder l'objet correspondant, vous provoquerez une erreur d'excution. Par exemple, le programme suivant est compil sans erreur :
class TestArray11 { public static void main(String[] args) { A[] x = new A[10]; x[0].setValeur(10); } }

CHAPITRE 10 LES TABLEAUX ET LES COLLECTIONS


class A { int valeur; A(int v){ valeur = v; } void setValeur(int v) { valeur = v; } int getValeur() { return valeur; } }

403

Cependant, si vous tentez de l'excuter, vous obtiendrez le message :


java.lang.NullPointerException at TestArray11.main(TestArray11.java:4)

car x[0] contient null et non un handle pointant vers une instance de la classe A. Pour initialiser les lments du tableau, nous pouvons procder de la manire suivante :
class TestArray12 { public static void main(String[] args) { A[] x = new A[10]; for (int i = 0; i < x.length; i++) x[i] = new A(0); x[0].setValeur(10); } }

404
programme. La premire consiste crire :
for (int i = 0; i <= x.length; i++)

LE DVELOPPEUR JAVA 2
Attention : Deux erreurs se produisent frquemment dans ce type de

Dans ce cas, le programme sera compil correctement mais produira une erreur l'excution. En effet, si la taille du tableau est 10, les indices vont de 0 9. La condition teste doit donc tre i < x.length et non i <= x.length. Une autre erreur plus difficile dtecter produit le message suivant lors de la compilation :

TestArray.java:4: Attempt to reference field lenght in a A[]. for (int i = 0; i < x.lenght; i++) ^

Il est arriv certains de chercher longtemps la cause de cette erreur avant de s'apercevoir qu'elle venait d'une faute de frappe (lenght au lieu de length).

Les tableaux multidimensionnels


Les tableaux Java peuvent comporter plusieurs dimensions. Il est ainsi possible de crer des tableaux rectangulaires, cubiques, ou un nombre quelconque de dimensions. Pour dclarer un tableau multidimensionnel, vous devez utiliser la syntaxe suivante :

int [][] x;

L'initialisation du tableau peut tre effectue l'aide de tableaux littraux :

int [][] x = {{1, 2, 3, 4},{5, 6, 7, 8}};

CHAPITRE 10 LES TABLEAUX ET LES COLLECTIONS


ou l'aide de l'oprateur new :
int [][] x = new int[2][4];

405

Pour accder tous les lments du tableau, vous devez utiliser des boucles imbriques :
int [][] x = new int[2][4]; for (int i = 0; i < x.length; i++) { for (int j = 0; j < x[i].length; j++) { x[i][j] = 10; } }

Ces exemples mettent en vidence la vritable nature des tableaux multidimensionnels. Il s'agit en fait de tableaux de tableaux. Par consquent, il est possible de crer des tableaux de forme quelconque, par exemple des tableaux triangulaires :
int [][] x = {{1}, {2, 3}, {4, 5, 6}};

ou en losange :
int [][] x = {{1}, {2, 3}, {4, 5, 6}}, {7, 8}, {9}};

En fait, la syntaxe :
int [][] x = new int[4][5];

cre un tableau de quatre tableaux. Ces quatre tableaux ont tous cinq lments. Si vous voulez utiliser cette syntaxe pour crer des tableaux non

406

LE DVELOPPEUR JAVA 2
rectangulaires, il faut initialiser sparment chaque sous-tableau. Pour crer le tableau en triangle de l'exemple prcdent, vous pouvez procder de la faon suivante :
int [][] x = new int[3][]; for (int i = 0; i < 3; i++) { x[i] = new int[i + 1]; }

Vous pouvez, de la mme faon, construire un tableau pyramidal de hauteur 10 :


int [][][] x = new int[10][][]; for (int i = 0; i < 10; i++) { x[i] = new int[i + 1][i + 1]; }

ou encore un tableau ttradrique :


int [][][] x = new int[10][][]; for (int i = 0; i < 10; i++) { x[i] = new int[i + 1][]; for (int j = 0; j < 10; j++) { x[i][j] = new int [j + 1]; } }

Les tableaux et le passage d'arguments


Les tableaux peuvent tre passs comme arguments lors des appels de mthodes, ou utiliss par celles-ci comme valeurs de retour, au mme titre que n'importe quels autres objets : en effet, les arguments passs ou les valeurs retournes ne sont que des handles. Vous devez videmment en

CHAPITRE 10 LES TABLEAUX ET LES COLLECTIONS

407

tenir compte lors de la modification des tableaux (comme pour tous les autres objets d'ailleurs). Si, l'intrieur d'une mthode, vous modifiez un tableau pass en argument, cette modification affecte galement le tableau l'extrieur de la mthode, puisqu'il s'agit du mme objet. Le programme suivant en fait la dmonstration :
class MesAnimaux3 { public static void main(String[] args) { Animal[] menagerie = new Animal[3]; menagerie[0] = new Chien(); menagerie[1] = new Chat(); menagerie[2] = new Canari(); menagerie[0].afficheType(); menagerie[1].afficheType(); menagerie[2].afficheType(); System.out.println(); Animal[] menagerie2 = modifierElments(menagerie); menagerie2[0].afficheType(); menagerie2[1].afficheType(); menagerie2[2].afficheType(); System.out.println(); menagerie[0].afficheType(); menagerie[1].afficheType(); menagerie[2].afficheType(); }

static Animal[] modifierElments(Animal[] a) { for (int i = 0; i < a.length; i++) { a[i] = new Canari(); } return a; } }

408
abstract class Animal { public void afficheType() { System.out.println("Animal"); } } class Chien extends Animal { public void afficheType() { System.out.println("Chien"); } } class Chat extends Animal { public void afficheType() { System.out.println("Chat"); } } class Canari extends Animal { public void afficheType() { System.out.println("Canari"); } }

LE DVELOPPEUR JAVA 2

Ce programme affiche :
Chien Chat Canari Canari Canari Canari Canari Canari Canari

CHAPITRE 10 LES TABLEAUX ET LES COLLECTIONS

409

ce qui met en vidence le fait que le tableau original est modifi par la mthode modifierElments, pour la raison bien simple que, pendant toute l'excution de ce programme, il n'existe qu'un seul tableau (mais trois handles).

Copie de tableaux
Il peut toutefois tre ncessaire de travailler sur une copie d'un tableau en laissant l'original intact. Nous verrons dans un prochain chapitre qu'il est possible d'obtenir une copie d'un tableau, comme d'autres objets, au moyen de techniques conues cet effet. Le cas des tableaux est toutefois un peu particulier. En effet, les tableaux ne possdent d'autres caractristiques spcifiques que leurs nombres d'lments et leurs lments. Il est donc possible de faire une copie d'un tableau en crant un nouveau tableau de mme longueur, et en recopiant un par un ses lments, comme dans l'exemple suivant :
class MesAnimaux4 { public static void main(String[] args) { Animal[] menagerie = new Animal[3]; menagerie[0] = new Chien(); menagerie[1] = new Chat(); menagerie[2] = new Canari(); System.out.println("menagerie:"); menagerie[0].afficheType(); menagerie[1].afficheType(); menagerie[2].afficheType(); System.out.println(); Animal[] menagerie2 = copierTableau(menagerie); System.out.println("menagerie2:"); menagerie2[0].afficheType(); menagerie2[1].afficheType();

410
menagerie2[2].afficheType(); System.out.println();

LE DVELOPPEUR JAVA 2

Animal[] menagerie3 = modifierElments(menagerie); System.out.println("menagerie3:"); menagerie3[0].afficheType(); menagerie3[1].afficheType(); menagerie3[2].afficheType(); System.out.println(); System.out.println("menagerie:"); menagerie[0].afficheType(); menagerie[1].afficheType(); menagerie[2].afficheType(); System.out.println(); System.out.println("menagerie2:"); menagerie2[0].afficheType(); menagerie2[1].afficheType(); menagerie2[2].afficheType(); } static Animal[] modifierElments(Animal[] a) { for (int i = 0; i < a.length; i++) { a[i] = new Canari(); } return a; } static Animal[] copierTableau(Animal[] a) { Animal[] b = new Animal[a.length]; for (int i = 0; i < a.length; i++) { b[i] = a[i]; } return b; } }

CHAPITRE 10 LES TABLEAUX ET LES COLLECTIONS


abstract class Animal { public void afficheType() { System.out.println("Animal"); } }

411

class Chien extends Animal { public void afficheType() { System.out.println("Chien"); } }

class Chat extends Animal { public void afficheType() { System.out.println("Chat"); } }

class Canari extends Animal { public void afficheType() { System.out.println("Canari"); } }

Ce programme affiche :
menagerie: Chien Chat Canari menagerie2: Chien Chat Canari

412
menagerie3: Canari Canari Canari menagerie: Canari Canari Canari menagerie2: Chien Chat Canari

LE DVELOPPEUR JAVA 2

Nous voyons ici que le tableau menagerie2[] est bien une copie du tableau original et non un handle vers le mme tableau, et n'est donc pas modifi lorsque la mthode modifierElments est appele. Notez toutefois qu'il ne s'agit que d'une copie sur un seul niveau. En effet, les handles du tableau menagerie2[] sont bien des copies des handles du tableau original, mais ils pointent vers les mmes lments. La Figure 10.2 met en vidence la situation obtenue aprs la copie.

mnagerie

mnagerie2

0 1 2

Instance de Chien Instance de Chat Instance de Canari

0 1 2

Figure 10.2 : Le rsultat de la copie

CHAPITRE 10 LES TABLEAUX ET LES COLLECTIONS

413

Cette situation serait galement obtenue lors de la copie d'un tableau deux dimensions ou plus si vous ne copiez que le premier niveau. En effet, comme nous l'avons dit prcdemment, les tableaux deux dimensions sont des tableaux de tableaux, les tableaux trois dimensions sont des tableaux de tableaux de tableaux, etc. En fait, la copie de tableaux par ce procd est une vritable copie uniquement dans le cas des tableaux de primitives. Dans le cas des tableaux d'objets, seule la structure du tableau est copie, mais pas les objets qu'il contient.

Les vecteurs
Les tableaux permettent de grer de faon simple et efficace des collections d'objets de mme type. Leur principal inconvnient est que leur taille est fixe. Ce choix est fait pour des raisons d'efficacit. La manipulation des tableaux est beaucoup plus rapide lorsque leur taille est fixe. Cependant, il est trs facile de crer des tableaux taille variable. Il suffit de crer un tableau de taille fixe et de le recopier dans un tableau plus grand chaque fois que cela est ncessaire. Il faudra ventuellement aussi, grer la diminution de la taille du tableau. De plus, il est clair qu'il n'est pas trs efficace d'augmenter la taille de une unit chaque fois qu'un lment doit tre ajout. Il serait prfrable de crer un tableau assez grand, puis d'augmenter sa taille de plusieurs units lorsque l'on approche du remplissage. Java dispose d'un type d'objet qui prend en charge ces oprations sans que vous ayez vous en soucier. Il s'agit du type Vector (en franais : vecteur). Outre le fait que leur manipulation est beaucoup plus lente que celle des tableaux, les vecteurs ont une autre particularit : ils n'existent que pour le type Object. Cela ne pose pas de problme lors de l'affectation d'un objet un vecteur. Java effectue alors un sur-casting exactement comme si vous affectiez un objet quelconque un tableau dclar de type Object[]. Une des consquences de cette particularit est que les vecteurs ne peuvent pas contenir de primitives. Pour contourner cette limitation, il faut utiliser

414

LE DVELOPPEUR JAVA 2
les enveloppeurs, qui sont des classes spcialement conues cet effet : Integer, Boolean, Float, etc. L'autre particularit est que vous devez effectuer un sous-casting explicite pour utiliser les objets contenus dans un vecteur. Cependant, contrairement au cas tudi prcdemment avec les tableaux, ce sous-casting ne pose pas de problme si votre vecteur ne contient que des objets d'un type connu, ce qui est le cas le plus frquent. De plus, Java surveille les souscastings et ne vous laissera pas effectuer une telle opration vers un type ne correspondant pas au type de l'objet concern. Un vecteur peut tre cr de plusieurs faons. En effet, la classe Vector dispose de quatre constructeurs :

Vector() Vector(int initialCapacity) Vector(int initialCapacity, int capacityIncrement) Vector(Collection c)

La premire version cre un vecteur de taille 10 et d'incrment 0. L'incrment d'un vecteur est la quantit dont sa taille sera augmente lorsqu'il sera plein. Si l'incrment est 0, la taille est double. La deuxime version permet de crer un vecteur en indiquant sa capacit initiale, alors que la troisime permet de spcifier l'incrment. La quatrime version cre un vecteur de la taille ncessaire pour contenir les lments de l'objet de type Collection qui lui est pass en argument. Nous parlerons des collections dans une prochaine section. Vous pouvez connatre la longueur d'un vecteur en utilisant l'accesseur (qui, incidemment, aurait d s'appeler getSize). (Souvenez-vous que les variables ne doivent pas tre accessibles directement de l'extrieur, mais seulement travers une mthode spciale appele accesseur. Dans le cas des tableaux, la longueur est directement accessible car il s'agit d'une constante.)

size()

CHAPITRE 10 LES TABLEAUX ET LES COLLECTIONS

415

Vous pouvez galement modifier la taille d'un vecteur tous moments en utilisant la mthode setSize(). La classe Vector dispose de nombreuses autres mthodes permettant de grer la taille et de manipuler les lments. Pour plus de dtails, vous pouvez vous reporter la documentation en ligne fournie avec le JDK.

Note : La classe Vector appartient au package java.util.


L'ajout d'un lment s'effectue au moyen de la mthode addElement(). Pour obtenir un lment correspondant une position, il suffit d'utiliser la mthode elementAt() en fournissant, en paramtre, l'indice de l'lment recherch. Pour modifier un lment, vous devez employer la mthode setElementAt(). L'exemple suivant montre le programme MesAnimaux2 rcrit pour utiliser des vecteurs :

import java.util.*; class MesAnimauxV2 { public static void main(String[] args) { Vector menagerie = new Vector(3); menagerie.addElement(new Chien()); menagerie.addElement(new Chat()); menagerie.addElement(new Canari()); ((Animal)menagerie.elementAt(0)).afficheType(); ((Animal)menagerie.elementAt(1)).afficheType(); ((Animal)menagerie.elementAt(2)).afficheType(); System.out.println(); Vector menagerie2 = modifierElments(menagerie); ((Animal)menagerie2.elementAt(0)).afficheType(); ((Animal)menagerie2.elementAt(1)).afficheType(); ((Animal)menagerie2.elementAt(2)).afficheType(); System.out.println(); ((Animal)menagerie.elementAt(0)).afficheType(); ((Animal)menagerie.elementAt(1)).afficheType();

416
}

LE DVELOPPEUR JAVA 2
((Animal)menagerie.elementAt(2)).afficheType();

static Vector modifierElments(Vector a) { for (int i = 0; i < a.size(); i++) { a.setElementAt(new Canari(), i); } return a; } } abstract class Animal { public void afficheType() { System.out.println("Animal"); } } class Chien extends Animal { public void afficheType() { System.out.println("Chien"); } } class Chat extends Animal { public void afficheType() { System.out.println("Chat"); } } class Canari extends Animal { public void afficheType() { System.out.println("Canari"); } }

Notez le problme pos par le fait que les vecteurs ne contiennent que des objets :

CHAPITRE 10 LES TABLEAUX ET LES COLLECTIONS


((Animal)menagerie.elementAt(0)).afficheType();

417

Nous devons ici effectuer un sous-casting pour pouvoir utiliser la mthode de l'objet contenu par le vecteur. Remarquez que nous ne savons pas de quel type exact il s'agit. Nous effectuons donc un sous-casting vers la classe parente Animal. Faites bien attention aux parenthses. L'oprateur du souscasting est (Animal) . Il est prfix l'objet sous-caster qui, ici, est menagerie.elementAt(0). Il n'est pas ncessaire de placer la rfrence d'objet entre parenthses, car le point (.) a priorit sur l'oprateur de sous-casting. Cette mme priorit oblige placer l'ensemble ((Animal)menagerie.elementAt(0)) entre parenthses. Dans le cas contraire, le point suivant serait pris en compte avant le sous-casting et Java essaierait d'invoquer la mthode afficheType() d'un objet de type Object, ce qui provoquerait une erreur de compilation.

Le type Stack
Le type Stack (pile en franais) correspond un vecteur de type particulier. S'agissant d'une sous-classe de Vector, le type Stack possde les mmes caractristiques que celui-ci, plus un certain nombre de fonctionnalits spcifiques. Le principe d'une pile est d'offrir un accs uniquement au dernier lment ajout, un peu comme dans un distributeur de bonbons, dans lequel les bonbons sont placs dans un tube au fond duquel se trouve un ressort. Le dernier bonbon introduit est le premier disponible. (C'est mme le seul, comme avec la plupart des piles, alors qu'en Java, tous les lments restent disponibles grce leur indice.) Les mthodes spcifiques de la classe Stack sont les suivantes :

boolean empty(), qui renvoie true si la pile est vide. Object peek(), qui renvoie le premier objet disponible sur la pile (le
dernier y avoir t plac), sans le retirer de celle-ci.

Object pop(), qui renvoie le premier objet disponible sur la pile en le


retirant de celle-ci.

418

LE DVELOPPEUR JAVA 2

Object push(Object o), qui place l'objet o sur la pile, o il devient le


premier objet disponible, et renvoie celui-ci.
search(Object o),

int

qui renvoie la premire position de l'objet

argument dans la pile.

Note 1 : N'oubliez jamais que les objets renvoys ou placs sur la pile
sont en fait des handles.

Note 2 : Le type Stack tant implment l'aide d'un vecteur, le premier


objet disponible est en fait le dernier du vecteur. Les Stack ont un seul constructeur sans argument.

Le type BitSet
Le type BitSet est une structure semblable celle d'un vecteur dans lequel chaque lment serait reprsent par un bit. (Pour autant, la classe BitSet ne drive pas de la classe Vector, mais directement de la classe Object.) Les BitSet sont employs pour contenir de faon efficace des indicateurs logiques. Les mthodes qui permettent de consulter les bits renvoient des valeurs de type boolean. La cration d'un BitSet se fait de deux faons :
BitSet() BitSet(int i)

La premire version cre un BitSet de 64 bits. La seconde cre un BitSet du nombre de bits indiqu par l'argument. Par dfaut, tous les bits valent false. La classe BitSet possde (entre autres) les mthodes suivantes :

boolean get(int index) permet de lire la valeur d'un bit. void set(int index) donne au bit d'index i la valeur true.

CHAPITRE 10 LES TABLEAUX ET LES COLLECTIONS

419

void

clear(int index)

donne au bit d'index i la valeur false.

void and(BitSet b) effectue une opration logique ET entre ce BitSet


et l'argument b.

void or(BitSet b) effectue une opration logique OU entre ce BitSet


et l'argument b.

void xor(BitSet b) effectue une opration logique OU exclusif entre


ce BitSet et l'argument b.

int

hashCode() retourne un hashcode pour ce BitSet. Un hashcode est un code numrique unique correspondant une combinaison de bits. Deux BitSet diffrents ont obligatoirement un hashcode diffrent. Toute modification d'un BitSet modifie son hashcode. size()

int

renvoie la taille du BitSet.

Les tables (Map)


Les tables sont des structures dans lesquelles les objets, au lieu d'tre accessibles l'aide d'un index correspondant leur position, le sont au moyen d'une cl. Lorsque vous cherchez une dfinition dans le dictionnaire, peu vous importe qu'elle soit la premire, la dernire ou la 354e. Tout ce qui compte est qu'elle soit celle qui corresponde au mot dont vous voulez connatre le sens. Une structure de type Map permet ainsi de stocker des couples cl/valeur. La notion d'ordre n'y est pas pertinente. En Java, le type Map est une interface qui doit tre implmente par des classes et n'offre donc qu'une structure de mthodes sans dfinitions. Les mthodes dclares dans l'interface Map sont les suivantes :
void clear()

Retire toutes les paires cl/valeur de la structure. Cette mthode peut, au choix, tre dfinie pour excuter l'opration correspondante ou pour renvoyer une condition d'erreur UnsupportedOperationException (opration non supporte).

420
boolean containsKey(Object cl)

LE DVELOPPEUR JAVA 2
Renvoie true si la cl est trouve. Renvoie true si la valeur est trou-

boolean containsValue(Object valeur)

ve (associe une ou plusieurs cls).


Set entries() Renvoie le contenu de la table sous la forme d'un ensemble (Set). Le lien est dynamique. Toute modification apporte l'ensemble est

reflte dans la table. (Les ensembles sont prsents un peu plus loin.)
boolean equals(Object o) Map. Object get(Object key) int hashCode()

Renvoie true si l'objet est gal la structure

Renvoie la valeur correspondant la cl.

Renvoie le hashcode de la structure. Renvoie true si la structure est vide.

boolean isEmpty() Set keySet()

Renvoie l'ensemble des cls sous la forme d'un ensemble. Le lien est dynamique. Toute modification apporte l'ensemble est reflte dans la table.

Ajoute une entre associant la cl et la valeur. Cette mthode peut, au choix, tre dfinie pour excuter l'opration correspondante ou pour renvoyer une condition d'erreur UnsupportedOperationException (opration non supporte). Copie toutes les entres de l'argument. Cette mthode peut, au choix, tre dfinie pour excuter l'opration correspondante ou pour renvoyer une condition d'erreur UnsupportedOperationException (opration non supporte).
Object remove(Object key) void putAll(Map m)

Object put(Object cl, Object valeur)

Supprime l'entre correspondant la cl. Cette mthode peut, au choix, tre dfinie pour excuter l'opration correspondante ou pour renvoyer une condition d'erreur UnsupportedOperationException (opration non supporte).
int size()

Renvoie le nombre d'entres. Renvoie le contenu de la structure sous forme de

Collection values()

collection.

CHAPITRE 10 LES TABLEAUX ET LES COLLECTIONS

421

Les tables ordonnes (SortedMap)


Les tables ordonnes sont des tables dont les lments sont tris dans un ordre croissant. Le tri des lments est bas sur un objet d'un type spcial (Comparator) fourni comme argument lors de la cration de la table ou, dans le cas o cet objet n'est pas fourni, sur la mthode compareTo() des lments (qui doivent, dans ce cas, implmenter l'interface Comparable). L'interface SortedMap dclare les mthodes suivantes :
Comparator comparator()

Renvoie le comparateur utilis pour ordonner la table. Si aucun comparateur n'est utilis (les lments sont ordonns en fonction de leur mthode compareTo()), la mthode renvoie null. Renvoie la premire cl de la table.

Object firstKey()

SortedMap headMap(Object o)

Renvoie une table ordonne contenant les rfrences aux objets strictement infrieurs l'objet argument. Renvoie la dernire cl de la table.

Object lastKey()

SortedMap subMap(Object de, Object )

Renvoie une table ordonne contenant les rfrences aux objets compris entre l'objet de (inclus) et l'objet (exclu).
SortedMap tailMap(Object o)

Renvoie une table ordonne contenant les rfrences aux objets suprieurs ou gaux l'objet argument.

Les classes implmentant l'interface Map sont : AbstractMap, HashMap, HashTable et Attributes. L'interface SortedMap est implmente par la classe TreeMap.

Le type Hashtable
Le type Hashtable est une implmentation de l'interface Map. Dans cette structure, le hashcode des objets est utilis pour acclrer l'accs aux entres. Seuls les objets dfinissant la mthode hashcode() et la mthode

422

LE DVELOPPEUR JAVA 2
equals() peuvent figurer dans une Hashtable. Un handle est null ne peut donc tre plac dans cette structure.

dont la rfrence

Une Hashtable peut tre cre de quatre faons :


public public public public Hashtable(int capacitInitiale, float facteurDeRehashing) Hashtable(int initialCapacity) Hashtable() Hashtable(Map t)

La premire version cre une Hashtable avec la capacit initiale et le facteur de rehashing spcifis. Le facteur de rehashing est une valeur indiquant partir de quel taux de remplissage la table est agrandie. Cette valeur doit tre comprise entre 0 et 1. Pour une valeur de 0.7, par exemple, la table sera agrandie ds qu'elle sera remplie 70 %. L'agrandissement de la table ncessite un recalcul des hashcodes. La deuxime version cre une Hashtable avec la capacit initiale indique et un facteur de rehashing de 0.75. La troisime version cre une table de la capacit par dfaut et un facteur de rehashing de 0.75. La quatrime version cre une table contenant les entres de la structure Map fournie en argument.

Les collections
Le terme de collection dsigne de faon gnrique un ensemble d'objets. En Java, il est courant de traiter un objet comme un reprsentant d'une classe ou d'une interface parente. L'interface Collection permet de traiter ainsi des ensembles gnriques. Les ensembles d'objets peuvent tre soumis des contraintes. Si les objets sont ordonns, il s'agit de List. Si les entres sont uniques, il s'agit de Set. Si les deux contraintes sont runies, il

CHAPITRE 10 LES TABLEAUX ET LES COLLECTIONS

423

s'agit de SortedSet. Enfin, une collection sans contraintes est appele Bag (sac). L'interface Collection dclare les mthodes suivantes : Ajoute l'objet o la collection. Retourne true si la collection est modifie par l'opration, false dans le cas contraire. La dfinition de cette mthode dans les classes implmentant l'interface peut limiter les possibilits d'ajout d'un lment, par exemple en interdisant certains types d'objets, ou la duplication (cas des Set). Cette mthode peut, au choix, tre dfinie pour excuter l'opration correspondante ou pour renvoyer une condition d'erreur UnsupportedOperationException (opration non supporte).
boolean addAll(Collection c) Cette mthode est identique la prcdente, mais prend pour argument une seconde collection dont tous les objets sont ajouts la premire. void clear() boolean add(Object o)

Retire tous les lments de la collection. Cette mthode peut, au choix, tre dfinie pour excuter l'opration correspondante ou pour renvoyer une condition d'erreur UnsupportedOperationException (opration non supporte).
boolean contains(Object o)

Renvoie true si l'objet est contenu dans la

collection.
boolean containsAll(Collection c) Mthode identique la prcdente, mais prenant pour argument une collection. Renvoie true si tous les objets de la collection argument sont prsents dans la collection contenant la mthode. boolean equals(Object o) int hashCode()

Renvoie true si l'objet est gal la collection.

Calcule un hashcode pour cette collection. Renvoie true si la collection est vide.

boolean isEmpty()

Iterator iterator()

Renvoie un Iterator sur les lments de la collection. Les itrateurs sont prsents dans une prochaine section.

boolean remove(Object o) Supprime la premire instance de l'objet dans la collection. Renvoie true si la collection est modifie par l'opration. Cette

424

LE DVELOPPEUR JAVA 2
mthode peut, au choix, tre dfinie pour excuter l'opration correspondante ou pour renvoyer une condition d'erreur UnsupportedOperationException (opration non supporte).
boolean removeAll(Collection c)

Supprime de la collection contenant la mthode toutes les instances de tous les lments prsents dans la collection argument. Renvoie true si la collection est modifie par l'opration. Le rsultat de cette mthode est que les deux collections ne contiennent plus aucun lment commun. Cette mthode peut, au choix, tre dfinie pour excuter l'opration correspondante ou pour renvoyer une condition d'erreur UnsupportedOperationException (opration non supporte).

boolean retainAll(Collection c)

Supprime de la collection contenant la mthode toutes les instances de tous les lments absents de la collection argument. Renvoie true si la collection est modifie par l'opration. Le rsultat de cette mthode est que la collection appelante ne contient plus qu'un sous-ensemble des lments de la collection argument. Cette mthode peut, au choix, tre dfinie pour excuter l'opration correspondante ou pour renvoyer une condition d'erreur UnsupportedOperationException (opration non supporte).
int size()

Renvoie le nombre d'lments dans la collection. Renvoie un tableau contenant tous les lments de la

Object[] toArray()

collection.
Object[] toArray(Object[] a)

Renvoie un tableau contenant tous les lments de la collection dont le type correspond l'argument. Le rsultat est plac dans le tableau argument si celui-ci est assez grand. Dans le cas contraire, un tableau de mme type est cr.

Les listes (List)


Une liste est semblable un vecteur ordonn, c'est--dire qu'un lment peut tre ajout n'importe quelle position, et non seulement la fin (comme dans un vecteur) ou au dbut (comme dans une pile). La structure List est ralise, en Java, l'aide d'une interface tendant l'interface Collection et ajoutant quatre mthodes nouvelles ainsi que des versions supplmentaires de certaines mthodes existantes :

CHAPITRE 10 LES TABLEAUX ET LES COLLECTIONS


void add(int index, Object o)

425

Insre l'objet la position indique. Cette mthode peut, au choix, tre dfinie pour excuter l'opration correspondante ou pour renvoyer une condition d'erreur UnsupportedOperationException (opration non supporte).
boolean addAll(int index, Collection c) Insre tous les lments de la collection partir de la position indique. Cette mthode peut, au choix, tre dfinie pour excuter l'opration correspondante ou pour renvoyer une condition d'erreur UnsupportedOperationException (opration non supporte). Object get(int index) int indexOf(Object o)

Renvoie l'objet se trouvant la position indique.

Renvoie l'index de la premire occurrence de l'objet argument, ou -1 si l'objet est absent.


int indexOf(Object o, int index)

Renvoie l'index de la premire occurrence de l'objet argument partir de l'index indiqu, ou -1 si aucune occurrence de l'objet n'est trouve partir de cette position.

int lastIndexOf(Object o)

Renvoie l'index de la dernire occurrence de l'objet argument, ou -1 si l'objet est absent.

int lastIndexOf(Object o, int index) Renvoie l'index de la dernire occurrence de l'objet argument avant la position indique, ou - 1 si l'objet est absent. ListIterator listIterator()

Renvoie un itrateur de type ListIterator sur les lments de la liste. Les itrateurs sont prsents dans une prochaine section.
ListIterator listIterator(int index) Renvoie un itrateur de type ListIterator sur les lments de la liste partir de l'lment indiqu. Object remove(int index)

Renvoie l'objet se trouvant la position indique en le supprimant de la liste.


void removeRange(int de, int ) Retire de la liste tous la position est comprise entre de (inclus) et (exclu).

les lments dont

426

LE DVELOPPEUR JAVA 2
L'interface List est implmente par les classes AbstractList, LinkedList, Vector et ArrayList.

Les ensembles (Set)


Un ensemble est une collection non ordonne qui ne peut contenir qu'un seul exemplaire de chacun de ses lments. L'unicit des objets est base sur la valeur renvoye par la mthode equals(). Deux objets o1 et o2 ne peuvent se trouver dans le mme ensemble si o1.equals(o2) renvoie True. De plus, un ensemble ne peut contenir qu'un seul handle s'valuant null. La notion d'un seul exemplaire est quelque peu trompeuse. En effet, la mthode equals() de la classe Object renvoie true si et seulement si les deux handles compars font rfrence au mme objet. En revanche, cette mthode peut tre redfinie dans d'autres classes. La mthode equals() peut tre redfinie de faon quelconque condition de respecter les rgles suivantes :

Elle doit tre rflexive : pour tout x, x.equals(x) doit renvoyer true. Elle doit tre symtrique : quels que soient x et y, si x.equals(x) renvoie true, alors y.equals(x) doit renvoyer true.

Elle doit tre transitive : quels que soient x, y et z, si x.equals(y) Elle doit tre cohrente : quels que soient x et y, x.equals(y) doit
toujours renvoyer la mme valeur.

renvoie true et si y.equals(z) renvoie true, alors x.equals(z) doit renvoyer true.

Quel que soit x, x.equals(null) doit renvoyer false.


Les objets dont la valeur peut changer (relativement leur mthode equals()) posent un problme particulier. Si la valeur d'un tel objet est modifie pendant que l'objet fait partie de l'ensemble, l'tat de l'ensemble devient indfini. L'interface Set est implmente par les classes AbstractSet et HashSet.

CHAPITRE 10 LES TABLEAUX ET LES COLLECTIONS

427

Les ensembles ordonns (SortedSet)


Les SortedSet sont des ensembles dont les lments sont ordonns. Le tri des lments est bas sur un objet d'un type spcial (Comparator) fourni comme argument lors de la cration de l'ensemble, ou, dans le cas o cet objet n'est pas fourni, sur la mthode compareTo() des lments (qui doivent, dans ce cas, implmenter l'interface Comparable). L'interface SortedSet dclare les mthodes suivantes :
Comparator comparator()

Renvoie le comparateur utilis pour ordonner l'ensemble. Si aucun comparateur n'est utilis (les lments sont ordonns en fonction de leur mthode compareTo()), la mthode renvoie null. Renvoie le premier lment de l'ensemble.

Object first()

SortedSet headSet(Object o) Renvoie un SortedSet contenant les rfrences aux objets strictement infrieurs l'objet argument. Object last()

Renvoie le dernier objet de l'ensemble.

SortedSet subSet(Object de, Object ) Renvoie un sous-ensemble contenant les rfrences aux objets compris entre l'objet de (inclus) et l'objet (exclu). SortedSet tailSet(Object o)

Renvoie un SortedSet contenant les rfrences aux objets suprieurs ou gaux l'objet argument. L'interface SortedSet est implmente par la classe TreeSet.

Les itrateurs
Il est parfois utile de traiter une Collection de faon gnrique, sans se proccuper de savoir s'il s'agit d'une liste, d'une pile, d'un vecteur ou d'un ensemble. On utilise pour cela un Itrateur, reprsent par l'interface Iterator. L'interface Iterator dclare trois mthodes :

428
boolean hasNext() Object next() void remove()

LE DVELOPPEUR JAVA 2
Renvoie true si l'itrateur contient encore des lments.

Renvoie l'lment suivant. Supprime de la collection le dernier lment renvoy par

l'itrateur. Un itrateur n'est pas une copie d'une collection, mais seulement une vue diffrente. Toute modification apporte un objet de l'itrateur est reflte dans la collection correspondante. Le programme suivant montre un exemple d'utilisation d'un itrateur :
import java.util.*; class Iterateur { public static void main(String[] args) { Vector menagerie = new Vector(5); menagerie.addElement(new menagerie.addElement(new menagerie.addElement(new menagerie.addElement(new menagerie.addElement(new Chien()); Chat()); Canari()); Chat()); Chien());

Iterator it = menagerie.iterator(); while (it.hasNext()) { ((Animal)it.next()).afficheType(); } } } abstract class Animal { public void afficheType() { System.out.println("Animal"); } }

CHAPITRE 10 LES TABLEAUX ET LES COLLECTIONS


class Chien extends Animal { public void afficheType() { System.out.println("Chien"); } } class Chat extends Animal { public void afficheType() { System.out.println("Chat"); } } class Canari extends Animal { public void afficheType() { System.out.println("Canari"); } }

429

L'avantage des itrateurs est qu'ils permettent d'accder tous les lments d'une collection sans se proccuper du type rel de celle-ci. De cette faon, il est possible de modifier le type de structure utilis pour stocker les donnes dans un programme en limitant au minimum les modifications apporter celui-ci, et en localisant ces modifications un seul endroit : celui o la structure est manipule. En revanche, la manipulation des lments de la structure n'est pas remise en question, ce qui facilite beaucoup la maintenance des programmes.

Les itrateurs de listes


Le type List dispose d'un itrateur particulier de type ListIterator. Cette interface dfinit des mthodes supplmentaires permettant (entre autres) de parcourir la liste en ordre descendant : Insre l'objet argument dans la liste immdiatement avant l'lment renvoy par next() et aprs l'lment renvoy par previous(). L'indice n'est pas modifi, ce qui fait qu'aprs l'insertion, getNext() renvoie l'lment qui vient d'tre insr.
void add(Object o)

430
boolean hasNext()

LE DVELOPPEUR JAVA 2
Comme dans Iterator. Renvoie true s'il existe un lment prcdent.

boolean hasPrevious() Object next()

Comme dans Iterator.

int nextIndex() Renvoie l'index de l'lment qui serait renvoy par le prochain appel de next(), ou -1 s'il n'existe aucun lment suivant. Object previous()

Renvoie l'lment prcdent.

int previousIndex() Renvoie l'index de l'lment qui serait renvoy par le prochain appel de previous(), ou -1 s'il n'existe aucun lment prcdent. void remove() Supprime de la liste l'lment renvoy par le dernier appel de next() ou previous(). void set(Object o) Remplace l'lment renvoy next() ou previous() par l'objet argument.

par le dernier appel de

Les comparateurs
Les structures ordonnes classent leurs lments de deux faons : selon leur ordre naturel, c'est--dire en utilisant le rsultat de leur mthode compareTo(), ou l'aide d'un comparateur, c'est--dire d'un objet implmentant l'interface Comparator.

Note : Pour que des objets puissent tre classs selon l'ordre naturel, il est ncessaire qu'ils implmentent l'interface Comparable, qui dclare la mthode compareTo(). Ils doivent videmment aussi dfinir cette mthode de faon fournir soit un ordre total (il ne peut y avoir d'galit) soit un ordre partiel.
L'utilit des comparateurs vient de ce que l'ordre naturel ne convient pas toujours certaines structures. Par exemple, certains objets peuvent disposer d'une mthode compareTo() donnant un ordre partiel alors qu'une structure peut ncessiter un ordre total.

CHAPITRE 10 LES TABLEAUX ET LES COLLECTIONS


La mthode :
int compareTo(Object o)

431

renvoie une valeur ngative si l'argument est plus petit, nulle s'il est gal et positive s'il est plus grand. Il faut noter que si :
x.equals(y) == true

alors :
x.compareTo(y) == 0

En revanche :
x.compareTo(y) == 0

n'implique pas :
x.equals(y) == true

Si on peut avoir x.compareTo(y) == 0 et x.equals(y) == false, l'ordre n'est pas total. Dans ce cas, si l'on veut un ordre total (ou si l'on veut simplement utiliser un critre de tri diffrent), il faut utiliser un comparateur. L'interface Comparator dclare simplement la mthode :
int compare(Object o1, Object o2)

Cette mthode doit renvoyer une valeur ngative pour indiquer que le premier argument est plus petit que le second, une valeur nulle si les deux

432

LE DVELOPPEUR JAVA 2
arguments sont gaux et une valeur positive si le second est plus petit que le premier. De plus, elle doit satisfaire aux conditions suivantes :

Quels que soient x et y :


sgn(compare(x, y)) == -sgn(compare(y, x))

Quels que soient x, y et z :


((compare(x, y) > 0) && (compare(y, z)>0))

implique :
compare(x, z) > 0

Quels que soient x et y :


x.equals(y) || (x == null && y == null)

implique :
compare(x, y) == 0

Quels que soient x, y et z :


compare(x, y) == 0

implique :
sgn(compare(x, z)) == sgn(compare(y, z))

CHAPITRE 10 LES TABLEAUX ET LES COLLECTIONS

433

Le programme suivant montre un exemple de TreeSet, structure implmentant l'interface SortedSet, cr en appelant le constructeur prenant un Comparator comme argument. L'ensemble (TreeSet) reoit des caractres qui doivent tre tris en ordre alphabtique sans tenir compte des majuscules ou des signes diacritiques (cdilles et accents). L'interface Comparator est implmente par la classe Comparaison. La mthode compare() utilise une version modifie de la classe Conversion (dveloppe dans un chapitre prcdent) pour convertir les caractres en majuscules non accentues. Notez que cette mthode est incomplte car elle ne prend pas en compte le cas o les arguments (qui sont des objets quelconques) ne sont pas des instances de la classe Lettre. Le traitement d'erreur dans ce cas fait appel des techniques que nous n'avons pas encore abordes. La classe Lettre est un enveloppeur pour le type char car les ensembles, comme les autres structures de donnes l'exception des tableaux, ne peuvent contenir que des objets. Nous aurions pu tout aussi bien utiliser la classe Character qui fait partie du package java.lang. La classe Lettre contient une mthode supplmentaire permettant de convertir les caractres Unicode utiliss par Java en caractres ASCII tendus permettant l'affichage dans une fentre DOS.

import java.util.*; class Comparateur { public static void main(String[] args) { TreeSet lettres = new TreeSet(new Comparaison()); lettres.add(new lettres.add(new lettres.add(new lettres.add(new lettres.add(new lettres.add(new lettres.add(new lettres.add(new lettres.add(new Lettre('C')); Lettre('')); Lettre('S')); Lettre('u')); Lettre('c')); Lettre('')); Lettre('e')); Lettre('R')); Lettre(''));

Iterator it = lettres.iterator();

434

LE DVELOPPEUR JAVA 2
while (it.hasNext()) { ((Lettre)it.next()).affiche(); } } } class Lettre { char valeur; Lettre (char v) { valeur = v; } public void affiche() { System.out.println(win2DOS(valeur)); } public static char win2DOS(char c) { switch (c) { case '': return (char)133; case '': return (char)131; case '': return (char)132; case '': return (char)130; case '': return (char)138; case '': return (char)137; case '': return (char)136; case '': return (char)139; case '': return (char)140; case '': return (char)147;

CHAPITRE 10 LES TABLEAUX ET LES COLLECTIONS


case '': return (char)148; case '': return (char)129; case '': return (char)150; case '': return (char)151; case '': return (char)135; } return c; } }

435

class Comparaison implements Comparator { int retval = 0; public int compare(Object o1, Object o2) { if ((o1 instanceof Lettre) && (o2 instanceof Lettre)) { if (Conversion.conv(((Lettre)o1).valeur) < Conversion.conv(((Lettre)o2).valeur)) retval = -1; else if (Conversion.conv(((Lettre)o1).valeur) > Conversion.conv(((Lettre)o2).valeur)) retval = 1; } else { // traitement d'erreur } return retval; } } class Conversion { static int conv (char c) { switch (c) { case '': return 'A' * 10 + 2;

436
case '': return 'A' * 10 + 3; case '': return 'A' * 10 + 4; case '': return 'E' * 10 + 2; case '': return 'E' * 10 + 3; case '': return 'E' * 10 + 4; case '': return 'E' * 10 + 5; case '': return 'I' * 10 + 2; case '': return 'I' * 10 + 3; case '': return 'O' * 10 + 2; case '': return 'O' * 10 + 3; case '': return 'U' * 10 + 2; case '': return 'U' * 10 + 3; case '': return 'U' * 10 + 4; case '': return 'C' * 10 + 2; default: if (c > 96)

LE DVELOPPEUR JAVA 2

return (c - 32) * 10 + 1; else return c * 10; } } }

CHAPITRE 10 LES TABLEAUX ET LES COLLECTIONS

437

Note : Ce programme n'est pas d'une grande utilit. En effet, la classe


TreeSet

n'est suppose fonctionner correctement que si l'ordre naturel ou celui fourni par le comparateur est total (deux lments ne peuvent tre gaux). Il n'est donc pas possible d'utiliser un comparateur renvoyant une galit (0) pour les caractres avec et sans accent, ou majuscules et minuscules. Si vous utilisez un comparateur ne fournissant pas un ordre total, les rsultats seront incohrents. Certains objets seront ajouts alors qu'un objet gal est dj prsent, d'autres non. Nous verrons plus loin comment effectuer des tris avec un ordre partiel. La version suivante du programme donne le mme rsultat en utilisant l'ordre naturel, c'est--dire celui fourni par la mthode compareTo() des objets ajouts la structure :

import java.util.*; class OrdreNaturel { public static void main(String[] args) { TreeSet lettres = new TreeSet(); lettres.add(new lettres.add(new lettres.add(new lettres.add(new lettres.add(new lettres.add(new lettres.add(new lettres.add(new lettres.add(new Lettre('r')); Lettre('')); Lettre('S')); Lettre('u')); Lettre('c')); Lettre('')); Lettre('e')); Lettre('R')); Lettre(''));

Iterator it = lettres.iterator(); while (it.hasNext()) { ((Lettre)it.next()).affiche(); } } }

438
class Lettre implements Comparable { char valeur; Lettre (char v) { valeur = v; } public void affiche() {

LE DVELOPPEUR JAVA 2

System.out.println(win2DOS(valeur)); } public int compareTo(Object o) { int retval = 0; if (o instanceof Lettre) { if (Conversion.conv(((Lettre)o).valeur) < Conversion.conv(valeur)) retval = 1; else if (Conversion.conv(((Lettre)o).valeur) > Conversion.conv(valeur)) retval = -1; } else { // traitement d'erreur } return retval; } public static char win2DOS(char c) { // identique la version prcdente } }

class Conversion { static char conv (char c) { // identique la version prcdente } }

CHAPITRE 10 LES TABLEAUX ET LES COLLECTIONS

439

Les mthodes de la classe Collections


La classe java.util.Collections ( ne pas confondre avec l'interface java.util.Collection, avec laquelle il n'existe aucun lien hirarchique) contient dix-neuf mthodes statiques qui manipulent ou renvoient des Collection. Les mthodes qui renvoient des collections sont appeles vues car elles oprent sur des collections dont elles renvoient une vue dtermine en fonction d'une caractristique particulire. Il n'y a en effet jamais de duplication des lments des collections manipules. Recherche la cl dans la liste en utilisant l'algorithme de recherche dichotomique. Toutes les contraintes de cet algorithme s'appliquent ici. En particulier, la liste doit tre trie. L'utilisation d'une liste non trie entrane un rsultat imprvisible et peut conduire une boucle infinie. Par ailleurs, si la liste contient des objets gaux (selon le critre employ par leur mthode compareTo()), l'objet trouv est celui dont le chemin de recherche produit par l'algorithme de recherche dichotomique est le plus court, ce qui ne garantit en aucun cas qu'il s'agisse du premier. Pour une liste indexe de n lments (un vecteur, par exemple), cette mthode offre un temps d'accs proportionnel log(n) (quasi constant pour les valeurs leves de n).
static int binarySearch(List liste, Object cl, Comparator c) Effectue la mme recherche que la mthode prcdente, mais en utilisant un Comparator au lieu de la mthode compareTo() des objets de la liste. static int binarySearch(List liste, Object cl)

Produit une numration partir de la collection argument. Une numration est une instance de l'interface Enumeration, qui possde des fonctionnalits rduites de l'interface Iterator. L'interface Iterator a remplac Enumeration partir de la version 2 de Java. Cette mthode n'est donc utile que pour les besoins de la compatibilit avec les versions prcdentes.
static Object max(Collection c) Renvoie l'objet maximal contenu dans la collection en utilisant l'ordre naturel, c'est--dire celui fourni par la mthode compareTo() des objets de la collection.

static Enumeration enumeration(Collection c)

440

LE DVELOPPEUR JAVA 2
static Object max(Collection c, Comparator comp)

Renvoie l'objet maximal contenu dans la collection en utilisant l'ordre impliqu par la mthode compare() du comparateur.
static Object min(Collection c)

Renvoie l'objet minimal contenu dans la collection en utilisant l'ordre naturel, c'est--dire celui fourni par la mthode compareTo() des objets de la collection.

static Object min(Collection c, Comparator comp)

Renvoie l'objet minimal contenu dans la collection en utilisant l'ordre impliqu par la mthode compare() du comparateur.
static List nCopies(int n, Object o) compose de n occurrences de l'objet o. static void sort(List liste)

Renvoie une liste non modifiable

Trie les lments de la liste en fonction de leur ordre naturel. Il s'agit d'un tri stable, c'est--dire que l'ordre des lments gaux n'est pas modifi.
static void sort(List liste, Comparator c) Trie les lments de la liste en fonction de l'ordre impliqu par la mthode compare() du comparateur.

Il s'agit d'un tri stable, c'est--dire que l'ordre des lments gaux n'est pas modifi.
static List subList(List list, int de, int ) Renvoie pose des lments d'indice de (inclus) (exclu).

une liste com-

static Collection synchronizedCollection(Collection c)

Renvoie une collection synchronise contenant les mmes lments que la collection argument. Une collection synchronise est protge par le fait qu'elle ne peut tre utilise que par un seul processus la fois, ce qui garantit qu'elle ne sera pas modifie pendant qu'une opration est en cours. Les processus seront tudis dans un prochain chapitre. Renvoie une liste synchronise contenant les mmes lments que la liste argument.

static List synchronizedList(List liste)

static Map synchronizedMap(Map m)

Renvoie une structure Map synchronise contenant les mmes lments que la structure Map argument.

CHAPITRE 10 LES TABLEAUX ET LES COLLECTIONS


static Set synchronizedSet(Set s)

441

Renvoie un ensemble synchronis contenant les mmes lments que l'ensemble argument.

static SortedMap synchronizedSortedMap(SortedMap m) Renvoie une structure SortedMap synchronise contenant les mmes lments que la structure SortedMap argument. static SortedSet synchronizedSortedSet(SortedSet s) Renvoie un ensemble ordonn synchronis contenant les mmes lments que l'ensemble ordonn argument. static Collection unmodifiableCollection(Collection c)

Renvoie une

version non modifiable de l'argument.


static List unmodifiableList(List list)

Renvoie une version non mo-

difiable de l'argument.
static Map unmodifiableMap(Map m)

Renvoie une version non modifiable

de l'argument.
static Set unmodifiableSet(Set s)

Renvoie une version non modifiable

de l'argument.
static SortedMap unmodifiableSortedMap(SortedMap m)

Renvoie une ver-

sion non modifiable de l'argument.


static SortedSet unmodifiableSortedSet(SortedSet s)

Renvoie une ver-

sion non modifiable de l'argument.


Collections possde galement le champ static REVERSE_ORDER, de type Comparator permettant de trier une collection d'objets implmentant l'interface Comparable en ordre inverse de l'ordre naturel.

Note : La classe

Exemple: utilisation de la mthode sort()


L'exemple prcdent de tri des caractres peut maintenant tre ralis de faon complte en utilisant un comparateur et la mthode sort() de la classe Collections :

442

LE DVELOPPEUR JAVA 2
import java.util.*; class Tri { public static void main(String[] args) { Vector lettres = new Vector(); lettres.add(new Lettre('C')); lettres.add(new Lettre('')); lettres.add(new Lettre('S')); lettres.add(new Lettre('u')); lettres.add(new Lettre('c')); lettres.add(new Lettre('')); lettres.add(new Lettre('e')); lettres.add(new Lettre('R')); lettres.add(new Lettre('')); Collections.sort(lettres, new Comparaison()); Iterator it = lettres.iterator(); while (it.hasNext()) { ((Lettre)it.next()).affiche(); } } } class Lettre { char valeur; Lettre (char v) { valeur = v; } public void affiche() { System.out.println(win2DOS(valeur)); } public static char win2DOS(char c) { switch (c) { case '': return (char)133; case '': return (char)131; case '': return (char)132;

CHAPITRE 10 LES TABLEAUX ET LES COLLECTIONS


case '': return case '': return case '': return case '': return case '': return case '': return case '': return case '': return case '': return case '': return case '': return case '': return } return c; } }

443

(char)130; (char)138; (char)137; (char)136; (char)139; (char)140; (char)147; (char)148; (char)129; (char)150; (char)151; (char)135;

class Comparaison implements Comparator { int retval = 0; public int compare(Object o1, Object o2) { if ((o1 instanceof Lettre) && (o2 instanceof Lettre)) { if (Conversion.conv(((Lettre)o1).valeur) < Conversion.conv(((Lettre)o2).valeur)) retval = -1; else if (Conversion.conv(((Lettre)o1).valeur) > Conversion.conv(((Lettre)o2).valeur)) retval = 1; }

444
else { // traitement d'erreur } return retval; } } class Conversion { static char conv (char c) { switch (c) { case '': case '': case '': return 'A'; case '': case '': case '': case '': return 'E'; case '': case '': return 'I'; case '': case '': return 'O'; case '': case '': case '': return 'U'; case '': return 'C'; default: if (c > 96) return (char)(c - 32); else return c; } } }

LE DVELOPPEUR JAVA 2

CHAPITRE 10 LES TABLEAUX ET LES COLLECTIONS

445

Rsum
Dans ce chapitre, nous n'avons fait qu'effleurer le sujet des structures de donnes, qui mriterait un livre lui tout seul. Si vous souhaitez plus d'informations sur les classes et les interfaces disponibles pour la manipulation de structures complexes, nous vous conseillons de vous reporter la documentation en ligne fournie avec le JDK.

Chapitre 11 : Les objets meurent aussi

Les objets meurent aussi

1 1

U CHAPITRE 5, NOUS AVONS TUDI TOUT CE QUI CONCERNE le commencement de la vie des objets. Nous allons maintenant nous intresser ce qui se passe l'autre extrmit de la chane, lorsque les objets cessent leur existence.

Certains objets deviennent inaccessibles


Comme nous l'avons dj dit, les objets sont crs dans une partie de la mmoire appele tas (heap). Au moment de leur cration, les objets sont

448

LE DVELOPPEUR JAVA 2
crs en mme temps qu'une rfrence permettant de les manipuler. Cette rfrence peut tre un handle, comme dans le cas suivant :

Object monObjet; monObjet = new Object();

L'objet peut galement tre rfrenc par un lment d'une structure comme dans le cas suivant :

Vector monVecteur; monVecteur.addElement(new Object());

Ici, aucun handle n'est cr. La rfrence l'objet est tout simplement un des lments du vecteur (le dernier au moment de la cration). Un objet peut tre cr sans qu'une rfrence explicite soit employe. La rfrence peut alors tre dfinie dans un autre objet :
TreeSet lettres = new TreeSet(new Comparaison());

Ici, un objet de type TreeSet est cr et rfrenc par le handle lettres. En revanche, un objet de type Comparaison est cr sans rfrence. Cet objet est pass en argument au constructeur de la classe TreeSet. A ce moment, l'objet en question se trouve rfrenc par le handle dclar comme argument du constructeur. Nous savons qu'il existe une version du constructeur de TreeSet ayant pour argument un handle pour un objet de type Comparaison ou d'un type parent (en l'occurrence Comparator). L'objet ainsi cr peut galement tre pass une mthode. Dans ce cas, il se trouve rfrenc par le handle correspondant dclar dans les arguments de la mthode :
Collections.sort(lettres, new Comparaison());

CHAPITRE 11 LES OBJETS MEURENT AUSSI

449

La question qui peut se poser ici est de savoir ce que deviennent ensuite ces objets. Dans le premier cas, l'objet existe de faon vidente tant que le handle correspondant lui est affect. En revanche, considrez l'exemple suivant :

Object monObjet; Object nouvelObjet; monObjet = new Object(); nouvelObjet = new Object(); monObjet = nouvelObjet;

Au moment o le handle monObjet est affect l'objet point par le handle nouvelObjet, le premier objet devient inaccessible. La Figure 11.1 rsume la situation. partir de ce moment, le premier objet cr, qui n'est plus rfrenc par aucun handle, n'est plus accessible. Pour autant, il n'est pas supprim de la mmoire. Il reste simplement sa place, en attendant qu'on s'occupe de lui. Dans le deuxime exemple :

TreeSet lettres = new TreeSet(new Comparaison());

on ne peut pas priori savoir ce que devient l'objet cr. Pour le savoir, il faut consulter les sources de la classe TreeSet (livres avec le JDK), puis celles de la classe TreeMap, ce qui nous amne au code suivant :
public class TreeMap extends AbstractMap implements SortedMap, Cloneable, java.io.Serializable { private Comparator comparator = null; . . .

450
Object monObjet; Object nouvelObjet; monObjet = new Object(); nouvelObjet = new Object();

LE DVELOPPEUR JAVA 2

monObjet

nouvelObjet

Objet

Objet

monObjet = nouvelObjet;

monObjet

nouvelObjet

Objet

Objet

Figure 11.1 : Lorsque le handle monObjet est affect l'objet point par le handle nouvelObjet, le premier objet devient inaccessible.

public TreeMap(Comparator c) { this.comparator = c; . .

Nous savons maintenant que l'objet de type Comparaison cr est affect un membre de l'objet TreeSet rfrenc par le handle lettres.

CHAPITRE 11 LES OBJETS MEURENT AUSSI

451

Que se passe-t-il si le lien entre le handle lettres et l'objet correspondant est coup, par exemple dans le cas suivant :
lettres = null;

La Figure 11.2 reprsente la situation correspondante.

TreeSet lettres = new TreeSet(new Comparaison());


lettres

Objet instance de TreeSet (et de TreeMap) handle comparator Objet instance de Comparaison (et de Comparator)

lettres = null;
lettres
null

Objet instance de TreeSet (et de TreeMap) handle comparator Objet instance de Comparaison (et de Comparator)

Figure 11.2 : Le lien entre le handle lettres et l'objet correspondant est coup.

452

LE DVELOPPEUR JAVA 2
L'objet de type TreeSet n'est plus rfrenc et n'est donc plus accessible. L'objet de type Comparaison est toujours rfrenc. Il n'est cependant plus utilisable puisque l'objet qui le rfrence ne l'est pas lui-mme. Notez qu'une situation diffrente peut se produire si nous crivons le code de la faon suivante :
Comparaison comparateur = new Comparaison(); TreeSet lettres = new TreeSet(comparateur); lettres = null;

Dans ce cas, la situation est celle dcrite par la Figure 11.3. Dans ce cas, l'objet de type TreeSet n'est plus accessible. En revanche, l'objet de type Comparaison l'est toujours. Dans le troisime cas :
Collections.sort(lettres, new Comparaison());

l'objet de type Comparaison est pass la mthode statique sort de la classe Collections. Si nous examinions le code source de cette mthode, nous verrions que l'objet est affect un handle ( c) puis pass en argument la mthode statique sort de la classe Arrays (aprs que lettres a t converti en tableau). L'objet est ensuite pass la mthode MergeSort qui, elle, ne le passe aucune autre mthode. Aucune de ces mthodes ne retourne l'objet. Lorsque Collections.sort retourne, l'objet n'est donc plus accessible.

Que deviennent les objets inaccessibles?


La question est importante. En effet, ces objets occupent de la place en mmoire. Si l'on ny prend garde, la mmoire risque de devenir entirement pleine, ce qui entranerait un blocage de l'ordinateur.

CHAPITRE 11 LES OBJETS MEURENT AUSSI

453

Comparaison comparateur = new Comparaison();

comparateur

Objet instance de Comparaison (et de Comparator)

TreeSet lettres = new TreeSet(new Comparaison());

lettres

comparateur

Objet instance de TreeSet (et de TreeMap) handle comparator Objet instance de Comparaison (et de Comparator)

lettres = null;

lettres
null

comparateur

Objet instance de TreeSet (et de TreeMap) handle comparator Objet instance de Comparaison (et de Comparator)

Figure 11.3 : L'objet de type TreeSet n'est plus accessible.

454

LE DVELOPPEUR JAVA 2
On entend souvent dire que Java s'occupe automatiquement de ce problme contrairement d'autres langages, qui obligent le programmeur s'en proccuper. Pour cette raison, certains pensent qu'il n'est pas ncessaire, en Java, de s'inquiter de ce que deviennent les objets dont on n'a plus besoin. La ralit est un peu diffrente. En fait, Java ne dispose, d'aprs ses spcifications, d'aucun moyen de rsoudre le problme. Il se trouve que toutes les versions existantes de Java disposent d'un mcanisme prvu cet effet. Cependant, il faut savoir que cela n'est pas obligatoire. Vous pouvez trs bien rencontrer un jour une implmentation de Java n'en disposant pas. Toutes les versions actuelles de Java en tant munies, nous ferons comme si cela tait systmatique. Les diffrentes JVM peuvent toutefois disposer de mcanismes totalement diffrents. Nous dcrirons ici celui de la JVM de Sun Microsystems.

Le garbage collector
Le mcanisme qui permet Java de rcuprer la mmoire occupe par les objets devenus inutiles est appel garbage collector, ce qui signifie ramasseur de dchets. Ce mcanisme fonctionne grce un processus plus ou moins indpendant de votre programme et selon deux modes : synchrone et asynchrone.

Principe du garbage collector


Le rle du garbage collector consiste non seulement rcuprer la mmoire occupe par les objets devenus inaccessibles, mais galement compacter la mmoire disponible. En effet, si la mmoire tait simplement libre, cela laisserait des trous inoccups entre les espaces occups par les objets valides. Lors de la cration de nouveaux objets, la JVM devrait rechercher un trou de taille suffisante pour y placer ceux-ci. Certaines JVM peuvent fonctionner de cette faon. Leurs performances en matire de gestion de la mmoire en sont fortement diminues. En matire de rcupration de la mmoire, il existe de nombreux algorithmes. Les spcifications de Java ne prconisent pas une approche plutt

CHAPITRE 11 LES OBJETS MEURENT AUSSI

455

qu'une autre. Pour simplifier, nous dirons que la JVM de Sun utilise une approche consistant parcourir tous les objets se trouvant en mmoire partir des rfrences disponibles au niveau "racine" et marquer ceux qui sont accessibles. Lors d'une session ultrieure, les objets non marqus peuvent tre limins. Cette approche permet d'liminer les objets disposant de rfrences circulaires (l'objet A possde un membre b qui fait rfrence l'objet B ; l'objet B possde un membre a qui fait rfrence l'objet A). Une partie du travail du garbage collector (la recherche et le marquage des objets) est effectue de manire asynchrone, sans qu'il soit possible de le contrler. Les seuls contrles possibles consistent :

Empcher ce fonctionnement asynchrone ; Dclencher le fonctionnement synchrone, ce qui a bien entendu un


effet sur le processus qui vient d'tre dcrit. L'autre partie du travail du garbage collector (libration de la mmoire et compactage) se droule de faon synchrone. Il est dclench par deux conditions :

La diminution des ressources mmoire au-del d'une certaine limite ; L'appel explicite du garbage collector par le programmeur.
Lorsque le garbage collector dmarre son processus synchrone, deux cas peuvent se prsenter :

Le fonctionnement asynchrone tait autoris et a eu lieu. Les objets


sont donc dj marqus ;

Le fonctionnement asynchrone n'tait pas autoris. Les objets ne sont


donc pas encore marqus. Dans le second cas, le garbage collector recherche tous les objets devenus inaccessibles (sans rfrence) et les marque comme candidats la destruction. Dans le premier cas, cette tape est inutile. Puis, la mthode finalize() de chacun de ces objets est excute et les objets sont marqus pour indiquer qu'ils ont t finaliss. (Nous reviendrons dans une prochaine sec-

456

LE DVELOPPEUR JAVA 2
tion sur la mthode finalize().) Et c'est tout ! Ce n'est qu'au prochain dmarrage du garbage collector que les objets finaliss sont enfin dtruits et que la mmoire est libre et compacte. Par la mme occasion, les objets qui seraient devenus inaccessibles lors de l'tape prcdente sont traits leur tour, c'est--dire que leur mthode finalize() est excute, et ainsi de suite. Le garbage collector s'arrte lorsqu'une quantit suffisante de mmoire a t libre. De ce principe de fonctionnement, on peut dduire les conclusions suivantes :

Le programmeur n'a aucun moyen d'empcher le garbage collector de

dmarrer. En revanche, l'utilisateur peut empcher le garbage collector de fonctionner de manire asynchrone, grce un paramtre de la ligne de commande. De cette faon, un programme peut contrler un processus en temps rel sans risquer d'tre ralenti. Il faut seulement s'assurer que le processus synchrone ne dmarrera pas suite un encombrement de la mmoire. ait jamais dmarr. Dans ce cas, les mthodes finalize() des objets devenus inaccessibles ne seront jamais excutes.

Il est possible qu'un programme se termine sans que le garbage collector Il n'y a aucun moyen de dterminer l'ordre dans lequel les objets dont
les rfrences sont de mme nature sont finaliss (sauf s'il s'agit d'objets contenus dans d'autres objets, les contenants tant alors finaliss avant les contenus).

Il existe de nombreuses faons de faciliter et d'optimiser le travail du


garbage collector et la gestion de la mmoire. L'utilisation d'un garbage collector garantit qu'il n'y aura pas de fuites de mmoire dues la persistance d'objets inaccessibles. Elle ne garantit pas que tous les objets inutiles seront rendus inaccessibles, ni que les objets seront crs de la manire la plus efficace.

Optimiser le travail du garbage collector


Une faon d'optimiser la gestion de la mmoire est de s'assurer que les objets devenus inutiles sont immdiatement reconnaissables comme tels

CHAPITRE 11 LES OBJETS MEURENT AUSSI

457

par le garbage collector. Considrons l'exemple suivant dans lequel un objet est cr sans handle :
g.setColor(new Color(223, 223, 223)); g.drawLine(545, 0, 545, 338);

Dans cet exemple, un objet de type Color est cr la premire ligne afin de servir de paramtre la mthode setColor qui dtermine la couleur qui sera utilise pour tracer une ligne. Cet objet devient inaccessible ds la fin de la ligne suivante. En revanche, dans l'exemple suivant :
couleur = new Color(223, 223, 223); g.setColor(couleur); g.drawLine(545, 0, 545, 338);

l'objet cr reste accessible grce au handle couleur et continue donc d'occuper de l'espace en mmoire. Cette faon de faire peut tre plus efficace, comme nous le verrons plus loin. Cependant, si vous n'y prenez pas garde, l'objet en question risque de traner en mmoire beaucoup plus longtemps que ncessaire. Une faon de s'assurer que l'objet devient ligible pour le prochain passage du garbage collector consiste supprimer explicitement le lien entre le handle et l'objet, au moyen de l'expression :
couleur = null;

Cette mthode est la plus efficace si vous devez rutiliser l'objet en question plusieurs reprises. Le handle permet de le conserver en mmoire et de le rutiliser autant de fois que ncessaire. Lorsqu'il est devenu inutile, vous pouvez vous en dbarrasser au moyen de l'instruction ci-dessus. Souvenezvous toutefois que l'objet ainsi trait peut rester en mmoire un certain temps, puisqu'il faut au moins deux passages du garbage collector pour l'liminer. Ainsi, si votre programme comporte plusieurs tracs de couleurs diffrentes, comme dans l'exemple suivant :

458
couleur = new Color(223, 223, 223); g.setColor(couleur); couleur = null; g.drawLine(545, 0, 545, 338); couleur = new Color(123, 12, 255); g.setColor(couleur); couleur = null; g.drawLine(245, 0, 245, 338);

LE DVELOPPEUR JAVA 2

les deux objets de type Color qui ont t crs risquent de sjourner en mmoire un long moment avant d'tre limins. L'utilisation d'objets anonymes, si elle simplifie l'criture, ne rsout pas le problme :
g.setColor(new Color(223, 223, 223)); g.drawLine(545, 0, 545, 338); g.setColor(new Color(123, 12, 255)); g.drawLine(245, 0, 245, 338);

Aprs l'excution de ces lignes, nous nous retrouvons dans la mme situation, avec deux objets inaccessibles mais occupant de l'espace mmoire jusqu' leur limination. De plus, la cration d'objets est une opration longue et pnalisante en ce qui concerne les performances. Chaque fois que cela est possible, il est prfrable de recycler un objet existant en modifiant ses caractristiques plutt que d'en crer un nouveau. C'est malheureusement impossible pour les instances de la classe Color. En revanche, cela peut tre le cas pour les classes que vous crez vous-mme, comme nous l'avons vu au Chapitre 8.

Les finaliseurs
Tout comme il existe des initialiseurs qui permettent d'effectuer certains traitements lors de la cration des objets, Java met notre disposition des finaliseurs, qui sont excuts avant la destruction des objets par le garbage

CHAPITRE 11 LES OBJETS MEURENT AUSSI

459

collector. Il s'agit simplement d'une mthode dont le nom est finalize(). Le programme suivant met en vidence l'excution d'une telle mthode :
public class Elevage2 { public static void main(String[] argv) { while (!Lapin.gc) { Lapin.crerLapin(); } } } class Lapin { private static int nombre = 0; private int numero; static boolean gc = false; private Lapin() { numero = ++nombre; System.out.println("Creation du lapin no " + numero); } public void finalize() { System.out.println("Finalisation du lapin no " + numero); gc = true; } public static Lapin crerLapin() { return new Lapin(); } }

Ce programme cre des instances de Lapin sans rfrence tant que la variable statique gc vaut false. Chaque instance cre est immdiatement ligible pour le garbage collector. Lorsque la mmoire commence tre remplie, le garbage collector se met en route et trouve le premier Lapin sans rfrence. Cet objet disposant d'une mthode appele finalize(), celle-ci est excute avant que l'objet soit supprim. Cette mthode affiche un message et donne la variable statique gc la valeur true, ce qui a pour effet d'arrter la cration de lapins.

460

LE DVELOPPEUR JAVA 2
L'excution de ce programme affiche le rsultat suivant :
. . . Creation du lapin no 2330 Creation du lapin no 2331 Finalisation du lapin no 1 Finalisation du lapin no 2 Creation du lapin no 2332 Finalisation du lapin no 3

(Les valeurs peuvent changer en fonction de la mmoire disponible.) On voit que, dans cet exemple, le premier objet est supprim aprs que 2 331 ont t crs. Par ailleurs, on peut remarquer que le garbage collector a le temps de supprimer 3 objets avant que le programme soit arrt. Si nous modifions la mthode finalize() pour la ralentir, par exemple en incluant une boucle vide avant que la variable gc soit modifie :
public class Elevage2 { public static void main(String[] argv) { while (!Lapin.gc) { Lapin.crerLapin(); } } } class Lapin { private static int nombre = 0; private int numero; static boolean gc = false; private Lapin() { numero = ++nombre; System.out.println("Creation du lapin no " + numero); }

CHAPITRE 11 LES OBJETS MEURENT AUSSI

461

public void finalize() { System.out.println("Finalisation du lapin no " + numero); for (int i = 0;i < 10000000;i++) {} gc = true; } public static Lapin crerLapin() { return new Lapin(); } }

nous obtenons le rsultat suivant :


. . . Creation du lapin no 2330 Creation du lapin no 2331 Finalisation du lapin no 1 Creation du lapin no 2332 Creation du lapin no 2333 Creation du lapin no 2334 Creation du lapin no 2335 Creation du lapin no 2336 Finalisation du lapin no 2

qui montre que la cration de quatre objets supplmentaires a pu avoir lieu pendant l'excution de la mthode finalize(). En revanche, si la modification de la variable est effectue au dbut de la mthode :

public void finalize() { gc=true; System.out.println("Finalisation du lapin no " + numero); for (int i = 0;i < 10000000;i++) {} }

462

LE DVELOPPEUR JAVA 2
deux objets seulement sont finaliss avant que le programme soit arrt :
. . . Creation du lapin no 2330 Creation du lapin no 2331 Finalisation du lapin no 1 Creation du lapin no 2332 Finalisation du lapin no 2

On voit que, s'il est possible d'interfrer avec le garbage collector par ces moyens, ce n'est toutefois pas d'une faon exploitable. On pourrait tre tent de pousser le contrle un peu plus loin, par exemple de la faon suivante :

public class Elevage3 { public static void main(String[] argv) { while (true) { if (!Lapin.gc) Lapin.crerLapin(); } } }

class Lapin { private static int nombre = 0; private int numero; static boolean gc = false; private Lapin() { numero = ++nombre; System.out.println("Creation du lapin no " + numero); }

CHAPITRE 11 LES OBJETS MEURENT AUSSI


public void finalize() { --nombre; if (nombre > 2000) gc = true; else

463

gc = false; System.out.println("Finalisation du lapin no " + numero); } public static Lapin crerLapin() { return new Lapin(); } }

Ce programme tente de contrler la cration des lapins en maintenant leur nombre un peu au-dessous du maximum (ici entre 2 000 et le maximum, mais cette valeur doit tre adapte chaque cas.) En fait, le fonctionnement obtenu est tout fait alatoire du fait de l'asynchronisme des processus. (Pour arrter le programme, vous devrez taper les touches CTRL + C.) Il est intressant galement de remarquer que lorsque le programme s'arrte, de trs nombreux objets n'ont pas vu leur mthode finalize() excute. Par ailleurs, les objets sont ici finaliss dans l'ordre dans lequel ils ont t crs, mais cela n'est absolument pas garanti par Java.

Contrler le travail du garbage collector


Au-del des techniques dcrites dans les pages prcdentes, il existe d'autres possibilits de contrler la faon dont le garbage collector dispose des objets devenus plus ou moins inutiles. En effet, si certains objets deviennent totalement inutiles, d'autres restent cependant moyennement utiles, en ce sens que, si la mmoire est suffisante, on prfrerait les conserver, sachant qu'ils sont susceptibles d'tre rutiliss, alors qu'en cas de pnurie, on les sacrifierait toutefois volontiers.

464

LE DVELOPPEUR JAVA 2
Prenons, par exemple, le cas d'une application multimdia de type livre lectronique. Cette application peut comporter une page servant de table des matires. Chaque fois que l'utilisateur quitte une page pour en consulter une autre, les lments de la page prcdente peuvent tre conservs en mmoire pour le cas o cette page serait de nouveau consulte. Cependant, si la mmoire manque, il est ncessaire de supprimer les pages les plus anciennes de la mmoire. Toutefois, on pourrait prfrer conserver, si possible, la table des matires, sachant que les chances de retourner celle-ci sont plus leves que pour les autres pages. De la mme faon, on peut souhaiter conserver en mmoire avec une priorit plus ou moins leve certaines pages slectionnes par l'utilisateur. D'aprs le processus dcrit prcdemment, le garbage collector est susceptible de dtruire tout objet pour lequel il n'existe plus de rfrence. En revanche, s'il existe une seule rfrence un objet, celui-ci ne sera jamais dtruit. Java permet toutefois d'tablir un type de rfrence spciale, sorte de rfrence limite, au moyen d'un objet particulier de la classe Reference. Cette classe est une classe abstraite et est tendue par diverses autres classes permettant d'tablir des rfrences plus ou moins fortes. L'exemple suivant montre la cration d'une rfrence de type SoftReference pour un objet de la classe Lapin. Le premier objet cr est ainsi rfrenc :

import java.lang.ref.*; public class Elevage3 { public static void main(String[] argv) { while (!Lapin.gc) { Lapin.crerLapin(); } } } class Lapin { private static int nombre = 0; private int numero; static boolean gc = false; static SoftReference sr;

CHAPITRE 11 LES OBJETS MEURENT AUSSI

465

private Lapin() { numero = ++nombre; if (numero == 1) sr = new SoftReference(this); System.out.println("Creation du lapin no " + numero); } public void finalize() { gc=true; System.out.println("Finalisation du lapin no " + numero); } public static Lapin crerLapin() { return new Lapin(); } }

L'excution du programme montre que, lorsque le garbage collector dmarre, il ignore cet objet :
. . . Creation du lapin no 2330 Creation du lapin no 2331 Finalisation du lapin no 2 Finalisation du lapin no 3 Creation du lapin no 2332 Finalisation du lapin no 4

(Vous remarquerez par ailleurs que cela a galement une incidence sur les performances des deux processus mis en uvre.) Vous objecterez peut-tre que ce comportement est tout fait normal et qu'une SoftReference produit exactement le mme rsultat qu'une rfrence ordinaire. Le programme suivant montre que ce n'est pas le cas :

466
import java.lang.ref.*;

LE DVELOPPEUR JAVA 2

public class Elevage4 { public static void main(String[] argv) { while (!Lapin.gc) { Lapin.crerLapin(); } } } class Lapin { private static int nombre = 0; private int numero; static boolean gc = false; static Reference[] r = new Reference[300000]; private Lapin() { numero = ++nombre; r[numero] = new SoftReference(this); System.out.println("Creation du lapin no " + numero); } public void finalize() { gc = true; System.out.println("Finalisation du lapin no " + numero); } public static Lapin crerLapin() { return new Lapin(); } }

Cette fois, tous les objets crs reoivent une rfrence de type SoftReference. L'excution de ce programme donne le rsultat suivant :
. . Creation du lapin no 59278

CHAPITRE 11 LES OBJETS MEURENT AUSSI


Creation du lapin no 59279 Finalisation du lapin no 1 Finalisation du lapin no 2 Finalisation du lapin no 3 Finalisation du lapin no 4 . . . Finalisation du lapin no 31 Finalisation du lapin no 32 Creation du lapin no 59280 Finalisation du lapin no 33

467

Cette fois, le garbage collector a attendu beaucoup plus longtemps avant d'liminer les objets.

Rfrences et accessibilit
(Notez que l'accessibilit dont il est question ici n'a rien voir avec celle dont nous avons parl au Chapitre 8.) Pour comprendre les rfrences limites, il est ncessaire de revenir sur la faon dont les objets Java sont rfrencs et sur ce que cela implique sur la possibilit ou non de les atteindre. Nous avons vu au dbut de ce chapitre qu'un objet pouvait tre accessible s'il existait une rfrence cet objet dans un espace particulier, que l'on appelle racine des rfrences. Le programme :
TreeMap lettres = new TreeMap(new Comparaison()); Collections.sort(lettres, new Comparaison()); Comparaison comparateur = new Comparaison(); Collections.sort(lettres, comparateur); lettres = null;

468

LE DVELOPPEUR JAVA 2
aboutit la situation dcrite par la Figure 11.4. Dans ce schma, on voit qu'il existe des rfrences pour deux des objets, alors que les deux autres n'en ont pas. Les objets sans rfrence sont inaccessibles, et donc ligibles pour le garbage collector. Une des instances de Comparaison est rfrence par le handle comparator, mais ce handle n'appartient pas la racine des rfrences. Bien que rfrenc, cet objet est galement inaccessible et sera supprim par le garbage collector (dans un dlai plus long que les objets sans rfrence).

Racine des rfrences

lettres
null

comparateur

Objet instance de TreeMap (non rfrenc donc non atteignable) comparator

Objet instance de Comparaison (rfrenc et atteignable)

Objet instance de Comparaison (rfrenc mais non atteignable)

Objet instance de Comparaison (non rfrenc, donc non atteignable)

Figure 11.4 : Deux des objets nont pas de rfrences.

CHAPITRE 11 LES OBJETS MEURENT AUSSI

469

Les rfrences faibles


Les objets peuvent galement tre rfrencs l'aide de rfrences faibles, comme nous l'avons vu dans l'exemple des lapins. Une rfrence faible est obligatoirement une chane de rfrences dont la premire est forte. En effet, une rfrence faible est tablie par l'intermdiaire d'un objet de type Reference. Cet objet doit lui-mme avoir une rfrence. Il peut s'agir soit d'une rfrence forte, soit d'une rfrence faible, ce qui nous ramne au cas prcdent. Sur le schma de la Figure 11.5, on peut voir les diffrents cas possibles. Dans cet exemple, tous les objets sont accessibles.

L'objet 1 a une rfrence forte car son handle appartient la racine des
rfrences.

L'objet 2 a une rfrence forte pour la mme raison. L'objet 3 a une rfrence forte car la chane de rfrence qui le rend
accessible (elle commence dans la racine des rfrences) ne comporte que des rfrences fortes.

L'objet 4 a une rfrence faible car la chane de rfrence qui le rend


accessible comporte une rfrence faible.

L'objet 5 a une rfrence forte car une des chanes qui le rendent
accessible ne comporte que des rfrences fortes.

L'objet 6 a une rfrence forte car son handle appartient la racine des
rfrences.

L'objet 7 a une rfrence faible.


Pour rsumer :

Un objet a une rfrence forte si une des chanes de rfrences qui le


rendent accessible ne comporte que des rfrences fortes.

Un objet a une rfrence faible si toutes les chanes de rfrences qui


le rendent accessible comportent au moins une rfrence faible.

470

LE DVELOPPEUR JAVA 2

Racine des rfrences

handle1

handle2

handle3

Objet quelconque

Objet quelconque

6
Objet instance de Reference

handle4

handle5

Objet instance de Reference

Objet quelconque

4
Objet quelconque

handle6

5
Objet quelconque

Rfrence forte Rfrence faible

Figure 11.5 : Tous les objets sont accessibles.

CHAPITRE 11 LES OBJETS MEURENT AUSSI

471

Tous les objets qui ont une rfrence faible sont ligibles pour le garbage collector. Cela ne signifie pas pour autant qu'ils seront supprims automatiquement. Le garbage collector traite ces objets diffremment selon le type exact de rfrence. Il existe en effet plusieurs sortes de rfrences faibles, correspondant diffrentes classes drives de la classe Reference. Par ordre de force dcroissante, on trouve les rfrences suivantes :

SoftReference
Les objets rfrencs par cette classe peuvent tre supprims lorsque la quantit de mmoire devient faible. Le garbage collector fait de son mieux pour effectuer la suppression en commenant par l'objet le moins rcemment utilis, et pour supprimer tous les objets de ce type avant d'indiquer une erreur de type OutOfMemoryError. Ce faisant, il essaie tout de mme d'en conserver le plus grand nombre. La classe SoftReference comporte deux mthodes :

public public

Object get() retourne l'objet rfrenc, ou null si la rfrence a t supprime par le garbage collector ou au moyen de la mthode clear().

permet de supprimer la rfrence et de rendre ainsi l'objet rfrenc inaccessible (et donc plus immdiatement supprimable par le garbage collector). Toutefois, cette mthode ne supprime pas les autres rfrences l'objet (fortes ou faibles).

void clear()

WeakReference
Les objets rfrencs par cette classe sont supprims de la mme faon que les prcdents. La seule diffrence est que le garbage collector n'essaie pas d'en conserver un nombre maximal en mmoire. Une fois qu'il a dmarr, tous les objets de ce type seront supprims. L'intrt de cette classe de rfrences est qu'elle permet un processus d'tre inform lorsqu'un objet cr par un autre processus n'a plus de rfrence forte. Pour cela, il est ncessaire d'utiliser une queue. Les queues seront dcrites dans la prochaine section.

472
PhantomReference

LE DVELOPPEUR JAVA 2

Les objets rfrencs par cette classe sont placs dans la queue correspondante par le garbage collector aprs qu'ils ont t finaliss (c'est--dire aprs l'excution de leur mthode finalize()) mais ne sont pas supprims. La mthode get() de cette classe ne permet pas de rcuprer les objets rfrencs et retourne toujours la valeur null. Ce type de rfrence permet d'effectuer divers traitements de dsallocation de ressources (par exemple fichiers ou sockets ouverts) avant la suppression des objets. Il est de la responsabilit du programmeur d'appeler ensuite la mthode clear() pour rendre l'objet supprimable par le garbage collector.

Les queues
Chaque rfrence faible peut tre associe une queue. Une queue est un objet instance de la classe ReferenceQueue. Il s'agit d'une structure dans laquelle seront placs les objets traits par le garbage collector. (L'utilisation d'une queue est mme obligatoire avec les rfrences de type PhantomReference.) Un processus peut tester une queue au moyen de la mthode poll() pour savoir si elle contient une rfrence. Cette mthode retourne la premire rfrence disponible, s'il y en a une, null dans le cas contraire. La classe ReferenceQueue contient galement les mthodes remove(), qui attend jusqu' ce qu'un objet soit disponible dans la queue pour le retourner, et remove(long timeout), qui attend le nombre de millisecondes indiqu par timeout. Si une rfrence est disponible avant l'expiration du dlai, elle est immdiatement retourne par la mthode. Dans le cas contraire, elle retourne la valeur null. Si timeout vaut 0, la mthode se comporte comme dans le cas o il n'y a pas d'argument. Les queues sont particulirement utiles avec les tables. En effet, une table stocke des couples cls/valeurs. Les cls comme les valeurs sont des objets. L'utilisation de rfrences faibles associes une queue pour les valeurs

CHAPITRE 11 LES OBJETS MEURENT AUSSI

473

permet d'informer un processus de la suppression d'une valeur de faon qu'il soit possible de supprimer la cl correspondante.

Exemple d'utilisation de rfrences faibles


Les rfrences faibles sont particulirement intressantes pour stocker les images en mmoire cache. Il est ainsi possible de conserver ces images en mmoire condition que la quantit de mmoire soit suffisante. L'utilisation des diffrents types de rfrences permet d'tablir une priorit. Le programme suivant cre une applet qui affiche deux images mises en mmoire cache :
import java.applet.*; import java.awt.*; import java.lang.ref.*; public class Cache extends Applet { Reference[] rf = new Reference[10]; public void init() { // Ici, le code de l'applet } public void paint (Graphics g) { Image image; // affichage de l'image 1 if (rf[1] != null) image = (Image)(rf[1].get()); else { image = getImage(getCodeBase(), "image1.gif"); rf[1] = new SoftReference(image); } g.drawImage(image, 20, 40, this); // affichage de l'image 2 if (rf[2] != null) image = (Image)(rf[2].get());

474

LE DVELOPPEUR JAVA 2
else { image = getImage(getCodeBase(), "image2.gif"); rf[1] = new SoftReference(image); } g.drawImage(image, 120, 230, this); image = null; } }

Ce programme fait appel des notions que nous n'avons pas encore tudies. Vous devriez nanmoins tre mme de comprendre son fonctionnement. La ligne :
Reference[] rf = new Reference[10];

cre un tableau de rfrences. La classe Reference est utilise afin de pouvoir y stocker plusieurs types de rfrences (grce au polymorphisme). Le tableau comporte 10 entres, mais il devrait en fait en contenir autant qu'il y a d'images mettre en cache. (L'utilisation d'un tableau est beaucoup plus pratique que celle de rfrences individuelles, mais un tout petit peu moins efficace en termes de gestion de la mmoire.) La mthode init() :
public void init() { // Ici, le code de l'applet }

contient le code de l'applet. La mthode paint() est celle qui est appele chaque fois que l'applet est affiche. C'est donc elle qui contient le code affichant les images. Le handle image est dclar de type Image :
Image image;

CHAPITRE 11 LES OBJETS MEURENT AUSSI

475

Avant d'afficher la premire image, nous testons le tableau des rfrences pour savoir si le premier lment est non null. Si c'est le cas, cela signifie que l'image a dj t affiche et se trouve donc en mmoire. Elle est alors rcupre l'aide de la mthode get(). Notez que cette mthode retourne un Object, qui doit donc tre sous-cast explicitement en Image.
if (rf[1] != null) image = (Image)(rf[1].get());

Dans le cas contraire, cela signifie que l'image n'a jamais t affiche ou qu'elle a t supprime de la mmoire. Il faut donc la charger :
else { image = getImage(getCodeBase(), "image1.gif");

puis crer une rfrence faible. Celle-ci est cre de type SoftReference :
rf[1] = new SoftReference(image); }

L'image est ensuite affiche :


g.drawImage(image, 20, 40, this);

Il faudrait normalement ensuite supprimer la rfrence forte l'image, mais cela n'est pas ncessaire puisque nous allons immdiatement rattribuer le handle image un autre objet :
// affichage de l'image 2 (non prioritaire) if (rf[2] != null) image = (Image)(rf[2].get());

476

LE DVELOPPEUR JAVA 2
else { image = getImage(getCodeBase(), "image2.gif"); rf[1] = new SoftReference(image); } g.drawImage(image, 120, 230, this);

Dans ces lignes, la deuxime image est traite de la mme faon. Une fois l'affichage termin, la rfrence forte de la dernire image affiche est supprime grce l'instruction :
image = null;

Note : Si vous souhaitez tester ce programme, vous devez disposer d'un


fichier HTML contenant les instructions suivantes :
<html> <head> <title>Titre facultatif</title> </head> <body> <applet code="Cache" width="400" height="300"> </applet> </body> </html>

Compilez le programme aprs l'avoir enregistr dans le fichier Cache.java puis ouvrez le fichier HTML (qui peut avoir n'importe quel nom) avec l'AppletViewer, en tapant :
AppletViewer nom_du_fichier_html

ou tout simplement :
va

CHAPITRE 11 LES OBJETS MEURENT AUSSI

477

suivi de la touche F3 si vous avez cr les fichiers batch indiqus au Chapitre 1. (Dans ce cas, vous devez placer la ligne :
//<applet code="PremireApplet" width="400" height="300"></applet>

au dbut du fichier cache.java.) Pour que le programme fonctionne, vous devez en outre disposer de deux fichiers images appels respectivement image1.gif et image2.gif et placs dans le mme dossier que le programme.

Autres formes de contrle du garbage collector


Java dispose d'autres moyens pour contrler le garbage collector. Parmi ceux-ci, le plus direct est la mthode gc(). Il s'agit d'une mthode statique de la classe java.lang.System, qui a pour effet de lancer le garbage collector. L'exemple suivant en fait la dmonstration. Tout d'abord, considrez le programme ci-dessous :
public class Elevage5 { public static void main(String[] argv) { for (int i = 0; i < 10; i++) { Lapin.crerLapin(); } } }

class Lapin { private static int nombre = 0; private int numero; private Lapin() { numero = ++nombre;

478
}

LE DVELOPPEUR JAVA 2
System.out.println("Creation du lapin no " + numero);

public void finalize() { System.out.println("Finalisation du lapin no " + numero); } public static Lapin crerLapin() { return new Lapin(); } }

Ce programme cr dix instances de la classe Lapin sans rfrences et se termine. Il affiche le rsultat suivant :
Creation Creation Creation Creation Creation Creation Creation Creation Creation Creation du du du du du du du du du du lapin lapin lapin lapin lapin lapin lapin lapin lapin lapin no no no no no no no no no no 1 2 3 4 5 6 7 8 9 10

Voyons maintenant ce que produit ce programme lgrement modifi :


public class Elevage5 { public static void main(String[] argv) { for (int i = 0; i < 10; i++) { Lapin.crerLapin(); } System.gc(); } }

CHAPITRE 11 LES OBJETS MEURENT AUSSI


class Lapin { . . . .

479

La ligne ajoute lance le garbage collector. Le programme affiche maintenant :

Creation du lapin no 1 Creation du lapin no 2 Creation du lapin no 3 Creation du lapin no 4 Creation du lapin no 5 Creation du lapin no 6 Creation du lapin no 7 Creation du lapin no 8 Creation du lapin no 9 Creation du lapin no 10 Finalisation du lapin no Finalisation du lapin no Finalisation du lapin no Finalisation du lapin no

1 2 3 4

Le rsultat peut tre lgrement diffrent selon la vitesse de votre ordinateur. Que se passe-t-il ? Aprs la cration des 10 lapins, le garbage collector est lanc et le programme se termine. Le garbage collector a ici le temps de finaliser quatre lapins avant d'tre interrompu. Dans ces conditions, la mthode gc() n'est pas trs utile. Elle ne permet pas, en effet, de s'assurer que les mthodes finalize() de tous les objets se trouvant en mmoire seront excutes. Une solution pourrait tre d'ajouter un temps d'attente aprs l'appel du garbage collector. Cela relve un peu du bricolage dans la mesure o il n'y a aucun moyen de connatre le temps ncessaire. Une meilleure solution consiste employer la mthode runFinalization() :

480

LE DVELOPPEUR JAVA 2
public class Elevage5 { public static void main(String[] argv) { for (int i = 0; i < 10; i++) { Lapin.crerLapin(); } System.gc(); System.runFinalization(); } } class Lapin { . . .

Le programme affiche maintenant :


Creation du lapin no 1 Creation du lapin no 2 Creation du lapin no 3 Creation du lapin no 4 Creation du lapin no 5 Creation du lapin no 6 Creation du lapin no 7 Creation du lapin no 8 Creation du lapin no 9 Creation du lapin no 10 Finalisation du lapin no Finalisation du lapin no Finalisation du lapin no Finalisation du lapin no Finalisation du lapin no Finalisation du lapin no Finalisation du lapin no Finalisation du lapin no Finalisation du lapin no Finalisation du lapin no

1 2 3 4 5 6 7 8 9 10

CHAPITRE 11 LES OBJETS MEURENT AUSSI

481

priori, le rsultat souhait a t obtenu. Pourtant, vous ne pouvez pas tre absolument sr du rsultat. En effet, la documentation prcise que Java fait de son mieux pour que cette mthode entrane la finalisation de tous les objets restant en mmoire, mais cela n'est pas garanti. Ainsi, le programme suivant :
public class Elevage6 { public static void main(String[] argv) { for (int i = 0; i < 10000; i++) { Lapin.crerLapin(); } System.gc(); System.runFinalization(); } } class Lapin { private static int nombre = 0; private static int nombre2 = 0; private Lapin() { ++nombre; if (nombre == 10000) System.out.println(nombre + " lapins crees"); } public void finalize() { ++nombre2; if (nombre2 == 10000) System.out.println(nombre2 + " lapins finalises"); } public static Lapin crerLapin() { return new Lapin(); } }

affiche :

482
10000 lapins crees 10000 lapins finalises

LE DVELOPPEUR JAVA 2

alors que si vous remplacez les valeurs 10000 par 100000, le rsultat devient :
100000 lapins crees

montrant que certains objets n'ont pas t finaliss.

Attention : La mthode runFinalization() ne concerne que les objets


qui ont t collects par le garbage collector. Si vous inversez l'ordre des lignes :
System.runFinalization(); System.gc();

le rsultat sera tout fait diffrent !


Exit(true),

Une autre possibilit consiste utiliser la mthode runFinalizersOncomme dans l'exemple suivant :
public class Elevage7 { public static void main(String[] argv) { for (int i = 0; i < 10; i++) { Lapin.crerLapin(); } Runtime.getRuntime().runFinalizersOnExit(true); } }

class Lapin { private static int nombre = 0; private int numero;

CHAPITRE 11 LES OBJETS MEURENT AUSSI

483

private Lapin() { numero = ++nombre; System.out.println("Creation du lapin no " + numero); } public void finalize() { System.out.println("Finalisation du lapin no " + numero); } public static Lapin crerLapin() { return new Lapin(); } }

qui produit le rsultat suivant :

Creation du lapin no 1 Creation du lapin no 2 Creation du lapin no 3 Creation du lapin no 4 Creation du lapin no 5 Creation du lapin no 6 Creation du lapin no 7 Creation du lapin no 8 Creation du lapin no 9 Creation du lapin no 10 Finalisation du lapin no Finalisation du lapin no Finalisation du lapin no Finalisation du lapin no Finalisation du lapin no Finalisation du lapin no Finalisation du lapin no Finalisation du lapin no Finalisation du lapin no Finalisation du lapin no

10 9 8 7 6 5 4 3 2 1

484

LE DVELOPPEUR JAVA 2
Notez qu'il n'a pas t ncessaire de lancer le garbage collector. Par ailleurs, vous pouvez remarquer que les objets sont finaliss dans l'ordre inverse de leur cration, mais cela n'est absolument pas garanti ! Par ailleurs, cela ne concerne que les objets non traits par le garbage collector. En revanche, il semble que vous puissiez compter sur le fait que tous les objets seront finaliss. (Nous l'avons test avec plusieurs millions d'objets en obtenant un rsultat positif.) De plus, cette faon de faire entrane la finalisation de tous les objets, qu'ils aient t ou non collects par le garbage collector. instance de la classe Runtime retourne par la mthode statique getRuntime(). C'est exactement ce que fait la mthode runFinalizersOnExit() de la classe System, comme en tmoignent les sources de Java :
public static void runFinalizersOnExit(boolean value) { Runtime.getRuntime().runFinalizersOnExit(value); }

Note : La mthode runFinalizersOnExit() est appele ici partir d'une

Ce programme avait t crit avec la version bta 4 de Java 2. Dans cette version, la mthode runFinalizersOnExit() tait dclare deprecated, c'est--dire obsolte parce que Cette mthode est intrinsquement dangereuse. Elle peut entraner l'appel de finaliseurs sur des objets valides pendant que d'autres processus sont en train de les manipuler et produire ainsi un comportement erratique ou un blocage. En revanche, la version de la classe Runtime ntait pas dclare deprecated. Ce problme a t corrig dans la version finale. En compilant la classe Elevage7, vous obtiendrez donc un message davertissement. Cela nempche toutefois pas le programme de fonctionner.

La finalisation et l'hritage
Il est une chose importante ne pas oublier lorsque vous drivez une classe comportant une mthode finalize(). Si la classe drive ne ncessite pas de

CHAPITRE 11 LES OBJETS MEURENT AUSSI

485

traitement particulier en ce qui concerne la finalisation, il n'y a pas de problme. En revanche, dans le cas contraire, vous devrez redfinir cette mthode. Vous ne devez pas oublier alors d'appeler explicitement la mthode finalize() de la classe parente, en terminant votre dfinition par :
super.finalize();

Dans le cas contraire, la finalisation ne serait pas excute correctement. En effet, contrairement aux constructeurs, les finaliseurs des classes parentes ne sont pas appels automatiquement par Java. Pourquoi finaliser la classe parente aprs la classe drive ? Tout simplement parce qu'il est possible que la finalisation de la classe drive repose sur certains membres de la classe parente, alors que le contraire est videmment impossible. Bien sr, vous pensez peut-tre que cela est inutile si la classe parente ne comporte pas de mthode finalise(). Tout d'abord, notez que, toutes les classes drivant de la classe Object, elles disposent automatiquement d'une telle mthode qu'elles hritent de cette classe. Par ailleurs, mme si votre classe drive d'une classe qui ne redfinit pas cette mthode, rien ne peut vous assurer qu'il en sera toujours ainsi. Si votre programme comporte la hirarchie suivante :
Object | |--------Classe1 | |--------Classe2

o Classe2 comporte une mthode finalize() mais pas Classe1, il est tout de mme prfrable de terminer le code de cette mthode par l'instruction :
super.finalize()

qui aura pour effet d'appeler la mthode de la classe parente Object, qui ne fait rien comme on peut le vrifier dans les sources de cette classe :

486

LE DVELOPPEUR JAVA 2
protected void finalize() throws Throwable { }

De cette faon, si vous modifiez un jour la classe Classe1 et que vous vous apercevez qu'il devient ncessaire de lui ajouter une redfinition de cette mthode, vous n'aurez pas modifier la classe Classe2. Il s'agit l encore du respect d'une des rgles essentielles de la programmation efficace. Imaginez maintenant que votre application comporte une centaine de classes drives de Classe1, et c'est une centaine de modifications que vous seriez oblig d'effectuer si vous n'aviez pas respect cette rgle. Qui plus est, imaginez le rsultat si vos classes sont destines une diffusion publique !

La finalisation et le traitement d'erreur


Nous verrons dans un prochain chapitre quel mcanisme permet de traiter les diffrentes erreurs d'excution qui peuvent se produire au cours du droulement d'un programme. Sachez simplement pour l'instant que lorsqu'une erreur survient, elle peut tre traite dans un bloc de code particulier. Il est frquent que ce traitement entrane l'arrt du programme. Si cette situation se produit dans une mthode finalize(), vous devrez prendre les mesures ncessaires pour que les oprations de nettoyage indispensables aient tout de mme lieu. Nous y reviendrons.

Contrler le garbage collector l'aidedesoptionsdel'interprteur


Une autre faon de contrler le fonctionnement du garbage collector consiste employer certains paramtres sur la ligne de commande du lanceur dapplications. En effet, nous avons vu que le garbage collector offre deux modes de fonctionnement : synchrone et asynchrone. Le paramtre :

-noasyncgc

CHAPITRE 11 LES OBJETS MEURENT AUSSI

487

permet d'empcher le fonctionnement asynchrone du garbage collector. De cette faon, aucun ralentissement n'aura lieu jusqu' ce que le garbage collector commence son fonctionnement synchrone cause d'un manque de mmoire ou la suite de l'appel de la mthode gc(). Par ailleurs, le paramtre :
-noclassgc

permet d'indiquer au garbage collector que les classes qui ont t charges et ne sont plus utilises ne doivent pas tre supprimes. L'utilisation de ce paramtre peut, lorsque la mmoire disponible est importante, acclrer certains programmes. Enfin, les deux paramtres suivants :
-ms initmem[k|m] -mx maxmem[k|m]

permettent d'indiquer, pour le premier, la quantit de mmoire initialement attribue au tas (la structure dans laquelle les objets sont crs) et, pour le second, la quantit maximale de mmoire qui pourra lui tre alloue. Les valeurs par dfaut sont respectivement de 1 Mo et 16 Mo. L'indication de valeurs suprieures peut amliorer le fonctionnement des programmes faisant une utilisation intensive de la mmoire. Les lettres k et m indiquent si les valeurs sont en kilo-octets ou en mgaoctets. La quantit de mmoire alloue a une influence dterminante sur la frquence de dmarrage du garbage collector ainsi que sur le temps mis par celui-ci effectuer sa tche.

Rsum
Dans ce chapitre, nous avons tudi le mcanisme de gestion de la mmoire de la JVM. Nous avons vu comment chacun des aspects de ce mca-

488

LE DVELOPPEUR JAVA 2
nisme pouvait tre plus ou moins contrl, directement ou indirectement. Il s'agit l d'un problme crucial pour les performances et la scurit des programmes. Nous avons galement tudi les rfrences faibles, qui sont une des nouveauts de la version 2 de Java.

Chapitre 12 : Les classes internes

Les classes internes

12

ANS LES CHAPITRES PRCDENTS, NOUS AVONS DJ EU PLUsieurs fois l'occasion d'indiquer que les classes Java peuvent contenir, outre des primitives, des objets (du moins leurs rfrences) et des dfinitions de mthodes, des dfinitions de classes. Nous allons maintenant nous intresser de plus prs cette possibilit.

Les classes imbriques


Il arrive frquemment que certaines classes ne soient utilises que par une seule autre classe. Considrez, par exemple, le programme suivant :

490
import java.util.*;

LE DVELOPPEUR JAVA 2

class Tri { public static void main(String[] args) { Vector lettres = new Vector(); lettres.add(new Lettre('C')); lettres.add(new Lettre('')); lettres.add(new Lettre('S')); lettres.add(new Lettre('u')); lettres.add(new Lettre('c')); lettres.add(new Lettre('')); lettres.add(new Lettre('e')); lettres.add(new Lettre('R')); lettres.add(new Lettre('')); Collections.sort(lettres, new Comparaison()); Iterator it = lettres.iterator(); while (it.hasNext()) { ((Lettre)it.next()).affiche(); } } } class Lettre { char valeur; Lettre (char v) { valeur = v; } public void affiche() { System.out.println(win2DOS(valeur)); } public static char win2DOS(char c) { switch (c) { case '': return (char)133; case '': return (char)131;

CHAPITRE 12 LES CLASSES INTERNES


case '': return case '': return case '': return case '': return case '': return case '': return case '': return case '': return case '': return case '': return case '': return case '': return case '': return } return c; } }

491

(char)132; (char)130; (char)138; (char)137; (char)136; (char)139; (char)140; (char)147; (char)148; (char)129; (char)150; (char)151; (char)135;

class Comparaison implements Comparator { int retval = 0; public int compare(Object o1, Object o2) { if ((o1 instanceof Lettre) && (o2 instanceof Lettre)) { if (Conversion.conv(((Lettre)o1).valeur) < Conversion.conv(((Lettre)o2).valeur)) retval = -1;

492

LE DVELOPPEUR JAVA 2
else if (Conversion.conv(((Lettre)o1).valeur) > Conversion.conv(((Lettre)o2).valeur)) retval = 1; } else { // traitement d'erreur } return retval; } } class Conversion { static char conv (char c) { switch (c) { case '': case '': case '': return 'A'; case '': case '': case '': case '': return 'E'; case '': case '': return 'I'; case '': case '': return 'O'; case '': case '': case '': return 'U'; case '': return 'C'; default: if (c > 96) return (char)(c - 32);

CHAPITRE 12 LES CLASSES INTERNES


else return c; } } }

493

Ce programme dfinit quatre classes :

La classe Tri constitue le programme principal (celui qui contient la


mthode main()).

La classe Conversion sert convertir les minuscules accentues en


majuscules. Elle ne contient qu'une mthode statique.

La classe Lettre est un enveloppeur pour la primitive char et contient

en outre une mthode permettant de convertir les caractres ANSI (sousensemble des caractres Unicode utiliss par Java) en caractres ASCII (permettant l'affichage dans une fentre DOS).

La classe Comparaison dfinit un comparateur pour les objets instances


de la classe Lettre. Toutes ces classes sont dfinies dans le mme fichier, ce qui convient dans le cadre de la dmonstration, mais certainement pas pour la pratique courante de la programmation efficace. Chacune de ces classes pourrait tre dfinie sparment dans un fichier et affecte un package. Rappelons que, telles qu'elles sont dfinies ici, toutes ces classes sont affectes au package par dfaut. Cependant, si l'on examine de plus prs ce que fait chaque classe, on s'aperoit qu'elles n'ont pas du tout le mme statut. Ainsi, la classe Conversion ne contient qu'une mthode statique. Elle n'est pas destine tre instancie. Elle pourrait donc avantageusement tre range dans un package d'utilitaires. La classe Lettre est un enveloppeur cr spcialement pour l'application et pourrait donc tre place dans un package contenant toutes les classes spcifiques de celle-ci.

494

LE DVELOPPEUR JAVA 2
En revanche, il est vident que la classe Comparaison ne concerne que la classe Lettre. On voit donc qu'il existe de fait une sorte de lien hirarchique entre les classes Lettre et Comparaison puisque Comparaison est un outil de Lettre. Ds lors, il serait plus simple de placer la dfinition de Comparaison l'intrieur de celle de Lettre. En Java, cela est tout fait possible, comme le montre l'exemple suivant :
import java.util.*; class Imbrique { public static void main(String[] args) { Vector lettres = new Vector(); lettres.add(new lettres.add(new lettres.add(new lettres.add(new lettres.add(new lettres.add(new lettres.add(new lettres.add(new lettres.add(new Lettre('C')); Lettre('')); Lettre('S')); Lettre('u')); Lettre('c')); Lettre('')); Lettre('e')); Lettre('R')); Lettre(''));

Collections.sort(lettres, new Lettre.Comparaison()); Iterator it = lettres.iterator(); while (it.hasNext()) { ((Lettre)it.next()).affiche(); } } } class Lettre { char valeur; Lettre (char v) { valeur = v; } public void affiche() { System.out.println(win2DOS(valeur)); }

CHAPITRE 12 LES CLASSES INTERNES


public static char win2DOS(char c) { switch (c) { case '': return (char)133; case '': return (char)131; case '': return (char)132; case '': return (char)130; case '': return (char)138; case '': return (char)137; case '': return (char)136; case '': return (char)139; case '': return (char)140; case '': return (char)147; case '': return (char)148; case '': return (char)129; case '': return (char)150; case '': return (char)151; case '': return (char)135; } return c; }

495

static class Comparaison implements Comparator { int retval = 0; public int compare(Object o1, Object o2) { if ((o1 instanceof Lettre) && (o2 instanceof Lettre)) { if (Conversion.conv(((Lettre)o1).valeur) < Conversion.conv(((Lettre)o2).valeur)) retval = -1;

496

LE DVELOPPEUR JAVA 2
else if (Conversion.conv(((Lettre)o1).valeur) > Conversion.conv(((Lettre)o2).valeur)) retval = 1;

} } }

} else { // traitement d'erreur } return retval;

Les parties modifies sont indiques en gras. Notez que la dfinition de la classe Conversion n'a pas t incluse. En effet, cette classe a dj t compile et doit donc figurer dans le package par dfaut. Si vous n'avez pas compil le programme Tri, ajoutez simplement la dfinition de cette classe la fin du fichier ou compilez-la sparment. La dfinition de la classe Comparaison est maintenant imbrique dans la dfinition de la classe Lettre. Notez que la rfrence la classe Comparaison devient maintenant Lettre.Comparaison. Lors de la compilation du programme, le compilateur produit trois fichiers :

Imbrique.class (le programme principal), Lettre.class, Lettre$Comparaison.class (la classe imbrique). Le caractre $ est

employ par Java pour pallier le fait que certains systmes d'exploitation interdisent l'utilisation du point dans les noms de fichiers. Cela est cependant transparent pour le programmeur grce un mcanisme de conversion automatique. Toutes les rfrences une classe imbrique en Java se font en utilisant le point comme sparateur.

La seule diffrence, si vous utilisez cette faon d'organiser vos classes, rside dans l'emplacement o elles sont stockes. Notez au passage que cela contredit ce que nous avions affirm au chapitre consacr aux packages : si vous respectez la convention consistant faire commencer les noms de classes par une capitale, nous avions indiqu que les chemins d'accs ne pouvaient comporter une capitale que dans la partie la plus droite. Ainsi :

CHAPITRE 12 LES CLASSES INTERNES


mesclasses.util.Lettre

497

dsigne la classe Lettre dans le package mesclasses.util. En revanche :


mesclasses.util.Lettre.Comparaison

dsigne la classe Comparaison imbrique dans la classe Lettre se trouvant elle-mme dans le package mesclasses.util. Il est possible d'utiliser la directive import pour importer les classes imbriques explicitement :
import mesclasses.util.Lettre.Comparaison;

ou en bloc :
import mesclasses.util.Lettre.*;

Notez galement que la classe Comparaison est dclare static, ce qui est obligatoire. En revanche, les interfaces imbriques sont automatiquement statiques et il n'est pas ncessaire de les dclarer explicitement comme telles. (Vous tes toutefois libre de le faire.) Enfin, les classes imbriques peuvent elles-mmes contenir d'autres classes imbriques, sans limitation de profondeur, du moins du point de vue de Java. (En ce qui concerne le systme d'exploitation, une imbrication un niveau trop profond pourrait conduire dpasser la limite de longueur des noms de fichiers.)

Les classes membres


La particularit des classes imbriques est qu'elles sont dclares statiques. Il est cependant galement possible de dclarer une classe non statique

498

LE DVELOPPEUR JAVA 2
l'intrieur d'une autre classe. Il s'agit alors d'une classe membre, ce qui est fondamentalement diffrent. En effet, lorsqu'une instance d'une classe est cre, une instance de chacune de ses classes membres est galement cre. (Ce qui, au passage, explique pourquoi les interfaces imbriques n'ont pas besoin d'une dclaration explicite static, puisqu'elles ne peuvent pas, de toute faon, tre instancies.) Les exemples suivants montrent l'utilisation de classes membres et mettent en vidence la manire de faire rfrence leurs champs :
public class Mandat1 { class Montant { private int v; Montant(int m) { v = m; } public int valeur() { return v; } } class Destinataire { private String nom; Destinataire(String qui) { nom = qui; } String aQui() { return nom; } } public void envoyer(int mont, String dest) { Montant montant1 = new Montant(mont);

CHAPITRE 12 LES CLASSES INTERNES

499

Destinataire destinataire1 = new Destinataire(dest); System.out.println("Un mandat de " + montant1.v + " francs a ete expedie a " + destinataire1.nom); System.out.println("Un mandat de " + montant1.valeur() + " francs a ete expedie a " + destinataire1.aQui()); } public static void main (String[] args) { Mandat1 mandat1 = new Mandat1(); mandat1.envoyer(1500, "Albert"); } }

Dans cet exemple, les classes Montant et Destinataire sont des membres de la classe Mandat1. Une instance de la classe Mandat1 est cre dans la mthode main(), puis la mthode envoyer() est appele avec les paramtres qui serviront crer une instance de Montant et de Destinataire. Nous pouvons remarquer que, bien que les champs valeur et nom soient dclars private, ils sont tout fait accessibles depuis la classe externe Mandat1. En fait, les champs private des classes internes et externes sont accessibles librement toutes les classes internes et externes. Ici, le champ nom de la classe Destinataire est donc accessible librement depuis la classe externe Mandat1 mais galement depuis la classe Montant, tout comme il le serait dans les classes internes aux classes Montant et Destinataire. L'exemple suivant en fait la dmonstration :
public class Interne { private int n = 0; private Niveau1 o1; private Niveau2 o2; Interne() { o1 = new Niveau1(); o2 = new Niveau2(); } class Niveau1 { private int n1 = 1;

500
private Niveau11 o11; private Niveau12 o12; Niveau1() { o11 = new Niveau11(); o12 = new Niveau12(); } class Niveau11 { private int n11 = 11;

LE DVELOPPEUR JAVA 2

public void affiche() { System.out.println("Acces depuis niveau 2 :"); System.out.println(n); System.out.println(o1.n1); System.out.println(o1.o11.n11); System.out.println(o1.o12.n12); System.out.println(o2.n2); System.out.println(o2.o21.n21); System.out.println(o2.o22.n22); } } class Niveau12 { private int n12 = 12; } public void affiche() { System.out.println("Acces depuis niveau 1 :"); System.out.println(n); System.out.println(o1.n1); System.out.println(o1.o11.n11); System.out.println(o1.o12.n12); System.out.println(o2.n2); System.out.println(o2.o21.n21); System.out.println(o2.o22.n22); } }

CHAPITRE 12 LES CLASSES INTERNES


class Niveau2 { private int n2 = 2; private Niveau21 o21; private Niveau22 o22; Niveau2() { o21 = new Niveau21(); o22 = new Niveau22(); } class Niveau21 { private int n21 = 21; } class Niveau22 { private int n22 = 22; } } public void affiche() { System.out.println("Acces depuis niveau 0 :"); System.out.println(n); System.out.println(o1.n1); System.out.println(o1.o11.n11); System.out.println(o1.o12.n12); System.out.println(o2.n2); System.out.println(o2.o21.n21); System.out.println(o2.o22.n22); } public static void main (String[] args) { Interne i = new Interne(); i.affiche(); i.o1.affiche(); i.o1.o11.affiche(); } }

501

502
Ce programme affiche le rsultat suivant :

LE DVELOPPEUR JAVA 2

Acces depuis niveau 0 : 0 1 11 12 2 21 22 Acces depuis niveau 1 : 0 1 11 12 2 21 22 Acces depuis niveau 2 : 0 1 11 12 2 21 22

On voit que, bien que tous les membres soient dclars private, ils sont accessibles depuis toutes les classes exactement comme s'ils leur appartenaient. La syntaxe employe pour y accder reprend la hirarchie d'imbrication des classes. Ici, les trois versions de la mthode affiche() utilisent des rfrences explicites. Elles peuvent cependant tre simplifies. Dans une rfrence, les lments dsignant la classe contenant cette rfrence ou des classes d'un niveau hirarchique suprieur peuvent tre omis. Par exemple, la mthode i.o1.o11.affiche() peut tre remplace par :

CHAPITRE 12 LES CLASSES INTERNES


public void affiche() { System.out.println("Acces depuis niveau 2 :"); System.out.println(n); System.out.println(n1); System.out.println(n11); System.out.println(o12.n12); System.out.println(o2.n2); System.out.println(o2.o21.n21); System.out.println(o2.o22.n22); }

503

Pour comprendre le fonctionnement exact de ces rfrences, il faut savoir que nous avons parl de rfrences explicites par abus de langage. En fait, la version utilisant rellement des rfrences explicites est la suivante :
public void affiche() { System.out.println("Acces depuis niveau 2 :"); System.out.println(Interne.this.n); System.out.println(Interne.this.o1.n1); System.out.println(Interne.this.o1.o11.n11); System.out.println(Interne.this.o1.o12.n12); System.out.println(Interne.this.o2.n2); System.out.println(Interne.this.o2.o21.n21); System.out.println(Interne.this.o2.o22.n22); }

Interne.this est rieure Interne et

la faon de faire rfrence l'instance de la classe extcorrespond donc i dans la rfrence la mthode :

i.o1.o11.affiche()

(i tant l'instance de la classe Interne rfrence par Interne.this). Nous voyons qu'il est possible d'omettre dans les rfrences aux objets tous les lments qui figurent dans la rfrence source (ici la rfrence la

504

LE DVELOPPEUR JAVA 2
mthode affiche()). Si nous remplaons i par Interne.this, nous obtenons la rfrence :
Interne.this.o1.o11.

Nous pouvons donc supprimer les lments qui figurent ci-aprs en gras :
public void affiche() { System.out.println("Acces depuis niveau 2 :"); System.out.println(Interne.this.n); System.out.println(Interne.this.o1.n1); System.out.println(Interne.this.o1.o11.n11); System.out.println(Interne.this.o1.o12.n12); System.out.println(Interne.this.o2.n2); System.out.println(Interne.this.o2.o21.n21); System.out.println(Interne.this.o2.o22.n22); }

Bien entendu, les autres versions de la mthode affiche() peuvent galement tre simplifies, en respectant le mme principe. Le programme Mandat1 peut aussi tre crit d'une autre faon :
public class Mandat2 { class Montant { private int v; Montant(int m) { v = m; } public int valeur() { return v; } }

CHAPITRE 12 LES CLASSES INTERNES


class Destinataire { private String nom; Destinataire(String qui) { nom = qui; } String aQui() { return nom; } } public Montant crerMontant(int m) { return new Montant(m); } public Destinataire crerDestinataire(String s) { return new Destinataire(s); }

505

public static void main (String[] args) { Mandat2 mandat2 = new Mandat2(); Montant montant2 = mandat2.crerMontant(1500); Destinataire destinataire2 = mandat2.crerDestinataire("Alfred"); System.out.println("Un mandat de " + montant2.v + " francs a ete expedie a " + destinataire2.nom); System.out.println("Un mandat de " + montant2.valeur() + " francs a ete expedie a " + destinataire2.aQui()); } }

Remarquez la syntaxe utilise l'intrieur des mthodes crant les instances :

506
public Montant crerMontant(int m) { return new Montant(m); }

LE DVELOPPEUR JAVA 2

Rien n'indique ici dans quelle instance de la classe externe doit tre cre l'instance de la classe interne Montant. En fait, cette indication est passe automatiquement par le compilateur en fonction de l'appel de la mthode :
Montant montant2 = mandat2.crerMontant(1500);

Nous aurions ainsi pu crire la mthode sous la forme :


public Montant crerMontant(int m) { return this.new Montant(m); }

Le mot cl this peut tre remplac par toute expression faisant rfrence une instance de la classe externe. La rfrence ainsi value est passe au constructeur de la classe interne.

Instances externes anonymes


Nous pouvons crer une instance d'une classe interne dans une instance anonyme d'une classe externe. Il est alors tout fait possible de rcuprer une rfrence la classe externe, comme dans l'exemple suivant :
public class Mandat3 { static Mandat3 mandat3; class Montant { private int v; Montant(int m) { v = m; }

CHAPITRE 12 LES CLASSES INTERNES


public int valeur() { return v; } } class Destinataire { private String nom; Destinataire(String qui) { nom = qui; } String aQui() { return nom; } } public Montant crerMontant(int m) { mandat3 = Mandat3.this; return this.new Montant(m); } public Destinataire crerDestinataire(String s) { return this.new Destinataire(s); } public static void main (String[] args) { Montant montant3 = new Mandat3().crerMontant(1500); Destinataire destinataire3 = mandat3.crerDestinataire("Alfred"); System.out.println("Un mandat de " + montant3.v + " francs a ete expedie a " + destinataire3.nom); } }

507

Il aurait t parfaitement possible de crer une instance de la classe interne Montant l'aide de la syntaxe suivante :

508

LE DVELOPPEUR JAVA 2
public static void main (String[] args) { Montant montant3 = (new Mandat3()).new Montant(1500);

et de rcuprer la rfrence la classe externe dans le constructeur de la classe interne :

Montant(int m) { mandat3 = Mandat3.this; v = m; }

Classes membres et hritage


Il ne faut surtout pas confondre la hirarchie qui rsulte de l'extension d'une classe par une autre classe et la hirarchie implique par le fait qu'une classe contient d'autres classes, et cela d'autant plus qu'il est parfaitement possible qu'une classe externe tende une de ses classes membres. (Le fait que cela soit possible ne doit pas vous encourager utiliser cette particularit.) Nous avons vu qu'il tait possible, dans une classe interne, de faire rfrence implicitement un champ d'une classe externe. Il est donc possible galement que cette rfrence implicite conduise une ambigut si la classe interne drive d'une classe parente possdant un champ de mme nom. (En revanche, si la classe interne contient elle-mme un champ de mme nom, il n'y a pas d'ambigut car celui-ci masque le champ de la classe externe.) Voici un exemple :

public class Interne3 { private int n = 0; private Niveau1 o1; Interne3() { o1 = new Niveau1(); }

CHAPITRE 12 LES CLASSES INTERNES


class Niveau1 { private int n1 = 1; private Niveau11 o11; private Niveau12 o12; Niveau1() { o11 = new Niveau11(); o12 = new Niveau12(); } class Niveau11 extends NiveauX { private int n11 = 11; public void affiche() { System.out.println("Acces depuis niveau 2 :"); System.out.println(Interne3.this.n); System.out.println(this.n); } } class Niveau12 { private int n12 = 12; } } public static void main (String[] args) { Interne3 i = new Interne3(); i.o1.o11.affiche(); } } class NiveauX { int n = 99; }

509

Ici, la classe Niveau11 tend la classe NiveauX qui contient un champ n. L'instruction :

510
System.out.println(n);

LE DVELOPPEUR JAVA 2

serait donc ambigu. Une telle instruction provoque alors l'affichage d'un message d'erreur par le compilateur. Il est ainsi ncessaire de prciser la rfrence sous la forme :
this.n

pour dsigner le champ de la classe parente, et :


Interne3.this.n

pour dsigner celui de la classe externe. Pour rcapituler, l'exemple suivant reprend toutes les possibilits :
public class Interne4 { private String s = "Classe Interne4"; private Niveau1 o1; Interne4() { o1 = new Niveau1(); } class Niveau1 { private int n1 = 1; private String s = "Classe Niveau1"; private Niveau11 o11; Niveau1() { o11 = new Niveau11(); } class Niveau11 extends NiveauX { private int n11 = 11;

CHAPITRE 12 LES CLASSES INTERNES


private String s = "Classe Niveau11"; public void affiche() { System.out.println("Acces depuis niveau 2 :"); System.out.println(Interne4.this.s); System.out.println(this.s); System.out.println(super.s); System.out.println(Interne4.this.o1.s); } } } public static void main (String[] args) { Interne4 i = new Interne4(); i.o1.o11.affiche(); } } class NiveauX { String s = "Classe NiveauX"; }

511

Ce programme affiche :
Acces depuis niveau 2 : Classe Interne4 Classe Niveau11 Classe NiveauX Classe Niveau1

Remarque concernant les classes membres


Comme les classes imbriques et les autres classes internes, les classes membres ne peuvent pas contenir de membres statiques. Il ne s'agit pas d'une limitation intrinsque mais simplement d'un choix des concepteurs

512

LE DVELOPPEUR JAVA 2
de Java afin de limiter au maximum les risques d'erreurs en obligeant les programmeurs dclarer tous les membres statiques au niveau suprieur de la hirarchie, ce qui est de toute vidence la faon logique de procder. Par ailleurs, les classes membres ne peuvent pas porter le mme nom qu'une classe ou un package les contenant. Les classes membres sont accessibles d'autres classes en utilisant une syntaxe particulire, comme le montre l'exemple suivant :
public class Access { private Interne5 o; Interne5.Niveau1 n1; Access() { o = new Interne5(); n1 = o.new Niveau1(); } void affiche() { n1.affiche(); } public static void main (String[] args) { Access i = new Access(); i.o.affiche(); i.affiche(); } } class Interne5 { Niveau1 o1; Interne5() { o1 = new Niveau1(); } void affiche() { System.out.println(this);

CHAPITRE 12 LES CLASSES INTERNES


o1.affiche(); } class Niveau1 { int n1 = 1; Niveau11 o11; Niveau1() { o11 = new Niveau11(); } void affiche() { System.out.println(this); o11.affiche(); } class Niveau11 { int n11 = 11; public void affiche() { System.out.println(this); System.out.println(); } } } }

513

Dans cet exemple, deux classes sont dfinies : Access et Interne5. La classe Interne5 dfinit en outre deux classes membres, Niveau1 et Niveau11. Ce programme affiche le rsultat suivant :

Interne5@b0e1e784 Interne5$Niveau1@b7b9e784 Interne5$Niveau1$Niveau11@b615e784 Interne5$Niveau1@b611e784 Interne5$Niveau1$Niveau11@b61de784

514

LE DVELOPPEUR JAVA 2
ce qui montre bien que la premire instance de Niveau1 est cre depuis une instance de Interne5 l'adresse b7b9e784 alors que la seconde est cre directement l'adresse b611e784. La partie intressante se trouve dans le constructeur de la classe Access:
Access() { o = new Interne5(); n1 = o.new Niveau1(); }

Ici, une instance de la classe Interne est cre, ce qui entrane automatiquement la cration d'une instance de chacune des classes membres. A la ligne suivante, une instance de la classe membre Niveau1 est cre directement et affecte au handle n1, dclar de type Interne5.Niveau1. Cela est possible grce l'utilisation d'une syntaxe particulire et grce au fait que la classe membre Niveau1 dispose de l'accessibilit par dfaut (package). Les classes membres peuvent galement tre dclares public, protected ou private. Toutefois, il n'est pas trs logique de les dclarer public. Si nous modifions le programme de la faon suivante :
private class Niveau1 { int n1 = 1; Niveau11 o11;

le compilateur affiche alors le message d'erreur suivant :


Inner type Niveau1 in classe Interne5 not accessible from class Access

indiquant que la classe Niveau1 n'est plus accessible depuis la classe Access.

Attention : Ce message est parfaitement normal. Cependant, il est frquent de compiler les classes partir de fichiers spars, ce qui, en prin-

CHAPITRE 12 LES CLASSES INTERNES

515

cipe, devrait fournir le mme rsultat. Dans ce cas, il n'en est rien. Si vous compilez sparment les classes Interne5 puis Access, le message affich par le compilateur est alors :
Class Interne5 .Niveau1 not found

Les classes locales


Les classes locales prsentent la mme diffrence par rapport aux classes membres que celle qui existe entre les variables locales et les variables membres : elles sont dfinies l'intrieur d'un bloc qui en limite la porte. Elles ne sont donc visibles que dans ce bloc (et dans les blocs qui sont inclus dans celui-ci). Elles peuvent utiliser toutes les variables et tous les paramtres qui sont visibles dans le bloc qui les contient. Cependant, les variables et paramtres dfinis dans le mme bloc ne leur sont accessibles que s'ils sont dclars final. Le programme suivant montre un exemple d'utilisation de classes locales. (Cet exemple, comme les prcdents, est compltement stupide et n'a dautre intrt que de montrer les particularits des classes locales. Des exemples concrets seront abords au Chapitre 18, consacr aux interfaces utilisateurs.)
public class Access2 { private Interne6 o; Access2() { o = new Interne6(); } public static void main (String[] args) { Access2 i = new Access2(); i.o.afficheCarr(5); i.o.afficheCube(5); } }

516
class Interne6 { void afficheCarr(final int x) { class Local { Local() { System.out.println(this); } long carr() { return x * x; }

LE DVELOPPEUR JAVA 2

} System.out.println(new Local().carr()); } void afficheCube(final int x) { class Local { Local() { System.out.println(this); } long cube() { return x * x * x; } } System.out.println(new Local().cube()); } }

Ce programme dfinit les classes de premier niveau Access2 et Interne6. La classe Interne6 contient deux dfinitions de mthodes qui constituent donc deux blocs logiques. Chacun de ces blocs contient une dfinition diffrente de la classe Local. Chacune de ces classes contient une mthode qui retourne le carr ou le cube du paramtre pass la mthode qui contient la classe. Pour que ce paramtre soit accessible dans la classe locale, il est dclar final. Chaque classe interne contient en outre un constructeur qui affiche la classe et l'adresse mmoire de chaque instance cre. Les deux

CHAPITRE 12 LES CLASSES INTERNES

517

mthodes afficheCarr et afficheCube affichent leurs rsultats grce aux instructions :


System.out.println(new Local().carr());

et :
System.out.println(new Local().cube());

dans lesquelles une instance de la classe locale Local est cre. Ce programme affiche le rsultat suivant :
Interne6$1$Local@b758c0ff 25 Interne6$2$Local@b6ccc0ff 125

Nous voyons ici que le compilateur Java a cr deux classes distinctes appeles Interne6$1$Local et Interne6$2$Local, ce que nous pouvons vrifier en constatant la prsence des deux fichiers Interne6$1$Local.class et Interne6$2$Local.class.

Note : Tout comme les classes membres, les classes locales ne peuvent
pas contenir de membres statiques (et donc pas de dfinitions d'interfaces, puisque celles-ci seraient implicitement statiques). Elles ne peuvent pas non plus tre dclares static, public, protected ou private car ces modificateurs ne concernent que les membres.

Les handles des instances de classes locales


Le programme de l'exemple prcdent crait des instances des classes locales sans toutefois leur affecter des handles. Nous pouvons nous demander s'il est possible de crer des handles vers ces objets, par exemple :

518
class Interne6 { void afficheCarr(final int x) { Local y; class Local { Local() { System.out.println(this); } long carr() { return x * x; } } y = new Local(); System.out.println(y.carr()); }

LE DVELOPPEUR JAVA 2

Curieusement, cette faon de faire produit un message d'erreur. Le compilateur Java ne permet pas ici la rfrence en avant, c'est--dire qu'il ne nous autorise pas faire rfrence la classe Local avant de l'avoir dfinie. En revanche, il est possible d'utiliser la forme suivante :
class Interne6 { void afficheCarr(final int x) { class Local { Local() { System.out.println(this); } long result() { return x * x; } } Local y = new Local(); System.out.println(y.carr()); }

CHAPITRE 12 LES CLASSES INTERNES

519

Et si nous voulions utiliser l'objet en dehors du bloc dans lequel il est cr ? Est-il possible de l'affecter un handle utilisable en dehors du bloc ? Pour cela, il faudrait tout d'abord pouvoir faire rfrence la classe locale d'une faon explicite. En fait, nous savons que les deux classes sont nommes Interne6$1$Local et Interne6$2$Local. Il nous suffit donc d'utiliser ces noms de la faon suivante :
class Interne6 { Interne6$1$Local y; void afficheCarr(final int x) { class Local { Local() { System.out.println(this); } long carr() { return x * x; } } y = new Local(); }

Attention : Ce type de rfrence aux classes internes est possible l'extrieur de celles-ci, mais pas l'intrieur.
Supposons maintenant que nous modifiions notre programme de la faon suivante :
public class Access3 { private Interne6 o; Access3() { o = new Interne6(); }

520

LE DVELOPPEUR JAVA 2
public static void main (String[] args) { Access3 i = new Access3(); i.o.afficheCarr(5); i.o.afficheCube(5); } } class Interne6 { void afficheCarr(final int x) { class Local { long result() { return x * x; } } print(new Local()); } void afficheCube(final int x) { class Local { long result() { return x * x * x; } } print(new Local()); } void print(Interne6$1$Local o) { System.out.println(o.result()); } void print(Interne6$2$Local o) { System.out.println(o.result()); } }

Ici, les instances des classes locales sont utilises l'extrieur des blocs o elles sont cres. Cependant, la prsence des deux mthodes print n'est pas satisfaisante car il semble que nous pourrions nous contenter d'une

CHAPITRE 12 LES CLASSES INTERNES

521

seule. Pour parvenir ce rsultat, deux solutions sont possibles. La premire consiste sur-caster les instances des classes locales en Object puis utiliser une fonction trs puissante appele RTTI (pour RunTime Type Identification) que nous tudierons dans un prochain chapitre. La seconde solution consiste crer une classe parente disposant de la mthode que l'on souhaite utiliser. En crant les classes locales par extension de cette classe, il devient possible d'y faire rfrence l'extrieur du bloc o elles sont cres et d'invoquer leurs mthodes. Pour une telle utilisation, les interfaces sont tout fait indiques :
public class Access3 { private Interne6 o; Access3() { o = new Interne6(); } public static void main (String[] args) { Access3 i = new Access3(); i.o.afficheCarr(5); i.o.afficheCube(5); } } class Interne6 { interface MonInterface { long result(); } void afficheCarr(final int x) { class Local implements MonInterface { public long result() { return x * x; } } print(new Local()); }

522

LE DVELOPPEUR JAVA 2
void afficheCube(final int x) { class Local implements MonInterface { public long result() { return x * x * x; } } print(new Local()); } void print(MonInterface o) { System.out.println(o.result()); } }

Un point intressant noter dans cet exemple est le fait que les mthodes result() des classes locales doivent tre dclares public. En l'absence de cette dclaration, la redfinition de la mthode de l'interface parente entranerait une restriction de l'accessibilit de celle-ci, ce qui est interdit. Pour plus de dtails, reportez-vous au chapitre consacr l'accessibilit.

Note : Une version de ce programme utilisant les fonctions RTTI sera prsente au Chapitre 17.

Les classes anonymes


Les classes anonymes sont un cas particulier des classes locales. Comme vous l'avez certainement devin, il s'agit simplement de classes dfinies sans noms, en utilisant une syntaxe particulire :
new nom_de_classe([liste_d'arguments]) {dfinition}

ou :
new nom_d'interface () {dfinition)

CHAPITRE 12 LES CLASSES INTERNES

523

Dans les expressions ci-dessus, les lments entre crochets carrs [ ] sont optionnels. (Les crochets ne doivent pas tre taps.) Les oprateurs new ne font pas partie de la dclaration de classe. Cependant, une classe anonyme est gnralement utilise pour la cration immdiate d'une instance. Sa dfinition est donc presque toujours prcde de cet oprateur. Le programme suivant correspond l'exemple prcdent rcrit en utilisant des classes anonymes :
public class Access4 { private Interne7 o; Access4() { o = new Interne7(); } public static void main (String[] args) { Access4 i = new Access4(); i.o.afficheCarr(5); i.o.afficheCube(5); } } class Interne7 { void afficheCarr(final int x) { System.out.println(new Object() { { System.out.println(this); } long result() { return x * x; } }.result()); } void afficheCube(final int x) { System.out.println(new Object() { { System.out.println(this); }

524
long result() { return x * x * x; } }.result()); } }

LE DVELOPPEUR JAVA 2

La principale particularit des classes anonymes est que, n'ayant pas de noms, elles ne peuvent avoir de constructeurs. Si des tches d'initialisation doivent tre effectues, ce que nous avons reprsent ici par :
System.out.println(this);

elles doivent l'tre au moyen d'initialiseurs. Rappelons que les initialiseurs sont des blocs de code qui sont excuts immdiatement aprs le constructeur de la classe parente et avant le constructeur de la classe qui les contient, lorsqu'il y en a un. Si la classe contient plusieurs initialiseurs, ils sont excuts dans l'ordre dans lequel ils se prsentent. Une autre particularit est qu'une classe anonyme doit driver explicitement d'une autre classe ou interface. En revanche, elle ne peut la fois tendre une classe et implmenter une interface, ni implmenter plusieurs interfaces. En d'autres termes, l'hritage multiple est impossible. Les classes anonymes, comme les classes locales, ne peuvent contenir aucun membre static. Elles ne peuvent elles-mmes recevoir aucun modificateur d'accessibilit. Il n'est pas non plus possible de dfinir des interfaces anonymes. Dans la plupart des cas, la dfinition des classes anonymes est incluse dans une expression qui fait elle-mme partie d'une instruction. Cette instruction doit donc se terminer par un point-virgule. Celui-ci se trouvant gnralement plusieurs lignes aprs le dbut de l'instruction, il est facile de l'oublier. Les classes anonymes sont principalement employes pour la dfinition de listeners, qui sont des classes d'objets destins recevoir des messages. Par exemple, si vous crez une fentre pour une interface, vous aurez besoin

CHAPITRE 12 LES CLASSES INTERNES

525

de recevoir le message envoy par le systme lorsque la fentre est ferme. Si la fentre est de type JInternalFrame, l'objet qui doit recevoir ce message doit tre un InternalFrameListener. Le programme suivant montre un exemple d'une telle classe :
this.addInternalFrameListener(new InternalFrameAdapter() { public void internalFrameClosed(InternalFrameEvent e) { System.exit(0); } });

L'instruction this.addInternalFrameListener() ajoute un InternalFrameListener l'objet dsign par this. La classe anonyme utilise n'implmente pas l'interface InternalFrameListener, comme on pourrait s'y attendre, mais tend la classe InternalFrameAdapter. Cette classe est elle-mme une implmentation de l'interface prcite. Cette interface contient six mthodes qui devraient toutes tre redfinies. L'adapter correspondant est une classe qui redfinit ces six mthodes comme ne faisant rien. De cette faon, l'utilisation de l'adapter permet de ne redfinir que les mthodes qui nous intressent. Ce type d'exemple sera tudi en dtail au chapitre consacr aux interfaces utilisateurs et la cration de fentres, boutons et autres menus droulants.

Comment sont nommes les classes anonymes


Toutes anonymes qu'elles soient, ces classes ont tout de mme un nom, ne serait-ce que pour que le compilateur puisse les placer dans un fichier. Si nous compilons l'exemple prcdent, nous constatons que le compilateur cre quatre fichiers :

Access4.class Interne7.class Interne7$1.class Interne7$2.class

526

LE DVELOPPEUR JAVA 2
Nous retrouvons ici les mmes conventions de nommage que pour les classes locales. Il est ainsi possible de faire rfrence explicitement des classes anonymes en dehors du bloc dans lequel elles sont cres :
public class Access4 { private Interne7 o; Access4() { o = new Interne7(); } public static void main (String[] args) { Access4 i = new Access4(); i.o.afficheCarr(5); i.o.afficheCube(5); } } class Interne7 { void afficheCarr(final int x) { print(new Object() { long result() { return x * x; } }); } void afficheCube(final int x) { print(new Object() { long result() { return x * x * x; } }); } void print(Interne7$1 o) { System.out.println(o.result()); }

CHAPITRE 12 LES CLASSES INTERNES


void print(Interne7$2 o) { System.out.println(o.result()); } }

527

Rsum
Dans ce chapitre, nous avons tudi toutes les formes de classes internes. Nous reviendrons en dtail sur les classes locales et les classes anonymes au chapitre consacr la construction d'interfaces utilisateurs (fentres, boutons, etc.). En attendant, le prochain chapitre sera consacr un sujet peu passionnant, mais cependant trs important : le traitement des erreurs.

Chapitre 13 : Les exceptions

Les exceptions

13

ANS TOUT PROGRAMME, LE TRAITEMENT DES ERREURS REPRsente une tche importante, souvent nglige par les programmeurs. Java dispose d'un mcanisme trs efficace pour obliger, autant que possible, les programmeurs prendre en compte cet aspect de leur travail, tout en leur facilitant la tche au maximum. La philosophie de Java, en ce qui concerne les erreurs, peut se rsumer deux principes fondamentaux :

La plus grande partie des erreurs qui pourraient survenir doit tre d Le traitement des erreurs doit tre spar du reste du code, de faon

tecte par le compilateur, de faon limiter autant que possible les occasions de les voir se produire pendant l'excution des programmes.

que celui-ci reste lisible. L'examen d'un programme doit faire appara-

530

LE DVELOPPEUR JAVA 2
tre, au premier coup dil, ce que celui-ci est cens faire lors de son fonctionnement normal, et non lorsque des erreurs se produisent.

Stratgies de traitement des erreurs


Lorsqu'une erreur se produit pendant l'excution d'un programme, deux cas peuvent se prsenter :

L'erreur a t prvue par le programmeur. Dans ce cas, il ne s'agit pas

rellement d'une erreur, mais d'un cas exceptionnel. Cela peut se produire, par exemple, si l'utilisateur entre une valeur ngative ou une valeur trop grande, lorsqu'on lui demande son ge, ou encore lorsqu'un fichier ne peut tre ouvert (par exemple parce qu'il est utilis par quelqu'un d'autre). Dans ce cas, le programme doit contenir des lignes de codes spcialement prvues pour traiter l'erreur.

Il s'agit d'une erreur imprvue. Nous sommes alors de nouveau en


prsence d'une alternative :

L'erreur tait prvisible. Ce type de situation ne doit pas se pro-

duire en Java. En effet, comme nous l'avons dit plus haut, le compilateur Java dtecte les risques d'erreurs de ce type et oblige le programmeur les prendre en compte. Le cas de l'impossibilit d'ouvrir un fichier fait partie de cette catgorie. Si le programmeur crit une instruction ouvrant un fichier sans prendre en compte le cas o l'ouverture est impossible, le compilateur refuse de compiler le programme.

L'erreur tait imprvisible. Dans ce cas, il s'agit d'une erreur de


conception du programme qui dpend des conditions d'excution. L'interprteur produit un message d'erreur et arrte l'excution. Nous sommes alors en prsence d'un bug.

Nous savons donc maintenant que toutes les erreurs prvisibles doivent tre traites par le programmeur. Cependant, devant la possibilit qu'une erreur survienne, le programmeur peut adopter trois stratgies.

CHAPITRE 13 LES EXCEPTIONS

531

Signaler et stopper
La premire attitude consiste signaler l'erreur et arrter le traitement. Par exemple, si l'ouverture d'un fichier est impossible, le programme affiche un message d'erreur indiquant l'utilisateur que l'ouverture du fichier n'a pas pu tre effectue, puis le traitement est interrompu.

Corriger et ressayer
En prsence d'une erreur, le programme peut tenter de la corriger puis essayer de reprendre le traitement. Dans certains cas, le traitement consiste simplement attendre. Par exemple, si un fichier ne peut pas tre ouvert parce qu'il est verrouill par un autre utilisateur, il peut suffire d'attendre que celui-ci le dverrouille. Le programme peut ainsi attendre quelques secondes et tenter d'accder de nouveau au fichier. Il va de soi que si aprs un certain nombre d'essais l'accs est toujours impossible, il faudra envisager une autre voie. Dans le cas contraire, on risquerait de se trouver devant une boucle infinie. Dans d'autres cas, par exemple si l'erreur provient d'une valeur hors des limites acceptables, le programme peut corriger lui-mme cette valeur au moyen d'algorithmes divers (par exemple prendre une valeur par dfaut).

Signaler et ressayer
En prsence d'une erreur, le programme peut aussi signaler celle-ci et demander l'utilisateur de la corriger. Cette approche est videmment adapte aux cas o l'erreur provient d'une saisie incorrecte.

La stratgie de Java
Java ne se proccupe pas des deux dernires options, qui sont laisses la discrtion du programmeur. En revanche, la premire option est impose au programmeur (ce qui ne l'empche pas de mettre en uvre les deux autres).

532

LE DVELOPPEUR JAVA 2
Il peut paratre tout fait insuffisant de simplement signaler une erreur et arrter le traitement. C'est effectivement le cas avec les programmes qui doivent fonctionner de faon autonome. Cependant, avec ce type de programme, le compilateur ne peut pas faire grand-chose. C'est au programmeur qu'il revient d'imaginer tous les cas d'erreurs possibles et les traitements mettre en uvre pour que l'excution puisse continuer. Si une erreur non prvue se produit, il n'y a alors pas d'autre solution que d'arrter le traitement. En revanche, avec un programme interactif, la solution signaler et stopper est tout fait approprie. En effet, stopper n'implique pas l'arrt du programme, mais simplement l'arrt du traitement en cours. Supposons, par exemple, qu'un programme permette un utilisateur d'ouvrir un fichier. Le programme affiche une interface utilisateur (rien voir avec les interfaces de Java) comportant, par exemple, un bouton Ouvrir. Tant que l'utilisateur ne fait rien, le programme ne fait rien non plus. S'il clique sur le bouton, le programme affiche une bote de dialogue comportant la liste des fichiers prsents sur le disque et un bouton OK, puis il s'arrte de nouveau, attendant que l'utilisateur slectionne un fichier et clique sur OK. Une fois cela fait, le programme tente d'ouvrir le fichier. S'il y parvient, il en affiche le contenu (par exemple). Dans le cas contraire, il affiche un message d'erreur et s'arrte. Cela ne signifie pas que le programme soit stopp et effac de la mmoire. Il s'arrte simplement et attend la suite. L'utilisateur peut alors corriger l'erreur, et slectionner de nouveau le mme fichier, ou choisir un autre fichier, ou quitter le programme. Ce qui est important ici est que ces trois options ne font pas partie du traitement de l'erreur. Du point de vue du programme, le traitement de l'erreur consiste simplement la signaler.

Les deux types d'erreurs de Java


En Java, on peut classer les erreurs en deux catgories :

Les erreurs surveilles, Les erreurs non surveilles.

CHAPITRE 13 LES EXCEPTIONS

533

Java oblige le programmeur traiter les erreurs surveilles. Les erreurs non surveilles sont celles qui sont considres trop graves pour que leur traitement soit prvu priori. Par exemple, lorsque vous crivez :

int x, y, z; . . . . z = x / y;

une erreur se produira si y vaut 0. Il s'agit l cependant d'une erreur non surveille. Les concepteurs de Java ont considr qu'il n'tait pas possible d'obliger les programmeurs traiter priori ce type d'erreur. Parfois, il existe un risque qu'elle se produise. Par exemple, si nous crivons un programme permettant de calculer la vitesse d'un vhicule, connaissant le temps qu'il met parcourir un kilomtre, il se produira immanquablement une erreur si l'utilisateur entre 0 pour le temps. Ce type d'erreur est cependant parfaitement prvisible et il revient au programmeur d'en tenir compte. Si vous compilez le programme suivant :

public class Erreur { public static void main(String[] args) { int x = 10, y = 0, z = 0; z = x / y; } }

il est vident qu'il produira une erreur. Pourtant, le compilateur ne s'en proccupe pas. Cette erreur appartient la catgorie des erreurs non surveilles. L'excution de ce programme produit le rsultat suivant :

Exception in thread "main" java.lang.ArithmeticException: / by zero at Erreur.main(Erreur.java:5)

534

LE DVELOPPEUR JAVA 2
Ce message nous apprend plusieurs choses. Tout d'abord, le fait qu'il soit affich indique que l'erreur a t traite. Si ce n'tait pas le cas, l'interprteur ne saurait pas quoi faire. Ici, il sait parfaitement. Un message est affich et l'excution est interrompue. Il ne s'agit donc pas d'une erreur mais d'un traitement exceptionnel.

Les exceptions
Lorsqu'une erreur de ce type est rencontre (ici dans la mthode main de la classe Erreur), l'interprteur cre immdiatement un objet instance d'une classe particulire, elle-mme sous-classe de la classe Exception. Cet objet est cr normalement l'aide de l'oprateur new. Puis l'interprteur part la recherche d'une portion de code capable de recevoir cet objet et d'effectuer le traitement appropri. S'il s'agit d'une erreur surveille par le compilateur, celui-ci a oblig le programmeur fournir ce code. Dans le cas contraire, le traitement est fourni par l'interprteur lui-mme. Cette opration est appele lancement (en anglais throw) d'une exception, par analogie avec le lancement d'un objet qui doit tre attrap par le code prvu pour le traiter. Pour trouver le code capable de traiter l'objet, l'interprteur se base sur le type de l'objet, c'est--dire sur la classe dont il est une instance. S'il existe un tel code, l'objet lui est transmis, et l'excution se poursuit cet endroit. Il faut noter que le premier bloc de code capable de traiter l'objet reoit celui-ci. A cause du polymorphisme, il peut s'agir d'un bloc de code traitant une classe parente de celle de l'objet en question. Dans le cas de notre programme prcdent, une instance de la classe est donc lance. Notre programme ne comportant aucun bloc de code capable de traiter cet objet, celui-ci est attrap par l'interprteur lui-mme. Le traitement effectu consiste afficher la chane de caractres Exception in thread suivie du nom de la mthode d'o l'objet a t lanc, du nom de la classe de l'objet prcd du nom du package auquel elle appartient, d'un message dcrivant l'erreur (/ by zero) et d'une indication sur l'origine de l'erreur at Erreur.main(Erreur.java:5). Nous allons revenir sur chacun de ces lments.
ArithmeticException

CHAPITRE 13 LES EXCEPTIONS

535

Exception

int thread main

Cet lment est fourni par l'interprteur sur la base des indications extraites des lments suivants.

java.lang.ArithmeticException
Il s'agit l simplement du nom de la classe de l'objet reu. Cette indication peut tre dtermine au moyen d'un procd que nous n'avons pas encore tudi. Java permet en effet, en prsence d'un objet, de dterminer la classe dont il est une instance.

by zero

La classe ArithmeticException comporte deux constructeurs. Le premier ne prend aucun paramtre. Le deuxime prend pour paramtre une chane de caractres. Lorsque l'instance d'ArithmeticException a t cre, elle l'a t avec pour paramtre la chane de caractres /byzero. C'est cette chane qui est restitue ici.

at

Erreur.main(Erreur.java:5)

Lors de l'excution d'un programme, l'interprteur Java tient jour la pile des appels de mthode. Chaque fois qu'une mthode est appele, une entre est ajoute sur la pile avec l'emplacement de l'appel et le nom de la mthode appelante. Le lancement d'une exception est assimilable un appel de mthode. Au moment du lancement de l'exception, l'entre Erreur.main(Erreur.java:5) a t ajoute sur la pile. Erreur.main est le nom de la mthode appelante. Erreur.java:5 est l'emplacement de l'appel, constitu du nom du fichier et du numro de ligne.

Attraper les exceptions


Nous avons vu que Java n'oblige pas le programmeur attraper tous les types d'exceptions. Seuls ceux correspondant des erreurs surveilles doivent obligatoirement tre attraps. En fait, les exceptions qui peuvent ne

536

LE DVELOPPEUR JAVA 2
pas tre attrapes sont les instances de la classe RuntimeException ou d'une classe drive de celle-ci. Cependant, rien n'interdit d'attraper ces exceptions. Nous avons dit que lorsqu'une exception est lance, l'interprteur part la recherche d'un bloc de code susceptible de la traiter. Il fait cela en commenant par parcourir le code. S'il s'agit d'une exception surveille, il trouve forcment un tel bloc. Dans le cas d'une exception non surveille, deux cas se prsentent :

Un bloc de code est trouv. Le contrle est pass ce bloc avec, pour
paramtre, un handle vers l'objet exception.

Aucun bloc n'est trouv. Le contrle est pass au code de traitement


des exceptions de type RuntimeException de l'interprteur. Pour pouvoir attraper une exception, il faut entourer le code susceptible de la lancer dans un bloc prcd de l'instruction try. L'exception lance dans le bloc peut tre attrape par une sorte de mthode d'un type particulier dsigne par le mot cl catch et prenant pour paramtre un objet du type de l'exception lance (ou, grce au polymorphisme du type d'une classe parente). Nous pouvons rcrire le programme prcdent pour attraper l'exception lance :
public class Erreur1 { public static void main(String[] args) { int x = 10, y = 0, z = 0; try { z = x / y; } catch (ArithmeticException e) { } } }

Ici, la ligne susceptible de lancer une exception est place dans un bloc try. Si une exception est lance dans ce bloc, l'interprteur cherche un bloc catch susceptible de la traiter. Le premier trouv fait l'affaire. L'exception lui

CHAPITRE 13 LES EXCEPTIONS

537

est passe en paramtre. Ici, le bloc catch ne fait tout simplement rien. Si vous excutez ce programme, vous constaterez qu'il n'affiche aucun message d'erreur. Aucune ligne de code ne doit se trouver entre le bloc try et le bloc catch. Cependant, le bloc try peut inclure plusieurs lignes susceptibles de lancer une exception.

Dans quelle direction sont lances les exceptions ?


Lorsqu'une exception est lance, nous avons dit que l'interprteur cherchait un bloc catch capable de la traiter, et que, s'il n'en trouvait pas, il la traitait lui-mme. Mais dans quelle direction effectue-t-il sa recherche ? La recherche est effectue partir de l'endroit o l'exception est cre, et vers le bas, c'est--dire dans la suite du code. Cependant, la recherche ne continue pas jusqu' la fin du fichier, mais seulement jusqu' la fin du bloc logique contenant le bloc try. Si aucun bloc catch (galement appel handler d'exception attention ne pas confondre handler et handle) n'est trouv, l'exception remonte vers le bloc de niveau suprieur. Nous pouvons en faire la dmonstration l'aide du programme suivant :
public class Erreur2 { public static void main(String[] args) { int x = 10, y = 0, z = 0; try { z = calcul(x, y); } catch (ArithmeticException e) { } } static int calcul(int a, int b) { return a / b; } }

Ici, l'exception est lance dans la mthode calcul. Celle-ci ne comporte pas de handler (bloc catch) pour cette exception. L'exception remonte alors

538

LE DVELOPPEUR JAVA 2
dans le bloc de niveau suprieur, c'est--dire celui o a eu lieu l'appel de la mthode. L, un handler est trouv et l'exception est traite.

Manipuler les exceptions


Le programme ci-dessus ne fait rien de bien intressant en matire de traitement d'exception. Nous allons le modifier afin qu'il affiche les mmes informations que l'interprteur :

public class Erreur3 { public static void main(String[] args) { int x = 10, y = 0, z = 0; try { z = calcul(x, y); } catch (ArithmeticException e) { System.out.print("Exception in thread \"main\" "); e.printStackTrace(); } } static int calcul(int a, int b) { return a / b; } }

Ce programme affiche exactement le mme message que la premire version.

Note : Vous vous demandez peut-tre s'il est possible d'afficher de faon
dynamique le nom de la mthode dans laquelle est lance l'exception. Cela est tout fait possible mais ncessite des connaissances que nous n'avons pas encore abordes.

CHAPITRE 13 LES EXCEPTIONS

539

Crer un handler d'erreur pour afficher le mme message que l'interprteur n'a videmment pas beaucoup l'intrt. En fait, il peut exister deux raisons pour crer des handlers pour les exceptions de type RuntimeException :

Empcher l'excution du handler de l'interprteur ; Complter le handler de l'interprteur.


Nous supposerons donc que nous souhaitons complter l'affichage du handler de l'interprteur en ajoutant un message plus explicite (du moins pour des utilisateurs francophones). Pour cela, nous crerons un handler qui affichera le message voulu puis appellera le handler de l'interprteur. Comme nous l'avons vu, notre handler attrape l'exception lance. Pour excuter le handler de l'interprteur aprs avoir affich un message, il suffit de relancer cette exception, de la faon suivante :

public class Erreur4 { public static void main(String[] args) { int x = 10, y = 0, z = 0; try { z = calcul(x, y); } catch (ArithmeticException e) { System.out.println("Erreur : division par zero."); throw e; } } static int calcul(int a, int b) { return a / b; } }

Ce programme affiche :

540

LE DVELOPPEUR JAVA 2
Erreur : division par zero. Exception in thread "main" java.lang.ArithmeticException: / by zero at Erreur4.main(Erreur4.java:5)

Il faut noter ici un point important : le message d'erreur affich par le handler de l'interprteur indique que l'exception s'est produite la ligne 5, alors qu'elle a t lance la ligne 9. La raison en est trs simple. Le handler ne se proccupe de vrifier ni la provenance ni le contenu de l'exception. Comme l'exception qui est lance la ligne 9 est celle cre la ligne 5, elle contient les mmes informations. Pour obtenir un rsultat diffrent, Java propose deux options. La premire consiste modifier l'exception avant de la relancer. Une exception possde trois caractristiques importantes (qui sont, en fait, hrites de la classe parente Throwable) :

Son type ; Le message qu'elle contient ; Son origine.


Le message qu'elle contient ne peut pas tre modifi car il s'agit d'un champ priv auquel ne correspond aucun mutateur. En revanche, nous pouvons agir sur son type et sur son origine.

Modification de l'origine d'une exception


Si nous voulons que l'exception soit renvoye avec pour origine l'endroit o le renvoi est effectu, il nous suffit d'utiliser la mthode fillInStackTrace() de la faon suivante :
public class Erreur5 { public static void main(String[] args) { int x = 10, y = 0, z = 0; try { z = calcul(x, y); }

CHAPITRE 13 LES EXCEPTIONS


catch (ArithmeticException e) { System.out.println("Erreur : division par zero."); e.fillInStackTrace(); throw e; } } static int calcul(int a, int b) { return a / b; } }

541

Le programme affiche maintenant :

Erreur : division par zero. Exception in thread "main" java.lang.ArithmeticException: / by zero at Erreur5.main(Erreur5.java:9)

Une autre faon de modifier l'exception est de changer son type. Cela n'est videmment pas possible n'importe comment et ne peut tre fait qu'au moyen d'un sur-casting, par exemple de la faon suivante :
public class Erreur6 { public static void main(String[] args) { int x = 10, y = 0, z = 0; try { z = calcul(x, y); } catch (ArithmeticException e) { System.out.println("Erreur : division par zero."); throw (RuntimeException)e; } }

542
static int calcul(int a, int b) { return a / b; } }

LE DVELOPPEUR JAVA 2

Ici, la modification n'a pas beaucoup d'influence sur le rsultat, car le handler d'exception de l'interprteur traite toutes les exceptions de la mme faon, en affichant leur origine et le message qu'elles contiennent puis en arrtant l'excution. En revanche, nous verrons dans une prochaine section que cette technique peut permettre d'effectuer un traitement spcifique une exception particulire, puis de renvoyer celle-ci pour qu'elle soit intercepte de nouveau par un handler traitant les cas plus gnraux. Ici, nous avons renvoy une exception de la classe parente RuntimeException, ce qui ne change pas grand-chose car il s'agit galement d'un type non surveill. En revanche, si nous voulons sur-caster l'exception au niveau suprieur, c'est--dire Exception, le compilateur affiche le message d'erreur suivant :
Erreur6.java:9 Exception java.lang.Exception must be caught, or it must be declared in the throws clause of this method.

Cela est d au fait que les exceptions de cette classe sont surveilles et doivent donc recevoir, de la part du programmeur, un traitement spcial. Ce traitement peut prendre deux formes :

Attraper l'exception. Signaler que l'exception n'est pas attrape.


Java n'est donc vraiment pas contraignant dans ce domaine, d'autant que attraper l'exception ne vous oblige pas la traiter ! Ici, attraper l'exception n'aurait aucun sens. On ne lance pas une exception pour l'attraper au mme endroit ! C'est pourtant possible, comme le montre l'exemple suivant :

CHAPITRE 13 LES EXCEPTIONS


public class Erreur7 { public static void main(String[] args) { int x = 10, y = 0, z = 0; try { z = calcul(x, y); }

543

catch (ArithmeticException e) { System.out.println("ArithmeticException attrapee ligne 8."); try { throw (Exception)e; } catch (Exception e2) { System.out.println("Exception attrape ligne 13."); } } } static int calcul(int a, int b) { return a / b; } }

Cet exemple n'a videmment aucun sens. L'autre solution consiste indiquer que la mthode est susceptible de lancer une exception. Ici, la mthode concerne est main. Sa dclaration doit tre modifie de la faon suivante :
public class Erreur8 { public static void main(String[] args) throws Exception { int x = 10, y = 0, z = 0; try { z = calcul(x, y); }

544

LE DVELOPPEUR JAVA 2
catch (ArithmeticException e) { System.out.println("ArithmeticException attrapee ligne 8."); throw (Exception)e; } } static int calcul(int a, int b) { return a / b; } }

Un tel exemple n'a pas beaucoup d'intrt non plus ! Dans la pratique, le fait d'indiquer qu'une mthode est susceptible de lancer une exception oblige l'utilisateur de cette mthode l'attraper ou la transmettre. Par exemple, la mthode readLine() de la classe BufferedReader permet de lire une ligne saisie par l'utilisateur. Si nous consultons la documentation de Java, nous constatons que sa signature est :
public String readLine() throws IOException

Si nous voulons crer une mthode demander() qui cre un objet de type BufferedReader, lit une ligne et renvoie le rsultat, nous pouvons le faire de la faon suivante :
public static int demander(String s) { System.out.print(s); BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); try { return (new Integer(br.readLine())).intValue(); } catch (IOException e) { System.out.println("Erreur de lecture."); } }

CHAPITRE 13 LES EXCEPTIONS

545

Cependant, une telle mthode ne pourra pas tre compile. En effet, le compilateur constate que, si une erreur se produit dans le bloc try, la mthode ne retourne pas. Une solution consiste ajouter une instruction return dans le bloc catch :
catch (IOException e) { System.out.println("Erreur de lecture."); return 0; }

ou aprs celui-ci :
catch (IOException e) { System.out.println("Erreur de lecture."); } return 0;

La deuxime solution n'est vraiment pas conseiller. Une autre faon, plus lgante, consiste sortir l'instruction return du bloc try. Elle prsente l'inconvnient d'empcher l'anonymat de la valeur de retour :
public static int demander(String s) { int f = 0; System.out.print(s); BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); try { f = (new Integer(br.readLine())).intValue(); } catch (IOException e) { System.out.println("Erreur de lecture."); } return f; }

546

LE DVELOPPEUR JAVA 2
Le programme complet utilisant la mthode demander() pourrait tre le suivant :
import java.io.*; public class Erreur9 { public static void main(String[] args) { int x = demander("Temps en secondes pour 1 km : "); int z = calcul(x); System.out.println("La vitesse est de " + z + " km/h."); } static int calcul(int a) { int x = 0; try { x = 3600 / a; } catch (ArithmeticException e) { System.out.println("Erreur : division par zero"); } return x; } public static int demander(String s) { int f = 0; System.out.print(s); BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); try { f = (new Integer(br.readLine())).intValue(); } catch (IOException e) { System.out.println("Erreur de lecture."); }

CHAPITRE 13 LES EXCEPTIONS


return f; } }

547

Si maintenant nous dcidons de ne pas attraper l'exception IOException dans la mthode demander(), nous devons le signaler afin que les utilisateurs de cette mthode soient avertis que cette exception risque de remonter vers la mthode appelante. Nous le faisons en ajoutant une indication dans la dclaration de la mthode (nous avons retir le traitement de la division par zro pour plus de clart) :

public static int demander(String s) throws IOException { System.out.print(s); BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); return (new Integer(br.readLine())).intValue(); }

Le problme est ainsi vacu de la mthode. L'utilisateur de cette mthode sait maintenant qu'il doit attraper les exceptions de type IOException ou bien indiquer qu'elles doivent tre relances. Le programme complet pourra tre le suivant :
import java.io.*; public class Erreur10 { public static void main(String[] args) { int x = 0; try { x = demander("Temps en secondes pour 1 km : "); } catch (IOException e) { System.out.println("Erreur de lecture."); }

548

LE DVELOPPEUR JAVA 2
int z = calcul(x); System.out.println("La vitesse est de " + z + " km/h."); } static int calcul(int a) { return 3600 / a; } public static int demander(String s) throws IOException { System.out.print(s); BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); return (new Integer(br.readLine())).intValue(); } }

Notez que d'autres petites modifications ont d tre effectues pour que le programme fonctionne. En effet, placer une ligne dans un bloc try limite automatiquement ce bloc la porte des variables qui y sont dclares. Pour que x puisse tre utilise pour le calcul et l'affichage, elle doit tre dclare en dehors du bloc. Une autre solution consiste ne pas traiter l'erreur et la relancer implicitement :
import java.io.*; public class Erreur11 { public static void main(String[] args) throws IOException { int x = demander("Temps en secondes pour 1 km : "); int z = calcul(x); System.out.println("La vitesse est de " + z + " km/h."); } static int calcul(int a) { return 3600 / a; }

CHAPITRE 13 LES EXCEPTIONS

549

public static int demander(String s) throws IOException { System.out.print(s); BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); return (new Integer(br.readLine())).intValue(); } }

Dans ce cas, l'exception remonte jusqu' l'interprteur.

Crer ses propres exceptions


Les exceptions sont des objets comme les autres. En particulier, les classes d'exceptions peuvent tre tendues. Ainsi, dans notre exemple, rien n'empche de rentrer une valeur ngative. Cependant, si nous voulons l'empcher, nous pouvons crer une classe spciale d'exceptions. Le programme suivant en montre un exemple et permet d'illustrer d'autres particularits du traitement des exceptions :

import java.io.*; public class Erreur12 { public static void main(String[] args) { int x = 0; int z = 0; try { x = demander("Temps en secondes pour 1 km : "); } catch (IOException e) { System.out.println("Erreur de lecture."); }

550
try { z = calcul(x); }

LE DVELOPPEUR JAVA 2

catch (NegativeValueException e) { System.out.println("Erreur : " + e.getMessage()); } catch (ArithmeticException e) { System.out.println("Erreur : division par zero."); } System.out.println("La vitesse est de " + z + " km/h."); } static int calcul(int a) { if (a < 0) throw new NegativeValueException("Valeur negative"); return 3600 / a; } public static int demander(String s) throws IOException { System.out.print(s); BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); return (new Integer(br.readLine())).intValue(); } } class NegativeValueException extends ArithmeticException { NegativeValueException(String s) { super(s); } }

La classe NegativeValueException est cre dans le mme fichier que le programme pour les besoins de l'exemple. Normalement, elle devrait probablement tre cre de faon indpendante. Elle tend simplement la classe

CHAPITRE 13 LES EXCEPTIONS


ArithmeticException

551

et dclare un constructeur qui fait simplement appel celui de la classe parente en lui passant son paramtre.

La mthode calcul lance une exception de type NegativeValueException si la valeur qui lui est passe en paramtre est ngative. Cette exception est cre normalement, comme n'importe quel autre objet, et est utilise comme argument de l'instruction throw. Notez que la dclaration de la mthode n'indique pas que celle-ci est susceptible de lancer ce type d'exception. Cette dclaration n'est pas ici obligatoire. De la mme faon, lutilisation du bloc catch correspondant n'est pas ncessaire. En effet, la classe NegativeValueException drive de ArithmeticException, et ce type d'exception n'est pas surveill. L'utilisateur de la mthode demander est donc libre d'attraper ou non cette exception. Que se passe-t-il s'il ne le fait pas ? L'exception lance remonte jusqu' l'interprteur. Celui-ci ne dispose d'aucun handler pour le type NegativeValueException. Cependant, il dispose d'un handler pour la classe parente. Grce au polymorphisme, c'est ce handler qui est excut. Evidemment, le rsultat n'est pas celui que nous souhaitions car le message indique qu'il s'agit d'une division par zro. Notez qu'il existe un autre danger dans le fait de driver notre classe d'exception d'une classe explicitement utilise. En effet, si nous inversons l'ordre des blocs catch :
try { z = calcul(x); } catch (ArithmeticException e) { System.out.println("Erreur : division par zero."); } catch (NegativeValueException e) { System.out.println("Erreur : " + e.getMessage()); } System.out.println("La vitesse est de " + z + " km/h."); }

552

LE DVELOPPEUR JAVA 2
Le programme ne peut plus tre compil. Le compilateur affiche alors le message d'erreur :

Erreur12.java:19: catch not reached catch (NegativeValueException e) { ^ 1 error

pour indiquer que le bloc catch ne sera jamais excut. En effet, nous avons dit que Java cherche le premier bloc susceptible de correspondre au type d'exception lance. En raison du polymorphisme, le premier bloc convient parfaitement. Le second ne sera donc jamais atteint. Pour remdier ce problme, il nous faut driver notre classe d'une classe d'exception non utilise par ailleurs. D'autre part, il est prfrable d'utiliser un type d'exception surveill si nous voulons que les utilisateurs de notre mthode soient obligs de la traiter. Nous pouvons le faire en modifiant simplement sa dclaration :

class NegativeValueException extends Exception { NegativeValueException(String s) { super(s); } }

Cette fois, si nous conservons la dclaration et la dfinition de la mthode calcul inchanges :


static int calcul(int a) { if (a < 0) throw new NegativeValueException("Valeur negative"); return 3600 / a; }

CHAPITRE 13 LES EXCEPTIONS


le compilateur produit deux messages d'erreur :

553

Erreur13.java:19: Exception NegativeValueException is never thrown in the body of the corresponding try statement. catch (NegativeValueException e) { ^ Erreur13.java:27: Exception NegativeValueException must be caught or it must be declared in the throw clause of this method. throw new NegativeValueException ("Valeur negative") { ^ 2 errors

Nous connaissons dj le deuxime. Le premier message indique que nous essayons d'attraper une exception qui n'est jamais lance. L'exemple suivant montre le programme corrig :
import java.io.*; public class Erreur13 { public static void main( String[] args ) { int x = 0; int z = 0; try { x = demander("Temps en secondes pour 1 km : "); } catch (IOException e) { System.out.println("Erreur de lecture."); } try { z = calcul(x); } catch (ArithmeticException e) { System.out.println("Erreur : division par zero."); }

554

LE DVELOPPEUR JAVA 2
catch (NegativeValueException e) { System.out.println("Erreur : " + e.getMessage()); } System.out.println("La vitesse est de " + z + " km/h."); } static int calcul(int a) throws NegativeValueException { if (a < 0) throw new NegativeValueException("Valeur negative"); return 3600 / a; } public static int demander(String s) throws IOException { System.out.print(s); BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); return (new Integer(br.readLine())).intValue(); } } class NegativeValueException extends Exception { NegativeValueException(String s) { super(s); } }

La clause finally
Ce programme n'est pas encore satisfaisant. En effet, si nous l'excutons, voici le rsultat obtenu :
Temps en seconde pour 1 km : -5 Erreur : Valeur Negative La vitesse est de 0 km/h.

CHAPITRE 13 LES EXCEPTIONS

555

Ici, malgr l'erreur, le programme affiche un rsultat. Ce que nous souhaiterions, par exemple, serait que le programme soit interrompu. La mme chose doit se produire s'il s'agit d'une division par 0. Nous pourrions bien sr ajouter chacun des blocs catch une instruction terminant le programme, comme :

System.exit(0);

Cependant, cela est contraire aux principes de la programmation efficace, qui prescrivent que le mme traitement ne doit pas tre effectu plusieurs endroits diffrents. La meilleure solution serait d'indiquer qu'un certain traitement doit tre effectu quel que soit le type d'erreur rencontre. Une solution serait d'appeler une mthode qui arrte le programme :

static void arrteProgramme() { System.exit(0); }

Bien sr, l'appel de la mthode serait dupliqu dans chaque bloc, mais cela ne contredit pas nos principes :
try { z = calcul(x); } catch (ArithmeticException e) { System.out.println("Erreur : division par zero."); arrteProgramme(); } catch (NegativeValueException e) { System.out.println("Erreur : " + e.getMessage()); arrteProgramme(); }

556
} static void arrteProgramme() { System.exit(0); }

LE DVELOPPEUR JAVA 2
System.out.println("La vitesse est de " + z + " km/h.");

Une autre solution serait de relancer une exception de type RuntimeException, qui remonterait jusqu' l'interprteur et provoquerait l'arrt du programme. Ce n'est ni lgant, ni propre, en raison de l'affichage provoqu par le handler d'exception de l'interprteur.

try { z = calcul(x); } catch (ArithmeticException e) { System.out.println("Erreur : division par zero."); throw new RuntimeException(); } catch (NegativeValueException e) { System.out.println("Erreur : " + e.getMessage()); throw new RuntimeException(); } System.out.println("La vitesse est de " + z + " km/h.");

Java dispose d'une instruction spciale pour traiter ce cas. Il s'agit simplement d'un bloc ajout aprs les blocs catch et indiqu par le mot cl finally. Ce bloc est excut aprs que n'importe quel bloc catch de la structure a t excut. Malheureusement, il est galement excut si aucun de ces blocs ne l'est. Cette structure est particulirement utile si un nettoyage doit tre effectu, par exemple dans le cas de l'ouverture d'un fichier, qui devra tre referm mme si aucune erreur ne s'est produite.

CHAPITRE 13 LES EXCEPTIONS

557

Dans notre cas, nous pouvons utiliser cette structure condition de dtecter si une erreur s'est produite ou non. Le programme peut donc tre rcrit de la faon suivante :
import java.io.*; public class Erreur14 { public static void main( String[] args ) { int x = 0; int z = 0; try { x = demander("Temps en secondes pour 1 km : "); } catch (IOException e) { System.out.println("Erreur de lecture."); } try { z = calcul(x); } catch (ArithmeticException e) { System.out.println("Erreur : division par zero."); } catch (NegativeValueException e) { System.out.println("Erreur : " + e.getMessage()); } finally { if (z == 0) System.exit(0); } System.out.println("La vitesse est de " + z + " km/h."); }

558

LE DVELOPPEUR JAVA 2
static int calcul(int a) throws NegativeValueException { if (a < 0) throw new NegativeValueException("Valeur negative"); return 3600 / a; } public static int demander(String s) throws IOException { System.out.print(s); BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); return (new Integer(br.readLine())).intValue(); } } class NegativeValueException extends Exception { NegativeValueException(String s) { super(s); } }

Organisation des handlers d'exceptions


L'organisation des handlers d'exceptions doit tre envisage soigneusement. En effet, il n'est pas toujours vident de savoir comment les regrouper. Ainsi, notre programme pourrait galement tre crit de la faon suivante :
import java.io.*; public class Erreur15 { public static void main( String[] args ) { int x = 0; int z = 0; try { x = demander("Temps en secondes pour 1 km : ");

CHAPITRE 13 LES EXCEPTIONS


z = calcul(x); } catch (IOException e) { System.out.println("Erreur de lecture."); } catch (ArithmeticException e) { System.out.println("Erreur : division par zero."); } catch (NegativeValueException e) { System.out.println("Erreur : " + e.getMessage()); } finally { System.exit(0); }

559

System.out.println("La vitesse est de " + z + " km/h."); } static int calcul(int a) throws NegativeValueException { if (a < 0) throw new NegativeValueException("Valeur negative"); return 3600 / a; } public static int demander(String s) throws IOException { System.out.print(s); BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); return (new Integer(br.readLine())).intValue(); } } class NegativeValueException extends Exception { NegativeValueException(String s) { super(s); } }

560

LE DVELOPPEUR JAVA 2
Ici, il n'existe plus qu'une seule structure, dans laquelle toutes les erreurs possibles sont traites. Il revient au programmeur de dcider quel type d'organisation est le mieux adapt au problme traiter.

Pour un vritable traitement des erreurs


Les procdures que nous avons dcrites jusqu'ici consistent signaler l'erreur et arrter le traitement. Elles sont adaptes certains types de programmes. Cependant, il est souvent prfrable d'essayer de corriger les erreurs et de reprendre le traitement. Java ne propose pas de moyen particulier pour cela. Il revient donc au programmeur de s'en proccuper. Par exemple, dans le programme prcdent, deux types d'erreurs au moins peuvent tre traits. En fait, deux types d'erreurs se ramnent mme un seul traitement. Ainsi, le fait qu'une valeur soit ngative ou nulle peut parfaitement se traiter de la mme faon, en essayant d'obtenir une autre valeur. Le programme suivant montre une faon de traiter le problme :

import java.io.*; public class Erreur17 { public static void main( String[] args ) { int x = 0; int z = 0; x = demanderVP("Entrez le temps en secondes pour 1 km"); z = calcul(x); System.out.println("La vitesse est de " + z + " km/h."); } static int calcul(int a) { return 3600 / a; } public static int demanderVP(String s) { int v = 0; int i = 0;

CHAPITRE 13 LES EXCEPTIONS


BufferedReader br =

561

new BufferedReader(new InputStreamReader(System.in)); System.out.println(s); while (v <= 0) { System.out.print("La valeur doit etre positive : "); try { v = (new Integer(br.readLine())).intValue(); } catch (NumberFormatException e) { } catch (IOException e) { System.out.println("Erreur de lecture."); System.exit(0); } i++; if (i > 3) { System.out.println("Maintenant, y'en a marre !"); System.exit(0); } } return v; } }

La mthode demander a t renomme demanderVP pour indiquer qu'elle doit retourner une valeur positive. En situation relle, nous aurions probablement besoin d'une mthode avec plusieurs signatures, par exemple sans argument pour une valeur quelconque, et avec un ou des arguments si l'on veut que la valeur retourne soit limite d'une faon ou d'une autre.

562

LE DVELOPPEUR JAVA 2

D'autres objets jetables


Si vous tes curieux, vous avez peut-tre dj jet un coup d'il la gnalogie des exceptions. Dans ce cas, vous aurez remarqu que les exceptions sont une extension d'une classe plus gnrale appele Throwable (qui signifie jetable en anglais). La classe Throwable possde deux sous-classes : Error et Exception. Les Exception sont les objets susceptibles d'tre jets et intercepts (ou attraps). Les Errors sont les objets jets lorsque les conditions sont telles qu'il n'y a rien faire pour remdier au problme. Elles ne devraient donc pas tre interceptes.

Les exceptions dans les constructeurs


Les constructeurs peuvent lancer des exceptions au mme titre que les mthodes. Cependant, il faut noter une particularit importante. Si une exception est lance par un constructeur, il est possible que la clause finally soit excute alors que le processus de cration de l'objet n'est pas termin, laissant celui-ci dans un tat d'initialisation incomplte. Nous reviendrons sur ce problme dans le chapitre consacr aux entres/sorties, avec un exemple concernant l'ouverture d'un fichier.

Exceptions et hritage
Vous savez maintenant depuis longtemps qu'une classe peut redfinir les mthodes des classes parentes. Lorsque celles-ci dclarent des exceptions, les possibilits de redfinition supportent une restriction. Les mthodes redfinies ne peuvent pas lancer d'autres exceptions que celles dclares par les mthodes de la classe parente, ou celles drives de celles-ci.

CHAPITRE 13 LES EXCEPTIONS

563

Rsum
Dans ce chapitre, nous avons tudi le mcanisme qui permet de traiter les conditions exceptionnelles. Cette tude tait indispensable en raison du fait que Java oblige le programmeur tenir compte des exceptions dclares par les mthodes. De fait, plus de la moiti des mthodes des classes standard de Java dclarent des exceptions et seraient donc inutilisables sans la connaissance de ces techniques. Le prochain chapitre sera consacr aux entres/sorties, un domaine dans lequel il est largement fait appel aux exceptions.

Chapitre 14 : Les entres/sorties

Les entres/sorties

1 4

UCUN PROGRAMME NE PEUT SE PASSER DE COMMUNIQUER AVEC le monde extrieur. Cette communication consiste recevoir des donnes traiter, et renvoyer des donnes traites. La premire opration constitue une entre, la seconde une sortie. Les programmes que nous avons raliss jusqu'ici ne comportaient le plus souvent aucune entre, car ils traitaient les donnes qui avaient t incluses dans le programme lui-mme. Par ailleurs, la seule sortie employe tait l'cran de la console. Dans ce chapitre, nous allons tudier certaines possibilits d'entre/sortie des programmes Java. Nous laisserons de ct le cas des programmes utilisant une interface fentre, qui sera trait dans un chapitre spcifique, ainsi que celui des entres/sorties rseau. Nous nous intresserons plus particulirement la console, c'est--dire l'ensemble clavier/cran pour l'entre et la sortie en mode caractres, ainsi qu'aux fichiers sur disques.

566

LE DVELOPPEUR JAVA 2

Principe des entres/sorties


Pour effectuer une entre ou une sortie de donnes en Java, le principe est simple et se rsume aux oprations suivantes :

Ouverture d'un moyen de communication. criture ou lecture des donnes. Fermeture du moyen de communication.
En Java, les moyens de communication sont reprsents par des objets particuliers appels (en anglais) stream. Ce mot, qui signifie courant, ou flot, a une importance particulire. En effet, dans de nombreux langages, on ouvre un canal de communication. La diffrence smantique entre les deux termes est flagrante : le canal est l'endroit o coule le flot. Java s'intresse donc davantage ce qui est transmis qu'au moyen physique utilis pour la transmission. Dans la suite de cette discussion, afin qu'il n'y ait aucun risque de confusion, nous utiliserons le mot stream (prononcez striiiime). Il existe de nombreuses sortes de streams, qui peuvent tre classs selon plusieurs critres :

Les streams d'entres et les streams de sortie. Les streams de caractres (texte) et les streams de donnes binaires. Les streams de traitement des donnes et les streams de communication de donnes.

Les streams accs squentiel et les streams accs direct (parfois


appel improprement accs alatoire).

Les streams avec et sans tampon de donnes.


Les streams relient le programme et un lment particulier appel sink, ce qui signifie en anglais vier et doit tre pris au sens de rcipient. Cepen-

CHAPITRE 14 LES ENTRES/SORTIES

567

dant, plusieurs streams peuvent tre chans, par exemple pour cumuler divers traitements sur les donnes transfres.

Les streams de donnes binaires


Les streams de donnes binaires drivent de deux classes du package java.io : InputStream, pour les entres de donnes, et OutputStream, pour les sorties.

Les streams d'entre


Les streams d'entre sont des sous-classes de la classe java.io.InputStream :

Streams de communication

FileInputStream : permet la lecture squentielle de donnes dans un


fichier.

PipedInputStream : permet d'tablir une connexion entre un stream


d'entre et un stream de sortie (de type PipedOutputStream).

ByteArrayInputStream : permet la lecture de donnes binaires au moyen


d'un tampon index.

Streams de traitement

FilterInputStream : cette classe sert de classe parente diverses classes de streams effectuant des traitements sur les donnes lues. Les principales classes drives sont les suivantes :

BufferedInputStream : lecture des donnes l'aide d'un tampon.

568

LE DVELOPPEUR JAVA 2

CheckedInputStream :

lecture des donnes avec vrification (contrle de checksum). Cette classe se trouve dans le package java.util.zip.
int, long, char, float, double, boolean,

DataInputStream : lecture de donnes au format Java (byte, short,


etc.).

DigestInputStream : lecture de donnes avec vrification de l'intgrit. Cette classe se trouve dans le package java.security.

InflaterInputStream : lecture de donnes compresses. Trois


sous-classes sont disponibles pour trois algorithmes de dcompression : GZIPInputStream, ZipInputStream et JarInputStream.

ProgressMonitorInputStream : lecture de donnes avec affichage

d'une barre de progression. L'utilisateur peut interrompre la lecture en cliquant sur un bouton. Ce stream concerne uniquement les applications fentres et se trouve dans le package javax.swing.

PushBackInputStream : lecture de donnes avec la possibilit de

renvoyer la dernire donne lue. De cette faon, la dernire donne lue sera galement la premire lue lors de la prochaine lecture.

SequenceInputStream : enchanement d'InputStream. Un tel stream per-

met, par exemple, d'effectuer une lecture tamponne avec dcompression et vrification des donnes.

ObjectInputStream : ce stream permet de lire des donnes reprsentant directement des objets Java (qui ont pralablement t crits l'aide d'un ObjectOutputStream ). Cette opration est appele dsrialisation.

Les streams de sortie


Il existe un stream de sortie correspondant chaque stream d'entre, condition qu'un tel objet ait un sens :

CHAPITRE 14 LES ENTRES/SORTIES Streams de communication

569

FileOutputStream : criture squentielle de donnes dans un fichier. PipedOutputStream : permet d'tablir une connexion entre un stream
d'entre (de type PipedInputStream) et un stream de sortie.

ByteArrayOutputStream : criture de donnes binaires dans un tampon index.

Streams de traitement

FilterOutputStream : cette classe sert de classe parente diverses classes


de streams effectuant des traitements sur les donnes crites. Les principales classes drives sont les suivantes :

BufferedOutputStream : criture de donnes l'aide d'un tampon.

CheckedOutputStream : criture des donnes avec vrification (con-

trle de checksum). Cette classe se trouve dans le package java.util.zip.

DataOutputStream : criture de donnes au format Java (byte,


short, int, long, char, float, double, boolean,

etc.). Ces donnes sont ainsi portables d'une application une autre, indpendamment du systme hte.

DigestOutputStream : criture de donnes avec cration d'un

hashcode permettant d'en vrifier l'intgrit lors de la relecture au moyen d'un DigestInputStream. Cette classe se trouve dans le package java.security.

DeflaterInputStream : criture de donnes avec compression.


Trois sous-classes sont disponibles pour diffrents algorithmes de compression : GZIPOutputStream , ZipOutputStream et JarOutputStream.

570

LE DVELOPPEUR JAVA 2

PrintStream : criture de donnes avec conversion en octets en

fonction du systme hte. Ce type de stream est ddi l'affichage. Il offre deux particularits : dune part, il ne lance pas d'exception de type IOException en cas d'erreur, mais initialise un indicateur qui peut tre consult l'aide de la mthode checkError() ; d'autre part, il est tamponn et le tampon est vid automatiquement lorsqu'un tableau de caractres a t entirement crit, ou lors de l'criture du caractre \n.

Note : Vous pouvez penser que cette classe n'a rien faire ici et devrait
figurer avec les streams de caractres. C'est galement ce que se sont dit les concepteurs de Java lors du passage la version 1.1. Cette classe a alors t remplace par PrintWriter. La classe PrintStream s'est alors trouve deprecated, c'est--dire conserve uniquement pour assurer la compatibilit. Avec la version 2, elle est rhabilite, avec l'explication suivante : L'exprience montre que le remplacement de PrintStream par PrintWriter n'est pas toujours pratique, ce qui, en langue de bois, signifie nous avons fait une btise et les utilisateurs ont hurl leur mcontentement.

ObjectOutputStream : ce stream permet d'crire des donnes repr-

sentant directement des objets Java (qui pourront tre relus l'aide d'un ObjectInputStream). Cette opration est appele srialisation.

Les streams de caractres


Les streams de caractres sont conus pour la lecture et l'criture de texte. Les caractres pourraient parfaitement tre considrs comme des donnes binaires. Cependant, un certain nombre de particularits justifient l'utilisation de classes de streams spcialises. En effet, les caractres Java sont des donnes de 16 bits, alors que les streams de donnes binaires traitent des octets. Les streams de caractres sont drivs de deux classes abstraites : Reader et Writer.

CHAPITRE 14 LES ENTRES/SORTIES

571

Les streams d'entre


Les streams d'entre sont des sous-classes de la classe java.io.Reader. Deux des mthodes de cette classe sont abstraites :

read(char[] cbuf, int off, int len), qui permet de lire len caractres et de les placer dans le tableau cbuf, partir de l'indice off.

close(), qui ferme le stream.


Toutes les classes drives de Reader redfinissent donc obligatoirement ces deux mthodes.

Streams de communication

PipedReader : permet d'tablir une connexion entre un stream d'entre et un stream de sortie (de type PipedWriter).

CharArrayReader : permet la lecture de caractres au moyen d'un tampon index.

StringReader : permet la lecture de caractres partir d'une chane. La


chane est ainsi traite comme la source d'un stream.

FileReader : sous-classe particulire de InputStreamReader utilisant

l'encodage et la taille de tampon par dfaut. Cette classe convient dans la plupart des cas de lecture d'un fichier de caractres. Pour utiliser un autre encodage ou une taille de tampon personnalise, il est ncessaire de sous-classer InputStreamReader.

Streams de traitement

InputStreamReader : permet la conversion d'un stream de donnes


binaires en stream de caractres.

FilterReader : cette classe sert de classe parente aux classes de streams

effectuant des traitements sur les caractres lus. java.io contient une sous-classe de FilterReader, PushBackReader, qui permet de lire des

572

LE DVELOPPEUR JAVA 2
caractres avec la possibilit de renvoyer le dernier caractre lu. De cette faon, il est possible de lire des caractres comportant un indicateur de dbut de champ. Lorsque cet indicateur (un caractre particulier) est rencontr, il peut tre renvoy de faon tre le premier caractre lu lors de la prochaine lecture.

BufferedReader : lecture de caractres l'aide d'un tampon. Les caractres peuvent ainsi tre lus en bloc. java.io contient une sous-classe de BufferedReader, LineNumberReader, qui permet de lire des caractres en comptant les lignes. (Les lignes sont dlimites par les caractres CR (\r), LF (\n) ou CRLF (\r\n).)

Les streams de sortie


Les streams de sortie correspondent aux streams d'entre, l'exception de ceux qui n'auraient pas de sens. (Aucun stream de sortie ne correspond PushBackReader ni LineNumberReader.) Les streams de sortie sont des sous-classes de la classe java.io.Writer, qui contient trois mthodes abstraites :

write(char[] cbuf, int off, int len), qui permet d'crire len caractres partir du tableau cbuf, en commenant l'indice off.

flush(), qui vide le tampon ventuel du stream, provoquant l'criture


effective des caractres qui s'y trouvent. Si le stream est chan avec un autre stream, la mthode flush() de celui-ci est automatiquement appele.

close(), qui ferme le stream.


Toutes les classes drives de Writer redfinissent donc obligatoirement ces trois mthodes.

Streams de communication

PipedWriter : permet d'tablir une connexion entre un stream d'entre (de type PipedReader) et un stream de sortie.

CHAPITRE 14 LES ENTRES/SORTIES

573

CharArrayWriter : permet la lecture de caractres au moyen d'un tampon index.

StringWriter : permet l'criture de caractres dans un StringBuffer,


qui peut ensuite tre utilis pour crer une chane de caractres.

FileWriter : sous-classe particulire de OutputStreamWriter utilisant

l'encodage et la taille de tampon par dfaut. Cette classe convient dans la plupart des cas d'criture d'un fichier de caractres. Pour utiliser un autre encodage ou une taille de tampon personnalise, il est ncessaire de sous-classer OutputStreamWriter.

Streams de traitement

OutputStreamWriter : permet la conversion d'un stream de donnes


binaires en stream de caractres.

FilterWriter : cette classe sert de classe parente aux classes de streams BufferedWriter : criture de caractres l'aide d'un tampon. Les ca-

effectuant des traitements sur les caractres crits. java.io ne contient aucune sous-classe de FilterWriter. ractres peuvent ainsi tre crits en bloc. L'utilisation la plus courante consiste crire des lignes de texte en les terminant par un appel la mthode newLine(), de faon obtenir automatiquement le caractre de fin de ligne correspondant au systme CR (\r), LF (\n) ou CRLF (\r\n). est particulirement utilise pour l'affichage en mode texte.

PrintWriter : cette classe permet d'crire des caractres formats. Elle Les streams de communication
Il est parfois plus utile de classer les streams selon des critres diffrents. Nous les avons prcdemment classs selon la nature de ce qu'ils manipulent, c'est--dire en streams de caractres et streams de donnes binaires. Il

574

LE DVELOPPEUR JAVA 2
est parfois plus pertinent de les classer selon ce qu'ils font des donnes qu'ils manipulent, c'est--dire en streams de communication et streams de traitement. Les streams de communication peuvent leur tour tre classs en fonction de la destination des donnes. Un stream de communication tablit une liaison entre le programme et une destination, qui peut tre :

La mmoire :
Donnes binaires :
ByteArrayInputStream ByteArrayOutputStream StringBufferInputStream

Caractres :
CharArrayReader CharArrayWriter StringReader StringWriter

Un fichier :
Donnes binaires :
FileInputStream FileOutputStream

Caractres :
FileReader FileWriter

Un chanage de streams (pipe) :


Donnes binaires :
PipedInputStream PipedOutputStream

Caractres :
PipedReader PipedWriter

CHAPITRE 14 LES ENTRES/SORTIES

575

Lecture et criture d'un fichier


A titre d'exemple, nous allons crire un programme capable de lire le contenu d'un fichier et de l'crire dans un autre fichier. Nous commencerons par traiter un fichier de texte. Pour faire fonctionner ce programme, vous devrez disposer d'un fichier texte contenant n'importe quoi et nomm original.txt. La copie sera nomme copie.txt. Nous aurons besoin d'un stream de type FileReader et d'un stream de type FileWriter. Si nous consultons la documentation de Java concernant ces deux classes, nous constatons qu'il existe trois versions de leurs constructeurs, prenant respectivement pour argument :

Un objet de type File, Un objet de type FileDescriptor, Une chane de caractres.


La version utilisant une chane de caractres est la plus simple utiliser :
import java.io.*; public class CopieTXT { public static void main (String[] args) throws IOException { int c; FileReader entre = new FileReader("original.txt"); FileWriter sortie = new FileWriter("copie.txt"); while ((c = entre.read()) != -1) sortie.write(c); entre.close(); sortie.close(); } }

Ce programme fonctionne parfaitement, mais il est un peu simpliste. En effet, il ne peut copier un fichier que s'il est nomm original.txt. Si ce fichier n'existe pas, le programme produit une erreur. En revanche, il crase sans avertissement le fichier de sortie s'il existait dj. Nous le perfectionnerons plus tard. En attendant, il est utile d'analyser son fonctionnement.

576

LE DVELOPPEUR JAVA 2
La mthode main du programme dclare qu'elle lance des exceptions de type IOException. De cette faon, elle n'a pas les traiter et laisse ce soin l'interprteur. Deux streams de type FileReader et FileWriter sont crs en appelant les constructeurs de ces classes avec deux chanes de caractres reprsentant les noms des fichiers. La partie la plus intressante se trouve dans la boucle while. La condition de cette boucle appelle la mthode read() du stream entre. Dans sa version sans argument, cette mthode lit un caractre du stream et le retourne sous la forme d'un int. (On retrouve l la vielle habitude de Java de surcaster toutes les valeurs entires en int.) Si la fin du stream est atteinte, cette fonction retourne -1. (Ce qui explique qu'il n'est pas possible d'utiliser le type char.) Une fois la boucle termine, les deux streams sont ferms. Pour que ce programme soit plus efficace, il faudrait demander l'utilisateur le nom du fichier copier, tester son existence et afficher un message d'erreur s'il n'existe pas, puis tester l'existence du fichier de sortie et, s'il existe, demander l'utilisateur s'il doit tre cras. Pour cela, nous avons besoin d'un stream de traitement.

Les streams de traitement


Les streams de traitement peuvent tre classs en fonction du type de traitement que subissent les donnes :

Tamponnage :
Donnes binaires :
BufferedInputStream BufferedOutputStream

Caractres :
BufferedReader BufferedWriter

CHAPITRE 14 LES ENTRES/SORTIES

577

Le tamponnage consiste regrouper les donnes dans un tampon de faon acclrer certains traitements. Par exemple, il est plus rapide de lire un fichier ligne par ligne et de placer chaque ligne dans un tampon (une zone de mmoire rserve cet effet) d'o chaque caractre pourra tre extrait individuellement, plutt que de lire le fichier caractre par caractre. En effet, la lecture dans un fichier est beaucoup plus lente que la lecture du tampon. Ainsi, si une opration de lecture dans un fichier prend 10 millisecondes et une opration de lecture dans le tampon 0,1 milliseconde, la lecture de 10 000 caractres prendra 100 secondes sans tampon (10 000 x 10 millisecondes) et 2 secondes avec un tampon ((100 x 10) + (10 000 x 0,1) millisecondes).

Filtrage :
Donnes binaires :
FilterInputStream FilterOutputStream

Caractres :
FilterReader FilterWriter

Concatnation :
Donnes binaires :
SequenceInputStream

Conversion de donnes :
Donnes binaires :
DataInputStream DataOutputStream

Caractres :
InputStreamReader OutputStreamWriter

Comptage :
Caractres :
LineNumberReader

578

LE DVELOPPEUR JAVA 2

Srialisation :
Donnes binaires :
ObjectInputStream ObjectOutputStream

Lecture anticipe :
Donnes binaires :
PushBackInputStream

Caractres :
PushBackReader

Affichage et impression :
Donnes binaires :
PrintStream

Caractres :
PrintReader

Nous utiliserons pour l'instant un stream de type BufferedReader pour lire les donnes entres par l'utilisateur. (Nous avons dj utilis cette technique au chapitre prcdent sans la dcrire.) Un BufferedReader peut tre cr en utilisant, comme paramtre de son constructeur, un objet de type InputStreamReader. La cration de celui-ci ncessite pour sa part le passage d'un paramtre de type InputStream. Nous utiliserons ici le champ statique in de la classe System, qui correspond au clavier de la console. L'instruction complte crant le chanage de ces streams se prsente sous la forme :
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

La classe BufferedReader contient une mthode readLine() qui lit une ligne de texte, c'est--dire, dans le cas prsent, tous les caractres fournis

CHAPITRE 14 LES ENTRES/SORTIES

579

par l'InputStreamReader jusqu'au caractre fin de ligne. Le rsultat de cette mthode est affect une chane de caractres :
String s = br.readLine();

Il ne reste plus alors qu' utiliser cette chane pour crer un FileReader. Bien sr, le traitement des erreurs reprsente la plus grosse partie du travail. Le programme suivant montre un exemple de ce type de traitement :
import java.io.*; public class CopieTXT3 { public static void main (String[] args) throws IOException { FileReader original = Util.demanderOriginal ("Entrez le nom du fichier texte a copier : "); FileWriter copie = Util.demanderCopie ("Entrez le nom a donner a la copie du fichier : "); int c; while ((c = original.read()) != -1) copie.write(c); original.close(); copie.close(); } } class Util { public static FileReader demanderOriginal(String s) { FileReader fr = null; String s1 = null; int i = 0; BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); System.out.print(s); while (fr == null) { try { s1 = br.readLine(); }

580

LE DVELOPPEUR JAVA 2
catch (IOException e) { System.out.println("Erreur de lecture de la console."); System.exit(0); } try { fr = new FileReader(new File(s1)); } catch (NullPointerException e) { } catch (FileNotFoundException e) { if (i < 2) System.out.print ("Ce fichier n'existe pas. Entrez un autre nom : "); else if (i == 2) System.out.print ("Vous avez encore droit a un essai : "); } i++; if (i > 4) { System.out.println("Nombre d'essais depasse."); System.exit(0); } } return fr; } public static FileWriter demanderCopie(String s) { FileWriter fr = null; File f = null; String s1 = null; int i = 0; BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); System.out.print(s); while (fr == null) { try { s1 = br.readLine(); }

CHAPITRE 14 LES ENTRES/SORTIES

581

catch (IOException e) { System.out.println("Erreur de lecture de la console."); System.exit(0); } try { f = new File(s1); if (f.exists()) { System.out.print ("Le fichier existe. Voulez-vous l'ecraser (o/n) : "); try { s1 = br.readLine(); } catch (IOException e) { System.out.println ("Erreur de lecture de la console."); System.exit(0); } if (!s1.equals("O") && !s1.equals("o")) { fr = null; i = 0; System.out.print ("Entrez un autre nom de fichier : "); } else fr = new FileWriter(f); } else fr = new FileWriter(f); } catch (NullPointerException e) { } catch (IOException e) { if (i < 2) System.out.print ("Ce fichier n'existe pas. Entrez un autre nom : ");

582

LE DVELOPPEUR JAVA 2
else if (i == 2) System.out.print ("Vous avez encore droit a un essai : "); } i++; if (i > 4) { System.out.println("Nombre d'essais depasse."); System.exit(0); } } return fr; } }

Le programme principal est extrmement simple. Il fait appel aux mthodes demanderOriginal() et demanderCopie() de la classe Util. Ces mthodes sont beaucoup plus complexes car elles doivent prendre en compte de nombreux paramtres. Les exemples prsents ici sont de type Q&D (Quick and Dirty), ce qui signifie qu'elles sont crites rapidement sans souci d'optimisation. Ce qui importe est que leurs interfaces soient clairement dfinies. Il sera toujours possible de rcrire ces mthodes par la suite. Dans les exemples de la suite de ce chapitre, nous ferons appel ces mthodes sans les reproduire.

Exemple de traitement : utilisation d'un tampon


Jusqu'ici, la faon de traiter les entres/sorties en Java ne semblait pas particulirement excitante. Cependant, nous allons voir maintenant qu'elle offre de nombreux avantages. Il est en effet possible de combiner loisir les diffrents streams, par exemple pour effectuer des traitements pendant la transmission des donnes. Un traitement simple consiste utiliser un tampon pour acclrer la copie. Il suffit pour cela d'envelopper les streams FileReader et FileWriter dans des streams BufferedReader et BufferedWriter, ce qui ne prsente aucune difficult :

CHAPITRE 14 LES ENTRES/SORTIES


import java.io.*;

583

public class Tampon { public static void main (String[] args) throws IOException { FileReader o = Util.demanderOriginal ("Entrez le nom du fichier texte a copier : "); FileWriter d = Util.demanderCopie ("Entrez le nom a donner a la copie du fichier : "); BufferedReader original = new BufferedReader(o); BufferedWriter copie = new BufferedWriter(d); int c; while ((c = original.read()) != -1) copie.write(c); original.close(); copie.close(); } }

Les modifications ncessaires apparaissent en gras. La copie est maintenant beaucoup plus rapide.

Exemple de traitement : conversion des fins de lignes


Le programme prcdent copiait simplement le contenu d'un fichier. Le prochain exemple ajoutera un traitement entre la lecture et l'criture. Ce traitement consistera convertir les fins de lignes MAC (CR), UNIX (LF) ou MS-DOS (CRLF) en fins de lignes adaptes au systme. Ce programme permettra donc de convertir un fichier texte venant de n'importe quel systme vers le systme utilis.

Attention : Il ne s'agit ici que de la conversion des fins de lignes et non


des caractres accentus.

584
import java.io.*;

LE DVELOPPEUR JAVA 2

public class CRLF { public static void main (String[] args) throws IOException { FileReader o = Util.demanderOriginal ("Entrez le nom du fichier texte a copier : "); FileWriter d = Util.demanderCopie ("Entrez le nom a donner a la copie du fichier : "); BufferedReader original = new BufferedReader(o); BufferedWriter copie = new BufferedWriter(d); String c; while ((c = original.readLine()) != null) { copie.write(c); copie.newLine(); } original.close(); copie.close(); } }

Ce type de traitement ne ncessite pas la mise en uvre d'un nouveau stream, mais simplement l'utilisation de la mthode readLine() au lieu de la mthode read(). Cette mthode lit une ligne entire en ignorant le caractre de fin de ligne. Java reconnat les fins de lignes de type CR, LF ou CRLF. Lors de la copie, la mthode newLine() est appele pour crire une fin de ligne correspondant au systme utilis. Nous n'avons donc pas nous proccuper de sa nature !

Compression de donnes
La compression des donnes est un type de traitement frquemment employ. Il peut tre ralis facilement en Java en utilisant un stream de type DeflaterOutputStream, qui est une sous-classe de FilterOutputStream. Java propose trois classes de DeflaterOutputStream:

CHAPITRE 14 LES ENTRES/SORTIES

585

ZipOutputStream, pour la cration de fichiers de type Zip, ce qui est le


standard de compression sous MS-DOS et Windows.

GZIPOutputStream, pour la cration de fichiers de type GZIP, couramment employs sous UNIX.

JarOutputStream, pour la cration de fichiers de type JAR, standard

Java utilisant le mme algorithme que les fichiers Zip mais ajoutant certaines informations. Les fichiers JAR permettent de compresser en un seul fichier toutes les classes ncessaires une application et, surtout, une applet, ce qui permet d'acclrer considrablement son chargement travers un rseau tel qu'Internet.

Nous utiliserons le type Zip. Avant de crer le programme proprement dit, il nous faut ajouter notre classe Util deux mthodes retournant des FileInputStream et FileOutputStream au lieu des FileReader et FileWriter. En effet, les fichiers compresss contiennent des donnes binaires et non du texte. Il nous suffit simplement de recopier les deux mthodes en effectuant quelques petites modifications :
import java.io.*; public class Util { public static FileReader demanderOriginal(String s){ . . . return fr; } public static FileWriter demanderCopie(String s) { . . . return fr; } public static FileInputStream binOriginal(String s) { FileInputStream fr = null;

586

LE DVELOPPEUR JAVA 2
String s1 = null; int i = 0; BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); System.out.print(s); while (fr == null) { try { s1 = br.readLine(); } catch (IOException e) { System.out.println("Erreur de lecture de la console."); System.exit(0); } try { fr = new FileInputStream (new File(s1)); } catch (NullPointerException e) { } catch (FileNotFoundException e) { if (i < 2) System.out.print ("Ce fichier n'existe pas. Entrez un autre nom : "); else if (i == 2) System.out.print ("Vous avez encore droit a un essai : "); } i++; if (i > 4) { System.out.println("Nombre d'essais depasse."); System.exit(0); } } return fr; } public static FileOutputStream binCopie(String s) { FileOutputStream fr = null; File f = null; String s1 = null;

CHAPITRE 14 LES ENTRES/SORTIES

587

int i = 0; BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); System.out.print(s); while (fr == null) { try { s1 = br.readLine(); } catch (IOException e) { System.out.println("Erreur de lecture de la console."); System.exit(0); } try { f = new File(s1); if (f.exists()) { System.out.print ("Le fichier existe. Voulez-vous l'ecraser (o/n) : "); try { s1 = br.readLine(); } catch (IOException e) { System.out.println ("Erreur de lecture de la console."); System.exit(0); } if (!s1.equals("O") && !s1.equals("o")) { fr = null; i = 0; System.out.print ("Entrez un autre nom de fichier : "); } else fr = new FileOutputStream (f); } else fr = new FileOutputStream (f); }

588

LE DVELOPPEUR JAVA 2
catch (NullPointerException e) { } catch (IOException e) { if (i < 2) System.out.print ("Ce fichier n'existe pas. Entrez un autre nom : "); else if (i == 2) System.out.print ("Vous avez encore droit a un essai : "); } i++; if (i > 4) { System.out.println("Nombre d'essais depasse."); System.exit(0); } } return fr; } }

Le programme lui-mme est trs simple. Nous raliserons tout d'abord une version copiant des fichiers binaires sans compression :
import java.io.*; public class Zip { public static void main (String[] args) throws IOException { FileInputStream o = Util.binOriginal ("Entrez le nom du fichier texte a copier : "); FileOutputStream d = Util.binCopie ("Entrez le nom a donner a la copie du fichier : "); BufferedInputStream original = new BufferedInputStream(o); BufferedOutputStream copie = new BufferedOutputStream(d); int c; while ((c = original.read()) != -1) copie.write(c);

CHAPITRE 14 LES ENTRES/SORTIES


original.close(); copie.close(); } }

589

Pour ajouter la compression, les modifications apporter sont minimes :


import java.io.*; import java.util.zip.*; public class Zip { public static void main (String[] args) throws IOException { FileInputStream o = Util.binOriginal ("Entrez le nom du fichier texte a copier : "); FileOutputStream d = Util.binCopie ("Entrez le nom a donner a la copie du fichier : "); BufferedInputStream original = new BufferedInputStream(o); BufferedOutputStream cB = new BufferedOutputStream(d); ZipOutputStream copie = new ZipOutputStream(cB); int c; copie.setMethod(ZipOutputStream.DEFLATED); copie.putNextEntry(new ZipEntry("fichier1.txt")); while ((c = original.read()) != -1) copie.write(c); original.close(); copie.close(); } }

Le BufferedOutputStream est simplement envelopp dans un ZipOutputStream. Deux appels de mthodes spcifiques sont ensuite ajouts :

setMethod(ZipOutputStream.DEFLATED) dtermine le type d'opration


qui va tre effectue. DEFLATED signifie que les donnes seront com-

590

LE DVELOPPEUR JAVA 2
presses. STORED signifie que les donnes seront simplement stockes sans compression. Il peut paratre bizarre qu'un stream de compression soit employ pour crire des donnes non compresses, mais cela s'explique par le fait que ce stream permet de stocker plusieurs fichiers dans un mme fichier zip.

putNextEntry(new ZipEntry("fichier1.txt")) cre une nouvelle en-

tre dans le fichier zip. Si plusieurs fichiers sont compresss dans le mme fichier zip, une ZipEntry doit tre cre pour chaque fichier. Ici, le nom donn l'entre est arbitrairement fichier.txt. Normalement, on utilise le nom du fichier qui a t compress.

Note : Pour compresser plusieurs fichiers, il faut crer la premire entre,


copier le fichier, crer la deuxime entre, copier le deuxime fichier, et ainsi de suite, et non crer toutes les entres la suite les unes des autres.

Dcompression de donnes
Si vous voulez tester le fichier compress, vous pouvez employer le programme suivant :
import java.io.*; import java.util.zip.*; public class Unzip { public static void main (String[] args) throws IOException { FileInputStream o = new FileInputStream(args[0]); ZipInputStream z = new ZipInputStream(new BufferedInputStream(o)); ZipEntry ze; ze = z.getNextEntry(); int c; while ((c = z.read()) != -1) System.out.write(c); z.close(); } }

CHAPITRE 14 LES ENTRES/SORTIES

591

Ce programme dcompresse un fichier dont le nom lui est pass sur la ligne de commande, sous la forme :
java Unzip x.zip

et affiche le contenu du fichier dcompress l'cran. (Ici, on suppose qu'il s'agit d'un fichier texte ayant t compress sous le nom x.zip. L'adaptation de ce programme pour qu'il crive les donnes dcompresses dans un fichier ne devrait maintenant vous poser aucun problme.

La srialisation
Nous avons vu que certains streams permettent d'enregistrer sur disque des objets Java. Cette opration est appele srialisation. Elle permet de conserver l'tat des objets entre deux excutions d'un programme, ou d'changer des objets entre programmes. Le programme suivant montre un exemple de srialisation :
import java.io.*; public class Serialisation { public static void main (String[] args) throws IOException { Voiture voiture = new Voiture("V6", "Cabriolet"); voiture.setCarburant(50); FileOutputStream f = new FileOutputStream("garage"); ObjectOutputStream o = new ObjectOutputStream(f); o.writeObject(voiture); o.close(); } } class Voiture implements Serializable { Moteur moteur;

592
Carrosserie carrosserie; transient int essence;

LE DVELOPPEUR JAVA 2

Voiture (String m, String c) { moteur = new Moteur(m); carrosserie = new Carrosserie(c); } String getMoteur() { return moteur.getValeur(); } String getCarrosserie() { return carrosserie.getValeur(); } void setCarburant(int e) { essence += e; } int getCarburant() { return essence; } } class Carrosserie implements Serializable { String valeur; Carrosserie (String s) { valeur = s; } String getValeur() { return valeur; } } class Moteur implements Serializable { String valeur; Moteur (String s) { valeur = s; }

CHAPITRE 14 LES ENTRES/SORTIES


String getValeur() { return valeur; } }

593

Ce programme contient la classe Voiture, qui possde deux membres qui sont des instances des classes Carrosserie et Moteur, et un membre essence de type int dclar transient. Ces trois classes implmentent l'interface Serializable. Cette interface ne comporte aucune mthode. Elle constitue simplement un marqueur qui indique que les instances pourront tre srialises, c'est--dire, par exemple, enregistres sur disque. Lors de la srialisation, tous les membres sont srialiss, condition que les classes dont ils sont des instances soient elles-mmes srialisables. La srialisation est effectue par Java de manire rcursive avec autant de niveaux que ncessaire. En revanche, les champs qui sont dclars de type transient ne sont pas srialiss. Par exemple, si les lments d'une transaction effectue par un utilisateur l'aide d'un mot de passe sont srialiss, il est probable que le mot de passe sera dclar de type transient afin qu'il ne soit pas enregistr avec le reste des donnes. Le programme cre une instance de Voiture, modifie la valeur du champ transient grce un appel la mthode setCarburant, puis srialise l'objet l'aide d'un stream ObjectOutputStream envelopp dans un FileOutputStream. L'objet srialis est enregistr sur disque dans un fichier nomm garage. Le programme suivant permet de relire l'objet srialis et de constater que les objets membres ont t automatiquement srialiss. En revanche, le champ transient a perdu sa valeur.
import java.io.*; public class Deserialisation { public static void main (String[] args) throws IOException, ClassNotFoundException { FileInputStream f = new FileInputStream("garage");

594

LE DVELOPPEUR JAVA 2
ObjectInputStream o = new ObjectInputStream(f); Voiture voiture = (Voiture)o.readObject(); o.close(); System.out.println("Carrosserie : " + voiture.getCarrosserie()); System.out.println("Moteur : " + voiture.getMoteur()); System.out.println("Carburant : " + voiture.getCarburant()); } }

Ce programme affiche le rsultat suivant :


Carrosserie : Cabriolet Moteur : V6 Carburant : 0

Les fichiers accs direct


Tous les exemples que nous avons vus jusqu'ici impliquaient un accs squentiel aux donnes. Il s'agit l du principe mme des streams, dont les donnes doivent tre traites de la premire la dernire. La recherche d'un lment d'information particulier dans une telle structure peut tre trs longue. En moyenne, il faudra lire la moiti des donnes pour accder une donne quelconque, comme si, pour rechercher une information dans un livre, on tait contraint de lire celui-ci depuis le dbut. Heureusement, les livres disposent de tables des matires et de la possibilit d'accder directement une page donne. La constitution d'une table des matires revient au crateur des donnes. En revanche, le langage doit fournir un moyen d'accder directement des donnes dont l'adresse a t trouve dans la table. Dans de nombreuses structures de donnes conues pour un accs direct, la table des matires est place la fin des donnes. Du point de vue de

CHAPITRE 14 LES ENTRES/SORTIES

595

la programmation, cela ne fait aucune diffrence, sinon que l'adresse de cette table doit tre connue ou inscrite au dbut des donnes. Les fichiers accs direct tant fondamentalement diffrents des streams, ils ne drivent pas des mmes classes que ces derniers. Java dispose de la classe RandomAccessFile, qui drive directement de la classe Object. La cration d'un fichier accs direct s'effectue trs simplement l'aide de deux paramtres. Le premier peut tre une chane de caractres indiquant le nom du fichier, ou un objet de type File, qui offre l'avantage de pouvoir tre manipul de diverses faons pour obtenir des informations concernant le systme hte (chemin d'accs rel du fichier, existence, attributs, etc.). Le deuxime paramtre est une chane de caractres indiquant si le fichier est ouvert en lecture seule (r) ou en lecture et criture (rw). L'utilisation de deux modes diffrents permet de ne pas verrouiller les fichiers dont l'accs est fait en lecture seule. Un nouveau fichier pourra donc tre cr de la faon suivante :

RandomAccesFile raf; raf = new RandomAccessFile("fichier.txt", "rw");

Le principe d'un fichier accs direct repose sur l'utilisation d'un pointeur indiquant la position dans le fichier. Chaque opration de lecture ou d'criture augmente la valeur du pointeur. Il est donc tout fait possible d'utiliser ce type de fichier comme un stream. L'exemple suivant effectue une copie de fichiers l'aide d'un RandomAccessFile :
import java.io.*; public class AccesDirect { public static void main (String[] args) throws IOException { RandomAccessFile original, copie; original = new RandomAccessFile(ROriginal( "Entrez le nom du fichier source : "), "r");

596

LE DVELOPPEUR JAVA 2
copie = new RandomAccessFile(Rcopie( "Entrez le nom du fichier destination : "), "rw"); int c; while ((c = original.read()) != -1) copie.write(c); original.close(); copie.close(); } public static File ROriginal(String s) { File f = null; String s1 = null; int i = 0; BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); System.out.print(s); while (f == null) { try { s1 = br.readLine(); } catch (IOException e) { System.out.println("Erreur de lecture de la console."); System.exit(0); } try { f = new File(s1); } catch (NullPointerException e) { } if (!f.exists()) { if (i < 2) System.out.print ("Ce fichier n'existe pas. Entrez un autre nom : "); else if (i == 2) System.out.print ("Vous avez encore droit a un essai : ");

CHAPITRE 14 LES ENTRES/SORTIES


f = null; } i++; if (i > 3) { System.out.println("Nombre d'essais depasse."); System.exit(0); } } return f; }

597

public static File Rcopie(String s) { File f = null; String s1 = null; int i = 0; BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); System.out.print(s); while (f == null) { try { s1 = br.readLine(); } catch (IOException e) { System.out.println("Erreur de lecture de la console."); System.exit(0); } try { if (s1.length() == 0) throw new IOException(); f = new File(s1); if (f.exists()) { System.out.print ("Le fichier existe. Voulez-vous l'ecraser (o/n) : "); try { s1 = br.readLine(); } catch (IOException e) { System.out.println ("Erreur de lecture de la console.");

598
System.exit(0);

LE DVELOPPEUR JAVA 2

} if (!s1.equals("O") && !s1.equals("o")) { f = null; i = 0; System.out.print ("Entrez un autre nom de fichier : "); } } } catch (NullPointerException e) { } catch (IOException e) { if (i < 2) System.out.print ("Nom de fichier incorrect. Entrez un autre nom de fichier : "); else if (i == 2) System.out.print ("Vous avez encore droit a un essai : "); } i++; if (i > 3) { System.out.println("Nombre d'essais depasse."); System.exit(0); } } return f; } }

Note : La gestion des erreurs dans l'entre des noms de fichiers est trs
sommaire. En particulier, seule l'entre d'un nom de longueur nulle est considre comme incorrecte. Dans la ralit, il faudrait videmment tester la validit du nom de fichier de faon plus approfondie. Il faut noter que les mthodes read() et write(), bien qu'utilises avec des donnes de type int, lisent et crivent des byte. La classe RandomAccessFile dispose de mthodes permettant de lire et crire tous les types de don-

CHAPITRE 14 LES ENTRES/SORTIES

599

nes : readChar(), readShort(), readInt(), readFloat(), readBoolean(), etc. Mais les mthodes les plus intressantes sont celles qui permettent de lire et de modifier le pointeur indiquant la position courante dans le fichier :

public long getFilePointer() Cette mthode permet de connatre la public public


void seek(long pos) Cette mthode permet de modifier la valeur du pointeur afin de lire ou d'crire une position donne.

valeur du pointeur, c'est--dire la position laquelle aura lieu la prochaine lecture ou criture.

long length() Renvoie la longueur du fichier, c'est--dire la valeur maximale que peut prendre le pointeur, plus 1. (Pour un fichier de longueur l, le pointeur peut prendre une valeur comprise entre 0 et l - 1.)

public

void setLength(long newLength) Modifie la longueur du fichier. Si la nouvelle longueur est plus petite que la longueur courante, le fichier est tronqu. Dans ce cas, si le pointeur avait une valeur suprieure la nouvelle longueur, il prend la valeur de la nouvelle longueur. (Cette valeur est suprieure de 1 la position du dernier octet du fichier. Une opration de lecture renvoie donc alors la valeur -1 pour indiquer la fin du fichier.)

Rsum
Dans ce chapitre, nous avons tudi un certain nombre des fonctions d'entres/sorties de Java. Il existe d'autres possibilits d'entres/sorties. Par exemple, certains programmes utilisent une interface base sur l'affichage de fentres. Des sorties peuvent alors tre effectues sur l'cran grce des objets spciaux que nous tudierons au Chapitre 18, consacr aux interfaces utilisateur. De mme, les entres/sorties d'un programme peuvent avoir lieu par l'intermdiaire d'un rseau. Nous traiterons ce cas particulier au Chapitre20.

Chapitre 15 : Le passage des paramtres

Le passage des paramtres

15

ANS CE CHAPITRE, NOUS ALLONS REVENIR RAPIDEMENT SUR un sujet que nous avons dj abord implicitement : la faon dont les paramtres sont passs aux mthodes et l'influence que cela peut avoir sur la vie des objets. Traditionnellement, on considre qu'il existe deux faons de passer les paramtres : par valeur et par rfrence.

Passage des paramtres par valeur


La premire faon de procder consiste passer la mthode la valeur du paramtre dont elle a besoin. Considrons l'exemple d'une mthode qui

602

LE DVELOPPEUR JAVA 2
prend pour paramtre une valeur entire et affiche son carr. Nous pourrions crire ce programme de la faon suivante :
public class Param1 { public static void main (String[] args) { int nombre = 12; System.out.println(nombre); afficheCarr(nombre); System.out.println(nombre); } public static void afficheCarr(int n) { n = n * n; System.out.println(n); } }

Ce programme affiche :
12 144 12

Lors de l'appel de la mthode afficheCarr, la valeur de nombre est passe la mthode qui utilise cette valeur pour initialiser la variable n. Cette variable n'a rien voir avec la variable nombre, si ce n'est qu'elle a temporairement la mme valeur. la deuxime ligne de la mthode, la valeur de n est modifie. Cela ne modifie en rien la valeur de nombre. Lorsque la mthode retourne, nous constatons (en l'affichant) que cette variable n'a pas t modifie. Il s'agit ici d'un paramtre pass par valeur, car seule la valeur de la variable est passe, et non la variable elle-mme.

Passage des paramtres par rfrence


Une autre faon de passer les paramtres consiste transmettre un pointeur vers une variable. Considrons l'exemple suivant :

CHAPITRE 15 LE PASSAGE DES PARAMTRES


public class Param2 { public static void main (String[] args) { Entier nombre = new Entier(12); System.out.println(nombre); afficheCarr(nombre); System.out.println(nombre); } public static void afficheCarr(Entier n) { n.setValeur(n.getValeur() * n.getValeur()); System.out.println(n); } } class Entier { private int valeur; Entier(int v) { valeur = v; } public int getValeur() { return valeur; } public void setValeur(int v) { valeur = v; } public String toString() { return ("" + valeur); } }

603

Ce programme fait peu prs la mme chose que le prcdent, la diffrence qu'il manipule un objet (instance de la classe Entier) qui est un enveloppeur pour le type int. La classe Entier comporte un champ valeur de type int, un constructeur initialisant la valeur de ce champ, et deux

604

LE DVELOPPEUR JAVA 2
mthodes, getValeur() et setValeur(), qui permettent respectivement de lire et de modifier la valeur du champ valeur. De plus, la mthode toString() est redfinie afin d'afficher la valeur de ce champ. Si nous excutons le programme, nous obtenons le rsultat suivant :
12 144 144

La diffrence est ici fondamentale : la mthode afficheCarr a modifi l'objet qui lui a t pass et non une copie de celui-ci. Certains langages permettent de choisir si une mthode (ou une procdure, ou une fonction, selon la terminologie employe par chacun) utilise le passage de paramtres par valeur ou par rfrence. Avec Java, ce n'est pas le cas. Il peut paratre curieux qu'une seule possibilit soit offerte pour les primitives et une autre, diffrente, pour les objets. En fait, il n'en est rien. Le mme mcanisme est mis en uvre dans les deux cas. En effet, nous devons nous souvenir de ce que nous avons dit sur les handles. Une primitive est ce qu'elle reprsente. (Cela est vrai, du moins, du point de vue du programmeur en Java. Pour celui qui dveloppe une machine virtuelle Java, c'est une autre histoire !) En revanche, un handle pointe vers un objet mais il n'est pas l'objet vers lequel il pointe. Dans le deuxime programme, le handle nombre est pass la mthode afficheCarr par valeur. Cependant, cette mthode n'en fait qu'un seul usage : elle cre un nouveau handle pointant vers le mme objet. A l'intrieur de la mthode, nous n'avons aucun accs au handle pass comme paramtre.

Note : Bien sr, si le handle avait t dclar membre de la classe Param2, comme dans l'exemple suivant :
public class Param2 { static Entier nombre;

CHAPITRE 15 LE PASSAGE DES PARAMTRES


public static void main (String[] args) { nombre = new Entier(12); . .

605

il serait accessible dans la mthode afficheCarr, mais cela ne serait en aucun cas d au passage de paramtre.

Passer les objets par valeur


Il arrive souvent que l'on souhaite passer un objet comme paramtre une mthode pour que celle-ci y apporte des modifications, sans souhaiter pour autant que ces modifications soit refltes dans l'objet original. La solution parat simple. Il suffit d'effectuer une copie de l'objet et d'apporter les modifications cet objet. Dans le cas du programme prcdent, ce n'est pas trs compliqu :
public class Param3 { static Entier nombre; public static void main (String[] args) { nombre = new Entier(12); System.out.println(nombre); afficheCarr(nombre); System.out.println(nombre); } public static void afficheCarr(Entier n2) { Entier n = new Entier(n2.getValeur()); n.setValeur(n.getValeur() * n.getValeur()); System.out.println(n); } }

Ce programme affiche maintenant le rsultat correct :

606
12 144 12

LE DVELOPPEUR JAVA 2

Ici, il a t possible de crer une copie de l'objet pass comme paramtre parce que nous connaissions la nature de cet objet. De plus, cette cration tait un processus trs simple. Cependant, ce n'est pas toujours le cas. Il est parfaitement possible qu'une mthode reoive en paramtre un objet surcast vers une classe parente. Comment, alors, en effectuer une copie ? Java dispose d'un mcanisme spcialement prvu pour cela.

Le clonage des objets


Effectuer une copie d'un objet est trs simple. Il suffit de faire appel sa mthode clone(). Pour l'instant, cela ne nous avance pas beaucoup. En effet, notre classe Entier ne dispose pas d'une telle mthode. Nous pourrions l'crire de la faon suivante :
public class Param4 { static Entier nombre; public static void main (String[] args) { nombre = new Entier(12); System.out.println(nombre); afficheCarr(nombre); System.out.println(nombre); } public static void afficheCarr(Entier n2) { Entier n = n2.clone(); n.setValeur(n.getValeur() * n.getValeur()); System.out.println(n); } } class Entier { private int valeur;

CHAPITRE 15 LE PASSAGE DES PARAMTRES


Entier(int v) { valeur = v; } public int getValeur() { return valeur; } public void setValeur(int v) { valeur = v; } public String toString() { return ("" + valeur); } public Entier clone() { return new Entier(valeur); } }

607

Malheureusement (ou heureusement), ce programme ne se compile pas. En effet, la classe Object contient dj une mthode clone() dont la valeur de retour est de type Object. Il est donc impossible de redfinir cette mthode avec un autre type. En revanche, il est possible de modifier le programme de la faon suivante :

public static void afficheCarr(Entier n2) { Entier n = (Entier)n2.clone();

et :
public Object clone() { return new Entier(valeur); }

608

LE DVELOPPEUR JAVA 2
Mais il y a mieux ! Nous pouvons utiliser la mthode clone() de la classe Object. Si nous examinons la documentation de cette classe, nous constatons que sa mthode clone() est protected. Il nous est donc impossible de l'appeler directement depuis la classe Entier sans l'avoir redfinie dans notre classe. Cela est conu de la sorte pour empcher que tous les objets soient automatiquement clonables. Cependant, il est parfaitement possible de redfinir la mthode clone() de la faon suivante :

public Object clone() { return super.clone(); }

Si cela est si simple, on ne voit pas bien pourquoi la mthode clone() de la classe Object est protected. En fait, si vous essayez de compiler le programme ainsi, vous obtiendrez un message indiquant que l'exception CloneNotSupportedException doit tre traite. Il peut paratre simple de la dclarer lance par notre mthode clone() :

public Object clone() throws CloneNotSupportedException { return super.clone(); }

Nous devrons dans ce cas faire de mme pour les mthodes afficheCarr() et main(). Il est peut-tre plus facile d'intercepter l'exception dans la mthode clone() :
public Object clone() { Object o = null; try { o = super.clone(); }

CHAPITRE 15 LE PASSAGE DES PARAMTRES


catch (CloneNotSupportedException e) { System.out.println(e); } return o; }

609

Malheureusement, ainsi modifi, le programme ne fonctionne pas. En effet, une exception CloneNotSupportedException est systmatiquement lance lors de l'appel de la mthode clone(). En fait, nous avons l la rponse notre question prcdente. Bien qu'accessible grce la redfinition public de la mthode, celle-ci ne fonctionne pas. En effet, avant d'accomplir son travail, cette mthode vrifie si elle agit sur un objet instance de l'interface Cloneable. De cette faon, seuls les objets explicitement dclars comme implmentant cette interface pourront tre clons. Il nous suffit donc, pour que notre programme fonctionne, de modifier la dclaration de la mthode :
public Object clone() implements Cloneable { Object o = null; try { o = super.clone(); } catch (CloneNotSupportedException e) { System.out.println(e); } return o; }

Que contient l'interface Cloneable ? Rien du tout. Elle ne sert en fait que de marqueur, pour indiquer qu'un objet peut tre clon. Nous avons ici un exemple d'un type d'utilisation des interfaces dont nous avions parl au Chapitre 9.

Clonage de surface et clonage en profondeur


La mthode clone() renvoie une copie de l'objet. Cependant, qu'en est-il des objets rfrencs par l'objet clon ? Le programme suivant permet de

610

LE DVELOPPEUR JAVA 2
comprendre ce qui se passe lors du clonage d'un objet contenant des liens vers d'autres objets clonables :
public class Clone1 { public static void main (String[] args) { Voiture voiture = new Voiture(); voiture.setCarburant(30); System.out.println(voiture.getCarburant()); pleinClone(voiture); System.out.println(voiture.getCarburant()); } public static void pleinClone(Voiture v) { Voiture voiture2 = (Voiture)v.clone(); voiture2.setCarburant(100); System.out.println(voiture2.getCarburant()); } } class Voiture implements Cloneable { Reservoir reservoir; Voiture () { reservoir = new Reservoir(); } void setCarburant(int e) { reservoir.setContenu(e); } int getCarburant() { return reservoir.getContenu(); } public Object clone() { Object o = null;

CHAPITRE 15 LE PASSAGE DES PARAMTRES


try { o = super.clone(); } catch (CloneNotSupportedException e) { System.out.println(e); } return o; } } class Reservoir implements Cloneable { private int contenu; public int getContenu() { return contenu; } public void setContenu(int e) { contenu = e; } public Object clone() { Object o = null; try { o = super.clone(); } catch (CloneNotSupportedException e) { System.out.println(e); } return o; } }

611

Ce programme cre une instance de la classe Voiture. Le constructeur de cette classe initialise la variable reservoir en crant une instance de la classe Reservoir. Cette classe comporte un membre priv de type int appel contenu qui peut tre lu et mis jour au moyen des mthodes setContenu

612

LE DVELOPPEUR JAVA 2
et getContenu. Ces mthodes sont appeles par les mthodes setCarburantet getCarburant de la classe Voiture. La mthode main() de notre programme cre tout d'abord une instance de Voiture, puis appelle la mthode setCarburant qui affecte la valeur utilise comme paramtre au champ contenu de l'instance de Reservoir. La valeur de ce champ est ensuite affiche. La mthode pleinClone est ensuite appele pour crer un clone de l'objet voiture et appeler la mthode setCarburant de ce nouvel objet avec un paramtre diffrent. La mthode getCarburant est alors appele pour afficher la valeur modifie. Au retour de la mthode, getCarburant est de nouveau appele sur l'objet voiture. Nous constatons alors que cet objet a galement t modifi. La Figure 15.1 illustre ce qui s'est pass. La mthode clone a bien cr une copie de l'objet voiture. Cette copie contient une copie de chacun des membres de l'original. Les membres qui sont des primitives sont donc galement copis. En revanche, lorsque les membres sont des objets, les handles correspondants sont copis mais ils pointent toujours vers les mmes objets. Il s'agit l d'une copie en surface. Pour remdier ce problme, il faudrait raliser une copie en profondeur, c'est--dire en copiant de faon rcursive tous les objets rfrencs l'intrieur de l'objet copi, puis de nouveau tous les objets rfrencs par ces objets, et ainsi de suite. Pour excuter un clonage en profondeur, vous devez modifier la mthode clone() de la classe Voiture afin qu'elle effectue un clonage explicite de tous les membres qui sont des objets. Pour obtenir ce rsultat, nous pouvons modifier la mthode de la faon suivante :

public Object clone() { Object o = null; try { o = super.clone(); ((Voiture)o).reservoir = (Reservoir)this.reservoir.clone(); }

CHAPITRE 15 LE PASSAGE DES PARAMTRES

613

Voiture voiture = new Voiture();

voiture

Objet instance de Voiture

reservoir

Objet instance de Reservoir

contenu = 0

voiture.setCarburant(30);

voiture

Objet instance de Voiture

reservoir

Objet instance de Reservoir

contenu = 30

Voiture voiture2 = (Voiture)v.clone();

voiture

Objet instance de Voiture

reservoir

Objet instance de Reservoir

contenu = 30

voiture2

Objet instance de Voiture

reservoir

voiture2.setCarburant(100);

voiture

Objet instance de Voiture

reservoir

Objet instance de Reservoir

contenu = 100

voiture2

reservoir setCarburant

Figure 15.1 : Le clonage nest effectu quen surface. Les objets membres de lobjet clon ne sont pas eux-mme clons.

614

LE DVELOPPEUR JAVA 2
catch (CloneNotSupportedException e) { System.out.println(e); } return o; }

Notez la syntaxe particulire utilise ici pour le sous-casting de la valeur de retour de super.clone() (de type Object) en Voiture, sous-casting indispensable pour pouvoir accder au champ reservoir :
((Voiture)o).reservoir =

Si vous prfrez, vous pouvez modifier la mthode de la faon suivante :

public Object clone() { Voiture o = null; try { o = (Voiture)super.clone(); o.reservoir = (Reservoir)this.reservoir.clone(); } catch (CloneNotSupportedException e) { System.out.println(e); } return o; }

Ici, c'est le rsultat de super.clone() qui est explicitement sous-cast en Voiture. Le sur-casting inverse se produit implicitement dans l'instruction :
return o;

o est de type Voiture mais est implicitement sur-cast en Object car la valeur de retour de la mthode est dclare de ce type.

CHAPITRE 15 LE PASSAGE DES PARAMTRES

615

Bien entendu, pour que le clonage rcursif fonctionne, il faut que chaque objet rfrenc soit lui-mme clonable en profondeur. Pour les objets qui sont des instances de vos propres classes, il n'y a pas de problme. Il suffit que vous ayez dfini correctement la mthode clone() pour chacune d'elles. La situation est videmment diffrente lorsque vous ne contrlez pas la dfinition des classes. Le cas le plus frquent est celui des collections d'Object. Nous avons vu au Chapitre 10 que les collections rfrencent des objets sur-casts en Object. Dans le cas du clonage d'un objet contenant ce type de collection, il est parfois impossible de savoir a priori si les objets rfrencs sont clonables en profondeur ou non. Il est facile de savoir s'ils sont clonables en utilisant l'expression logique (o instanceof Cloneable), mais cela n'indique pas s'il s'agit d'un clonage de surface ou en profondeur.

Clonage en profondeur d'un objet de type inconnu


Si l'on souhaite cloner en profondeur un objet dont le type exact n'est pas connu, il est ncessaire d'effectuer les oprations suivantes :

Interroger l'objet pour connatre la liste de ses champs. Tester chaque champ pour savoir s'il s'agit d'une primitive ou d'un
handle d'objet.

Pour chaque champ objet, vrifier s'il est clonable et, le cas chant,
cloner l'objet correspondant.

Rpter l'opration de faon rcursive.


Il s'agit l d'une opration trs longue et complexe, faisant appel des techniques que nous n'avons pas encore tudies (RTTI et rflexion) qui permettent d'obtenir des informations sur la structure de la classe d'un objet (et en particulier, le nom de sa classe, la liste de ses champs, de ses mthodes, etc.).

616

LE DVELOPPEUR JAVA 2

Clonabilit et hritage
Bien que toutes les classes drivent de la classe Object, elles ne sont clonables que si elles runissent deux conditions :

Elles redfinissent la mthode clone ; Elles implmentent l'interface Clonable.


Si l'on considre maintenant l'hritage, on peut remarquer plusieurs points intressants :

Une classe drive d'une classe clonable est clonable. Une classe drive d'une classe ne runissant aucune des deux conditions ci-dessus peut tre clonable si elle runit elle-mme ces conditions. Dans l'exemple ci-aprs :

class Vehicule { } class Voiture extends Vehicule implements Cloneable { Reservoir reservoir; Voiture () { reservoir = new Reservoir(); } void setCarburant(int e) { reservoir.setContenu(e); } int getCarburant() { return reservoir.getContenu(); } public Object clone() { Voiture o = null;

CHAPITRE 15 LE PASSAGE DES PARAMTRES


try { o = (Voiture)super.clone(); o.reservoir = (Reservoir)this.reservoir.clone(); } catch (CloneNotSupportedException e) { System.out.println(e); } return o; } }

617

la classe Voiture est clonable, bien qu'elle drive d'une classe qui ne l'est pas.

Les deux conditions peuvent tre remplies des chelons diffrents de


la hirarchie. Dans les deux cas suivants, la classe Voiture est clonable. Dans le premier cas, la classe parente Vhicule implmente l'interface Cloneable et la classe drive Voiture redfinit la mthode Clone() :

class Vehicule implements Cloneable { } class Voiture extends Vehicule { Reservoir reservoir; Voiture () { reservoir = new Reservoir(); }

void setCarburant(int e) { reservoir.setContenu(e); } int getCarburant() { return reservoir.getContenu(); }

618

LE DVELOPPEUR JAVA 2
public Object clone() { Voiture o = null; try { o = (Voiture)super.clone(); o.reservoir = (Reservoir)this.reservoir.clone(); } catch (CloneNotSupportedException e) { System.out.println(e); } return o; } }

Dans le second cas, c'est la classe parente qui redfinit la mthode clone() et la classe drive qui implmente l'interface Cloneable :
class Vehicule { public Object clone() { Voiture o = null; try { o = (Voiture)super.clone(); o.reservoir = (Reservoir)((Voiture)this).reservoir.clone(); } catch (CloneNotSupportedException e) { System.out.println(e); } return o; } } class Voiture extends Vehicule implements Cloneable { Reservoir reservoir; Voiture () { reservoir = new Reservoir(); }

CHAPITRE 15 LE PASSAGE DES PARAMTRES


void setCarburant(int e) { reservoir.setContenu(e); } int getCarburant() { return reservoir.getContenu(); } }

619

Notez la syntaxe quelque peu alambique due la ncessit d'effectuer un sous-casting explicite.

Interdire le clonage d'une classe drive


Il est parfois ncessaire d'interdire qu'une classe drive soit clonable. Deux cas se prsentent :

La classe parente n'est pas clonable. La classe parente est clonable.


Dans le premier cas, tout est simple. Il suffit de dfinir dans la classe parente une mthode clone() lanant l'exception CloneNotSupportedException :
public Object clone() throws CloneNotSupportedException { throw new CloneNotSupportedException(); }

Cette mthode ne retourne pas, mais cela n'a aucune importance car elle lance systmatiquement une exception. Rien n'empchera un utilisateur de la classe de la driver en implmentant l'interface Cloneable et en dfinissant une mthode clone() . Cependant, si cette mthode fait appel super.clone(), une exception CloneNotSupportedException sera systmatiquement lance.

620

LE DVELOPPEUR JAVA 2
Dans le second cas, il suffit de dclarer final la mthode clone() de la classe parente.

Note : Il est galement possible de dclarer cette mthode final dans le


premier cas, ce qui produira une erreur de compilation si l'utilisateur tente de redfinir la mthode. C'est vous, en tant que dveloppeur de classes, de dterminer votre stratgie.

Une alternative au clonage : la srialisation


Une autre approche est possible pour copier des objets en profondeur. Elle consiste utiliser la srialisation, technique que nous avons tudie au chapitre prcdent. Telle que nous l'avions prsente, la srialisation permettait d'enregistrer des objets sur disque afin de pouvoir les rcuprer lors d'une excution ultrieure du programme. Nous avons vu par ailleurs que la srialisation tait automatiquement excute en profondeur. Pour raliser le mme programme en utilisant la srialisation, il nous faut :

Modifier la classe de l'objet copier afin qu'elle implmente l'interface


Serializable.

Modifier les classes des objets membres de cette classe afin qu'elles
implmentent galement l'interface Serializable.

Crer un ObjectOutputStream afin d'y crire l'objet copier. Crer un ObjectInputStream afin de lire l'objet copi. Relier ces deux streams afin que la sortie du premier corresponde
l'entre du second. Les deux premires conditions sont trs faciles raliser. Voici le listing des classes modifies :

CHAPITRE 15 LE PASSAGE DES PARAMTRES


class Voiture implements Serializable { Reservoir reservoir; Voiture () { reservoir = new Reservoir(); } void setCarburant(int e) { reservoir.setContenu(e); } int getCarburant() { return reservoir.getContenu(); } } class Reservoir implements Serializable { private int contenu; public int getContenu() { return contenu; } public void setContenu(int e) { contenu = e; } }

621

Vous pouvez voir que ces classes sont beaucoup plus simples car elles n'ont pas besoin de dfinir l'quivalent de la mthode clone(), pas plus en ce qui concerne la copie en surface (cas de la classe Reservoir) que la copie en profondeur (classe Voiture). Avec la srialisation, tout est pris en charge automatiquement. La troisime et la quatrime condition sont galement faciles raliser en fonction de ce que nous avons appris au chapitre prcdent. Il suffit de crer les deux streams de la faon suivante :
ObjectOutputStream objOutput = new ObjectOutputStream(...); ObjectInputStream objInput = new ObjectInputStream(...);

622

LE DVELOPPEUR JAVA 2
Pour connecter ces deux streams, il nous faut crer deux PipedStream relis l'un l'autre. Nous pouvons le faire de deux faons quivalentes :
PipedOutputStream pipedOut = new PipedOutputStream(); PipedInputStream pipedIn = new PipedInputStream(pipedOut);

ou :
PipedInputStream pipedIn = new PipedInputStream(); PipedOutputStream pipedOut = new PipedOutputStream(pipedIn);

Les deux streams ainsi crs sont utiliss comme paramtres des constructeurs des ObjectStream prcdents. Nous pouvons maintenant crire les lignes compltes :
ObjectOutputStream objOutput = new ObjectOutputStream(pipedOut); ObjectInputStream objInput = new ObjectInputStream(pipedIn);

Le listing complet du programme apparat ci-aprs :


import java.io.*; public class Clone2 { public static void main (String[] args) throws IOException { Voiture voiture = new Voiture(); voiture.setCarburant(30); System.out.println(voiture.getCarburant()); pleinSerial(voiture); System.out.println(voiture.getCarburant()); } public static void pleinSerial(Voiture v) throws IOException { Voiture v2 = null;

CHAPITRE 15 LE PASSAGE DES PARAMTRES


PipedInputStream PipedOutputStream ObjectOutputStream ObjectInputStream

623

pipedIn = new PipedInputStream(); pipedOut = new PipedOutputStream(pipedIn); objOutput = new ObjectOutputStream(pipedOut); objInput = new ObjectInputStream(pipedIn);

objOutput.writeObject(v); try { v2 = (Voiture)objInput.readObject(); } catch (ClassNotFoundException e) { e.printStackTrace(); } objInput.close(); objOutput.close(); v2.setCarburant(100); System.out.println(v2.getCarburant()); } } class Voiture implements Serializable { Reservoir reservoir; Voiture () { reservoir = new Reservoir(); } void setCarburant(int e) { reservoir.setContenu(e); } int getCarburant() { return reservoir.getContenu(); } } class Reservoir implements Serializable { private int contenu; public int getContenu() { return contenu; }

624
public void setContenu(int e) { contenu = e; } }

LE DVELOPPEUR JAVA 2

Vous pouvez noter quelques modifications supplmentaires :

Nous importons le package java.io ; La mthode main et la mthode pleinSerial lancent une exception de
type IOException ;

La variable v2 recevant sa valeur dans un bloc try, sa dclaration

et son initialisation doivent tre effectues sparment, avant le bloc. Dans le cas contraire, le compilateur affiche un message d'erreur indiquant que la variable pourrait ne pas avoir t initialise.

Ce programme donne le mme rsultat que le prcdent, une diffrence (de taille !) prs. Il met, pour s'excuter, 490 millisecondes, alors que le premier ne met que 160 millisecondes. Ces valeurs dpendent bien entendu de la machine sur laquelle le programme fonctionne. Si vous voulez les mesurer, il suffit de modifier la mthode main de la faon suivante :
long t1 = System.currentTimeMillis(); Voiture voiture = new Voiture(); voiture.setCarburant(30); System.out.println(voiture.getCarburant()); lePlein(voiture); System.out.println(voiture.getCarburant()); System.out.println(System.currentTimeMillis() - t1);

Ce qui est important ici est le rapport entre les temps d'excution. Le programme utilisant la srialisation met trois fois plus de temps s'excuter.

CHAPITRE 15 LE PASSAGE DES PARAMTRES

625

Une autre alternative au clonage


Une autre faon de copier un objet consiste fournir une mthode ou un constructeur accomplissant le travail. De cette faon, vous pouvez contrler totalement la faon dont les objets sont copis. Voici un exemple utilisant une mthode :

class Voiture { Reservoir reservoir; Voiture () { reservoir = new Reservoir(); } Voiture copier() { Voiture v = new Voiture(); v.setCarburant(this.getCarburant()); return v; } void setCarburant(int e) { reservoir.setContenu(e); } int getCarburant() { return reservoir.getContenu(); } }

Cette classe peut tre utilise de la faon suivante :


public static void lePlein(Voiture v) { Voiture v2 = v.copier(); System.out.println(v2.getCarburant());

626

LE DVELOPPEUR JAVA 2
Vous pouvez galement employer un constructeur spcial :

class Voiture { Reservoir reservoir; Voiture () { reservoir = new Reservoir(); } Voiture (Voiture v) { reservoir = new Reservoir(); this.setCarburant(v.getCarburant()); } void setCarburant(int e) { reservoir.setContenu(e); } int getCarburant() { return reservoir.getContenu(); } }

Dans ce cas, la copie sera effectue de la faon suivante :

public static void lePlein(Voiture v) { Voiture v2 = new Voiture(v); System.out.println(v2.getCarburant());

Bien sr, ces techniques sont beaucoup moins sophistiques et ne permettent pas toujours de rpondre tous les problmes, particulirement dans le domaine de l'hritage.

CHAPITRE 15 LE PASSAGE DES PARAMTRES

627

Rsum
Dans ce chapitre, nous avons tudi la faon dont les arguments sont passs aux mthodes, ce qui nous a amens dcouvrir le clonage des objets. La faon dont le clonage des objets est implment en Java est source de discussions interminables. La question qui revient le plus souvent est : pourquoi cela a-t-il t fait ainsi ? Pourquoi implmenter la clonabilit dans la classe Object, mre de toutes les autres, et ne pas permettre automatiquement d'utiliser cette fonction dans les sous-classes. Chacun a son explication. Il n'y en a pas d'officielle. Une explication parfois avance est un changement d'orientation dans les lignes directrices imposes aux concepteurs du langage du fait des problmes de scurit survenus en raison de son adaptation Internet. C'est peut-tre le cas. Pourtant, il n'y a rien de choquant dans la faon dont les choses sont organises. La clonabilit est implmente pour tous les objets, mais il y a une scurit. Avancer l'explication prcdente revient se poser cette question : pourquoi les fabricants de pistolets automatiques ont-ils prvu un cran de sret ? Est-ce que cela signifie qu'en fin de compte ils n'taient plus trs srs d'avoir envie de voir leurs produits utiliss ? Cette supposition est absurde. Telle qu'elle est implmente, la clonabilit est une fonction trs sre et trs facile utiliser. Que demander de plus ? La clonabilit rcursive automatique !

Chapitre 16 : Excuter plusieurs processus simultanment

Excuter plusieurs processus simultanment

16

USQU'ICI, NOUS NE NOUS SOMMES PAS VRAIMENT OCCUPS DE savoir comment nos programmes fonctionnaient. Chaque programme tait compos d'une classe principale et, ventuellement, d'autres classes annexes. La commande utilise pour lancer le programme consistait en le nom de l'interprteur suivi du nom de la classe. Nous allons voir maintenant ce qui se passe lorsque nous excutons un programme de cette faon. Lorsque nous lanons l'interprteur l'aide d'une ligne de commande en lui passant en paramtre le nom d'une classe, l'interprteur est charg en mmoire, puis il charge la classe indique et la parcourt la recherche d'une mthode nomme main. S'il trouve une telle mthode, il dmarre un processus et appelle cette mthode.

630

LE DVELOPPEUR JAVA 2

Qu'est-ce qu'un processus ?


Jusqu'ici, nous n'avons pas eu rpondre cette question. L'interprteur Java se chargeait pour nous de toutes les tches ncessaires l'excution de nos programmes. Ce faisant, nous utilisions Java comme un langage mono-processus, c'est--dire ne permettant d'excuter qu'une seule squence d'instructions la fois. Cependant, Java est un langage multi-processus, c'est--dire capable d'excuter plusieurs squences d'instructions. Chaque squence est appele thread , ce qui signifie fil en anglais. En Java, les threads sont tout simplement des objets instances de la classe java.lang.Thread, qui drive directement de la classe java.lang.Object. Une instance de la classe Thread permet de crer un thread, mais sans grand intrt, car il s'agit d'un thread ne faisant rien !

Avertissement
Dans ce chapitre, nous crerons et excuterons des programmes qui lancent plusieurs processus (threads ) s'excutant en mmoire. Sous Windows, dans certaines circonstances, un programme peut se terminer alors qu'un processus continue de fonctionner. C'est le cas, en particulier, si vous interrompez l'aide des touches CTRL + C un programme qui est entr dans une boucle infinie. Si ce programme fonctionne dans une fentre DOS dmarre normalement, les threads seront automatiquement interrompus. En revanche, si vous excutez ce type de programme depuis un environnement de dveloppement (un simple diteur de texte permettant de compiler et d'excuter les programmes, par exemple), il est possible que certains processus ne soient pas interrompus et continuent d'occuper le processeur. Si vous constatez un ralentissement du systme, tapez les touches C TRL + ALT + DEL (une seule fois !) pour afficher une fentre indiquant tous les processus en cours d'excution. Si vous y trouvez le couple Java/Winoldap, vous tes en prsence du problme prcit. Interrompez alors explicitement le processus en slectionnant Java et en cliquant sur Fin de tche.

CHAPITRE 16 EXCUTER PLUSIEURS PROCESSUS SIMULTANMENT

631

Comment fonctionnent les threads


Lorsqu'un programme Java est excut, le lanceur dclenche le chargement en mmoire de la classe qui comporte le point d'entre du programme, c'est--dire, pour une application, la mthode main. Puis, lorsqu'un certain nombre de tches annexes ont t accomplies, le contrle de l'excution est transfr cette mthode. Cette mthode tant statique, notez qu'il n'y a pas lieu d'instancier la classe qui la contient. En programmation oriente objet, le transfert du contrle de l'excution une mthode d'un objet est souvent vu sous la forme de l'envoi d'un message cet objet. Ici, le message est "excuter la mthode main". Ce message comporte un ou plusieurs arguments. Dans le cas qui nous occupe, il s'agit d'un tableau contenant les arguments qui ont t placs par l'utilisateur sur la ligne de commande. La mthode main est donc excute jusqu' sa dernire instruction. A ce moment, on dit que la mthode retourne. Il peut s'agir d'une instruction return place n'importe quel endroit de la mthode. S'il n'y a pas d'instruction return, la mthode retourne lorsqu'il n'y a plus d'instructions excuter. L'excution ne s'arrte pas l. En effet, le contrle de l'excution est rendu au programme qui avait appel la mthode main. Normalement, il s'agit du lanceur. Celui-ci effectue quelques tches utilitaires puis s'arrte. Le systme d'exploitation rcupre alors la mmoire utilise. Pendant l'excution de la mthode main, de nombreux objets peuvent tre chargs en mmoire ou crs. Si la mthode main tente de crer une instance d'une classe l'aide de l'oprateur new, cette classe est charge en mmoire et un de ses constructeurs est excut. Le contrle de l'excution est donc transfr au constructeur qui, une fois termin, retourne. Le contrle de l'excution revient ensuite la mthode main. Si celle-ci dispose d'un handle pour l'instance cre, elle peut appeler les mthodes de cet objet, transfrant ainsi le contrle de l'excution celui-ci. A tout moment, l'excution (le thread) ne concerne qu'un seul objet. Le thread peut ainsi se promener d'objet en objet en suivant un parcours complexe. Il finit toujours par revenir l'objet d'origine. La Figure 16.1 montre le schma d'un tel fonctionnement.

632

LE DVELOPPEUR JAVA 2

Dmarrage de Java Tches utilitaires

Thread
Objet

Thread
Objet
Constructeur(){ ----------} Main(){ ----------} Mthode(){ ----------}

Thread

Tches utilitaires Arrt de Java

Constructeur(){ ----------} Mthode(){ ----------} Mthode(){ ----------}

Figure 16.1 : Schma du fonctionnement d'un programme mettant en uvre un seul thread Java.

Dans les programmes mono-processus, il s'agit l du seul schma d'excution possible. En Java, les choses peuvent tre plus complexes. La ncessit de dcouper l'excution d'un programme en plusieurs flux (plusieurs threads) apparat ds qu'une tche dpend, pour son excution, d'un squencement extrieur. Par exemple, une mthode peut contrler le chargement d'une image. Dans un programme mono-processus, le traitement s'arrte pendant le chargement de l'image, qui peut durer quelques diximes de seconde si l'image se trouve sur un disque local, ou plusieurs secondes si elle se trouve sur un serveur. En Java, ce type de traitement est dit asynchrone, ce qui signifie que la mthode correspondante se contente de dclencher le chargement de l'image, puis retourne immdiatement. Le programme poursuit son excution sans attendre que

Thread

Thread

Objet
Constructeur(){ ----------} Mthode(){ ----------} Mthode(){ ----------}

Thread

CHAPITRE 16 EXCUTER PLUSIEURS PROCESSUS SIMULTANMENT

633

Dmarrage de Java Tches utilitaires

Thread
Objet

Thread
Objet
Constructeur(){ ----------} Main(){ ----------} Mthode(){ ----------}

Thread

Tches utilitaires Arrt de Java

Constructeur(){ ----------} Mthode(){ ----------} Mthode(){ ----------}

Processus asynchrone

Figure 16.2 : Schma du fonctionnement d'un programme mettant en uvre un seul thread excutant une mthode asynchrone.

le chargement soit termin. Pendant le chargement de l'image, deux processus diffrents sont excuts simultanment. Si le programme principal n'a rien d'autre faire, il doit attendre que le chargement de l'image soit termin. Dans le cas contraire, il peut continuer son excution. La Figure 16.2 montre le schma d'un tel programme. Bien sr, ce type de fonctionnement implique un certain nombre de contraintes. Le programmeur doit savoir si chaque mthode qu'il appelle est synchrone (elle ne retourne que lorsque le traitement est termin) ou asynchrone (elle retourne immdiatement aprs avoir dclench le traitement). Par ailleurs, l'objet qui contient la mthode asynchrone doit disposer d'un moyen permettant d'avertir un autre objet de l'tat d'avancement

Thread

Thread

Objet
Constructeur(){ ----------} Mthode(){ Traitement asynchrone } Mthode(){ ----------}

Thread

634

LE DVELOPPEUR JAVA 2

Dmarrage de Java Tches utilitaires

Thread
Objet

Thread
Objet
Constructeur(){ ----------} Main(){ ----------} Mthode(){ ----------}

Thread

Tches utilitaires Arrt de Java

Constructeur(){ ----------} Mthode(){ ----------} Mthode(){ ----------}

Thread

Thread

Objet B
Constructeur(){ ----------} Mthode(){ ----------} Mthode(){ ----------}

Objet A
Constructeur(){ -----Thread -----} run(){ ----------} start(){ ----------}

Thread

Thread Thread

Figure 16.3 : Le traitement effectu par la mthode Mthode1 de l'objet B est synchrone.

du processus asynchrone. Par exemple, dans le cas du chargement d'une image partir d'un rseau, l'objet chargeant l'image (celui qui contient la mthode asynchrone) enverra rgulirement des messages un autre objet (un observer) pour le tenir au courant de l'avancement. Ce message prend videmment la forme de l'appel d'une mthode avec, pour paramtres, les informations en question. En ce qui concerne les mthodes des objets standard de Java, vous n'avez pas le choix. Certaines sont synchrones, d'autres asynchrones. La documentation prcise lorsqu'une mthode est asynchrone. Lorsque rien n'est indiqu, la mthode est synchrone. Toutefois, il est toujours possible de rendre synchrone une mthode asynchrone. Il suffit de demander au pro-

CHAPITRE 16 EXCUTER PLUSIEURS PROCESSUS SIMULTANMENT

635

Dmarrage de Java Tches utilitaires

Thread1
Objet

Thread1
Objet
Constructeur(){ ----------} Main(){ ----------} Mthode(){ ----------}

Thread1

Tches utilitaires Arrt de Java

Constructeur(){ ----------} Mthode(){ ----------} Mthode(){ ----------}

Fin du Thread1

Thread1

Thread1

Objet B
Constructeur(){ ----------} Mthode(){ ----------} Mthode(){ ----------}

Objet A
Constructeur(){ -----Thread1 -----} run(){ ----------Fin du Thread2 } start(){ ----------}

Thread2

Figure 16.4 : La mthode start cre un nouveau thread qui commence par excuter la mthode run.

gramme de commencer attendre la fin du traitement immdiatement aprs l'avoir dclench. Il est tout aussi possible de rendre asynchrone un traitement synchrone. Il suffit pour cela de crer explicitement un nouveau thread et de reporter l'appel de la mthode dans celui-ci. La Figure 16.3 ressemble beaucoup la Figure 16.1. La seule diffrence est qu'un des objets possde une mthode nomme run et que nous avons ajout un objet B, dont une mthode est appele par la mthode run. Le traitement effectu par la mthode de ce nouvel objet est synchrone, car il est effectu dans le mme thread. La suite du traitement ne peut tre reprise dans l'objet A que lorsque la mthode Mthode1 de l'objet B retourne.

636

LE DVELOPPEUR JAVA 2

Dmarrage de Java Tches utilitaires Objet C


Constructeur(){ ----------} Mthode1(){ ----------} Mthode2(){ ----------}

Thread1 Thread1
Thread1
Objet
Constructeur(){ ----------} Main(){ ----------} Mthode(){ ----------}

Thread1 Thread2 Thread2

Tches utilitaires Arrt de Java

Thread1

Fin du Thread1

Objet B
Constructeur(){ ----------} Mthode(){ ----------} Mthode(){ ----------}

Objet A
Constructeur(){ -----Thread1 -----} run(){ ----------Fin du Thread2 } start(){ ----------}

Thread2

Figure 16.5 : Schma du fonctionnement d'un programme dont les deux threads excutent des mthodes d'un mme objet.

Sur l'exemple de la Figure 16.4, les choses sont diffrentes. Le thread Thread1 n'excute pas la mthode run de l'objet A, mais sa mthode start. Cette mthode cre un nouveau thread (Thread2) et lui donne, comme point d'entre, la mthode run. La mthode start est videmment asynchrone. Elle retourne immdiatement aprs avoir dmarr le nouveau thread. L'excution des deux threads se poursuit alors simultanment (du moins en apparence). En l'absence de synchronisation des threads, la fin du programme se droule de faon un peu diffrente selon que l'un ou l'autre thread se termine le premier. Si le nouveau thread se termine avant le thread principal, ce dernier s'arrte normalement lorsque son excution est termine. En revanche, si le thread principal se termine avant le nouveau thread, Java attend la

CHAPITRE 16 EXCUTER PLUSIEURS PROCESSUS SIMULTANMENT

637

fin de celui-ci avant de s'arrter, sauf si le nouveau thread a t dfini en tant que dmon. Dans ce cas, la fin du thread principal interrompt automatiquement le nouveau thread.

Principes de synchronisation
Les threads peuvent s'excuter de faon totalement indpendante, ou tre synchroniss de diffrentes faons. Nous avons dj voqu une faon de synchroniser les threads. Par exemple, si le thread principal dpend, un moment ultrieur de son excution, de l'excution complte du thread secondaire, celui-ci peut signaler son tat d'avancement un objet. La synchronisation peut toutefois prendre des aspects plus complexes. Un mme objet peut parfaitement tre utilis par plusieurs threads. Dans l'exemple de la Figure 16.4, l'objet A a une mthode excute par le thread principal (start) et une autre excute par le thread secondaire (run). La Figure 16.5 montre un exemple un peu plus complexe. Dans celui-ci, les deux threads excutent les mthodes de l'objet C. Il est mme tout fait possible que les deux threads excutent la mme mthode, comme dans l'exemple de la Figure 16.6. Le fait que deux threads excutent la mme mthode ne pose pas, intrinsquement, de problme particulier. En revanche, si cette mthode modifie des ressources, il peut y avoir un conflit. Ce conflit peut se produire aussi bien lorsque les threads excutent la mme mthode que lorsqu'ils utilisent des mthodes diffrentes d'un mme objet, voire d'objets diffrents. Le problme vient uniquement des ressources modifies et fait intervenir la notion d'opration atomique.

Les oprations atomiques


Une opration atomique ne dgage aucune radioactivit. Ici, la notion d'atome est prise dans le sens d'lment inscable. Supposons que deux threads s'excutent simultanment et fassent la mme chose, consistant lire la valeur d'une variable, la multiplier par deux et crire le rsultat dans la variable. Ce traitement n'est pas atomique car il est constitu de trois opra-

638

LE DVELOPPEUR JAVA 2

Dmarrage de Java Tches utilitaires Objet C


Constructeur(){ ----------} Mthode1(){ ----------} Mthode2(){ ----------}

Thread1 Thread2
Objet
Constructeur(){ ----------} Main(){ ----------} Mthode(){ ----------}

Thread1 Thread2 Thread1

Thread1

Tches utilitaires Arrt de Java

Thread1

Fin du Thread1

Objet B
Constructeur(){ ----------} Mthode(){ ----------} Mthode(){ ----------}

Objet A
Constructeur(){ -----Thread1 -----} run(){ ----------Fin du Thread2 } start(){ ----------}

Thread2

Figure 16.6 : Schma du fonctionnement d'un programme dont les deux threads excutent la mme mthode d'un mme objet.

tions distinctes du point de vue de l'excution des threads. La Figure 16.7 montre le schma des oprations. Lorsque deux threads effectuent chacun ce groupe d'oprations, il n'est pas possible de prvoir l'ordre dans lequel les oprations seront effectues. Le gestionnaire de threads effectue le basculement d'un thread l'autre indpendamment du contrle du programmeur. En effet, l'excution "simultane" des threads est une vue de l'esprit, du moins sur les machines un seul processeur. En fait, les threads sont effectus par petits morceaux, chacun leur tour, comme nous le verrons plus loin. La Figure 16.8 montre un des rsultats possibles. La seule chose dont nous sommes assurs est que, dans un thread, les oprations sont toujours effectues dans l'ordre.

CHAPITRE 16 EXCUTER PLUSIEURS PROCESSUS SIMULTANMENT

639

Opration Lire A Multiplier par 2 Ecrire le rsultat dans A

Rsultat 1 2

Valeur de A 1 1 1 2

Figure 16.7 : Les oprations effectues.

Opration Thread 1: Lire A Thread 1: Multiplier par 2 Thread 1: Ecrire le rsultat dans A Thread 1: Lire A Thread 1: Multiplier par 2 Thread 1: Ecrire le rsultat dans A

Rsultat 1 2 2 4

Valeur de A 1 1 1 2 2 2 4

Figure 16.8 : Exemple d'excution par deux threads.

Nous pouvons donc aussi bien obtenir le rsultat de la Figure 16.8 que celui de la Figure 16.9. Il est vident que seul le rsultat de la Figure 16.8 est correct. Pour nous assurer que nous obtiendrons toujours le rsultat correct, il faut rendre atomique le groupe des trois oprations, de faon que le gestionnaire de thread ne puisse oprer un basculement d'un thread un autre qu'avant ou aprs le groupe d'oprations, mais pas pendant son excution. En Java, la solution consiste dsigner un objet et acqurir un verrou sur celui-ci. Il peut s'agir de n'importe quel objet. Avant le groupe des oprations, une instruction spciale dsigne l'objet qui doit servir la synchronisation. Le premier thread qui atteint la premire opration du groupe synchronis demande le verrou sur cet objet. S'il est libre, il peut commencer le traitement. L'objet est alors

640

LE DVELOPPEUR JAVA 2

Opration Thread 1: Lire A Thread 2: Lire A Thread 2: Multiplier par 2 Thread 1: Multiplier par 2 Thread 1: Ecrire le rsultat dans A Thread 2: Ecrire le rsultat dans A

Rsultat 1 1 2 2

Valeur de A 1 1 1 1 1 2 2

Figure 16.9 : Un squencement diffrent des oprations.

verrouill. Aucun autre thread ne peut entamer l'excution du groupe synchronis. Si un thread se prsente, il doit attendre la libration du verrou. A la fin du groupe d'oprations, le thread libre le verrou. Le second thread peut alors commencer l'excution. Tout se passe comme sur un tronon d'une ligne de chemin de fer une seule voie. Tant qu'un train occupe un tronon, aucun autre train ne peut s'engager sur celui-ci.

Granularit de la synchronisation
La synchronisation, en crant des conditions d'attente pour les threads concurrents, a un impact important sur les performances. Si nous reprenons l'exemple du chemin de fer, nous nous rendons compte facilement que protger globalement le tronon Paris-Marseille est totalement inefficace puisqu'un seul train peut alors circuler sur la ligne. Il est ncessaire de dcouper la ligne en tronons qui doivent tre protgs individuellement. Il en est de mme dans les programmes. Il est inefficace de verrouiller un objet contenant une mthode critique du point de vue de l'excution par plusieurs threads si cet objet comporte d'autres mthodes ne posant pas de problme de synchronisation. Faire cela empcherait les autres threads d'accder ces mthodes. Il est prfrable de synchroniser uniquement la mthode critique. Parfois, seules quelques instructions de la mthode posent un problme de synchronisation. Il est alors possible de synchroniser uniquement les instructions correspondantes.

CHAPITRE 16 EXCUTER PLUSIEURS PROCESSUS SIMULTANMENT

641

Atomicit apparente
Certaines oprations ne sont atomiques qu'en apparence. En effet, les instructions Java sont compiles en bytecode, puis ce bytecode est excut par la machine virtuelle. Le rsultat de l'excution du bytecode est une srie d'instructions du processeur. Une instruction du bytecode est toujours atomique du point de vue du programmeur Java, mais une instruction Java peut tre compile en plusieurs instructions de bytecode. Dans ce cas, il peut tre ncessaire de synchroniser un bloc ne comportant qu'une seule instruction, mme si cela peut paratre illogique au premier abord. Aprs avoir absorb toute cette thorie, nous allons maintenant passer la pratique.

Crer explicitement un thread


En Java, il existe deux faons de crer un thread. La premire consiste driver la classe Thread et instancier la classe drive. La seconde consiste crer une classe implmentant une interface spciale (Runnable) et l'instancier. Cette deuxime faon de faire est utilise lorsque la classe cre doit tendre une classe particulire et ne peut donc tendre la classe Thread (car l'hritage multiple n'est pas possible). C'est souvent le cas, en particulier, des applets, que nous tudierons dans un prochain chapitre. Pour commencer, nous crerons un thread trs simple qui affichera les nombres de 1 1000. La premire chose faire consiste dclarer une classe tendant la classe Thread :
class monThread extends Thread { }

Il nous faut maintenant dfinir ce que fera notre thread. Pour cela, nous devons redfinir la mthode run() (en la dclarant public car nous ne

642

LE DVELOPPEUR JAVA 2
pouvons en restreindre l'accs par rapport la mthode de la classe parente) :
class monThread extends Thread { public void run() { for (int i = 1; i <= 1000; i++) System.out.println(i); } }

Et voil ! C'est aussi simple que cela. La question qui se pose maintenant est : comment excuter notre thread ? Ce n'est pas plus compliqu. Il suffit d'instancier cette classe et d'invoquer la mthode start() de cette instance. (La mthode start() est videmment dfinie dans la classe parente Thread.)
Thread t = new monThread(); t.start();

Ces instructions peuvent tre places dans la mthode main d'une autre classe :

public class Exemple { public static void main (String[] args) { Thread t = new monThread(); t.start(); } } class monThread extends Thread { public void run() { for (int i = 1; i <= 1000; i++) System.out.println(i); } }

CHAPITRE 16 EXCUTER PLUSIEURS PROCESSUS SIMULTANMENT

643

Nous pouvons galement inclure une mthode main dans la classe monThread afin de rendre celle-ci excutable :

public class monThread extends Thread { public void run() { for (int i = 1; i <= 1000; i++) System.out.println(i); } public static void main (String[] args) { Thread t = new monThread(); t.start(); } }

Bien entendu, nous n'avions pas besoin de crer un thread pour afficher les 1000 premiers entiers. L'interprteur Java aurait pu s'en charger pour nous. Il nous suffisait d'crire le programme de la faon suivante :
public class Exemple { public static void main (String[] args) { for (int i = 1; i <= 1000; i++) System.out.println(i); } }

Excuter plusieurs threads simultanment


Le principal intrt des threads est qu'ils permettent de dfinir plusieurs squences d'instructions compltement indpendantes. Dans certains cas, ces squences peuvent mme s'excuter de faon quasi simultane. Le programme suivant en fait la dmonstration :

644

LE DVELOPPEUR JAVA 2
public class monThread extends Thread { public void run() { for (int i = 1; i <= 1000; i++) System.out.println(i); } public static void main (String[] args) { new monThread().start(); new monThread().start(); } }

Ici, deux instances de monThread sont cres anonymement et excutes. Si vous lancez ce programme, vous constaterez que l'excution des deux threads est simultane, les lignes d'affichage tant parfaitement entrelaces :
1 1 2 2 3 3 4 4 etc.

Il n'y a cependant aucun moyen de savoir quel thread a produit chaque ligne, mme si on peut supposer que la premire de chaque groupe de deux lignes est due au premier thread lanc. Pour avoir plus d'informations ce sujet, nous pouvons modifier le programme de la faon suivante :

public class monThread extends Thread { public void run() { for (int i = 1; i <= 1000; i++)

CHAPITRE 16 EXCUTER PLUSIEURS PROCESSUS SIMULTANMENT


System.out.println(i + " } public static void main (String[] args) { new monThread().start(); new monThread().start(); } } " + getName());

645

Si vous excutez plusieurs fois ce programme, vous risquez d'obtenir des rsultats surprenants. Nous constatons qu'un thread peut parfois afficher plusieurs lignes pendant que l'autre semble ne rien faire ! Il s'agit l d'un point essentiel : priori, les deux threads se droulent de faon simultane sans aucune synchronisation. Cet aspect ne doit jamais tre nglig car il est source de nombreux problmes. Examinez, par exemple, le programme suivant :
public class monThread2 extends Thread { static int j = 1; public void run() { for (int i = 1; i <= 20; i++) System.out.println(j++); } public static void main (String[] args) { new monThread2().start(); new monThread2().start(); } }

Ce programme affiche les nombres de 1 40, ce qui nous parat tout fait naturel. En effet, chaque thread vient son tour augmenter d'une unit la valeur de la variable static j. Mais ce n'est qu'une apparence. Rien ne nous permet d'affirmer avec certitude que le premier thread affiche les valeurs

646

LE DVELOPPEUR JAVA 2
impaires et le second les valeurs paires. En modifiant cette fois encore le programme de la faon suivante :
System.out.println(j++ + " " + getName());

on obtient un rsultat variable, par exemple :


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 30 Thread-0 Thread-1 Thread-0 Thread-1 Thread-0 Thread-1 Thread-0 Thread-1 Thread-0 Thread-1 Thread-0 Thread-1 Thread-0 Thread-1 Thread-0 Thread-1 Thread-0 Thread-1 Thread-0 Thread-1 Thread-0 Thread-1 Thread-0 Thread-1 Thread-0 Thread-1 Thread-0 Thread-1 Thread-1

CHAPITRE 16 EXCUTER PLUSIEURS PROCESSUS SIMULTANMENT


31 32 29 33 34 35 36 37 38 39 40 Thread-1 Thread-1 Thread-0 Thread-1 Thread-0 Thread-1 Thread-0 Thread-1 Thread-0 Thread-0 Thread-0

647

Attention : Le phnomne constat ici n'est pas inhrent Java, mais au systme d'exploitation sur lequel fonctionne l'interprteur. Ce point prendra une importance fondamentale lorsque nous crirons des programmes exploitant cette particularit.
Si nous essayons une nouvelle version du programme, nous constatons un autre phnomne surprenant :
public class monThread2 extends Thread { static int j = 1; public void run() { for (int i = 1; i <= 20; i++) { System.out.println(j); j++; } } public static void main (String[] args) { new monThread2().start(); new monThread2().start(); } }

648

LE DVELOPPEUR JAVA 2
Ce programme affiche (par exemple) le rsultat suivant :
1 1 2 3 4 5 6 7 8 9 10 etc.

Il est tout fait possible que vous obteniez un rsultat diffrent avec, par exemple, des nombres manquants dans la srie. Comment expliquer un tel rsultat ? Il s'agit l encore du mme problme, l'absence de synchronisation. Si l'on essaie d'analyser le fonctionnement du programme, on s'aperoit que l'on peut avoir plusieurs cas de figure, par exemple :
Thread-0 Thread-0 Thread-1 Thread-1 : : : : System.out.println(j); j++; System.out.println(j); j++;

Dans ce cas, chaque thread affiche la valeur incrmente par le prcdent. Voici un autre cas possible :

Thread-0 Thread-1 Thread-0 Thread-1

: : : :

System.out.println(j); System.out.println(j); j++; j++;

CHAPITRE 16 EXCUTER PLUSIEURS PROCESSUS SIMULTANMENT


Dans ce cas, on obtient un affichage du type :

649

1 1 3 3 etc.

Les deux possibilits peuvent tre mlanges loisir. Dans le cas prcdent, on peut supposer que l'ordre d'excution tait :

Thread-0 Thread-1 Thread-0 Thread-0 Thread-1 Thread-1 Thread-0 etc.

: : : : : : :

System.out.println(j); System.out.println(j); j++; System.out.println(j); j++; System.out.println(j); j++;

Pour rsoudre ce type de problme, il faut utiliser la synchronisation, que nous tudierons un peu plus loin.

Caractristiques des threads


Comme vous avez pu le deviner la lecture des exemples prcdents, les threads ont un nom. Si aucun nom ne leur est attribu lors de leur cration, ils reoivent automatiquement le nom Thread- suivi du numro d'ordre de leur cration.

Attention : Le nom d'un thread n'a rien voir avec le handle qui permet de le rfrencer. La documentation de Java parle de thread anonyme pour dsigner les threads crs sans noms. Il ne s'agit pas pour autant

650

LE DVELOPPEUR JAVA 2
d'objets anonymes. La ligne suivante cre un thread anonyme (au sens de la documentation de Java) qui n'est pas un objet anonyme :
Thread t = new MonThread2();

alors que, dans l'exemple suivant, on cre un thread nomm Alfred qui est un objet anonyme :
new MonThread2("Alfred").start();

Un thread peut galement avoir une cible. Une cible est un objet instance d'une classe implmentant l'interface Runnable. Cette interface ne comporte qu'une seule mthode : run(). Si un thread est cr avec une cible, c'est la mthode run() de la cible qui est appele lorsque la mthode start() est invoque. Dans le cas contraire, la mthode run() du thread est invoque. Un thread peut galement tre cr avec l'indication d'un groupe auquel il appartiendra. Un groupe est une instance de la classe java.lang.ThreadGroup. L'appartenance un groupe permet de traiter plusieurs threads en bloc. Il est ainsi possible, par exemple, de contrler les autorisations de plusieurs threads en contrlant leur appartenance un groupe. L'ordre des paramtres pour la cration d'un thread est : groupe, cible, nom Chacun de ces paramtres peut tre omis, mais groupe ne peut tre utilis seul.

Contrler les threads


Plusieurs mthodes peuvent tre invoques pour contrler le droulement d'un thread :

CHAPITRE 16 EXCUTER PLUSIEURS PROCESSUS SIMULTANMENT

651

yield() interrompt le droulement de faon quasi imperceptible afin


de laisser du temps pour l'excution des autres threads d'un mme niveau de priorit.

sleep() interrompt le droulement pendant une dure qui peut tre


spcifie en millisecondes ou en millisecondes et nanosecondes.

interrupt() interrompt le droulement. join() attend la fin de l'excution (lance une interruption si le thread
est interrompu).

Note : La mthode yield() a une importance considrable souvent sousestime. En effet, Java ne gre pas le fonctionnement concurrent de plusieurs threads, mais se repose entirement sur le systme d'exploitation. Avec un systme comme Windows ou UNIX, le systme alloue des tranches de temps machine chaque thread. En revanche, avec d'autres systmes, le premier thread dmarr s'excute jusqu' ce qu'une des conditions suivantes soit remplie :

Le thread se termine. Un autre thread de priorit suprieure dmarre. Le thread cde explicitement le contrle un autre thread de priorit
gale au moyen des mthodes yield() ou sleep(). Si votre programme repose sur le fait que deux ou plusieurs threads doivent fonctionner de faon concurrente, et si ce programme doit tre utilisable dans tous les environnements supportant Java, chacun de vos threads doit cder explicitement du temps aux autres (sans toutefois pouvoir choisir lesquels). Faites attention la rciprocit : si un thread cde du temps un autre thread, il ne rcuprera le contrle que si celui-ci cde du temps son tour. Dans les environnements grant le fonctionnement concurrent des threads, l'utilisation de la mthode yield() peut entraner un lger ralentissement global du programme en augmentant le nombre de transitions d'un thread l'autre. (Deux threads s'excutant l'un aprs l'autre sont termins plus rapidement que deux threads s'excutant de faon concurrente.)

652

LE DVELOPPEUR JAVA 2
Il existe galement les mthodes stop(), suspend() et resume(), mais elles sont deprecated et ne doivent pas tre utilises. (Elles peuvent disparatre dans la prochaine version du langage.) Ce sont des mthodes agressives qui risquent de laisser des objets dans un tat indfini. Pour arrter un thread, par exemple, il est prfrable d'implmenter dans ce thread une fonction d'arrt pouvant tre appele de l'extrieur. Une faon de faire consiste modifier une variable qui sera teste par le thread. En voici un exemple :

class Th1 extends Thread { boolean arret = false; public void run() { for (int i = 1; i <= 10000; i++) { if (arret) break; System.out.println(i); } } }

Ce thread comporte une boucle affichant les nombres jusqu' 10 000. Avant chaque affichage, la variable arret est teste. Si elle vaut true, le thread est interrompu.

Note : Ici, seule la boucle est interrompue. Cependant, comme aucune


instruction ne se trouve aprs la boucle, cela revient interrompre le thread. Une autre faon de procder trs souvent utilise consiste employer une boucle while :
class Th1 extends Thread { boolean arret = false; public void run() {

CHAPITRE 16 EXCUTER PLUSIEURS PROCESSUS SIMULTANMENT


int i = 0; while (!arret) System.out.println(i++); } }

653

Un tel thread pourra tre arrt l'aide de l'instruction suivante :


non_du_thread.arret = true;

Il est cependant prfrable de restreindre l'accs au champ arret et d'utiliser un accesseur, comme dans l'exemple suivant :
class Th1 extends Thread { private boolean arret = false; Th1(ThreadGroup tg) { super(tg, "Th1"); } public void run() { int i = 0; while (!arret) System.out.println(i++); } public void arrete() { arret = true; } }

Utilisation des groupes pour rfrencer les threads


Pour contrler un thread de cette faon, il faut avoir accs un handle pointant vers celui-ci. Si un thread A cre deux threads B et C, il sera facile

654

LE DVELOPPEUR JAVA 2
de rfrencer B et C depuis A. En revanche, il est moins vident de rfrencer B depuis C ou C depuis B. Une des faons de procder consiste faire appartenir les deux threads un mme groupe. Chaque thread pourra ainsi interroger son groupe afin de connatre les autres membres du groupe. Le programme suivant montre un exemple de cette faon de procder :

import java.io.*; public class MonThread3 extends Thread { public static void main (String[] args) { ThreadGroup tg = new ThreadGroup("Groupe"); new Th1(tg).start(); new Th2(tg).start(); } } class Th1 extends Thread { private boolean arret = false; Th1(ThreadGroup tg) { super(tg, "Th1"); } public void run() { int i = 0; while (!arret){ System.out.println(i++); } } public void arrete() { arret = true; } } class Th2 extends Thread { Thread[] ta = new Thread[2];

CHAPITRE 16 EXCUTER PLUSIEURS PROCESSUS SIMULTANMENT


Th2(ThreadGroup tg) { super(tg, "Th2"); }

655

public void run() { enumerate(ta); String s1 = ""; BufferedReader r = new BufferedReader( new InputStreamReader(System.in)); while (!s1.equals("s")) { try { while (s1 == "") { s1 = r.readLine(); } if (!s1.equals("s")) { s1 = ""; } } catch (IOException e) { } } ((Th1)ta[0]).arrete(); } }

Le programme principal cre tout d'abord un groupe de threads appel tg, puis deux threads affects ce groupe, l'un instance de la classe Th1 et l'autre de la classe Th2. Ces deux threads sont volontairement crs en tant qu'objets anonymes et immdiatement dmarrs. Il n'existe donc aucun handle permettant de les manipuler :
ThreadGroup tg = new ThreadGroup("Groupe"); new Th1(tg).start(); new Th2(tg).start();

Les classes Th1 et Th2 sont pourvues d'un constructeur, qui appelle le constructeur de la classe parente en lui passant les arguments. (Souvenez-vous

656

LE DVELOPPEUR JAVA 2
qu'en l'absence de constructeur dans une classe, c'est le constructeur sans arguments de la classe parente qui est appel automatiquement, ce qui ne conviendrait pas ici.) La classe Th1 dfinit un thread qui affiche la valeur de la variable i en l'incrmentant chaque fois, et ce tant que la variable booleanarret vaut false. La variable arret est private, mais la classe dispose d'un mutateur pour modifier sa valeur. Il s'agit de la mthode public arrete(). Ce thread affiche donc les nombres successifs partir de 0 jusqu' ce qu'une des conditions suivantes soit remplie :

La variable arret prend la valeur true, Le thread est interrompu pour une cause extrieure (une panne d'lectricit, par exemple !). La classe Th2 est un peu plus complexe. Elle commence par dfinir un tableau de threads. Rappelons qu'un tableau de threads contient des handles de threads, et non des threads. Ce tableau comporte deux lments :
Thread[] ta = new Thread[2];

La premire chose que fait la mthode run() est de remplir ce tableau au moyen de l'instruction suivante :
enumerate(ta);

La mthode enumerate, dfinie dans la classe Thread, remplit le tableau de threads qui lui est pass en argument avec les threads appartenant au mme groupe que celui depuis lequel la mthode est invoque.

CHAPITRE 16 EXCUTER PLUSIEURS PROCESSUS SIMULTANMENT

657

La suite de la mthode run lit simplement le clavier jusqu' ce que le contenu de la chane s1 soit gal s. Lorsque c'est le cas, le premier thread du groupe est arrt grce l'instruction :
((Th1)ta[0]).arrete();

Le premier lment de ta (correspondant l'indice 0) est ici sous-cast en Th1 (car il s'agit d'un handle de Thread) et sa mthode arrete() est invoque, causant l'arrt du thread. Compilez et excutez ce programme. Les chiffres dfilent sur l'affichage jusqu' ce que vous tapiez s puis la touche Entre. Ce programme n'tait conu ainsi que pour montrer comment il est possible de rfrencer un thread anonyme. Normalement, il est plus simple d'utiliser des handles, comme dans l'exemple suivant :
import java.io.*; public class MonThread4 extends Thread { static Th1 th1; static Th2 th2; public static void main (String[] args) { th1 = new Th1(); th2 = new Th2(); th1.start(); th2.start(); } } class Th1 extends Thread { private boolean arret = false; private boolean interruption = false; public void run() {

658
int i = 0; while (!arret){ if (!interruption) System.out.println(i++); } } public void arrete() { arret = true; } public void interromp() { interruption = !interruption; } }

LE DVELOPPEUR JAVA 2

class Th2 extends Thread { public void run() { String s1 = ""; BufferedReader r = new BufferedReader( new InputStreamReader(System.in)); try { while (s1 == "") { s1 = r.readLine(); if (s1.equals("i")) MonThread4.th1.interromp(); if (s1.equals("s")) { MonThread4.th1.arrete(); break; } s1 = ""; } } catch (IOException e) { } } }

CHAPITRE 16 EXCUTER PLUSIEURS PROCESSUS SIMULTANMENT

659

Ce programme est plus perfectionn : il permet en effet d'interrompre ou de reprendre l'affichage en tapant I et la touche ENTRE, ou de l'arrter dfinitivement laide des touches S + ENTRE. Pourtant, il est plus facile lire car nous avons utilis l'instruction break pour arrter le thread Th2.

Grer la rpartition du temps entre les threads


Lorsque plusieurs threads fonctionnent en mme temps, il est parfois ncessaire de contrler la faon dont le temps du processeur est rparti entre chacun d'eux. Considrez l'exemple suivant, qui affiche les nombres pendant dix secondes :

public class MonThread5 extends Thread { static Th1 th1; static Th2 th2; public static void main (String[] args) { th1 = new Th1(); th2 = new Th2(); th1.start(); th2.start(); } } class Th1 extends Thread { private boolean arret = false; private boolean interruption = false; public void run() { int i = 0; while (!arret) { System.out.println(i++); } }

660
public void arrete() { arret = true; } }

LE DVELOPPEUR JAVA 2

class Th2 extends Thread { public void run() { long duree = 10000; long t1 = System.currentTimeMillis(); long t = 0; while (t < duree) { t = System.currentTimeMillis() - t1; } MonThread5.th1.arrete(); } }

Le premier thread affiche les nombres comme prcdemment. Le second compte le temps qui passe et arrte le premier lorsque ce temps a dpass dix secondes. Compilez et excutez ce programme et notez jusqu' combien les nombres ont t affichs (dans notre cas, environ 1 300). Vous trouvez que ce n'est pas beaucoup ? Pour en afficher un plus grand nombre en dix secondes, il suffit de donner plus de temps au premier thread. Une faon de procder consiste modifier le second thread de la manire suivante :
class Th2 extends Thread { public void run() { long duree = 10000; long t1 = System.currentTimeMillis(); long t = 0; while (t < duree) { t = System.currentTimeMillis() - t1; yield(); } MonThread5.th1.arrete(); } }

CHAPITRE 16 EXCUTER PLUSIEURS PROCESSUS SIMULTANMENT

661

L'instruction yield() signifie ne rien faire pendant un certain temps afin de donner du temps aux autres threads. Ainsi modifi, notre programme affiche les nombres jusqu' 23 000. Notez qu'il est possible de placer plusieurs instructions yield() les unes aprs les autres. Avec deux instructions, le compte atteint 30 000. Avec quatre, le programme va jusqu' 34 000. titre de comparaison, nous aurions pu effectuer le mme traitement avec un seul thread, de la faon suivante :
public class Simple { public static void main (String[] args) { int i = 0; long duree = 10000; long t1 = System.currentTimeMillis(); long t = 0; while (t < duree) { t = System.currentTimeMillis() - t1; System.out.println(i++); } } }

C'est beaucoup plus simple, mais galement beaucoup moins performant. Ce programme ne compte que jusqu' 9 000. Il est possible de l'optimiser :
public class MoinsSimple { public static void main (String[] args) { int i = 0; long duree = 10000; long t1 = System.currentTimeMillis(); long t = 0; while (t < duree) { for (int j = 0; j < 1000; j++) System.out.println(i++); t = System.currentTimeMillis() - t1; }

662
System.out.println(t); } }

LE DVELOPPEUR JAVA 2

Ce programme peut paratre plus performant, surtout si l'on remplace la valeur 1 000 (en gras) par 10 000. Cependant, nous nous trouvons devant un autre problme : ici, nous vrifions le temps chaque fois que nous avons affich 1 000 (ou 10 000) lignes. Le rsultat est qu'au moment de la vrification, le temps peut tre un peu dpass (environ 200 millisecondes pour une valeur de 1 000), voire beaucoup (2 000 millisecondes pour une valeur de 10 000). En revanche, le programme utilisant les threads ne varie pas de plus de 50 millisecondes.

La priorit
Une autre faon de grer la rpartition du temps consiste attribuer chaque thread des niveaux de priorit diffrents. La priorit d'un thread est une caractristique hrite du thread qui l'a cr. Il est cependant possible de la modifier, comme dans l'exemple suivant :
import java.io.*; public class MonThread6 extends Thread { static Th1 th1; static Th2 th2; public static void main (String[] args) { th1 = new Th1(); th2 = new Th2(); th1.start(); th2.start(); } } class Th1 extends Thread { private boolean arret = false; private boolean interruption = false;

CHAPITRE 16 EXCUTER PLUSIEURS PROCESSUS SIMULTANMENT


public void run() { setPriority(MAX_PRIORITY); int i = 0; while (!arret){ System.out.println(i++); } } public void arrete() { arret = true; } } class Th2 extends Thread { public void run() { setPriority(MIN_PRIORITY); long duree = 10000; long t1 = System.currentTimeMillis(); long t = 0; while (t < duree) { t = System.currentTimeMillis() - t1; } MonThread6.th1.arrete(); System.out.println(t); } }

663

MAX_PRIORITY

et MIN_PRIORITY sont des valeurs de type int dfinies dans la

classe Thread. Il faut bien comprendre ce que signifie la priorit. Sur un ordinateur un seul processeur, un seul thread peut fonctionner un moment donn. Dans ce cas, le thread qui possde la plus haute priorit est excut. S'il se termine, ou s'il donne explicitement du temps en excutant la mthode yield, le thread de priorit infrieure est excut. Cependant, certains systmes comme Windows ou UNIX simulent le fonctionnement d'un ordinateur multiprocesseur en affectant tour de rle

664

LE DVELOPPEUR JAVA 2
chaque thread un peu de temps machine. De cette faon, les threads semblent s'excuter simultanment. Le niveau de priorit dtermine le rapport entre les temps allous chaque thread. Tant qu'un thread s'excute normalement, il ne peut tre interrompu que par le systme, qui dcide de donner du temps un autre thread. Comme nous l'avons vu prcdemment, un thread peut galement laisser du temps aux autres threads en invoquant la mthode yield(). Tout cela fait que deux threads qui fonctionnent simultanment ne sont pas synchroniss, ce qui explique les rsultats que nous avons obtenus au dbut de ce chapitre.

La synchronisation
Une faon de rsoudre ce problme consiste synchroniser les threads. Lorsque deux threads doivent accder un mme objet, il est parfois ncessaire qu'ils aient la possibilit de verrouiller cet objet. Ce principe est mis en uvre par exemple, dans les bases de donnes. Si deux utilisateurs accdent simultanment la mme fiche pour la modifier, le premier qui aura termin sa modification verra son travail effac lorsque le second enregistrera la sienne. Pour viter cela, le premier utilisateur doit avoir un moyen de verrouiller la fiche pendant toute la dure de la modification. Un autre exemple est celui d'une imprimante partage. Si deux utilisateurs peuvent y accder en mme temps, il est tout de mme prfrable que le premier arriv puisse la verrouiller le temps que son impression soit termine. Dans le cas contraire, l'imprimante risquerait d'imprimer alternativement une page d'un utilisateur, puis une page d'un autre, et ainsi de suite. S'il s'agit d'imprimer deux documents de cent pages chacun, il n'y a plus qu' trier. Le programme suivant simule l'impression des deux documents :
public class Impression extends Thread { int j = 1;

CHAPITRE 16 EXCUTER PLUSIEURS PROCESSUS SIMULTANMENT


Impression(String s) { super(s); }

665

public void run() { for (int i = 1; i <= 10; i++) { System.out.println("Impression de la page " + j + " du " + getName()); j++; } } public static void main (String[] args) { new Impression("premier document").start(); new Impression("deuxieme document").start(); } }

Ce programme affiche le rsultat suivant :


Impression Impression Impression Impression Impression Impression Impression Impression Impression Impression Impression Impression Impression Impression Impression Impression de de de de de de de de de de de de de de de de la la la la la la la la la la la la la la la la page page page page page page page page page page page page page page page page 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 du du du du du du du du du du du du du du du du premier document deuxieme document premier document deuxieme document premier document deuxieme document premier document deuxieme document premier document deuxieme document premier document deuxieme document premier document deuxieme document premier document deuxieme document

666
Impression Impression Impression Impression de de de de la la la la page page page page

LE DVELOPPEUR JAVA 2
9 du premier document 9 du deuxieme document 10 du premier document 10 du deuxieme document

Pour viter ce rsultat, il faut que le premier thread puisse empcher le second de commencer son travail jusqu' ce qu'il ait fini le sien. Pour cela, il doit verrouiller un objet. videmment, il ne s'agit pas de n'importe quel objet. Il doit s'agir d'un objet sans le contrle duquel le thread ne pourra pas fonctionner. L'objet System.out est videmment un bon candidat. Le programme modifi apparat ci-dessous :
public class Impression extends Thread { int j = 1; Impression(String s) { super(s); } public void run() { synchronized (System.out) { for (int i = 1; i <= 10; i++) { System.out.println("Impression de la page " + j + " du " + getName()); j++; } } } public static void main (String[] args) { new Impression("premier document").start(); new Impression("deuxieme document").start(); } }

CHAPITRE 16 EXCUTER PLUSIEURS PROCESSUS SIMULTANMENT

667

La boucle correspondant l'intgralit du traitement est place dans un bloc contrl par synchronized (System.out). De cette faon, l'objet System.out est verrouill jusqu' ce que le traitement soit termin. Le programme produit maintenant le rsultat suivant :
Impression Impression Impression Impression Impression Impression Impression Impression Impression Impression Impression Impression Impression Impression Impression Impression Impression Impression Impression Impression de de de de de de de de de de de de de de de de de de de de la la la la la la la la la la la la la la la la la la la la page page page page page page page page page page page page page page page page page page page page 1 du premier document 2 du premier document 3 du premier document 4 du premier document 5 du premier document 6 du premier document 7 du premier document 8 du premier document 9 du premier document 10 du premier document 1 du deuxieme document 2 du deuxieme document 3 du deuxieme document 4 du deuxieme document 5 du deuxieme document 6 du deuxieme document 7 du deuxieme document 8 du deuxieme document 9 du deuxieme document 10 du deuxieme document

Note : La synchronisation consiste obtenir un verrou sur un objet utilis


pour le traitement (ce qui empche un thread concurrent de s'excuter). Ici, nous aurions pu galement verrouiller l'un ou l'autre thread ( condition de disposer d'un handle adquat) de la faon suivante :

public class Impression extends Thread { int j = 1; static Impression I1, I2; Impression(String s) {

668
super(s); }

LE DVELOPPEUR JAVA 2

public void run() { synchronized (I1) { for (int i = 1; i <= 10; i++) { System.out.println("Impression de la page " + j + " du " + getName()); j++; } } } public static void main (String[] args) { I1 = new Impression("premier document"); I2 = new Impression("deuxieme document"); I1.start(); I2.start(); } }

Cette mthode est beaucoup moins logique et lgante, mais il est bon de la connatre : cela peut parfois servir. La partie du code concern par la synchronisation est dite critique. Souvent, cette partie critique est l'ensemble d'une mthode, et l'objet qui doit tre verrouill est celui qui contient la mthode. Dans ce cas, il est possible d'utiliser le modificateur synchronized dans la dclaration de la mthode :
public synchronized void Methode() {

S'il s'agit d'une mthode d'instance, l'objet instance est verrouill. S'il s'agit d'une mthode statique, c'est la classe qui est verrouille. L'exemple suivant simule l'impression de documents sur une imprimante. Chaque thread construit tout d'abord un document sous la forme d'un ta-

CHAPITRE 16 EXCUTER PLUSIEURS PROCESSUS SIMULTANMENT

669

bleau de chanes de caractres reprsentant les pages, puis la mthode imprime de l'imprimante est appele avec ce tableau pour paramtre. Cette mthode imprime les pages une une (en les affichant l'cran). La premire version du programme n'est pas synchronise :
public class Impression extends Thread { Impression(String s) { super(s); } static Imprimante imprimante; String[] pages; public void run() { pages = new String[10]; for (int i = 0; i < pages.length; i++) pages[i] = "Page " + (i+1) + " du " + getName(); imprimante.imprime(pages); } public static void main (String[] args) { imprimante = new Imprimante(); new Impression("premier document").start(); new Impression("deuxieme document").start(); } } class Imprimante { public void imprime(String[] s) { for (int i = 0; i < s.length; i++) System.out.println(s[i]); } }

Elle imprime les pages mlanges :


Page 1 du premier document Page 1 du deuxieme document

670
Page Page Page Page Page Page Page Page Page Page Page Page Page Page Page Page Page Page 2 du premier document 2 du deuxieme document 3 du premier document 3 du deuxieme document 4 du premier document 4 du deuxieme document 5 du premier document 5 du deuxieme document 6 du premier document 6 du deuxieme document 7 du premier document 7 du deuxieme document 8 du premier document 8 du deuxieme document 9 du premier document 9 du deuxieme document 10 du premier document 10 du deuxieme document

LE DVELOPPEUR JAVA 2

Les deux versions suivantes utilisent les deux types de synchronisation et produisent le rsultat dsir :
public class Impression2 extends Thread { Impression2(String s) { super(s); } static Imprimante imprimante; String[] pages; public void run() { pages = new String[10]; for (int i = 0; i < pages.length; i++) pages[i] = "Page " + (i+1) + " du " + getName(); synchronized (imprimante) { imprimante.imprime(pages); } }

CHAPITRE 16 EXCUTER PLUSIEURS PROCESSUS SIMULTANMENT


public static void main (String[] args) { imprimante = new Imprimante(); new Impression2("premier document").start(); new Impression2("deuxieme document").start(); } } class Imprimante { public void imprime(String[] s) { for (int i = 0; i < s.length; i++) System.out.println(s[i]); } } public class Impression3 extends Thread { Impression3(String s) { super(s); } static Imprimante imprimante; String[] pages; public void run() { pages = new String[10]; for (int i = 0; i < pages.length; i++) pages[i] = "Page " + (i+1) + " du " + getName(); imprimante.imprime(pages); } public static void main (String[] args) { imprimante = new Imprimante(); new Impression3("premier document").start(); new Impression3("deuxieme document").start(); } } class Imprimante { public synchronized void imprime(String[] s) { for (int i = 0; i < s.length; i++) System.out.println(s[i]); } }

671

672

LE DVELOPPEUR JAVA 2

Problmes de synchronisation
Nous allons maintenant perfectionner ce programme en simulant une imprimante dote d'un spooler :
public class Impression4 extends Thread { Impression4(String s) { super(s); } static Imprimante imprimante; String[] pages; public void run() { synchronized(imprimante) { pages = new String[10]; for (int i = 0; i < pages.length; i++) pages[i] = "Page " + (i+1) + " du " + getName(); imprimante.spool(pages); } } public static void main (String[] args) { imprimante = new Imprimante(); new Impression4("premier document").start(); new Impression4("deuxieme document").start(); imprimante.imprime(); } } class Imprimante { String[] spooler = new String[100]; int index = 0; public void imprime() { System.out.println("Page de garde : dbut d'impression");

CHAPITRE 16 EXCUTER PLUSIEURS PROCESSUS SIMULTANMENT

673

int j = 0; for (int i = 0; i < spooler.length; i++) { if (spooler[i] != null) { System.out.println(spooler[i]); j++; spooler[i] = null; } } System.out.println("Fin d'impression. " + j + " pages imprimees."); } public synchronized void spool(String[] s) { for (int i = 0; i < s.length; i++) spooler[index++] = s[i]; } }

Cette fois, notre thread Impression4 effectue les oprations suivantes :

Il verrouille l'imprimante :
synchronized(imprimante) {

Il cre un document vide de 10 pages, reprsent par un tableau de


chanes de caractres :
pages = new String[10];

Il remplit ce tableau avec des chanes permettant d'identifier les pages


et le document auquel elles appartiennent :
for (int i = 0; i < pages.length; i++) pages[i] = "Page " + (i+1) + " du " + getName();

674

LE DVELOPPEUR JAVA 2
(getName() renvoie le nom du thread, c'est--dire, dans notre exemple premier document ou deuxime document.)

Le thread appelle ensuite la mthode spool de l'imprimante et lui transmet le document (c'est--dire le tableau).
imprimante.spool(pages);

Cette mthode copie une une les pages du document dans la mmoire de l'imprimante, reprsente par un autre tableau nomm spooler :
public synchronized void spool(String[] s) { for (int i = 0; i < s.length; i++) spooler[index++] = s[i]; }

La mthode main, de son ct, cre une imprimante puis deux threads correspondant deux documents :
public static void main (String[] args) { imprimante = new Imprimante(); new Impression4("premier document").start(); new Impression4("deuxieme document").start();

Enfin, elle appelle la mthode imprime de l'imprimante, dont le rle est de raliser l'impression (affichage) de toutes les pages (chanes de caractres) stockes dans sa mmoire :
imprimante.imprime();

Cette mthode est trs simple. Elle parcourt le tableau et affiche toutes les chanes qu'il contient puis les annule. Une fois le traitement termin, la mthode affiche un message rcapitulatif :

CHAPITRE 16 EXCUTER PLUSIEURS PROCESSUS SIMULTANMENT

675

public void imprime() { System.out.println("Page de garde : dbut d'impression"); int j = 0; for (int i = 0; i < spooler.length; i++) { if (spooler[i] != null) { System.out.println(spooler[i]); j++; spooler[i] = null; } } System.out.println("Fin d'impression. " + j + " pages imprimees."); }

Ce programme fonctionne trs bien, mais il pourrait tre amlior. En effet, il n'est pas normal d'tre oblig de demander explicitement l'imprimante d'imprimer les pages stockes en mmoire. Elle devrait tre capable de faire cela automatiquement. Nous entendons par l une contrainte bien prcise :

Le programme devra fonctionner de la mme faon en supprimant


simplement la ligne qui dclenche l'impression :
imprimante.imprime();

Aucune autre modification ne devra tre apporte la classe princi-

pale. En revanche, nous avons toute libert pour modifier la classe Imprimante ( condition de ne pas changer son interface, ce qui nous obligerait modifier le programme principal).

Pour que cela soit possible, il faut que l'imprimante soit en mesure de vrifier rgulirement si des pages se trouvent en mmoire. Une solution consiste faire de notre imprimante un thread. Pour compliquer un peu les choses, nous supposerons que la classe imprimante doive driver d'une autre classe (que nous appellerons Materiel et qui sera vide dans notre exemple).

676

LE DVELOPPEUR JAVA 2
Puisque notre classe doit driver de la classe Materiel, elle ne peut pas driver de la classe Thread. Nous lui ferons donc implmenter l'interface Runnable :
class Materiel { } class Imprimante extends Materiel implements Runnable { String[] spooler = new String[100]; int index = 0;

Nous ajouterons ensuite deux variables. La premire est une variable de type boolean qui indique si des pages se trouvent en attente d'tre imprimes. La deuxime est le thread que nous allons utiliser.
boolean pagesEnAttente = false; Thread imprimanteThread;

Nous aurons besoin d'initialiser le thread et de le dmarrer, ce que nous ferons dans le constructeur de la classe Imprimante.

Imprimante() { imprimanteThread = new Thread(Imprimante.this); imprimanteThread.start(); }

Nous voyons l la principale diffrence entre les deux faons de crer des threads (driver la classe Thread ou implmenter l'interface Runnable). Dans le deuxime cas, nous crons un thread en instanciant directement la classe Thread, et non une classe drive, et en passant son constructeur une cible qui est un objet implmentant l'interface Runnable et donc la mthode Run. Ici, nous avons choisi la classe imprimante comme cible, d'o l'utilisation de this.

CHAPITRE 16 EXCUTER PLUSIEURS PROCESSUS SIMULTANMENT

677

La mthode spool sera modifie par l'ajout d'une ligne permettant de donner la valeur true la variable pagesEnAttente :

pagesEnAttente = true;

De la mme faon, la mthode Imprime redonne la valeur false cet indicateur lorsque toutes les pages sont imprimes. La mthode run est videmment entirement nouvelle :

public void run() { imprimanteThread.setPriority(imprimanteThread.MIN_PRIORITY); while (true) { if (pagesEnAttente) imprime(); } }

Cette mthode est trs simple : elle commence par donner au thread la priorit minimale puis entre dans une boucle sans fin dans laquelle elle teste la variable pagesEnAttente. Si cette variable vaut true, les pages sont imprimes. Le listing complet du programme apparat ci-aprs :

public class Impression5 extends Thread { Impression5(String s) { super(s); } static Imprimante imprimante; String[] pages; public void run() { synchronized(imprimante) { pages = new String[10];

678

LE DVELOPPEUR JAVA 2
for (int i = 0; i < pages.length; i++) pages[i] = "Page " + (i+1) + " du " + getName(); imprimante.spool(pages); } } public static void main (String[] args) { imprimante = new Imprimante(); new Impression5("premier document").start(); new Impression5("deuxieme document").start(); } } class Materiel { } class Imprimante extends Materiel implements Runnable { String[] spooler = new String[100]; int index = 0; boolean pagesEnAttente = false; Thread imprimanteThread; Imprimante() { imprimanteThread = new Thread(Imprimante.this); imprimanteThread.start(); } public synchronized void imprime() { System.out.println("Page de garde : debut d'impression"); int j = 0; for (int i = 0; i < spooler.length; i++) { if (spooler[i] != null) { System.out.println(spooler[i]); j++; spooler[i] = null; } } System.out.println("Fin d'impression. " + j + " pages imprimees.");

CHAPITRE 16 EXCUTER PLUSIEURS PROCESSUS SIMULTANMENT


pagesEnAttente = false; } public synchronized void spool(String[] s) { for (int i = 0; i < s.length; i++) spooler[index++] = s[i]; pagesEnAttente = true; }

679

public void run() { imprimanteThread.setPriority(imprimanteThread.MIN_PRIORITY); while (true) { if (pagesEnAttente) imprime(); } } }

Ce programme fonctionne, mais prsente un dfaut. En effet, lorsque le thread principal (celui de la mthode main) et les deux threads crs par cette mthode sont termins, le thread de l'imprimante est toujours actif. Il ne s'arrtera jamais de lui-mme car il comporte une boucle infinie (while(true)). Cela est cependant tout fait normal. Lorsque nous crons l'imprimante, elle se met en marche automatiquement. Il nous faut l'teindre explicitement ou trouver un moyen d'obtenir automatiquement le mme rsultat. (En attendant, tapez CTRL + C pour arrter le programme.) Pour teindre automatiquement l'imprimante, il suffit de lui ajouter une mthode accomplissant cela, puis d'appeler cette mthode depuis la mthode main. Bien sr, il faudra faire attention ne pas teindre l'imprimante avant qu'elle ait fini d'imprimer. La meilleure solution consiste la doter d'un indicateur que nous appellerons marche et qui prendra la valeur true au dmarrage. La mthode eteindre lui donnera la valeur false. Il nous suffira de remplacer alors la boucle while(true) par while(marche || pagesEnAttente). Voici les parties du programme modifies :
public static void main (String[] args) { imprimante = new Imprimante();

680

LE DVELOPPEUR JAVA 2
new Impression6("premier document").start(); new Impression6("deuxieme document").start(); imprimante.eteindre(); } . . . class Imprimante extends Materiel implements Runnable { . . . public void eteindre() { marche = false; } public void run() { marche = true; imprimanteThread.setPriority(imprimanteThread.MIN_PRIORITY); while (marche || pagesEnAttente) { if (pagesEnAttente) imprime(); } } }

Malheureusement ce programme ne fonctionne pas. En effet, l'instruction :


imprimante.eteindre();

de la mthode main est excute avant mme que le premier thread ait pu terminer de spooler ses pages et de donner pagesEnAttente la valeur true. La solution qui parait vidente consiste modifier cet indicateur au dbut de la mthode, et non la fin :
public synchronized void spool(String[] s) { pagesEnAttente = true;

CHAPITRE 16 EXCUTER PLUSIEURS PROCESSUS SIMULTANMENT


for (int i = 0; i < s.length; i++) spooler[index++] = s[i]; }

681

Cependant, un autre problme se pose maintenant : le programme ne s'arrte pas ! Pour comprendre pourquoi, modifions le programme de la faon suivante :
public synchronized void spool(String[] s) { pagesEnAttente = true; System.out.println("Spool : " + imprimanteThread.getName() + " pagesEnAttente : " + pagesEnAttente + ", marche : " + marche); for (int i = 0; i < s.length; i++) spooler[index++] = s[i]; } public void eteindre() { marche = false; System.out.println("Eteindre : " + imprimanteThread.getName() + " pagesEnAttente : " + pagesEnAttente + ", marche : " + marche); }

Si on excute le programme ainsi modifi, on obtient le rsultat suivant :


Eteindre : Thread-0 pagesEnAttente : false, marche : false Spool : Thread-0 pagesEnAttente : true, marche : true Spool : Thread-0 pagesEnAttente : true, marche : true Page de garde : debut d'impression Page 1 du deuxieme document Page 2 du deuxieme document Page 3 du deuxieme document . . . .

682

LE DVELOPPEUR JAVA 2
qui nous montre que la mthode eteindre est appele avant que la mthode spool ait eu le temps de configurer l'indicateur pagesEnAttente. La solution consiste initialiser la variable avec la valeur true :
class Imprimante extends Materiel implements Runnable { String[] spooler = new String[100]; int index = 0; boolean pagesEnAttente = false, marche = true; Thread imprimanteThread;

(Bien sr, nous le savions dj et l'erreur tait volontaire, pour mettre en vidence les problmes de synchronisation.) Notre programme fonctionne ainsi, mais il ne respecte pas les critres que nous nous tions fixs. En effet, nous avons d modifier la mthode main() en lui ajoutant l'invocation de la mthode eteindre. Ce que nous souhaitons obtenir est une imprimante qui s'teigne automatiquement. Il existe plusieurs faons de le raliser. L'imprimante pourrait tester la variable pagesEnAttente pendant un certain temps, puis s'teindre si la valeur est toujours false. Nous allons cependant utiliser une autre mthode.

Mise en uvre d'un dmon


Une autre possibilit est de traiter l'imprimante comme un dmon. Un dmon (daemon) est un thread qui reste en mmoire pour servir d'autres threads. Il ne se termine pas de lui-mme. En revanche, il est automatiquement termin s'il reste seul s'excuter. Plusieurs dmons peuvent fonctionner en mme temps. Tant qu'il reste un thread vivant qui ne soit pas un dmon, les dmons continuent de fonctionner. S'il ne reste plus que des dmons, ils sont tous termins automatiquement. Ce type de thread parat adapt l'usage que nous voulons en faire. Cependant, il se pose l aussi un problme de synchronisation. Le constructeur de la classe Imprimante pourrait tre modifi de la faon suivante :

CHAPITRE 16 EXCUTER PLUSIEURS PROCESSUS SIMULTANMENT


Imprimante() { imprimanteThread = new Thread(Imprimante.this); imprimanteThread.setDaemon(true); imprimanteThread.start(); }

683

Cependant, voil ce qui risque d'arriver lors de l'excution de la mthode main :


public static void main (String[] args) { imprimante = new Imprimante();

Le constructeur de la classe Imprimante est excut. Le thread est lanc en tant que dmon. Comme le thread principal (celui de la mthode main) est en cours d'excution, le dmon reste actif.
new Impression5("premier document").start();

Le thread correspondant au premier document dmarre.


new Impression5("deuxieme document").start();

Le thread correspondant au deuxime document dmarre. Il faut bien comprendre que le dmarrage d'un thread prend un certain temps. Lorsque la mthode main se termine, ces deux threads sont toujours en cours de dmarrage. (On peut dire en quelque sorte qu'ils n'ont pas fini de commencer.) ce moment, aucun thread n'est en fonctionnement part le dmon. Celui-ci s'arrte donc avant que les deux autres ne soient considrs comme en fonctionnement. Pour bien comprendre ce qui se passe, il faut savoir qu'un thread peut avoir trois tats :

684

LE DVELOPPEUR JAVA 2

Vivant Fonctionnel (runnable) Non fonctionnel Mort


Lorsqu'un thread est initialis, par exemple :
imprimanteThread = new Thread(Imprimante.this);

il devient vivant mais non fonctionnel. C'est dans cet tat (et dans cet tat seulement) qu'il peut tre dclar dmon. Le thread devient fonctionnel lorsque sa mthode start est invoque. Un thread fonctionnel est susceptible d'tre en fonctionnement. Il n'y a cependant aucun moyen de s'en assurer. Cela dpend du gestionnaire de tches, de sa priorit, et d'autres paramtres. Un thread fonctionnel peut devenir non fonctionnel dans les cas suivants :

Sa mthode sleep est invoque. La mthode wait est appele pour attendre la ralisation d'une condition donne. tre/sortie.

Le thread est bloqu dans l'attente de l'excution d'une opration d'enUn thread est mort lorsque son excution est termine. A la lumire de ces explications, nous pouvons prciser la dfinition d'un dmon : Un dmon est un thread qui reste vivant tant qu'il existe au moins un thread fonctionnel qui n'est pas un dmon. Dans notre cas, lorsque le dmon est dmarr, les deux threads correspondant aux deux documents sont peut-tre dj vivants, mais pas encore

CHAPITRE 16 EXCUTER PLUSIEURS PROCESSUS SIMULTANMENT

685

fonctionnels. Le dmon se termine donc immdiatement. Nous pouvons facilement imaginer un moyen de mettre cela en vidence. Il suffit de modifier la mthode main pour que le thread dans lequel elle s'excute continue de vivre suffisamment longtemps pour que les deux autres threads prennent vie leur tour :
public static void main (String[] args) { imprimante = new Imprimante(); new Impression7("premier document").start(); new Impression7("deuxieme document").start(); try { do { sleep(1000); } while (imprimante.pagesEnAttente); } catch(InterruptedException e) { } }

Ainsi modifi, le programme fonctionne, mais cela ne correspond videmment pas ce que nous souhaitions. En revanche, ce fonctionnement pourrait convenir si la mthode main devait effectuer d'autres traitements dont nous serions assurs qu'ils dureraient suffisamment longtemps pour que les threads dmarrent. Cela peut paratre du bricolage, mais ce n'est pas obligatoirement le cas. Par exemple, si la suite du programme consiste attendre l'entre d'un utilisateur, on peut tre assur que cela durera suffisamment pour que cette condition soit remplie. Cependant il existe une solution bien plus lgante, et efficace dans tous les cas.

Communication entre les threads: wait et notifyAll


Si on observe notre programme, on se rend compte que, pour rsoudre ce problme, il suffirait de maintenir en vie un thread jusqu' ce que le dmon ait termin son travail. Cela ncessite deux choses :

686
min.

LE DVELOPPEUR JAVA 2

Que le dmon puisse envoyer un message pour indiquer qu'il a ter Qu'un thread reste en vie et attende ce message pour se terminer.
Nous avons immdiatement un volontaire pour attendre (et mme deux !) : chaque thread imprimant un document peut parfaitement, avant de se terminer, attendre un signal indiquant que l'impression a t entirement excute. Cette faon de procder lude d'ailleurs un problme potentiel : que se serait-il pass si le dmon avait correctement dmarr et si tous les autres threads s'taient termins avant que toutes les pages soient imprimes ? L'impression aurait t incomplte ! Pour que le thread envoyant chaque document l'imprimante attende le message indiquant que l'impression est termine, il suffit d'ajouter, la fin de la mthode spool, quelques lignes de code :
public synchronized void spool(String[] s) { for (int i = 0; i < s.length; i++) spooler[index++] = s[i]; pagesEnAttente = true; try { wait(); } catch(InterruptedException e) { } }

La mthode wait() ne vient pas de la classe Thread, mais de la classe Object, ce qui explique qu'elle puisse tre utilise ici de cette faon. (La classe imprimante n'tend pas la classe Thread.) Cette mthode entrane l'arrt du thread courant s'il possde le contrle de l'objet dont la mthode wait est invoque. Si la mthode n'a pas d'argument, le thread attend jusqu' ce qu'il reoive un message de notification. La mthode peut galement prendre en argument un int indiquant un nombre de millisecondes, ou deux int indiquant un temps en millisecondes et nanosecondes. Dans ce cas, le thread attend pendant le temps indiqu sauf s'il reoit un message

CHAPITRE 16 EXCUTER PLUSIEURS PROCESSUS SIMULTANMENT

687

de notification avant ce dlai. Lorsque l'attente est interrompue par l'une ou l'autre condition, le thread attend de pouvoir reprendre le contrle de l'objet pour continuer son excution. Pour envoyer le message de notification, nous pouvons utiliser les mthodes notify() ou notifyAll(), qui appartiennent galement la classe Object. La mthode notify() notifie arbitrairement un des threads en attente sur l'objet en question. Comme nous avons deux threads, nous pourrions utiliser deux fois de suite cette mthode. Nous pourrions galement tester le nombre de threads et utiliser une boucle pour invoquer notify() autant de fois que ncessaire. Cependant, il est plus simple d'utiliser notifyAll(), qui notifie tous les threads en attente sur l'objet en question. Lorsque cette mthode est excute, tous les threads ainsi sortis de leur sommeil se battent comme des chiffonniers pour prendre le contrle de l'objet sur lequel ils taient en attente (celui dont la mthode wait() a t invoque). Ce que nous voulons dire par l, c'est qu'il n'y a aucun moyen (simple) de dterminer quel thread reprendra son excution le premier.

Attention : Cela ne signifie pas que l'ordre de reprise de contrle soit


indtermin. Il est parfaitement dtermin pour la plupart des implmentations de JVM. Simplement, vous ne pouvez pas tre sr que l'ordre obtenu avec une JVM sera identique celui obtenu avec une autre. Par ailleurs, avec une mme JVM, il n'est pas impossible que des conditions d'excution modifies produisent un ordre diffrent. Cela dit, toutes les expriences que nous avons pu raliser ont montr que le premier arrt tait le premier reprendre le contrle. Le listing suivant montre le programme complet :
public class Impression7 extends Thread { Impression7(String s) { super(s); } static Imprimante imprimante; String[] pages; public void run() { synchronized(imprimante) {

688

LE DVELOPPEUR JAVA 2
pages = new String[10]; for (int i = 0; i < pages.length; i++) pages[i] = "Page " + (i+1) + " du " + getName(); imprimante.spool(pages); } } public static void main (String[] args) { imprimante = new Imprimante(); new Impression7("premier document").start(); new Impression7("deuxieme document").start(); } } class Materiel { } class Imprimante extends Materiel implements Runnable { String[] spooler = new String[100]; int index = 0; boolean pagesEnAttente = false, runnable = false; Thread imprimanteThread; Imprimante() { imprimanteThread = new Thread(Imprimante.this); imprimanteThread.setDaemon(true); imprimanteThread.start(); } public synchronized void imprime() { System.out.println("Page de garde : debut d'impression"); int j = 0; for (int i = 0; i < spooler.length; i++) { if (spooler[i] != null) { System.out.println(spooler[i]); j++; spooler[i] = null; } }

CHAPITRE 16 EXCUTER PLUSIEURS PROCESSUS SIMULTANMENT

689

System.out.println("Fin d'impression. " + j + " pages imprimees."); pagesEnAttente = false; notifyAll(); } public synchronized void spool(String[] s) { for (int i = 0; i < s.length; i++) spooler[index++] = s[i]; pagesEnAttente = true; try { wait(); } catch(InterruptedException e) { } } public void run() { imprimanteThread.setPriority(imprimanteThread.MIN_PRIORITY); while (true) { if (pagesEnAttente) imprime(); } } }

Rsum
Nous avons vu dans ce chapitre l'essentiel de ce qui concerne l'utilisation des threads. Les threads sont d'une utilisation trs simple, condition de bien matriser les problmes de synchronisation. Il est particulirement important, si vous dveloppez des applications devant faire l'objet d'une distribution publique, de bien faire la part de ce qui est du domaine fonctionnel de Java et de ce qui ressort des idiosyncrasies d'une JVM particulire. Ne supposez jamais qu'un thread s'excutera avant un autre simplement

690

LE DVELOPPEUR JAVA 2
parce que c'est logique ou parce que vous l'avez observ dans un environnement donn. Vous ne pouvez mme pas supposer que deux threads identiques se termineront dans l'ordre o ils ont t lancs. Si plusieurs threads doivent tre synchroniss, vous devez toujours le faire explicitement.

Exercice
Si vous avez encore du courage et souhaitez rviser les collections, vous pouvez essayer de modifier notre dernier programme pour que l'impression commence ds que la premire page est spoole. C'est un problme intressant, car, dans ce cas, vous ne pouvez synchroniser l'imprimante, celle-ci devant tre mme de recevoir les pages spooles tout en imprimant les prcdentes. (Pour que le problme soit raliste, vous devrez ajouter un dlai entre l'impression de chaque page afin de simuler le temps d'impression, qui doit tre suprieur au temps ncessaire pour spooler. Le temps d'impression peut mme tre alatoire pour simuler l'impression de pages plus ou moins complexes. Par ailleurs, toujours dans un souci de ralisme, la mthode spool devrait recevoir des tableaux de chanes (les documents) et les stocker dans une liste lie. Si ce problme vous semble trop complexe, vous pouvez essayer le suivant, plus simple :

crire une classe simulant une usine produisant des marchandises,


reprsentes par des nombres alatoires.

crire une classe simulant un entrept stockant ces marchandises. Lors-

que le stock atteint un niveau donn, l'entrept doit notifier l'usine qui arrte sa production. Si le stock descend au-dessous d'une certaine valeur, l'entrept notifie l'usine qui reprend sa production.

crire une classe simulant un client qui retire des marchandises de


l'entrept. Si l'entrept est vide, le client doit attendre.

CHAPITRE 16 EXCUTER PLUSIEURS PROCESSUS SIMULTANMENT

691

Instancier une fois l'usine et l'entrept et plusieurs fois le client. tablir


un mcanisme permettant de faire varier le rythme de la production en fonction du nombre de clients, de faon maintenir l'usine en fonctionnement permanent.

Chapitre 17 : RTTI et Rflexion

RTTI et rflexion

17

ANS LES CHAPITRES PRCDENTS, NOUS AVONS SOUVENT T amens effectuer des sur-castings implicites, en traitant un objet comme une instance d'une classe parente. Nous allons voir maintenant ce que cela implique du point de vue de l'interprteur Java.

Le RTTI ou comment Java vrifielessous-castingsexplicites


Java effectue automatiquement des sur-castings, par exemple lorsque nous stockons des objets dans un vecteur. En effet, un vecteur ne peut contenir que des objets. Lorsque nous plaons dans un vecteur une instance d'une

694

LE DVELOPPEUR JAVA 2
classe quelconque, elle est automatiquement sur-caste en instance de la classe Object. Considrez, par exemple, le programme suivant :
import java.util.*; public class Animaux5 { public static void main(String[] argv) { Vector zoo = new Vector(); int j; Random r = new Random(); for (int i = 0; i < 10;i++) { j = Math.abs(r.nextInt()) % 3 + 1; switch(j) { case 1: zoo.add(new Chien()); break; case 2: zoo.add(new Chat()); break; case 3: zoo.add(new Canari()); } } for (Enumeration e = zoo.elements() ; e.hasMoreElements() ;) { ((Animal)e.nextElement()).crie(); } } } interface Animal { void crier(); } class Canari implements Animal { public void crier() { System.out.println("Cui-cui !"); } }

CHAPITRE 17 RTTI ET RFLEXION


class Chien implements Animal { public void crier() { System.out.println("Ouah-Ouah !"); } } class Chat implements Animal { public void crier() { System.out.println("Miaou !"); } }

695

Ce programme cre au hasard dix instances de Chien, Chat ou Canari et les stocke dans le vecteur zoo. Une boucle for permet ensuite de les extraire une une. Une fois extraites du vecteur, nous sommes en prsence d'instances d'Object. Nous effectuons donc un sous-casting implicite en Animal afin de pouvoir invoquer la mthode crier() :
((Animal)e.nextElement()).crier();

Ce faisant, Java s'assure lors de l'excution du programme que le souscasting est possible en vrifiant le type de chaque objet. Cette vrification tant effectue pendant l'excution (et non pendant la compilation), elle est appele Run Time Type Identification ou RTTI. Pour vrifier si la vrification a lieu lors de l'excution, il suffit d'ajouter la ligne suivante :
for (int i = 0; i < 10;i++) { j = Math.abs(r.nextInt()) % 3 + 1; switch(j) { case 1: zoo.add(new Chien()); break; case 2: zoo.add(new Chat());

696
break; case 3: zoo.add(new Canari()); } }

LE DVELOPPEUR JAVA 2

zoo.add(new Object()); for (Enumeration e = zoo.elements() ; e.hasMoreElements() ;) ((Animal)e.nextElement()).crier(); } }

Le programme est alors compil sans erreur, mais son excution produit une exception :
java.lang.ClassCastException

Connatre la classe d'un objet


Ici, il nous a suffi de sous-caster les Object en Animal pour obtenir le rsultat souhait. Mais que faire si nous voulons connatre la classe exacte de chaque objet ? Pour comprendre la difficult de la chose, il faut raliser que, lorsque nous dsignons une classe dans un programme, nous ne manipulons pas rellement une classe, mais une chane de caractres qui la reprsente. Considrez le problme suivant : on souhaite demander l'utilisateur de taper un nom de classe au clavier. Si ce nom correspond une classe existante, on l'instancie. En supposant que le nom tap par l'utilisateur se trouve plac dans une chane de caractres appele s, on obtiendrait quelque chose comme :
// entre() est une mthode qui lit le nom entr au // clavier par l'utilisateur Object o;

CHAPITRE 17 RTTI ET RFLEXION


String s = entre(); . . o = new s();

697

Vous voyez le problme ? s n'est pas un nom de classe, mais un handle vers une chane contenant le nom de la classe. Certains langages possdent une instruction evaluate qui permet de remplacer une variable par sa valeur. Java utilise un autre mcanisme. A chaque classe correspond un objet de type Class. Cet objet est utilis pour toutes les manipulations des classes. Un tel objet peut tre obtenu de deux faons :

En utilisant la mthode statique forName() de la classe Class. En utilisant une classe littrale, obtenue en faisant suivre le nom de la
classe par .class. La premire manire donne, par exemple :
Class c = forName("Chien");

La deuxime manire est plus concise :


Class c = Chien.class

Grce cette technique, nous pouvons maintenant construire un tableau de classes contenant toutes les classes auxquelles un objet est susceptible d'appartenir et vrifier si une instance particulire appartient chacune de ces classes. Le programme suivant en donne un exemple :
import java.util.*; public class Animaux6 { public static void main(String[] argv) { Vector zoo = new Vector();

698

LE DVELOPPEUR JAVA 2
int j = 0, chats = 0, chiens = 0, canaris = 0; Random r = new Random(); for (int i = 0; i < 10;i++) { j = Math.abs(r.nextInt()) % 3 + 1; switch(j) { case 1: zoo.add(new Chien()); break; case 2: zoo.add(new Chat()); break; case 3: zoo.add(new Canari()); } } Class[] ct = {Chien.class, Chat.class, Canari.class}; Animal a; for (Enumeration e = zoo.elements() ; e.hasMoreElements() ;) { a = (Animal)(e.nextElement()); for (j = 0; j < ct.length; j++) { if (ct[j].isInstance(a)) break; } switch(++j) { case 1: System.out.print("Un chien : "); chiens++; break; case 2: System.out.print("Un chat : "); chats++; break; case 3: System.out.print("Un canari : "); canaris++; } a.crier(); }

CHAPITRE 17 RTTI ET RFLEXION

699

System.out.println("Total : " + chiens + " chien(s), " + chats + " chat(s) et " + canaris + " canari(s)."); } } interface Animal { void crier(); } class Canari implements Animal { public void crier() { System.out.println("Cui-cui !"); } } class Chien implements Animal { public void crier() { System.out.println("Ouah-Ouah !"); } } class Chat implements Animal { public void crier() { System.out.println("Miaou !"); } }

Le tableau de classes est cr l'aide de classes littrales :


Class[] ct = {Chien.class, Chat.class, Canari.class};

Deux boucles imbriques permettent de comparer toutes les instances a avec toutes les classes au moyen de l'instruction :
if (ct[j].isInstance(a))

700

LE DVELOPPEUR JAVA 2
Notez le nom particulier de cette mthode qui est assez trompeur. Elle s'appelle isInstance, qui signifie est instance alors que son vritable sens est plutt est la classe laquelle appartient le paramtre. Notez que nous aurions pu utiliser l'oprateur instanceof pour vrifier la classe de chaque objet, mais que cela nous aurait obligs crire explicitement le nom de chaque classe, ce qui est beaucoup moins souple. (Attention de ne pas confondre le nom de la classe, par exemple Chien, avec la classe littrale Chien.class.)

Instancier une classe l'aide de son objet Class


Jusqu'ici, nous avions instanci des classes en utilisant l'oprateur new. Nous pouvons maintenant utiliser une autre mthode. En effet, possdant un handle vers un objet de type Class, nous pouvons en crer une instance l'aide de la mthode newInstance(). Cette mthode cre une instance exactement comme si le constructeur sans argument tait utilis. Elle renvoie un Object, effectuant donc immdiatement un sur-casting. A l'aide de cette mthode, notre programme peut tre rcrit de faon plus concise :
import java.util.*; public class Animaux7 { public static void main(String[] argv) { Vector zoo = new Vector(); int j = 0, chats = 0, chiens = 0, canaris = 0; Random r = new Random(); Class[] ct = {Chien.class, Chat.class, Canari.class}; for (int i = 0; i < 10;i++) { j = Math.abs(r.nextInt()) % 3; try { zoo.add(ct[j].newInstance()); } catch(IllegalAccessException e) { e.printStackTrace(System.err); }

CHAPITRE 17 RTTI ET RFLEXION


catch(InstantiationException e) { e.printStackTrace(System.err); } }

701

Animal a; for (Enumeration e = zoo.elements() ; e.hasMoreElements() ;) { a = (Animal)(e.nextElement()); for (j = 0; j < ct.length; j++) { if (ct[j].isInstance(a)) break; }

Connatre la classe exacte d'un objet


La classe Class ne comporte pas de mthode permettant de connatre directement la classe d'un objet. En revanche, la classe Object possde la mthode getClass() qui permet d'obtenir ce rsultat. Cette mthode renvoie bien videmment un objet de type Class. Nous pouvons, dans notre programme, utiliser cette mthode pour remplacer la mthode isInstance :
for (j = 0; j < ct.length; j++) { if (a.getClass() == ct[j]) break; }

Il manque cependant une mthode importante : celle qui permettrait de sous-caster l'objet vers la classe obtenue. Si, par exemple, nos trois classes Chien, Chat et Canari possdent chacune une mthode non hrite d'une classe parente, ce que nous avons appris jusqu'ici ne nous permet pas de l'invoquer sans effectuer un sous-casting explicite. Il est videmment possible de le faire l'aide d'une srie d'instructions conditionnelles, mais cela n'est pas trs pratique. La rflexion peut alors apporter une solution.

702

LE DVELOPPEUR JAVA 2

Utiliser la rflexion pour connatrelecontenud'uneclasse


Les techniques que nous allons dcrire ici n'ont pas t prvues pour tre employes par les dveloppeurs d'applications Java, mais plutt par ceux qui doivent concevoir des mta-programmes, c'est--dire des programmes manipulant des programmes. C'est le cas, par exemple, des compilateurs ou des environnements de dveloppement.

Utilit de la rflexion
Le concepteur d'un environnement de dveloppement est amen crire un programme manipulant des composants Java (les JavaBeans). Un dveloppeur d'application peut connatre l'interface d'une classe qu'il souhaite utiliser en se rfrant la documentation fournie avec elle. En revanche, si un programme tel qu'un environnement doit utiliser un composant Java, il ne peut utiliser la mme documentation. Il aurait t possible de crer un format de documentation lisible par un programme. Les concepteurs de Java ont jug plus efficace de permettre un programme d'interroger directement un composant pour en connatre la structure. La rflexion est tout simplement la possibilit d'interroger une classe pour connatre ses membres. Cette technique peut tre employe, par exemple, pour crire un programme de dcompilation.

Attention : En dehors des cas cits, utiliser la rflexion n'est gnralement pas la meilleure solution. Comme nous l'avons dj dit plusieurs fois, le polymorphisme permet le plus souvent de rsoudre les problmes de faon plus efficace et plus lgante. C'est le cas, en particulier, des exemples prsents ici.
Imaginons que certains animaux de l'exemple prcdent soient capables de faire autre chose que crier, par exemple :
class Canari implements Animal { public void crier() {

CHAPITRE 17 RTTI ET RFLEXION


System.out.println("Cui-cui !"); } } class Chien implements Animal { public void crier() { System.out.println("Ouah-Ouah !"); } public void grogner() { System.out.println("Grrrrrrr..."); } } class Chat implements Animal { public void crier() { System.out.println("Miaou !"); } public void ronronner() { System.out.println("Rrrr...Rrrr..."); } }

703

Imaginons de plus que vous ne possdiez pas les sources de ces classes, mais seulement les classes compiles, sans documentation. Vous savez que chaque animal possde la mthode crier(). Comment savoir ce que chaque animal peut faire de plus ? En utilisant la rflexion. Ici, il ne nous servirait pas grand-chose de connatre la classe laquelle chaque objet appartient. Nous voulons seulement connatre la liste des mthodes qu'il dclare, ou plutt, sachant qu'il peut dclarer ou non une mthode public, nous voulons pouvoir savoir si c'est le cas et, si oui, l'excuter. Pour rsoudre ce problme, il nous suffit d'interroger l'objet pour connatre la liste de ces mthodes, puis comparer le dernier lment de cette liste avec le dernier lment de la liste des mthodes de la classe instancie par l'objet. Si ces deux lments dsignent la mme mthode, cela signifie que l'objet ne possde pas de mthode supplmentaire. Dans le cas contraire, nous invoquerons la dernire mthode de l'objet.

704

LE DVELOPPEUR JAVA 2
Note : Il s'agit d'un exemple simplifi. Normalement, nous devrions essayer de savoir quels sont les paramtres de la mthode. Nous supposerons ici que nous savons qu'elle n'en prend pas.
import java.util.*; import java.lang.reflect.*; public class Animaux9 { public static void main(String[] argv) { Vector zoo = new Vector(); int j = 0, chats = 0, chiens = 0, canaris = 0; Random r = new Random(); Class[] ct = {Chien.class, Chat.class, Canari.class}; for (int i = 0; i < 10;i++) { j = Math.abs(r.nextInt()) % 3; try { zoo.add(ct[j].newInstance()); } catch(IllegalAccessException e) { e.printStackTrace(System.err); } catch(InstantiationException e) { e.printStackTrace(System.err); } } Animal a; for (Enumeration e = zoo.elements() ; e.hasMoreElements() ;) { a = (Animal)(e.nextElement()); for (j = 0; j < ct.length; j++) { if (a.getClass() == ct[j]) break; } switch(++j) { case 1: System.out.print("Un chien : "); chiens++; break; case 2: System.out.print("Un chat : ");

CHAPITRE 17 RTTI ET RFLEXION


chats++; break; case 3: System.out.print("Un canari : "); canaris++; } a.crier(); try { Method[] ma = a.getClass().getMethods(); Method[] mAnimal = Animal.class.getMethods(); Method m1 = ma[ma.length - 1]; Method m2 = mAnimal[mAnimal.length - 1]; if (!m1.getName().equals(m2.getName())) m1.invoke(a, new Object[0]); } catch(IllegalAccessException se) { System.out.println(se); } catch(InvocationTargetException se) { System.out.println(se); }

705

} System.out.println("Total : " + chiens + " chien(s), " + chats + " chat(s) et " + canaris + " canari(s)."); } } interface Animal { void crier(); } class Canari implements Animal { public void crier() { System.out.println("Cui-cui !"); } } class Chien implements Animal { public void crier() { System.out.println("Ouah-Ouah !"); }

706

LE DVELOPPEUR JAVA 2
public void grogner() { System.out.println("Grrrrrrr..."); } } class Chat implements Animal { public void crier() { System.out.println("Miaou !"); } public void ronronner() { System.out.println("Rrrr...Rrrr..."); } }

La partie importante est celle imprime en gras. Elle construit tout d'abord deux tableaux, ma et mAnimal, contenant la liste des mthodes publiques de la classe Animal (en fait, une interface) et de la classe de l'objet a :
Method[] ma = a.getClass().getMethods(); Method[] mAnimal = Animal.class.getMethods();

Nous crons ensuite deux objets de type Method, appels m1 et m2, pour manipuler facilement le dernier lment de chacun des tableaux. Cette tape n'est l que pour la lisibilit du programme :
Method m1 = ma[ma.length - 1]; Method m2 = mAnimal[mAnimal.length - 1];

Si les noms de ces deux mthodes sont diffrents, la dernire mthode de l'objet a est invoque :
if (!m1.getName().equals(m2.getName())) m1.invoke(a, new Object[0]);

CHAPITRE 17 RTTI ET RFLEXION


La syntaxe utilise pour invoquer une mthode est :
mthode.invoke(objet, tableau_de_paramtres)

707

o mthode est un objet de type Method, objet l'objet dont la mthode est invoque et tableau_de_paramtres un tableau d'objets correspondant aux paramtres de la mthode. Ici, la mthode ne prend pas de paramtres, mais nous devons tout de mme lui passer un tableau de longueur nulle.

Attention : Nous comparons les noms des mthodes, ce que nous aurions pu faire sous la forme :
if (!(m1.getName() == m2.getName())

En revanche, il n'est pas possible de comparer directement les mthodes de la faon suivante :
if (!(m1 == m2)

car m1 et m2 sont forcment des handles pointant vers des objets diffrents. En effet, les tableaux ma et mAnimal ne contiennent pas les mthodes de l'objet a et de la classe Animal, mais des objets de type mthodes reprsentant ces mthodes et crs l'aide de getMethod(). Il faut tre bien conscient de cette diffrence. Tout comme un handle n'est pas l'objet qu'il reprsente, une instance de Method reprsente une mthode mais n'est pas une mthode. m1 et m2 ne sont pas des mthodes et ne reprsentent jamais la mme mthode, ce qui fait deux raisons suffisantes pour que l'galit :
m1 == m2

ne soit jamais vrifie. m1 reprsente la dernire mthode de la classe de l'objet a, c'est--dire par exemple :
public void Canari.crier()

708

LE DVELOPPEUR JAVA 2
si l'objet est une instance de Canari, alors que m2 reprsente la dernire mthode de la classe Animal, c'est--dire :
public abstract void crier()

car Animal est une interface. (Toutes les mthodes d'une interface sont implicitement abstract.) De plus, considrez les lignes suivantes :
Method[] ma = a.getClass().getMethods(); Method[] mAnimal = a.getClass().getMethods(); Method m1 = ma[ma.length - 1]; Method m2 = mAnimal[mAnimal.length - 1]; System.out.println(m1); System.out.println(m2); System.out.println(m1 == m2);

Si a est une instance de Chien, le rsultat affich par ces lignes sera :
public void Canari.crier() public void Canari.crier() false

Bien que les objets dsigns par m1 et m2 reprsentent ici la mme mthode (contrairement au cas prcdent), il s'agit tout de mme de deux objets diffrents. L'galit renvoie donc la valeur false.

Utiliser la rflexion pour manipuler une classe interne


Au Chapitre 12, nous avions voqu un problme qui pouvait tre rsolu l'aide de la rflexion. Voici maintenant le listing du programme mettant en uvre cette solution :

CHAPITRE 17 RTTI ET RFLEXION


import java.lang.reflect.*; public class Reflexion { private Interne7 o; Reflexion() { o = new Interne7(); } public static void main (String[] args) { Reflexion i = new Reflexion(); i.o.afficheCarr(5); i.o.afficheCube(5); } } class Interne7 { void afficheCarr(final int x) { class Local { public long result() { return x * x; } } print(new Local()); } void afficheCube(final int x) { class Local { public long result() { return x * x * x; } } print(new Local()); }

709

void print(Object o) { try { Method m = o.getClass().getMethod("result", new Class[0]); System.out.println(m.invoke(o, new Object[0])); }

710
} }

LE DVELOPPEUR JAVA 2
catch(Exception e) {System.out.println(e);}

Ici, la RTTI ne peut tre utilise car le compilateur refuse l'utilisation des noms de classes internes du type Interne7$1$Local. Ces noms peuvent tre employs uniquement dans la signature de la mthode, mais pas dans le corps de celle-ci. La rflexion permet de rsoudre le problme. Il va de soi que cet exemple n'a d'autre intrt que celui de la dmonstration. La seule solution logique consiste exploiter le polymorphisme en faisant driver les classes locales d'une interface comportant une mthode print, comme nous l'avons fait au Chapitre 12.

Utiliser la rflexion pour crer des instances


Nous avons vu que la mthode newInstance() permet de crer une instance d'un objet exactement comme avec l'oprateur new, en appelant le constructeur sans argument. La rflexion permet d'aller plus loin en invoquant n'importe quel constructeur avec les paramtres convenables. Les paramtres doivent simplement tre placs dans un tableau d'Object. La mthode getConstructor() renvoie un tableau contenant tous les constructeurs de la classe. La mthode getConstructor(Class[] type) renvoie le constructeur ayant pour signature les types correspondant aux lments du tableau t y p e . La classe C o n s t r u c t o r possde une mthode newInstance(Object[] initargs) qui permet d'invoquer un constructeur en lui passant ses paramtres sous forme d'un tableau d'objets. (Ne confondez pas type, qui est un tableau de Class et initargs, qui est un tableau d'objets.)

Conclusion : quand utiliser la rflexion ?


Nous avons dit que, dans la plupart des cas, il valait mieux utiliser le polymorphisme que de mettre en uvre la rflexion, sauf lorsque l'obtention

CHAPITRE 17 RTTI ET RFLEXION

711

d'information concernant une classe est l'objet principal du programme, comme dans le cas d'un dcompilateur ou lorsque vous utilisez des classes non documentes et dont les sources ne sont pas disponibles. Il est toutefois une autre situation o la rflexion peut tre utile. Si vous avez cr un certain nombre de classes que vous avez diffuses publiquement, vous pouvez tre amen ajouter des fonctionnalits sans vouloir modifier les classes existantes pour, par exemple, leur faire implmenter une interface particulire. Dans ce cas, la rflexion n'est peut-tre pas la solution la plus lgante, mais c'est parfois la seule qui vous permette d'tendre une application sans imposer une mise jour des classes existantes tous vos utilisateurs. Ce type de patch (Rustine ou pice) n'est peut-tre pas toujours d'une grande lgance, mais il rend tout de mme bien des services.

Chapitre 18 : Fentres et boutons

Fentres et boutons
J PRS DE SEPT CENT PAGES ET TOUJOURS PAS LA MOINDRE fentre, pas le plus petit bouton, aucun menu ni aucune liste droulante ! Vous vous demandez peut-tre si seulement Java dispose de ces lments. Java dispose de tous ces lments et de bien d'autres qui permettent de construire des interfaces utilisateur trs performantes. Il se trouve simplement que leur utilisation ncessite de bien comprendre les mcanismes du langage. Voil pourquoi leur tude a t mise de ct jusqu'ici, alors qu'il s'agit probablement d'un des sujets qui intressent le plus les utilisateurs. Java va beaucoup plus loin que la plupart des langages dans ce domaine, en proposant un choix de plusieurs interfaces utilisateur pouvant tre slectionnes pendant l'excution d'un programme. Un utilisateur peut ainsi choisir une interface semblable celle d'un PC sous Windows, d'un Macintosh, d'une station UNIX sous Motif, ou encore une interface spciale

18

714

LE DVELOPPEUR JAVA 2
dveloppe exclusivement pour Java. Cela est possible parce que les concepteurs de Java ont dissoci l'aspect fonctionnel de l'interface de son aspect graphique, encore appel look and feel. Il est aussi possible de choisir une interface de Macintosh sur un PC ou inversement. Ce choix peut mme tre fait pendant l'excution du programme. En tant que programmeur, vous pouvez laisser ou non l'utilisateur choisir son interface, ou faire en sorte que votre programme se conforme automatiquement l'interface du systme sur lequel il est utilis, ou encore qu'il ait la mme interface sur tous les systmes. Toutes ces possibilits reposent simplement sur le fait que les lments de l'interface (les boutons, les fentres, les barres de dfilement, etc.) sont crits en Java et non emprunts au systme ( quelques exceptions prs, comme nous allons le voir).

Les composants lourds et les composants lgers


En Java, les lments de l'interface utilisateur sont des composants, c'est-dire des objets drivant de la classe Component. Il existe deux sortes de composants : les composants lourds et les composants lgers.

Les composants lourds


Les composants lourds sont ceux dont l'implmentation dpend du systme. Dans la plupart des langages, l'interface utilisateur ne comporte aucun de ces composants. L'utilisation de fentres ou de boutons se fait grce des appels aux fonctions du systme hte. Dans ce cas, un programme ne peut fonctionner que sur un seul systme, mme si le langage existe sur un autre systme. Ainsi, un programme crit en C pour Windows ne pourra pas tre compil pour un Macintosh car les fonctions de ces deux systmes, bien que trs proches au plan fonctionnel, n'ont pas la mme syntaxe. Ce programme dj non compatible sous forme compile ne l'est donc pas non plus sous forme de source. Certains langages proposent leurs propres fonctions s'interfaant avec celles du systme. Ainsi, l'utilisation d'un bouton se fera l'aide d'une syntaxe

CHAPITRE 18 FENTRES ET BOUTONS

715

propre au langage que le compilateur traduira en un appel au systme. Le programme pourra tre compatible au niveau source si le programmeur (ou le compilateur) se restreint aux fonctions communes aux deux langages. En Java, quelques composants sont de ce type. Cela est indispensable car un programme doit bien, d'une faon ou d'une autre, communiquer avec le systme. La diffrence est qu'en Java, le nombre de composants de ce type est extrmement rduit. La fentre est un des composants qui, par nature, doivent dpendre du systme. Les composants contenus dans une fentre n'ont pas besoin de communiquer avec le systme. Il leur suffit de communiquer avec la fentre. Cela est galement vrai pour les fentres secondaires cres l'intrieur d'une autre fentre. Depuis le dbut de ce livre, nous avons dit et rpt que tout, en Java, tait objet. Comment un composant dpendant du systme peut-il tre un objet standard de Java ? Une solution consiste doter cet objet de mthode native, c'est--dire dpendante du systme. Dans ce cas, l'objet en question sera diffrent selon le systme, ce qui pose de nombreux problmes. La solution employe par Java est un peu diffrente. Le composant est identique pour tous les systmes, mais il est associ un lment dpendant du systme et appel peer. Grce ce procd, la communication entre le composant et le systme est rduite au minimum. Un tel composant est dit lourd.

Les composants lgers


Les composants lgers sont des composants entirement crits en Java et donc identiques sur tous les systmes. La plupart des composants d'interface utilisateur sont de ce type. Cela a une implication directe importante. Un bouton, par exemple, est un composant lger. Il n'est donc pas dessin par le systme, mais par Java. Par consquent, un programme fonctionnant sur un Macintosh peut parfaitement dessiner un bouton Windows. Le programme peut mme tre dot d'un menu transformant immdiatement l'interface sans avoir redmarrer le programme. Par ailleurs, si, en tant que programmeur, vous n'tes pas satisfait des interfaces proposes, vous pouvez parfaitement crer la vtre. Vous n'avez mme pas vous proccuper de l'aspect fonctionnel de l'interface, mais seulement de crer un nouvel aspect graphique. Vous pouvez cependant parfaitement crer de nouveaux

716

LE DVELOPPEUR JAVA 2
composants si vous avez des ides originales sur les fonctionnalits que devrait offrir une interface utilisateur.

Le look & feel


La modification de l'aspect de l'interface se fait l'aide des pluggable look and feel. Java est livr avec trois look and feel diffrents :

Windows Motif Metal


Le look Motif est celui employ sur de trs nombreuses stations de travail UNIX. Le look Metal a t cr spcialement pour Java. La Figure 18.1 montre les diffrents look & feel.

Motif

Windows

Metal
Figure 18.1 : Les diffrents look & feel.

CHAPITRE 18 FENTRES ET BOUTONS

717

Note : Java possde aussi tous les lments de l'interface utilisateur sous
forme de composants lourds. Nous ne nous y intresserons pas dans ce livre.

Les fentres
L'lment le plus important dans une interface utilisateur fentre est... la fentre. Il y a plusieurs sortes de fentres : celle cre par le systme pour le programme Java, et les autres, cres par Java l'intrieur de la premire. La fentre cre par le systme est obligatoirement un composant lourd. Java dispose de deux composants principaux de ce type : Window et Applet. Le premier est utilis pour les applications fonctionnant localement. Le second est employ pour les programmes fonctionnant par l'intermdiaire d'un rseau et auxquels de nombreuses restrictions sont imposes pour des raisons de scurit.

Hirarchie des fentres Java


La Figure 18.2 montre la hirarchie des fentres Java.

Window
lourd

Applet
lourd

hritage

java.awt

Frame
lourd

Dialog
lourd

hritage

hritage

composition

JFrame
lourd

JDialog
lourd

JWindow
lourd

JApplet
lourd

JInternalFrame
lger

composition

javax.swing

JRootPane
lger

Figure 18.2 : La hirarchie des fentre Java.

718

LE DVELOPPEUR JAVA 2
Outre les classes Window et Applet que nous avons dj mentionnes, il existe deux autres types de fentres implments sous forme de composants lourds et drivs de la classe Window : Frame et Dialog. Dialog sert produire les fentres de type bote de dialogue, qui ont un comportement particulier. Frame est une classe servant crer des fentres pourvues d'une barre de titre, de bordures redimensionnables, et des contrles habituels tels les cases de fermeture et d'agrandissement, ou le menu systme. Les instances de la classe Window sont simplement des rectangles uniformes sans aucun lment. Tous ces composants appartiennent au package java.awt, auquel appartiennent galement les versions lourdes des autres composants tels que boutons, menus droulants, etc. A chacune des quatre classes du package java.awt correspond une classe du package javax.swing, portant le mme nom prcd d'un J. Il s'agit galement de composants lourds. Ce sont d'ailleurs les seuls de ce type dans ce package, qui contient tous les composants ncessaires pour crer une interface. Ce package est gnralement dsign par les termes de Bibliothque Swing, ou Package Swing. Il existe par ailleurs un cinquime type de fentre, JInternalFrame, qui est un composant lger, et dont les instances sont des fentres contenues dans d'autres fentres. Toutes ces fentres ont certaines caractristiques en commun. En particulier, elles contiennent un composant de type JRootPane. C'est ce composant qui recevra tous les lments constituant le contenu de la fentre.

Structure d'une fentre


La Figure 18.3 montre la structure d'une fentre. Notez bien qu'il ne s'agit plus ici d'hritage, mais de composition : la classe JFrame, par exemple, hrite de la classe Frame qui hrite elle-mme de la classe Window. En revanche, la classe JFrame contient un lment de type JRootPane. La composition est reprsente sur les figures par un filet gras gris. La JRootPane contient une glassPane et une layeredPane. JRootPane correspond une classe de com.java.swing. Il s'agit donc d'un type de composant particulier. layeredPane et glassPane sont des concepts implments par la classe JLayeredPane pour le premier, et Component pour le se-

CHAPITRE 18 FENTRES ET BOUTONS

719

JRootPane

glassPane Component

layeredPane JLayeredPane

contentPane Component JMenuBar (Optionnel)

Figure 18.3 : Schma de la structure dune fentre.

cond. La glassPane peut donc tre n'importe quel type de composant (instance de la classe Component ou d'une de ses classes drives). La layeredPane contient son tour une contentPane, qui peut tre un composant quelconque, et une barre de menus optionnelle, instance de la classe JMenuBar. Ces lments sont disposs de la faon indique dans la Figure 18.4.

glassPane

layeredPane menuBar JRootPane

contentPane
Figure 18.4 : Disposition des lments dune fentre.

720

LE DVELOPPEUR JAVA 2
La glassPane se trouve au-dessus de tous les autres lments. C'est elle qui reoit les clic de souris. S'agissant d'un composant quelconque, il est possible de la choisir de faon pouvoir y afficher des objets divers ou des images, ou encore y dessiner. Elle est particulirement utile si on souhaite placer un objet ou un dessin au-dessus d'un autre composant sans tre limit par les bordures de celui-ci. Sur la Figure 18.5, un dessin est plac dans un composant situ sur la contentPane. Il est donc limit par la taille de ce composant. Au contraire, la Figure 18.6 montre le mme dessin plac sur la glassPane, il peut dborder du cadre du composant plac sur la contentPane

menuBar glassPane layeredPane JRootPane

contentPane

Component

Figure 18.5 : Un dessin plac dans un composant situ sur la contentPane est limit par la taille de celui-ci.

menuBar glassPane layeredPane JRootPane

contentPane

Component

Figure 18.6 : La taille dun dessin plac sur la glassPane nest pas limite.

CHAPITRE 18 FENTRES ET BOUTONS

721

Par dfaut, la glassPane n'est pas visible. Elle doit tre rendue visible explicitement si ncessaire. Ses limites sont identiques celles de la JRootPane. Les limites de la layeredPane sont galement identiques celles de la JRootPane. Elle peut contenir un nombre quelconque de composants organiss en couches. Un (et un seul) de ces composants peut tre une barre de menus. Il est plac en haut de la layeredPane. La contentPane occupe toute la surface de la layeredPane moins, ventuellement, la surface occupe par la barre de menus.

Les layout managers


Tous les composants qui peuvent contenir d'autres composants font appel un layout manager pour organiser la disposition de ceux-ci. La disposition qui vient d'tre dcrite est contrle par le layout manager de la JRootPane. Le programmeur qui souhaite organiser la disposition des lments quil ajoute l'interface doit ventuellement modifier le layout manager de la contentPane et non celui de la JRootPane. Java propose plusieurs layout managers en standard pour la contentPane. Vous pouvez en crer d'autres. En revanche, il n'existe qu'un seul layout manager pour la JRootPane. Si vous ne souhaitez pas l'utiliser, vous devrez obligatoirement en crer un nouveau (si vous souhaitez, par exemple, une barre de menus au bas de l'cran). Nous n'aborderons pas ici la programmation des layout managers. Nous montrerons comment les utiliser, et ventuellement sen affranchir lorsquils sont trop contraignants.

Crer une application fentre


Le programme suivant montre la faon la plus simple de crer une application fentre :
import javax.swing.*; public class Window1 { static JFrame maFentre;

722

LE DVELOPPEUR JAVA 2
public static void main( String[] args ) { maFentre = new JFrame("Ma fentre"); } }

Si vous compilez et excutez ce programme, vous ne verrez rien du tout. En effet, vous avez cr une instance de la classe JFrame, mais, par dfaut, ces instances ne sont pas visibles. Vous remarquerez, en revanche, que si votre programme semble ne rien faire, il ne rend pas la main pour autant. En effet, un thread est en fonctionnement, qui attend les vnements qui pourraient se produire pour cette fentre. La seule faon de l'interrompre proprement est d'intercepter un de ces vnements et de l'associer une instruction arrtant le programme. (En attendant, vous pouvez taper CTRL + C.) En premier lieu, il faut que la fentre soit visible. Pour cela, il nous faut utiliser la mthode setVisible. D'autre part, nous donnerons la fentre une position et une taille plus pratiques, grce la mthode setBounds qui permet de dterminer les coordonnes de l'angle suprieur gauche de la fentre, ainsi que ses dimensions :

import javax.swing.*; public class Window1 { static JFrame maFentre; public static void main( String[] args ) { maFentre = new JFrame("Ma fentre"); maFentre.setBounds(100, 100, 300, 200); maFentre.setVisible(true); } }

Si vous lancez le programme ainsi modifi, vous obtenez une fentre de dimensions 300 x 200. Vous pouvez agrandir et manipuler cette fentre comme n'importe quelle autre fentre d'application. En effet, les vnements reus par la fentre sont traits automatiquement en ce qui concerne la fentre elle-mme. En revanche, ils ne produisent aucun autre effet. En particulier, cliquer dans la case de fermeture (ou, sous Windows, slection-

CHAPITRE 18 FENTRES ET BOUTONS

723

ner Fermer dans le menu Systme) entrane bien la fermeture de la fentre, mais ne termine pas le programme pour autant. Pour obtenir cet effet, il nous faut intercepter l'vnement et lui associer une instruction permettant d'obtenir le rsultat souhait.

Quel vnement intercepter ?


Il pourrait sembler vident que l'vnement intercepter soit un clic de souris. Il faudrait alors dtecter l'endroit o l'utilisateur a cliqu et, s'il s'agit de la case de fermeture, quitter le systme. Cette faon de procder comporte de nombreux inconvnients :

Tout d'abord, elle est impossible car la case de fermeture n'est pas un

objet Java. Elle fait partie des attributs de la fentre et nous n'y avons pas accs. Cette raison, elle seule, est videmment suffisante, mais il y en a d'autres.

S'il tait possible d'intercepter le clic dans la fentre, il faudrait traiter


tous les cas possibles, c'est--dire ceux o le clic a lieu en dehors de la case de fermeture.

Cette solution ne fonctionnerait pas lorsque l'utilisateur slectionne la


commande du menu systme, ni son quivalent clavier (ALT + F4 sous Windows). Pour ces raisons, il est prfrable de laisser le systme s'occuper de la fermeture de la fentre. En revanche, nous pouvons intercepter l'vnement fermeture de la fentre. De cette faon, tous les contrles standard continueront de fonctionner sans que nous ayons nous en proccuper. spcifique. Comme de nombreux autres composants, JFrame a un quivalent dans le package java.awt, nomm Frame. Si vous tes amen utiliser cette classe, vous verrez que le fait de cliquer dans la case de fermeture d'une instance de Frame provoque bien l'invocation de windowClosing mais ne ferme pas la fentre. La raison cela est que JFrame drive de Frame mais dfinit la mthode setDefaultCloseOperation(int operation) qui dtermine son comportement lorsque l'on clique dans sa case de fermeture.

Attention : Le comportement dcrit ici pour une instance de JFrame est

724

LE DVELOPPEUR JAVA 2
Les paramtres possibles sont dfinis dans l'interface WindowConstants, implmente par JFrame :

DISPOSE_ON_CLOSE entrane la fermeture de la fentre. HIDE_ON_CLOSE entrane le masquage de la fentre. DO_NOTHING_ON_CLOSE ne fait rien, ce qui donne une JFrame le mme
comportement qu'une Frame. La valeur par dfaut est HIDE_ON_CLOSE.

Intercepter les vnements de fentre


Nous avons dit maintes fois qu'en Java, tout tait objet. Les vnements sont donc des objets. L'vnement fermeture de la fentre est un objet qui est envoy, un peu comme l'taient les exceptions (un peu seulement). Chaque fois qu'une fentre est ferme, active, icnifie, etc., un vnement est cr et envoy. Chaque fois que la souris est dplace ou qu'un de ses boutons est cliqu, un vnement est cr et envoy. Chaque fois qu'un article de menu est slectionn, qu'une case est coche, qu'une bande de dfilement est actionne, que le texte d'une zone de texte est modifi, un vnement est cr et envoy. Les vnements ainsi envoys doivent tre reus pour tre traits. C'est la fonction des listeners, ce qui signifie littralement couteurs. Pour que l'vnement correspondant la fermeture d'une fentre soit reu (afin d'tre trait), il faut que cette fentre possde un listener correspondant ce type d'vnement. Il existe un type de listener pour chaque type d'vnement. Un listener est simplement un objet qui contient du code qui est excut lorsque l'objet reoit un vnement.

Les vnements et les listeners


Les vnements sont des instances de classes Java dont les noms se terminent par Event. Ces classes sont rparties dans deux packages :

CHAPITRE 18 FENTRES ET BOUTONS

725

java.awt.event javax.swing.event
La raison de cette rpartition en deux packages est purement historique. La version 1.1 de Java ne possdait que le package java.awt.event. Lorsque les composants Swing ont t ajouts (soit comme une extension de la version 1.1, soit comme partie intgrante de la version 2), il a fallu crer les vnements correspondant aux nouveaux types de contrles. En revanche, un bouton Swing envoie le mme vnement que les anciens boutons du package java.awt, qui sont d'ailleurs toujours prsents. (Les anciens boutons sont des composants lourds, alors que les composants Swing sont tous des composants lgers, l'exception des quatre que nous avons mentionns prcdemment.) Si vous consultez la documentation du JDK, vous constaterez que le package java.awt.event contient quatorze vnements :

ActionEvent AdjustmentEvent ComponentEvent ContainerEvent FocusEvent InputEvent InputMethodEvent


et treize listeners :

InvocationEvent ItemEvent KeyEvent MouseEvent PaintEvent TextEvent WindowEvent ContainerListener EventQueueListener FocusListener

ActionListener AdjustmentListener ComponentListener

726

LE DVELOPPEUR JAVA 2

InputMethodListener ItemListener KeyListener MouseListener

MouseMotionListener TextListener WindowListener

De son ct, le package javax.swing.event contient vingt vnements :

DocumentEvent DocumentEvent.ElementChange AncestorEvent CaretEvent ChangeEvent EventListenerList HyperlinkEvent InternalFrameEvent ListDataEvent ListSelectionEvent
et vingt et un listeners :

MenuDragMouseEvent MenuEvent MenuKeyEvent PopupMenuEvent TableColumnModelEvent TableModelEvent TreeExpansionEvent TreeModelEvent TreeSelectionEvent UndoableEditEvent

AncestorListener CaretListener CellEditorListener

ChangeListener DocumentListener HyperlinkListener

CHAPITRE 18 FENTRES ET BOUTONS

727

InternalFrameListener ListDataListener ListSelectionListener MenuDragMouseListener MenuKeyListener MenuListener MouseInputListener PopupMenuListener

TableColumnModelListener TableModelListener TreeExpansionListener TreeModelListener TreeSelectionListener TreeWillExpandListener UndoableEditListener

Nous souhaitons traiter la fermeture d'une fentre. Nous nous intresserons donc aux vnements de type WindowEvent et aux WindowListener. En consultant la documentation, nous constatons que WindowListener est une interface. Cela a t conu ainsi pour permettre un objet quelconque d'tre un WindowListener. Il est ainsi devenu d'usage courant qu'une classe comportant une fentre soit galement WindowListener. La fentre peut mme tre son propre WindowListener. Cette pratique n'est pourtant pas trs lgante et il semble nettement prfrable d'utiliser un objet spcifiquement conu pour cela. Il y a en tout cas une excellente raison cela. L'interface WindowListener possde en effet sept mthodes. Si une classe implmente cette interface, elle doit redfinir les sept mthodes (sous peine d'tre implicitement abstract). En revanche, Java dispose galement d'une classe appele WindowAdapter qui implmente l'interface WindowListener et redfinit les six mthodes comme ne faisant rien. En tendant cette classe, nous n'avons redfinir que la mthode qui nous intresse. titre informatif, nous montrerons quatre faons de rsoudre le problme. Dans le premier cas, la fentre sera son propre WindowListener. Dans le deuxime, la classe contenant la mthode main sera un WindowListener. Dans le troisime cas, nous utiliserons une classe que nous crerons spcialement pour cette fonction. Enfin, le quatrime exemple utilisera une classe interne.

728
import javax.swing.*; import java.awt.event.*; public class Window1 { static MaJFrame maFentre;

LE DVELOPPEUR JAVA 2

public static void main( String[] args ) { maFentre = new MaJFrame("Ma fentre"); maFentre.addWindowListener(maFentre); maFentre.setBounds(100, 100, 300, 200); maFentre.setVisible(true); } } class MaJFrame extends JFrame implements WindowListener { MaJFrame(String s) { super(s); } public void windowClosing(WindowEvent e) { System.exit(0); } public public public public public public } void void void void void void windowActivated(WindowEvent e) {} windowClosed(WindowEvent e) {} windowDeactivated(WindowEvent e) {} windowDeiconified(WindowEvent e) {} windowIconified(WindowEvent e) {} windowOpened(WindowEvent e) {}

Dans ce programme, les seules modifications apportes la mthode main sont le remplacement de JFrame par MaJFrame et l'ajout de la ligne :
maFentre.addWindowListener(maFentre);

La mthode addWindowListener prend pour argument un objet de type WindowListener et l'attribue l'objet sur lequel la mthode est invoque.

CHAPITRE 18 FENTRES ET BOUTONS

729

Ici, l'objet WindowListener se trouve tre la fentre elle-mme, ce qui ne pose pas de problme. Bien entendu, il a fallu remplacer le type JFrame par une nouvelle classe tendant J F r a m e et implmentant l'interface WindowListener. La classe MaJFrame, pour implmenter l'interface WindowListener, doit redfinir les sept mthodes. Seule la mthode windowClosing est utilise et contient donc une dfinition. Lorsque l'on ferme la fentre par un moyen quelconque, un objet de type est envoy la fentre. Si celle-ci possde un WindowListener, celui-ci reoit l'objet et excute la mthode correspondant au type d'vnement. Cela prsente un avantage certain : bien que l'vnement reu soit simplement de type WindowEvent, vous n'avez pas dterminer de quel vnement exact il s'agit ! La mthode windowClosing(WindowEvent e) n'a donc rien faire du paramtre qui lui est pass ! Elle se contente de terminer le programme l'aide de l'instruction :
WindowEvent

System.exit(0);

Une autre faon d'crire ce programme est de faire implmenter l'interface WindowListener par la classe qui contient la mthode main, ce qui vite d'avoir crer une classe tendant la classe JFrame :
import javax.swing.*; import java.awt.event.*; public class Window2 implements WindowListener { static JFrame maFentre; public static void main( String[] args ) { maFentre = new JFrame("Ma fentre"); maFentre.addWindowListener(new Window2()); maFentre.setBounds(100, 100, 300, 200); maFentre.setVisible(true); }

730

LE DVELOPPEUR JAVA 2
public void windowClosing(WindowEvent e) { System.exit(0); } public public public public public public } void void void void void void windowActivated(WindowEvent e) {} windowClosed(WindowEvent e) {} windowDeactivated(WindowEvent e) {} windowDeiconified(WindowEvent e) {} windowIconified(WindowEvent e) {} windowOpened(WindowEvent e) {}

Ici, c'est la classe Window2 qui implmente WindowListener et qui doit donc redfinir les sept mthodes. La mthode addWindowListener doit prendre pour paramtre une instance de la classe Window2. La mthode main tant statique, il faut instancier spcialement cette classe. Nous verrons plus loin qu'avec les applets, dans lesquelles la fonction de la mthode main est tenue par une mthode non statique appele init, on peut faire rfrence l'instance qui contient la fentre, sous la forme :
maFentre.addWindowListener(this);

Les deux exemples prcdents ont l'inconvnient de nous obliger redfinir toutes les mthodes de l'interface WindowListener. L'exemple suivant utilise une classe cre spcifiquement pour servir de listener. L'avantage est qu'elle peut tendre la classe WindowAdapter, qui contient dj une redfinition vide des sept mthodes. Notre code sera ainsi plus concis :
import javax.swing.*; import java.awt.event.*; public class Window3 { static JFrame maFentre; public static void main( String[] args ) { maFentre = new JFrame("Ma fentre"); maFentre.addWindowListener(new MonAdapter());

CHAPITRE 18 FENTRES ET BOUTONS


maFentre.setBounds(100, 100, 300, 200); maFentre.setVisible(true); } } class MonAdapter extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); } }

731

L'inconvnient de ce code est qu'il y a peu de chances que la classe monAdapter serve autre chose. En effet, si l'application comporte d'autres fentres, il s'agira de fentres filles de la fentre principale et il est peu probable que le fait de les fermer doivent entraner l'arrt du programme. De la mme faon, un listener conu pour un bouton excutera une action spcifique ce bouton et ne sera donc instanci qu'une seule fois. Si une application comporte une trentaine de contrles diffrents, il faudra crer une trentaine de classes toutes instancies une seule fois. De plus, ici, le programme principal est trs court. Dans un cas rel, il pourrait tre beaucoup plus long. Pour savoir ce que fait un listener, il faudrait alors se reporter plusieurs pages en avant ou en arrire dans le listing. Cela n'amliore pas la lisibilit. De plus, il est frquent, pour ne pas dire plus, que le listener doive dialoguer avec l'objet qui contient le programme principal. Ici, le programme tant une mthode statique, ce n'est pas trs difficile. En revanche, c'est moins pratique avec les applets. Une solution lgante ces problmes consiste utiliser une classe interne. Cependant, nous avons toujours un problme li au fait que la mthode main est statique. Il n'est pas possible de crer une instance de classe interne sans une rfrence implicite une instance de la classe externe qui la contient. La solution pourrait consister crer une instance de la classe Window4 spcifiquement pour pouvoir instancier la classe interne, ce qui donne le listing suivant :
import javax.swing.*; import java.awt.event.*; public class Window4 { static JFrame maFentre;

732

LE DVELOPPEUR JAVA 2
public static void main( String[] args ) { maFentre = new JFrame("Ma fentre"); maFentre.addWindowListener((new Window4()).new MonAdapter()); maFentre.setBounds(100, 100, 300, 200); maFentre.setVisible(true); } class MonAdapter extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); } } }

Si vous ne comprenez pas parfaitement la syntaxe de la ligne imprime en gras, vous devriez peut-tre rviser le Chapitre 12 ! Plaisanterie mise part, ce n'est ni trs lisible, ni trs lgant. On pourrait bien sr simplifier en dclarant statique la classe interne :
import javax.swing.*; import java.awt.event.*; public class Window5 { static JFrame maFentre; public static void main( String[] args ) { maFentre = new JFrame("Ma fentre"); maFentre.addWindowListener(new MonAdapter()); maFentre.setBounds(100, 100, 300, 200); maFentre.setVisible(true); } static class MonAdapter extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); } } }

CHAPITRE 18 FENTRES ET BOUTONS

733

Une solution beaucoup plus lgante consiste utiliser une classe anonyme. De cette faon, nous n'avons plus besoin de faire rfrence la classe externe et le code du listener se trouve vraiment l'endroit o il est utilis. Cette faon de procder est de loin la plus adapte la conception du modle d'vnements de Java. C'est celle que nous utiliserons chaque fois que possible dans les exemples de la suite de ce livre :
import javax.swing.*; import java.awt.event.*; public class Window6 { static JFrame maFentre; public static void main( String[] args ) { maFentre = new JFrame("Ma fentre"); maFentre.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); }}); maFentre.setBounds(100, 100, 300, 200); maFentre.setVisible(true); } }

Utilisation des composants


Un des composants les plus simples est l'tiquette. Les tiquettes permettent simplement d'afficher du texte non modifiable par l'utilisateur. Dans l'exemple suivant, vous noterez que l'tiquette n'est pas ajoute la fentre, mais sa contentPane, obtenue au moyen de la mthode getContentPane() :
import javax.swing.*; import java.awt.event.*; public class Window7 extends JFrame { static Window7 maFentre;

734

LE DVELOPPEUR JAVA 2
Window7(String s) { super(s); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); }}); setBounds(100, 100, 300, 200); getContentPane().add(new JLabel("Une tiquette")); setVisible(true); } public static void main( String[] args ) { maFentre = new Window7("Ma fentre"); } }

La Figure 18.7 montre la fentre affiche par ce programme.

Figure 18.7 : Une tiquette ajoute la contentPane.

Dans ce programme, nous avons utilis une structure un peu diffrente. La classe principale tend la classe JFrame. La fentre est configure dans le constructeur de la classe principale. L'avantage est que nous n'avons plus besoin d'utiliser le handle de la fentre pour y faire rfrence. Nous pouvons donc crire :
setBounds(100, 100, 300, 200);

CHAPITRE 18 FENTRES ET BOUTONS


au lieu de :

735

maFentre.setBounds(100, 100, 300, 200);

La mthode main est ainsi rduite au minimum qui lui appartient, la cration d'une instance de Window7. (On pourrait videmment discuter pour savoir si l'instruction setVisible(true) appartient plus logiquement la mthode main ou au constructeur de la fentre. Il nous semble qu'il est plus frquent de crer des fentres visibles que des fentres invisibles et qu'il est donc prfrable que la fentre se rende visible elle-mme.) Dans la plupart des cas, vous utiliserez une technique mi-chemin entre les deux derniers exemples. La fentre aura une bonne dose de self-control, mais elle ne sera pas implmente dans la classe principale. L'exemple suivant, qui affiche deux tiquettes, montre cette technique :

import javax.swing.*; import java.awt.event.*; public class Window8 { static FenetrePrincipale maFentre; public static void main( String[] args ) { maFentre = new FenetrePrincipale("Ma fentre"); } static class FenetrePrincipale extends JFrame { FenetrePrincipale(String s) { super(s); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); }}); setBounds(100, 100, 300, 200); getContentPane().add(new JLabel("Une tiquette")); getContentPane().add(new JLabel("Une autre tiquette"));

736
setVisible(true); } } }

LE DVELOPPEUR JAVA 2

Indpendamment de l'utilisation d'une classe interne statique, ce programme a une particularit. En effet, si vous le compilez et l'excutez, vous constaterez qu'il n'affiche que la deuxime tiquette. Pour comprendre ce qui se passe, essayez de modifier les dimensions de la fentre. Vous constaterez que l'tiquette est toujours place gauche, au milieu de la hauteur de la fentre. La raison ce comportement trange tient dans l'utilisation systmatique par Java des layout managers.

Utilisation des layout managers


Avec un langage classique (normal ?), une tiquette ainsi affiche serait probablement positionne dans l'angle suprieur gauche de la fentre. L'quivalent s'tait d'ailleurs produit lorsque nous n'avions pas spcifi la position de la fentre : elle avait t affiche dans l'angle suprieur gauche de l'cran. A l'intrieur des fentres, tout est diffrent. Un composant ajout un conteneur (une fentre, par exemple) est pass au layout manager de ce conteneur. Le layout manager dcide de la position de chaque composant.

Philosophie des layout managers


La philosophie qui sous-tend l'utilisation des layout managers est tout fait respectable. Il en va des layout managers comme de certaines utopies philosophiques ou politiques (nous ne nommerons personne pour ne pas nous faire d'ennemis, mais chacun reconnatra les siens) : une bonne intention qui apporte beaucoup de dsillusions. L'ide est que les programmes doivent fonctionner sur tous les systmes existants (c'est dj ambitieux) et venir (l est l'utopie). Il faut donc que la disposition des lments dans

CHAPITRE 18 FENTRES ET BOUTONS

737

une fentre soit la mme (fonctionnellement la mme, ce qui ne signifie pas forcment identique physiquement), quel que soit l'affichage utilis. Or, les divers systmes d'affichage diffrent par :

leur taille, leur rsolution, le nombre de couleurs, les ressources systme disponibles.
Vous vous attendiez peut-tre ne trouver que les trois premiers lments, suivant en cela les affirmations des bons aptres de Java qui clament haut et fort que ce langage doit permettre de construire une interface indpendante des ressources du systme (du moins partir du moment o le systme dispose d'un affichage graphique). Malheureusement, pour les interfaces utilisateur, Java fait un usage intensif de certaines ressources fournies par le systme : les polices de caractres. Pour simplifier le problme au maximum, les concepteurs de Java proposent de n'utiliser que des polices gnriques correspondant au minimum vital : police avec et sans empattement, police espacement fixe et police de symbole. Cependant, mme si vous vous limitez ces polices (par exemple en ne vous proccupant jamais d'en choisir une), vous n'tes pas l'abri de problmes. En effet, les polices gnriques sont un concept et n'ont pas d'existence propre. (Cela viendra peut-tre mais, pour l'instant, il n'y a pas de polices Java.) Elles sont donc remplaces lors de l'excution du programme par les polices les plus proches physiquement prsentes sur le systme (en gnral, Times New Roman, Arial et Courier New sous Windows, Times, Helvetica et Courier sous Motif et MacOs). Les polices de caractres pouvant tre de tailles diffrentes, cela peut poser un problme sur certains systmes. Le problme de la taille et de la rsolution de l'cran est encore plus crucial. Si deux ordinateurs sont quips chacun d'un cran de 17 pouces, l'un affichant avec une rsolution de 800 x 600 points et l'autre avec une rsolution de 1600 x 1200, une fentre devra avoir une dfinition deux fois suprieure pour avoir la mme taille. (La dfinition est la taille en pixels, ou lment d'image. Un pixel correspond un point de l'affichage.)

738

LE DVELOPPEUR JAVA 2
Si l'on souhaite que l'affichage soit identique sur les deux systmes, il existe deux possibilits. La premire consiste utiliser une disposition fixe des lments avec un systme de coordonnes indpendant du systme physique, par exemple des dimensions absolues en centimtres. Cette approche ne rsout les problmes que dans la mesure o les caractristiques gomtriques de l'affichage le permettent. Une bote de dialogue de 4 centimtres par 3 sera affiche correctement sur les deux crans de 17 pouces dont nous parlions prcdemment. En revanche, une fentre de 30 centimtres par 20 ne pourra tre affiche sur un cran de 13 pouces. Cette approche est la seule qui permettrait d'atteindre le but recherch. Le programmeur devrait choisir une taille pour chaque bote de dialogue et une taille pour la police de caractres. Deux centimtres feraient toujours 2 centimtres et une police de 12 points aurait la mme taille sur tous les crans. (Il s'agit ici du point typographique, qui sert mesurer la taille des polices de caractres.) La seconde approche possible consiste ne pas indiquer la position prcise des lments, mais simplement le type de disposition souhaite. A chaque type de disposition correspond un layout manager. Si la disposition dont vous avez besoin est particulire, vous pouvez crer votre propre layout manager. Ce beau principe est cependant en partie inapplicable. Tout d'abord, la volont d'avoir un affichage indpendant des caractristiques du systme est souvent injustifiable. Si vous concevez une application multimdia consacre la peinture, vous n'aurez probablement rien faire de savoir que votre application ne fonctionne pas sur un cran de 640 x 480 points affichant 16 couleurs. Vous indiquerez simplement aux utilisateurs que la configuration minimale est, par exemple, 800 x 600 en 65 000 couleurs. Un autre cas o l'utilisation des layout managers devrait tre un avantage est celui des botes de dialogue redimensionnables. Il ne s'agit pas ici de la facult pour l'utilisateur de modifier la taille de la bote de dialogue, mais, pour le programmeur, d'afficher par exemple une bote de dialogue avec un lment centr. C'est exactement ce que fait le BorderLayout, qui est le layout manager par dfaut des JFrame. (Curieusement, le layout manager par dfaut des composants Swing est diffrent de celui de leurs quivalents non-Swing. Sans doute les concepteurs de Java se sont-ils rendu compte du peu d'efficacit de la solution choisie et essaient par l de corriger le tir.)

CHAPITRE 18 FENTRES ET BOUTONS

739

Voyons comment un programmeur traditionnel rsout le problme de l'affichage d'une bote de dialogue comportant un message (une question, par exemple) et deux boutons (oui/non). Il peut crer une fentre de taille fixe, calculer, en fonction de la longueur du message, la position de l'tiquette qui le contient, puis placer les deux boutons au-dessous du message gale distance de chaque bord, comme le montre la Figure 18.8.

Figure 18.8 : Disposition des lments de la bote de dialogue laide de coordonnes en pixels.

L'obtention d'un tel rsultat passe par un certain nombre de ttonnements. Il est obtenu en utilisant les coordonnes suivantes :

Fentre : setBounds(100, 100, 260, 100) tiquette : setBounds(65, 10, 200, 20) Bouton 1 : setBounds(30, 40, 70, 25) Bouton 2 : setBounds(150, 40, 70, 25)
Un problme est que cette bote de dialogue est totalement impossible rutiliser avec un autre message En effet, si le message est plus court, il n'est plus centr. S'il est plus long, il dpasse de la fentre. Si on agrandit la fentre, les boutons ne sont plus centrs. Un autre problme est que les valeurs indiques ne sont valables qu'en fonction de la taille des caractres utiliss. Ici, nous avons utilis la taille standard, c'est--dire que nous n'en avons spcifi aucune. La Figure 18.9 montre le rsultat obtenu sur un cran de 17 pouces en 800 x 600 (en haut) et en 1600 x 1200 (en bas).

740

LE DVELOPPEUR JAVA 2

Figure 18.9 : La mme bote de dialogue affiche sur un cran de 17 pouces en 800 x 600 (en haut) et en 1600 x 1200 (en bas)

On est loin de l'objectif initial stipulant qu'un programme doit tourner sur tous les systmes en donnant le mme rsultat. La seule solution consiste grer ce problme nous-mmes. Malheureusement, les layout managers livrs avec Java ne sont pas d'un grand secours ! La plupart des programmeurs se font leur propre bibliothque de sousprogrammes rutilisables. Pour une bote de dialogue telle que celle-ci, voici peu prs ce que devrait faire le sous-programme :

Dterminer la taille de police utiliser. Calculer la longueur physique du message. Calculer la largeur de la bote de dialogue en ajoutant une valeur fixe

ou un pourcentage la longueur du message. Si la valeur obtenue est infrieure une valeur minimale, prendre la valeur minimale. (On considre que le programmeur choisit des messages ne dpassant jamais la valeur maximale.)

Calculer la taille de l'tiquette (longueur du message + marge, hauteur


fixe).

CHAPITRE 18 FENTRES ET BOUTONS

741

Calculer la position de l'tiquette ((largeur de la fentre - longueur du


message) / 2).

Calculer la taille et la position des boutons (en fonction de la taille des


caractres et de la taille de la bote de dialogue, elle-mme dtermine par la longueur du message).

Il est parfaitement possible d'crire un sous-programme qui fonctionne dans tous les cas usuels, mais un certain nombre de problmes peuvent survenir :

La police utilise pour afficher le message est plus haute que prvu. Le
message est tronqu en hauteur. Ce problme est facile prvoir mais cela complique encore le sous-programme.

L'cran de l'utilisateur est d'excellente qualit et utilise une rsolution


trs leve. Rsultat, la bote de dialogue est minuscule.

Le programme doit tre traduit dans une langue trangre. Le message


est beaucoup plus long et dpasse la valeur maximale possible pour la largeur de la fentre.

Le programmeur a besoin d'une bote de dialogue trois boutons. Il


doit crire un autre sous-programme. On voit facilement que l'efficacit d'un tel sous-programme dpend de l'intelligence que l'on y met. Il est possible d'crire un tel sous-programme capable de traiter tous les cas possibles. C'est exactement ce qu'est suppos faire un layout manager. Pour juger de la capacit des layout managers standard, il suffit de regarder les exemples qui sont proposs dans les livres officiels sur Java. La Figure 18.10 montre un exemple de BorderLayout et un exemple de GridBagLayout. Si vous pensez vraiment avoir un jour besoin de l'une ou l'autre de ces botes de dialogue, n'hsitez pas. Il est probable que leurs auteurs ne vous demanderont pas de royalties pour vous permettre d'utiliser leur code source.

742

LE DVELOPPEUR JAVA 2

Figure 18.10 : Exemples de BorderLayout et de GridBagLayout.

Bien utiliser les layout managers


Peut-on utiliser quand mme les layout managers ? Un layout manager est un objet dot d'une certaine intelligence lui permettant de disposer au mieux des composants dans un conteneur. La tche qui revient au programmeur (ou plutt au concepteur de l'interface du programme, qui, sur les petits projets, est souvent la mme personne) est de modliser le contenu du conteneur en utilisant les termes d'un layout manager existant. Une question trs importante est la suivante : le mme layout manager doitil disposer tous les composants d'un conteneur ? La rponse est oui, car cela est impos par Java. Un mme conteneur ne peut possder qu'un seul layout manager. Ayant constat cela, certains programmeurs tentent dses-

CHAPITRE 18 FENTRES ET BOUTONS

743

prment de crer un modle de disposition exprimable, dans le meilleur des cas l'aide du layout manager qu'ils estiment le plus adapt et, dans le pire des cas, l'aide de celui qu'ils connaissent le mieux. Pour bien utiliser les layout managers, il faut comprendre qu'ils offrent chacun la traduction d'un concept essentiel en matire de disposition des composants :

Le FlowLayout implmente le concept d'une squence horizontale dont


les paramtres essentiels sont :

L'alignement ( droite, gauche, centr ou justifi). L'espacement horizontal sparant les composants. L'espacement vertical sparant les diffrentes lignes de composants. Le GridLayout implmente le concept d'une disposition en tableau
dont les paramtres essentiels sont :

Le nombre de colonnes. Le nombre de lignes. L'espace entre les colonnes. L'espace entre les lignes. Le ViewportLayout implmente le concept d'une disposition d'un seul
composant, align en bas de la surface disponible tant que celle-ci est infrieure la taille du composant et en haut lorsqu'elle est suprieure.

Le ScrollPaneLayout implmente le concept d'un composant pouvant


dfiler horizontalement et verticalement dans la surface d'affichage.

Le BorderLayout divise la zone d'affichage en cinq zones : Nord, Sud,


Est, Ouest et Centre. Ces principaux paramtres sont :

L'espacement horizontal entre les composants. L'espacement vertical entre les composants.

744

LE DVELOPPEUR JAVA 2

Le BoxLayout implmente un concept proche de celui du GridLayout


mais avec une seule dimension au lieu de deux. Il utilise un seul paramtre :

L'alignement (vertical ou horizontal). Le OverlayLayout correspond l'empilement des composants les uns
au-dessus des autres. Les autres layout managers ne sont pas fondamentaux (au sens propre du terme) en ce qu'ils expriment un concept complexe et non un concept de base. Ainsi, le CardLayout exprime le concept d'un arrangement en pages (sorte d'empilement fonctionnel). Le GridBagLayout tente d'exprimer un concept universel. Quant au JRootPane.RootLayout, il est l'exemple d'une implmentation complexe parfaitement fonctionnelle dont la tche est de grer la disposition de la layeredPane, de la glassPane et de la barre de menus d'une JRootPane. En fait, il nous faut exprimer le rsultat souhait en termes de trois fondamentaux : squence, tableau, bordures. Pour chaque cas, l'analyse peut amener plusieurs expressions quivalentes. Ainsi, notre bote de dialogue prcdente peut tre exprime l'aide d'une seule squence (Figure 18.11).

Conteneur 1 : Squence centre

Voulez-vous quitter ?
lment 1

Oui
lment 2

Non
lment 3

Figure 18.11 : La bote de dialogue ralise laide dune seule squence.

CHAPITRE 18 FENTRES ET BOUTONS

745

En assemblant les composants en plusieurs sous-groupes, on pourra parvenir la structure indique par la Figure 18.11. Il est galement possible d'arriver au mme rsultat avec une structure un seul niveau, mais plus complexe, comme on peut le voir sur la Figure 18.12.

Conteneur 1 : tableau vertical 1 colonne Conteneur 2 : Quelconque, centr

Conteneur 3 : Tableau horizontal, deux colonnes

Voulez-vous quitter ?

Oui

Non

Figure 18.11 : La mme disposition obtenue laide de plusieurs sous-groupes.

Conteneur 1 : Tableau 2 lignes, 2 colonnes

Voulez-vous quitter ?
Deux colonnes fusionnes

Oui

Non

Figure 18.12 : La mme disposition obtenue laide dune seule structure.

746
Conteneur 1 : non structur

LE DVELOPPEUR JAVA 2

Voulez-vous quitter ?
x2, y2 x1, y1 x3, y3

Oui

Non

Figure 18.13 : Disposition des lments sans laide dun layout manager.

Enfin, il est possible de positionner prcisment chaque lment en indiquant ses coordonnes x,y. Ces coordonnes peuvent avoir t calcules par le programmeur et intgres dans le programme (hardcoded, comme on dit en amricain). On utilise alors aucun layout manager. Elles peuvent aussi tre calcules au moment voulu par un layout manager personnalis (Figure 18.13). Nous allons donner un exemple de ralisation de chacun de ces cas.

Utilisation du FlowLayout
Le premier exemple utilisera le layout manager FlowLayout. Le rsultat parat efficace pour un exemple simple. Pourtant, les limites sont videntes. Compilez et excutez ce programme pour voir le rsultat obtenu :
import javax.swing.*; import java.awt.event.*; import java.awt.*; public class Flow1 { public static void main( String[] args ) {

CHAPITRE 18 FENTRES ET BOUTONS

747

FenetrePrincipale maFentre = new FenetrePrincipale("Ma fentre"); } } class FenetrePrincipale extends JFrame { FenetrePrincipale(String s) { super(s); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); }}); getContentPane().setLayout(new FlowLayout(FlowLayout.CENTER, 50, 20)); JLabel L1 = new JLabel("Voulez-vous quitter ?"); getContentPane().add(L1); JButton B1 = new JButton("Oui"); getContentPane().add(B1); JButton B2 = new JButton("Non"); getContentPane().add(B2); setBounds(calculBounds(L1)); setVisible(true); setResizable(false); } Rectangle calculBounds(JLabel jl) { Dimension tailleEcran = Toolkit.getDefaultToolkit().getScreenSize(); int largeurEcran = tailleEcran.width; int hauteurEcran = tailleEcran.height; int largeur = (int)((jl.getPreferredSize()).width + 100); int hauteur = (int)((jl.getPreferredSize()).height * 7); int xPos = (largeurEcran - largeur) / 2; int yPos = (hauteurEcran - hauteur) / 2; return new Rectangle(xPos, yPos, largeur, hauteur); } }

748

LE DVELOPPEUR JAVA 2
La mthode calculBounds est intressante, car elle montre ce que ne fait pas le layout manager. En effet, celui-ci se contente de disposer les lments les uns la suite des autres, comme le ferait un traitement de texte en disposant les mots sur une ligne. Si le mot suivant ne tient pas sur la ligne, il est report la ligne suivante. La ligne :

getContentPane().setLayout(new FlowLayout(FlowLayout.CENTER, 50, 20));

attribue la contentPane un FlowLayout manager utilisant l'alignement centr et laissant un espace de 50 pixels entre les lments et un espace de 20 pixels entre les lignes. Pour effectuer sa mise en page, le layout manager doit connatre les dimensions de la fentre. Lors de sa cration, celle-ci a des dimensions nulles. Le layout manager agit sur le contenu de la fentre, mais il est incapable de modifier la fentre elle-mme pour l'adapter son contenu. Pour qu'un tel layout manager soit utilisable, il faudrait lui fournir les contraintes suivantes :

Passer la ligne aprs le premier lment. Si la taille des deux boutons diffre de moins d'un certain seuil (20 %,

par exemple), donner aux deux boutons la taille du plus grand. Il est en effet inesthtique de juxtaposer deux boutons de tailles proches mais ingales.

Comparer la longueur des deux lignes et choisir pour taille de la bote


des lignes.

de dialogue la longueur de la plus grande ligne augmente d'une marge.

Calculer la hauteur de la bote de dialogue en fonction de la hauteur Calculer la hauteur et la largeur de l'cran. Renvoyer une exception si
la taille de la bote de dialogue est suprieure celle de l'cran.

Calculer la position de la bote de dialogue afin qu'elle soit centre


l'cran.

CHAPITRE 18 FENTRES ET BOUTONS

749

Notez que nous n'avons pas inclus le calcul de la taille de la police de caractres utiliser. Nous considrerons en effet que la taille standard a t configure par l'utilisateur du systme et convient donc ses gots et ses besoins. (Il est toujours mal peru par l'utilisateur de se voir imposer des choix esthtiques diffrents de ceux qu'il a effectus pour la configuration de son systme.) La mthode calculBounds effectue une partie du travail dcrit ici. Pour des raisons de simplicit, nous n'avons pas inclus la vrification de la taille des boutons ni de la longueur respective des lignes. Le calcul de la taille de l'cran se fait l'aide de l'instruction :
Dimension tailleEcran = Toolkit.getDefaultToolkit().getScreenSize();

Bien entendu, dans le cas d'une bote de dialogue d'une application, on pourra prendre une autre base de rfrence que la taille de l'cran, par exemple la taille de la fentre principale. La Figure18.14 montre le rsultat obtenu. Si vous modifiez le programme en remplaant les trois chanes de caractres, vous pourrez obtenir automatiquement le rsultat de la Figure18.15.

Figure 18.14 : La bote de dialogue dimensionne par rapport la taille de la fentre.

Figure 18.15 : Les dimensions sont automatiquement adaptes.

750

LE DVELOPPEUR JAVA 2

Figure 18.16 : Le texte des boutons est plus long que celui de ltiquette.

En revanche, si le texte des boutons est plus long que celui de l'tiquette, il risque de se poser un problme, comme on peur le voir sur la Figure18.16. Ici, le deuxime bouton a t repouss sur une troisime ligne. Il est facile de traiter ce cas, mais on finit par ne plus bien voir ce qu'apporte le layout manager. Notez que la longueur du texte a t obtenue en appelant la mthode getPreferredSize() de l'tiquette :
int largeur = (int)((jl.getPreferredSize()).width + 100);

(Nous rajoutons 100 pour les marges.) Chaque composant dispose des trois mthodes getPreferredSize(), getMinimuSize() et getMaximumSize() qui renvoient les dimensions idales, minimales et maximales du composant. (Pour une JLabel, ces trois mthodes renvoient les mmes valeurs.) Le layout manager utilise ces mthodes pour faire sa mise en page. Il interroge ainsi les composants pour savoir jusqu' quel point il peut les rduire ou les agrandir. Il peut galement se dimensionner par lui-mme en fonction de son contenu, comme nous le verrons plus loin. Les composants disposent aussi de la mthode getSize(), mais celle-ci ne peut tre employe ici. En effet, elle renvoie la taille relle du composant un moment donn. Avant que le layout manager du conteneur dans lequel se trouve le composant ait effectu son travail, la mthode ne renvoie pas la bonne taille, mais la taille avant la mise en page. Ici, comme il s'agit de la premire mise en page, getSize() renvoie une taille nulle. Pour que cette mthode renvoie la valeur correcte, il faudrait l'utiliser aprs que la mthode setVisible() du conteneur aura t invoque. Le rsultat serait que la fentre safficherait brivement dans l'angle suprieur gauche de l'cran avec des dimensions

CHAPITRE 18 FENTRES ET BOUTONS

751

nulles avant d'tre redimensionne et repositionne. Sur un systme peu rapide, cette opration est visible l'cran, ce qui ne fait pas trs professionnel. Une autre approche possible consiste interroger le layout manager pour connatre la dimension idale de la fentre :
getLayout().preferredLayoutSize(this)

Cette instruction obtient le layout manager au moyen de la mthode getLayout(), puis appelle la mthode preferredLayoutSize() de celui-ci en lui passant comme argument le conteneur dont on cherche la taille idale. Ici, il s'agit de celui depuis lequel la mthode est invoque et on utilise l'argument this. (Cette mthode peut servir pour interroger un layout manager pour savoir quelle taille il donnerait un autre conteneur si on l'y appliquait.) Cependant, cette faon de faire ne peut pas tre employe ici, car le layout manager indiquerait la taille idale pour que les trois composants soient sur une seule ligne, soit 451 x 67. Pour que ce procd donne le rsultat voulu, il faudrait qu'il existe un saut de ligne, ce qui n'est pas le cas avec le FlowLayout. Un espace inscable serait galement utile. De la mme faon, la mthode pack() permet de donner automatiquement la fentre la taille idale. Pour la mme raison, elle n'est d'aucun secours ici.

Utilisation d'un layout plusieurs niveaux


Les deux fonctions qui nous manquent (saut de ligne et espace inscable) peuvent tre implmentes facilement en utilisant une structure plusieurs niveaux. Pour le saut de ligne, il suffit d'utiliser un tableau une seule colonne et de placer chaque ligne dans une cellule du tableau. L'espace inscable peut tre obtenu en regroupant les deux composants qui ne doivent pas tre spars dans un mme sous-conteneur. Ici, le fait d'appliquer la premire technique implique automatiquement l'utilisation de la seconde.

752

LE DVELOPPEUR JAVA 2
Pour mettre en uvre ces techniques, il nous faut un conteneur spcial. Java dispose d'un tel lment : le panel. Un panel est tout simplement un conteneur invisible qui n'a d'autre fonction que de grouper les composants. Bien entendu, les composants peuvent tre eux-mmes des panels contenant des panels, etc. Chaque panel peut utiliser un layout manager diffrent. Nous pourrions tre tents d'utiliser le GridLayout, pour crer le tableau deux lignes et placer dans chaque cellule un panel muni d'un FlowLayout. Cette solution ne convient cependant pas car le contenu des cellules d'un GridLayout est align en haut et gauche. De plus, toutes les cellules ont la mme taille. Par consquent, le FlowLayout tant align en haut, il nous serait impossible d'obtenir un contenu centr. La Figure 18.17 explique pourquoi.

Vgap du FlowLayout 1

Vgap du GridLayout

Vgap du FlowLayout 2 Voulez-vous quitter ? Oui Non

Espace supplmentaire du GridLayout

Figure 18.17 : Organisation de lespace vertical.

On voit sur cette figure qu'il existe verticalement quatre espaces :

Celui ajout par le premier FlowLayout avant l'tiquette :


class PanelHaut extends JPanel { PanelHaut(String s) { setLayout(new FlowLayout(FlowLayout.CENTER, 0, 20));

Celui ajout entre les cellules par le GridLayout :

CHAPITRE 18 FENTRES ET BOUTONS

753

class FenetrePrincipale extends JFrame { FenetrePrincipale(String s) { super(s); getContentPane().setLayout(new GridLayout(2, 1, 0, 5));

Celui ajout par le second FlowLayout avant les boutons :


class PanelBas extends JPanel { PanelBas(String s1, String s2) { setLayout(new FlowLayout(FlowLayout.CENTER, 50, 10));

Celui ajout par le GridLayout aprs le second panel afin que les deux
cellules aient la mme taille. (Toutes les cellules ont la mme taille et leur contenu est align en haut et gauche.) On constate donc qu'il n'est pas possible d'aligner les lments correctement lorsque la premire ligne est moins haute que les suivantes. Il n'est pas trs logique que la hauteur des cellules soit dtermine par la premire ligne. Il serait plus utile que la ligne la plus haute soit prise en compte. Une solution peut consister rtablir la situation en imposant au premier panel la hauteur du plus haut. Ici, nous savons que le plus haut sera toujours le second. Le programme suivant utilise cette technique :
import javax.swing.*; import java.awt.event.*; import java.awt.*; public class Grid1 { public static void main( String[] args ) { FenetrePrincipale maFentre = new FenetrePrincipale("Ma fentre"); } } class FenetrePrincipale extends JFrame { FenetrePrincipale(String s) {

754
super(s);

LE DVELOPPEUR JAVA 2

addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); }}); getContentPane().setLayout(new GridLayout(2, 1, 0, 15)); PanelHaut P1 = new PanelHaut("Voulez-vous quitter ?"); getContentPane().add(P1); PanelBas P2 = new PanelBas("Oui", "Non"); getContentPane().add(P2); P1.setSize(P2.getSize()); setSize(getLayout().preferredLayoutSize(this)); center(); setVisible(true); setResizable(false); } void center() { Dimension tailleEcran = Toolkit.getDefaultToolkit().getScreenSize(); int largeurEcran = tailleEcran.width; int hauteurEcran = tailleEcran.height; int largeur = getSize().width; int hauteur = getSize().height; int xPos = (largeurEcran - largeur) / 2; int yPos = (hauteurEcran - hauteur) / 2; setLocation(xPos, yPos); } } class PanelHaut extends JPanel { PanelHaut(String s) { setLayout(new FlowLayout(FlowLayout.CENTER, 0, 15)); JLabel L1 = new JLabel(s); add(L1); setSize(getLayout().preferredLayoutSize(this)); } }

CHAPITRE 18 FENTRES ET BOUTONS


class PanelBas extends JPanel { PanelBas(String s1, String s2) { setLayout(new FlowLayout(FlowLayout.CENTER, 50, 0)); JButton B1 = new JButton(s1); add(B1); JButton B2 = new JButton(s2); add(B2); setSize(getLayout().preferredLayoutSize(this)); } }

755

Utilisation du GridBagLayout
Plutt que d'utiliser un layout complexe (compos de plusieurs panels utilisant des layout managers diffrents, il est possible d'utiliser un layout manager conu spcialement pour les situations complexes (pas trop quand mme !). Il s'agit du GridBagLayout. propos du GridBagLayout, on rencontre principalement deux attitudes : ceux qui n'osent pas dire qu'ils n'arrivent pas l'utiliser, et qui n'en fournissent que des exemples aussi intressants que celui que nous avons dj signal (voir page 718), et ceux qui avouent franchement qu'il est impossible d'en obtenir quelque chose d'utile. La ralit est un peu diffrente. Le GridBagLayout fonctionne comme un tableau dont les cellules peuvent tre fusionnes. Imaginez une feuille de papier millimtr. Vous pouvez dessiner dessus n'importe quelle interface avec tous les composants que vous voulez, mais chaque composant doit toujours occuper un rectangle fait d'un nombre entier de cellules. Vous pouvez indiquer une marge autour du tableau et entre les composants. Vous pouvez galement dterminer comment les composants sont aligns dans leur rectangle, et comment ils sont traits lorsque la taille de celui-ci n'est pas gale celle du composant. (Les composants peuvent garder une taille fixe, ou tre modifis horizontalement, verticalement, ou dans les deux directions.) Il semble qu'il y ait l de quoi satisfaire tous les besoins. La structure mettre en uvre est indique sur la Figure18.18.

756

LE DVELOPPEUR JAVA 2

Conteneur 1 : Tableau 2 lignes, 2 colonnes

Voulez-vous quitter ?
Deux colonnes fusionnes

Oui

Non

Figure 18.18 : Mise en uvre du GridBagLayout.

Le GridBagLayout est cr de la mme faon que les autres layouts la diffrence qu'il ne demande aucuns paramtres. Ceux-ci sont fournis par un objet de type particulier appel GridBagConstraints. Le processus peut donc tre reprsent de la faon suivante :

Cration du GridBagLayoout. Cration du GridBagConstraints correspondant au premier composant. Initialisation des paramtres du GridBagConstraints. Ajout du composant. Cration du GridBagConstraints correspondant au deuxime composant. Initialisation des paramtres du GridBagConstraints. Ajout du composant, etc.
Dans notre cas, nous utiliserons trois composants. Le premier sera une instance de JLabel et les deux autres des instances de JButton. Les paramtres d'un GridBagConstraints sont les suivants :

CHAPITRE 18 FENTRES ET BOUTONS

757

gridx,

: ces deux paramtres indiquent le numro de colonne et le numro de ligne correspondant l'angle suprieur gauche du composant. Notre JLabel utilisera les valeurs 0,0 alors que les boutons correspondront respectivement 0,1 et 1,1. teur du composant en nombre de cellules. Nous aurons donc 2,1 pour l'tiquette et 1,1 pour chacun des boutons.

gridy

gridwidth, gridheight : ces paramtres dsignent la largeur et la hau fill indique quel doit tre le comportement du composant lorsque sa
taille ne correspond pas celle de sa zone d'affichage. Les valeurs possibles sont :

GridBagConstraints.NONE : par de modification de la taille. GridBagConstraints.HORIZONTAL : le composant est ajust horizontalement.

GridBagConstraints.VERTICAL : le composant est ajust verticalement.

GridBagConstraints.BOTH : le composant est ajust dans les deux


directions.

anchor indique la faon dont le composant est align dans sa zone


d'affichage. Les valeurs possibles sont :

GridBagConstraints.CENTER : alignement centr verticalement et


horizontalement.

GridBagConstraints.NORTH : align en haut et centr horizontalement.

GridBagConstraints.NORTHEAST : align en haut et droite. GridBagConstraints.EAST : align droite et centr verticalement.

GridBagConstraints.SOUTHEAST : align droite et en bas.

758
ment.

LE DVELOPPEUR JAVA 2

GridBagConstraints.SOUTH : align en bas et centr horizontale GridBagConstraints.SOUTHWEST : align en bas et gauche. GridBagConstraints.WEST : align gauche et centr verticalement. GridBagConstraints.NORTHWEST : align en haut et gauche. weightx,
weighty dterminent, pour chaque direction, la faon dont la zone d'affichage sera agrandie (ou rtrcie) lorsque la taille totale du tableau sera modifie. Si la valeur est 0, la zone d'affichage n'est pas modifie.

ipadx, ipady sont des valeurs qui seront ajoutes systmatiquement


l'intrieur

la taille minimale du composant. (Ces valeurs sont ajoutes 2 fois du composant : au-dessus et au-dessous (ipady) ou gauche et droite (ipadx).) chage.

insets

correspond aux marges des composants dans leur zone d'affi-

Au lieu de spcifier la taille absolue des zones d'affichage (gridx, gridy), il est possible d'utiliser des coordonnes relatives en donnant ces paramtres la valeur GridBagConstraints.RELATIVE. Dans ce cas, les composants sont ajouts sur la mme ligne. Pour indiquer qu'un composant est le dernier d'une ligne, il suffit de donner la largeur de sa zone d'affichage (gridwidth) la valeur GridBagConstraints.REMAINDER. Pour la dernire ligne, il suffit de donner la mme valeur la hauteur (gridheight). Le programme suivant permet d'obtenir la mme bote de dialogue que les prcdents l'aide du GridBagLayout :
import javax.swing.*; import java.awt.event.*; import java.awt.*; public class GridBag1 { public static void main(String[] args) {

CHAPITRE 18 FENTRES ET BOUTONS

759

FenetrePrincipale maFentre = new FenetrePrincipale("Ma fentre"); } } class FenetrePrincipale extends JFrame { FenetrePrincipale(String s) { super(s); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); }}); getContentPane().setLayout(new GridBagLayout()); GridBagConstraints c = new GridBagConstraints(); JLabel L1 = new JLabel("Voulez-vous quitter ?"); c.weightx = 1 ; c.ipadx = 10; c.gridx = 0; c.gridy = 0; c.gridwidth = 2; c.gridheight = 1; c.insets = new Insets(30, 50, 10, 50); getContentPane().add(L1, c); JButton B1 = new JButton("Oui"); c.gridx = 0; c.gridy = 1; c.gridwidth = 1; c.gridheight = 1; c.insets = new Insets(10, 0, 30, 0); getContentPane().add(B1, c); JButton B2 = new JButton("Non"); c.gridx = 1; c.gridy = 1; c.gridwidth = 1; c.gridheight = 1; c.insets = new Insets(10, 0, 30, 0); getContentPane().add(B2, c);

760

LE DVELOPPEUR JAVA 2
setSize(getLayout().preferredLayoutSize(this)); center(); setVisible(true); setResizable(false); } void center() { Dimension tailleEcran = Toolkit.getDefaultToolkit().getScreenSize(); int largeurEcran = tailleEcran.width; int hauteurEcran = tailleEcran.height; int largeur = getSize().width; int hauteur = getSize().height; int xPos = (largeurEcran - largeur) / 2; int yPos = (hauteurEcran - hauteur) / 2; setLocation(xPos, yPos); } }

Si vous prfrez utiliser la spcification relative des colonnes, le code sera un tout petit peu plus compact. Voici le constructeur de la fentre principale ainsi modifi :
FenetrePrincipale(String s) { super(s); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); }}); getContentPane().setLayout(new GridBagLayout()); GridBagConstraints c = new GridBagConstraints(); JLabel L1 = new JLabel("Voulez-vous quitter ?"); c.weightx = 1; c.ipadx = 10; c.gridwidth = GridBagConstraints.REMAINDER;

CHAPITRE 18 FENTRES ET BOUTONS


c.gridheight = 1; c.insets = new Insets(30, 50, 10, 50); getContentPane().add(L1, c); JButton B1 = new JButton("Oui"); c.gridwidth = 1; c.gridheight = GridBagConstraints.REMAINDER; c.insets = new Insets(10, 0, 30, 0); getContentPane().add(B1, c); JButton B2 = new JButton("Non"); c.insets = new Insets(10, 0, 30, 0); getContentPane().add(B2, c); setSize(getLayout().preferredLayoutSize(this)); center(); setVisible(true); setResizable(false); }

761

Il est lgrement plus court car les valeurs de gridx et gridy n'ont plus besoin d'tre spcifies car GridBagConstraints.RELATIVE est la valeur par dfaut et ne doit tre spcifie qu'une seule fois (au dbut de la deuxime ligne).

Note : Il est ncessaire de donner

weightx une valeur quelconque diffrente de 0. Dans le cas contraire, chaque colonne prend pour largeur celle du premier composant qui l'occupe. Dans le cas prsent, la largeur des colonnes n'est fixe qu' la deuxime ligne car, dans la premire, les colonnes sont fusionnes. Nous obtenons donc une premire colonne de la largeur du bouton et la deuxime beaucoup plus grande (largeur de l'tiquette moins celle de la premire colonne). En donnant une valeur weightx pour chaque colonne, le GridBagLayout agrandit ou rtrcit les colonnes dans les proportions de ces valeurs. Nous donnons donc la valeur 1 pour la premire cellule. Les cellules suivantes utilisent automatiquement la mme valeur.

Attention : Le GridBagLayout a, dans certains domaines, un comportement un peu bizarre. (Irons-nous jusqu' dire qu'il est bugg ?) En effet, sa mthode preferredSizeLayout() renvoie une valeur nettement infrieure la somme des preferredSize() de ses composants (ici, 160 au lieu de 168). Voil pourquoi nous avons t obligs d'ajouter l'instruction ipadx = 10,

762

LE DVELOPPEUR JAVA 2
qui ajoute 10 pixels l'intrieur des composants (de chaque ct, soit 20 pixels en tout). Une autre solution (prfrable car elle vite des surprises dans les cas extrmes) serait de faire nous-mmes la somme des tailles des composants. C'est ce que nous ferons dans l'exemple suivant.

Rendre l'interface rellement indpendante du systme


Les exemples prcdents taient intressants, mais ils n'atteignent pas le but recherch qui est de rendre le programme indpendant du systme. En effet, certaines valeurs sont indiques en pixels. Le rsultat obtenu sera donc diffrent en fonction de la rsolution de l'cran. Une solution consiste exprimer ces valeurs en rapport avec la taille de la police de caractres. Nous pourrions choisir nous-mmes une taille de police en points, mais cela ne serait pas trs efficace. En effet, nous retomberions dans le travers dcrit prcdemment : en fonction de la rsolution, la taille physique de la police pourrait varier du simple au double. Nous pourrions interroger le systme pour connatre la rsolution, mais cela ne donnerait pas le rsultat escompt. En revanche, nous pouvons utiliser les dimensions des composants pour dimensionner la fentre. C'est videmment ce que Java devrait faire automatiquement mais, comme nous l'avons vu, il y a un lger dfaut. La solution que nous mettrons en uvre consistera faire la somme des dimensions des composants et dimensionner la fentre l'aide de ces valeurs, auxquelles nous appliquerons une fonction du type ax + b. Il ne sera ainsi plus du tout ncessaire d'utiliser les insets. Nous devrons en revanche spcifier weightx et weighty. Leur valeur sera quelconque mais identique pour les deux. Dans le programme suivant, nous avons ajout une ligne pour spcifier une police de caractres afin que vous puissiez comparer les rsultats obtenus avec diffrentes tailles, simulant par l les conditions rencontres sur des systmes varis :
import javax.swing.*; import java.awt.event.*; import java.awt.*;

CHAPITRE 18 FENTRES ET BOUTONS

763

public class GridBag3 { public static void main( String[] args ) { FenetrePrincipale maFentre = new FenetrePrincipale("Ma fentre"); } } class FenetrePrincipale extends JFrame { JLabel L1; JButton B1, B2; FenetrePrincipale(String s) { super(s); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); }}); Font f = new Font("Dialog", Font.BOLD, 12); getContentPane().setLayout(new GridBagLayout()); GridBagConstraints c = new GridBagConstraints(); L1 = new JLabel("Voulez-vous quitter ?"); L1.setFont(f); c.weightx = 1; c.weighty = 1; c.gridwidth = GridBagConstraints.REMAINDER; c.gridheight = 1; getContentPane().add(L1, c); B1 = new JButton("Oui"); B1.setFont(f); c.gridwidth = GridBagConstraints.RELATIVE; c.gridheight = GridBagConstraints.REMAINDER; getContentPane().add(B1, c); B2 = new JButton("Non"); B2.setFont(f); getContentPane().add(B2, c); setup(); setVisible(true);

764
setResizable(false); }

LE DVELOPPEUR JAVA 2

void setup() { int xB = Math.max(B1.getPreferredSize().width, B2.getPreferredSize().width); int yB = Math.max(B1.getPreferredSize().height, B2.getPreferredSize().height); B1.setPreferredSize(new Dimension(xB, yB)); B1.setMaximumSize(new Dimension(xB, yB)); B1.setMinimumSize(new Dimension(xB, yB)); B2.setPreferredSize(new Dimension(xB, yB)); B2.setMaximumSize(new Dimension(xB, yB)); B2.setMinimumSize(new Dimension(xB, yB)); int x = Math.max(L1.getPreferredSize().width, 2 * xB); int y = L1.getPreferredSize().height + yB; setSize((int)((x + 50) * 1.2), (int)((y + 20) * 1.8)); Dimension tailleEcran = Toolkit.getDefaultToolkit().getScreenSize(); int largeurEcran = tailleEcran.width; int hauteurEcran = tailleEcran.height; int largeur = getSize().width; int hauteur = getSize().height; int xPos = (largeurEcran - largeur) / 2; int yPos = (hauteurEcran - hauteur) / 2; setLocation(xPos, yPos); } }

Nous avons regroup le dimensionnement et le positionnement de la bote de dialogue dans la mthode setup(). Pour essayer diffrentes tailles de caractres, modifiez simplement la ligne :
Font f = new Font("Dialog", Font.BOLD, 12);

La taille 12, slectionne ici, correspond la taille par dfaut.

CHAPITRE 18 FENTRES ET BOUTONS

765

Affichage sans layout manager


Il est parfaitement possible de disposer les composants sans l'aide d'un layout manager. Ainsi, le programme prcdent peut tre rcrit de la faon suivante :
import javax.swing.*; import java.awt.event.*; import java.awt.*; public class NullLayout { static FenetrePrincipale maFentre; public static void main( String[] args ) { maFentre = new FenetrePrincipale("Ma fentre"); } static class FenetrePrincipale extends JFrame { FenetrePrincipale(String s) { super(s); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); }}); getContentPane().setLayout(null); JLabel L1 = new JLabel("Voulez-vous quitter ?"); L1.setBounds(39, 11, 135, 17); getContentPane().add(L1); JButton B1 = new JButton("Oui"); B1.setBounds(23, 50, 59, 27); getContentPane().add(B1); JButton B2 = new JButton("Non"); B2.setBounds(130, 50, 59, 27); getContentPane().add(B2);

766

LE DVELOPPEUR JAVA 2
Dimension tailleEcran = Toolkit.getDefaultToolkit().getScreenSize(); int largeurEcran = tailleEcran.width; int hauteurEcran = tailleEcran.height; int largeur = 222; int hauteur = 115; int xPos = (largeurEcran - largeur) / 2; int yPos = (hauteurEcran - hauteur) / 2; setBounds(xPos, yPos, largeur, hauteur); setVisible(true); } } }

Avec une police de caractres de taille 12, le rsultat obtenu est exactement identique (les valeurs utilises ici ont t obtenues l'aide du programme prcdent !). Si votre application est destine une seule plate-forme, vous pouvez utiliser cette approche. Cependant, vous aurez subir les inconvnients suivant :

Vous devrez calculer les valeurs correctes pour chacune de vos botes
de dialogue.

Si votre programme doit fonctionner un jour sous un autre systme, ou

si la prochaine version de votre systme utilise des caractres diffrents, vous devrez modifier chacune de vos botes de dialogue.

Si un utilisateur modifie son systme pour obtenir un affichage plus


gros ou plus petit, les rsultats risquent d'tre imprvisibles.

Crer votre propre layout manager


La plupart des programmeurs crent leur bibliothque personnelle de sousprogrammes. Dans le cas qui nous proccupe, cela consisterait simple-

CHAPITRE 18 FENTRES ET BOUTONS

767

ment calculer la position et la taille de chaque composant. Cela ne pose pas de problme particulier. Vous pouvez parfaitement implmenter cela sous la forme d'une classe quelconque tendant la classe JFrame ou un autre conteneur plus appropri. Vous pouvez galement crer un layout manager. Les deux approches peuvent tre mlanges. La cration d'un layout manager ne doit tre envisage que pour traiter des cas qui ne peuvent l'tre avec les layout managers existants. S'il s'agit uniquement de simplifier l'interface, il suffit de crer une classe implmentant un layout manager existant. L'exemple suivant montre une telle classe dont le constructeur prend pour argument une chane de caractres reprsentant une question et affiche une bote de dialogue avec trois boutons correspondant aux rponses Oui, Non et Je ne sais pas. Un autre constructeur permet de spcifier la taille des caractres utiliser. Ce programme n'est pas trs diffrent du prcdent :
import javax.swing.*; import java.awt.event.*; import java.awt.*; public class Question extends JFrame { JLabel L1; JButton B1, B2, B3; Question(String s) { this(s, 12); } Question(String s, int t) { addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); }}); Font f = new Font("Dialog", Font.BOLD, t); getContentPane().setLayout(new GridBagLayout()); GridBagConstraints c = new GridBagConstraints(); L1 = new JLabel(s); L1.setFont(f); c.weightx = 1;

768

LE DVELOPPEUR JAVA 2
c.weighty = 1; c.gridwidth = GridBagConstraints.REMAINDER; c.gridheight = 1; getContentPane().add(L1, c); B1 = new JButton("Oui"); B1.setFont(f); c.gridwidth = 1; getContentPane().add(B1, c); B2 = new JButton("Je ne sais pas"); B2.setFont(f); c.gridwidth = 2; getContentPane().add(B2, c); B3 = new JButton("Non"); B3.setFont(f); c.gridheight = GridBagConstraints.REMAINDER; getContentPane().add(B3, c); setup(); setVisible(true); setResizable(false); } void setup() { int xB = Math.max(B1.getPreferredSize().width, B3.getPreferredSize().width); int yB = B1.getPreferredSize().height; int xB2 = B2.getPreferredSize().width; B1.setPreferredSize(new Dimension(xB, yB)); B1.setMaximumSize(new Dimension(xB, yB)); B1.setMinimumSize(new Dimension(xB, yB)); B3.setPreferredSize(new Dimension(xB, yB)); B3.setMaximumSize(new Dimension(xB, yB)); B3.setMinimumSize(new Dimension(xB, yB)); int x = Math.max(L1.getPreferredSize().width, 2 * xB + xB2); int y = L1.getPreferredSize().height + yB; setSize((int)((x + 50) * 1.2), (int)((y + 20) * 1.8)); Dimension tailleEcran = Toolkit.getDefaultToolkit().getScreenSize(); int largeurEcran = tailleEcran.width;

CHAPITRE 18 FENTRES ET BOUTONS


int hauteurEcran = tailleEcran.height; int largeur = getSize().width; int hauteur = getSize().height; int xPos = (largeurEcran - largeur) / 2; int yPos = (hauteurEcran - hauteur) / 2; setLocation(xPos, yPos); } }

769

Le listing suivant montre un exemple d'utilisation de cette classe. (En principe, cette classe devrait tre affecte un package.)
public class TestQuestion { public static void main( String[] args ) { Question uneQuestion = new Question("Aimez-vous Java ?"); } }

Les lments de l'interface utilisateur


Nous ne dcrirons pas tous les lments de l'interface utilisateur. Tout d'abord, ils sont trs nombreux et beaucoup vous seront totalement inutiles. En effet, Java 2 est livr avec tout ce qui est ncessaire pour assurer la compatibilit avec les versions 1.0 et 1.1. La version 1.0 utilisait un modle d'vnement totalement diffrent et trs peu orient objet, dans lequel il tait ncessaire de consulter les vnements pour connatre leur nature et leur metteur (le plus souvent au moyen d'une srie d'instructions if else). Java 1.1 utilisait le mme modle d'vnements que la version 2, mais avec un ensemble de composants lourds. Nous ne nous intresserons ici qu'aux composants Swing, apparus comme une addition la version 1.1 et compltement intgrs la version 2.

Note : Ayant t tout d'abord distribue comme une bibliothque additionnelle la version 1.1 de Java, la bibliothque Swing respectait la con-

770

LE DVELOPPEUR JAVA 2
vention de nommage des packages utilisant l'adresse Internet inverse du crateur. Les packages Swing taient donc nomms com.sun.java.swing... Lors de l'intgration de la bibliothque Swing la version bta de Java 2, ces packages ont tout fait normalement t renomms java.awt.swing... ce qui a immdiatement entran des protestations de la part des utilisateurs de la version 1.1 qui se voyaient obligs de modifier tous leurs programmes (ce qui n'est pas vraiment conforme au slogan write once, run everywhere). C'est pourquoi dans la version 2 bta 4, on est revenu aux noms de la version 1.1. Cependant, la version dfinitive adopte un nouveau systme de nommage dans lequel les packages d'abord distribus comme des extensions sont ensuite intgrs avec un nom commenant par javax. Les packages Swing sont donc nomms javax.swing... Nous ne traiterons pas non plus tous les lments de la bibliothque Swing. Ils sont extrmement nombreux. En effet, contrairement l'approche des versions 1.0 et 1.1, utilisant les ressources du systme hte, ce qui restreignait l'interface de Java au sous-ensemble commun aux diffrents systmes utiliss (en gros Windows, Motif et Macintosh), le fait d'avoir choisi des lments lgers pour la bibliothque Swing a retir toute limitation. Il existe donc d'innombrables composants, dont certains mriteraient un livre eux seuls. Il existe par exemple un composant JTable offrant pratiquement les fonctionnalits d'un petit tableur. On trouve galement des composants qui sont de vritables diteurs de texte, au format texte seul, HTML ou RTF. La bibliothque Swing permet galement la gestion complte du presse-papiers, du glisser dposer, de l'annulation/rptition multiple, de l'affichage d'arbres (avec extension slective des diffrents niveaux), etc. Dans la suite de ce chapitre, nous ne donnerons que quelques exemples des composants les plus courants.

Les boutons
Nous avons dj employ des boutons, mais ceux-ci ne faisaient rien. Nous allons maintenant voir ce qui nous manque pour crer des boutons fonctionnels. Comme nous l'avons dj dit, un clic sur un bouton est un vnement. Cet vnement est un objet envoy un listener. Pour recevoir les clics sur nos boutons, nous devons donc crer des listeners, comme nous l'avions dj

CHAPITRE 18 FENTRES ET BOUTONS

771

fait pour la fermeture de la fentre. Nous allons modifier le programme prcdent afin d'afficher une bote de dialogue contenant deux boutons portant les inscriptions Rouge et Bleu. Lorsque nous cliquerons sur un bouton, la couleur correspondante sera affecte au texte de l'tiquette. Nous avions indiqu prcdemment qu'il tait plus simple de raliser un listener en tendant un adapter plutt qu'en implmentant directement l'interface correspondante, ce qui nous vitait d'avoir redfinir toutes les mthodes dont nous n'avons pas besoin. Pour les boutons, l'interface implmenter ne comporte qu'une seule mthode. Il n'existe donc pas d'adapter correspondant cette interface. Nous utiliserons pour chacun des boutons une technique diffrente. Dans les deux cas, nous crerons une classe pour le listener de chaque bouton. Nous utiliserons une classe interne pour le premier et une classe anonyme pour le second. Rappelons qu'il est, notre avis, tout fait dconseill (c'est un euphmisme) d'utiliser pour cela le bouton lui-mme, le conteneur dans lequel il se trouve ou la classe principale. Autant que possible, un listener doit tre une classe spcialement prvue cet effet.

import javax.swing.*; import java.awt.event.*; import java.awt.*; public class Boutons { public static void main( String[] args ) { ChoixCouleur choix = new ChoixCouleur("Choisissez une couleur"); } } class ChoixCouleur extends JFrame { JLabel L1; JButton B1, B2; ChoixCouleur(String s) { this(s, 12); }

772
ChoixCouleur(String s, int t) {

LE DVELOPPEUR JAVA 2

addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); }}); Font f = new Font("Dialog", Font.BOLD, t); getContentPane().setLayout(new GridBagLayout()); GridBagConstraints c = new GridBagConstraints(); L1 = new JLabel(s); L1.setFont(f); c.weightx = 1; c.weighty = 1; c.gridwidth = GridBagConstraints.REMAINDER; c.gridheight = 1; getContentPane().add(L1, c); B1 = new JButton("Rouge"); class AL1 implements ActionListener { public void actionPerformed(ActionEvent e) { L1.setForeground(new Color(255, 0, 0)); L1.repaint(); } } B1.addActionListener(new AL1()); B1.setFont(f); c.gridwidth = 1; getContentPane().add(B1, c); B2 = new JButton("Bleu"); B2.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { L1.setForeground(new Color(0, 0, 255)); L1.repaint(); }}); B2.setFont(f);

CHAPITRE 18 FENTRES ET BOUTONS


c.gridheight = GridBagConstraints.REMAINDER; getContentPane().add(B2, c); setup(); setResizable(false); setVisible(true); }

773

void setup() { int xB = Math.max(B1.getPreferredSize().width, B2.getPreferredSize().width); int yB = B1.getPreferredSize().height; B1.setPreferredSize(new Dimension(xB, yB)); B1.setMaximumSize(new Dimension(xB, yB)); B1.setMinimumSize(new Dimension(xB, yB)); B2.setPreferredSize(new Dimension(xB, yB)); B2.setMaximumSize(new Dimension(xB, yB)); B2.setMinimumSize(new Dimension(xB, yB)); int x = Math.max(L1.getPreferredSize().width, 2 * xB); int y = L1.getPreferredSize().height + yB; setSize((int)((x + 50) * 1.2), (int)((y + 20) * 1.8)); Dimension tailleEcran = Toolkit.getDefaultToolkit().getScreenSize(); int largeurEcran = tailleEcran.width; int hauteurEcran = tailleEcran.height; int largeur = getSize().width; int hauteur = getSize().height; int xPos = (largeurEcran - largeur) / 2; int yPos = (hauteurEcran - hauteur) / 2; setLocation(xPos, yPos); } }

En dehors de quelques modifications mineures, les parties importantes sont celles qui ont t imprimes en gras. Dans le cas du premier bouton, nous crons une classe locale (BL1) en implmentant l'interface ActionListener :
class AL1 implements ActionListener { public void actionPerformed(ActionEvent e) {

774

LE DVELOPPEUR JAVA 2
L1.setForeground(new Color(255, 0, 0)); L1.repaint(); } }

Dans cette classe, nous redfinissons la mthode actionPerformed afin qu'elle modifie la couleur de texte de l'tiquette L1 en lui attribuant une nouvelle couleur dont les composantes RVB (rouge, vert, bleu) sont 255 (le maximum), 0, 0. Nous appelons ensuite la mthode repaint() afin que la modification soit rpercute sur l'affichage.

Attention : Nous invoquons ici la mthode repaint() sur l'tiquette, ce qui ne pose pas de problme parce que nous savons qu'un changement de couleur n'a aucune rpercussion sur la mise en page (le layout). Si ce n'tait pas le cas, comme nous le verrons plus loin, il faudrait invoquer la mthode repaint() du conteneur principal. Par ailleurs, notez que cette mthode appelle simplement la mthode paint(), qui redessine le composant l'cran. La mthode paint() est galement appele automatiquement chaque fois que le composant doit tre redessin, et en particulier la premire fois qu'il est affich et chaque fois que la fentre est dcouverte aprs avoir t masque. Si vous omettez d'appeler repaint(), ne soyez pas surpris si le texte change de couleur chaque fois qu'il est masqu l'cran puis redcouvert !
Cette classe est ensuite instancie et l'instance est affecte au bouton au moyen de l'instruction :

B1.addActionListener(new AL1());

Note : Il n'y a aucune raison de dfinir la classe cet endroit prcis. Nous
aurions pu la dfinir la fin du constructeur. Nous aurions aussi pu utiliser une classe membre, ou mme une classe externe. Rappelons que les classes internes ne sont internes que dans le programme source. Elles sont compiles dans des fichiers spars. Cependant, nous faisons rfrence dans cette classe un membre (L1) de la classe ChoixCouleur. Il est donc beaucoup plus facile de le faire partir d'une classe interne la classe

CHAPITRE 18 FENTRES ET BOUTONS


ChoixCouleur

775

qu' partir d'une classe externe. De plus, L1 pourrait trs bien ne pas tre membre de la classe ChoixCouleur. Ici, il l'est pour pouvoir tre utilis facilement dans la mthode Setup. Sans cette mthode, L1 pourrait trs bien tre une variable locale au constructeur de la classe. Une classe locale permet de rfrencer facilement une variable locale, alors que cela ne serait pas possible avec une classe membre. (Il existe bien sr des moyens de parvenir au mme rsultat, mais ils sont plus complexes, moins lgants et moins performants.) Il faut remarquer que la classe AL1 n'est instancie qu'une seule fois, immdiatement aprs avoir t dfinie. Ce type d'architecture se prte parfaitement l'utilisation d'une classe anonyme, ce que nous avons fait pour le second bouton :
B2.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { L1.setForeground(new Color(0, 0, 255)); L1.repaint(); }});

Le code est ainsi plus compact et plus lgant. Il apporte l'avantage d'une dfinition locale de la classe (pas besoin de tourner trois pages de listing pour trouver ce que fait le bouton) tout en limitant au minimum les termes inutiles. C'est l'architecture que nous prfrerons chaque fois que possible.

Note : Un ActionListener reoit les vnements de type ActionEvent. Ces vnements n'ont rien voir avec la souris, mme si, ici, ils peuvent tre gnrs par un clic. Ils correspondent au fait qu'un bouton est actionn. Cela peut se faire en cliquant, mais galement en slectionnant le bouton l'aide de la touche tabulation et en l'actionnant l'aide de la barre d'espacement. Il est galement possible d'envoyer explicitement par programme un tel vnement.
La gestion des vnements en Java doit maintenant tre bien claire :

Un objet ragit des vnements parce qu'il est dot d'un listener pour
ce type d'vnement. Cliquer sur un bouton ne fait rien si le bouton n'est pas dot d'un listener adquat.

776

LE DVELOPPEUR JAVA 2

Un listener est ralis en implmentant l'interface xxxListener o xxx

dsigne le type d'vnement cout. Les types d'vnements sont nomms xxxEvent. lier. Le type WindowEvent regroupe sept vnements. Le type ActionEvent n'en comporte qu'un seul.

Un listener coute un type d'vnement et non un vnement particu Un listener n'a pas dterminer quel vnement prcis l'a rveill.

Java s'en charge en invoquant automatiquement la mthode correspondant l'vnement. (L'vnement est toutefois pass en paramtre la mthode.)

Un listener n'a pas dterminer l'origine de l'vnement. Cela est d'une

vidence absolue, mais c'est tellement contraire ce qui se passe dans d'autres langages, et en particulier avec la version 1.0 de Java, qu'il est utile de le rappeler. (Si vous ne comprenez pas l'importance de cela, de deux choses l'une : Java 2 est votre premire exprience de la programmation, et tout va bien, ou, si ce n'est pas le cas, il est urgent de relire tout ce qui prcde.)

La dernire affirmation, pour vidente qu'elle soit, ncessite quelques commentaires supplmentaires. Avec un langage plus ancien (par exemple la premire version de Java), la fonction du listener aurait t implmente globalement, par exemple dans le programme principal ou dans une seule classe ddie cet usage. Lorsqu'une instance de cette classe aurait reu un vnement, il lui aurait fallu en dterminer la nature et la cible. Ici, la cible est tout simplement l'objet auquel le listener a t ajout au moyen de la mthode addActionListener. Tous les objets qui dfinissent cette mthode ou qui drivent d'une classe qui la dfinit peuvent donc recevoir un ActionListener. Par ailleurs, cette structure implique que le lien entre un objet et son ou ses xxxListener (un objet peut recevoir plusieurs listeners pour chaque type d'vnement) soit tabli pendant l'excution du programme. Il est donc parfaitement possible de modifier le comportement du programme pendant son excution. C'est ce qui s'appelle le dynamic binding ou lien dynamique. Cela a des implications considrables. Vous pouvez parfaitement prvoir un bouton qui ne fasse rien dans une version d'un programme

CHAPITRE 18 FENTRES ET BOUTONS

777

et qui devienne fonctionnel dans la version suivante sans que le programme contenant le bouton soit modifi. Il suffit que, dans la premire version, vous ayez inclus un ActionListener ne faisant rien. Il suffira alors, pour mettre jour le programme, de distribuer le nouvel ActionListener pour que le programme soit modifi. S'il s'agit d'un programme de quelques dizaines de mgaoctets, on voit tout de suite l'avantage de pouvoir faire une mise jour en modifiant uniquement une classe de quelques octets ! On peut trouver d'autres applications au dynamic binding. On peut, par exemple, distribuer un programme dont le comportement des boutons sera dtermin par un ActionListener se trouvant l'autre bout de la plante, sur un serveur. (En ralit ce type d'application est impossible car une plante n'a pas de bouts.)

Les vnements de souris


Il est tout fait possible de modifier le programme prcdent pour obtenir le mme rsultat en coutant un autre type d'vnement. Nous pouvons couter les vnements souris (MouseEvent) en remplaant les ActionListener par des MouseListener, de la faon suivante :
B1 = new JButton("Rouge"); class AL1 extends MouseAdapter { public void mouseClicked(MouseEvent e) { L1.setForeground(new Color(255, 0, 0)); L1.repaint(); } } B1.addMouseListener(new AL1());

ou encore :
B2.addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { L1.setForeground(new Color(0, 0, 255)); L1.repaint(); }});

778

LE DVELOPPEUR JAVA 2
Dans ce cas, le fonctionnement obtenu avec la souris est exactement identique. En revanche, le clavier n'est plus oprationnel car il n'y a plus de listener pour les ActionEvent. Vous vous demandez peut-tre ce que deviennent alors ces vnements. Contrairement ce qui se passe avec d'autres langages, ils sont perdus. Pour mettre cela en vidence, il nous faut modifier le programme pour que le premier bouton et la fentre coutent les vnements de souris, et que le second bouton ne fasse rien :
ChoixCouleur(String s, int t) { addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); }}); Font f = new Font("Dialog", Font.BOLD, t); getContentPane().setLayout(new GridBagLayout()); GridBagConstraints c = new GridBagConstraints(); L1 = new JLabel(s); L1.setFont(f); c.weightx = 1; c.weighty = 1; c.gridwidth = GridBagConstraints.REMAINDER; c.gridheight = 1; getContentPane().add(L1, c); B1 = new JButton("Rouge"); class AL1 extends MouseAdapter { public void mouseClicked(MouseEvent e) { L1.setForeground(new Color(255, 0, 0)); L1.repaint(); } } B1.addMouseListener(new AL1()); B1.setFont(f); c.gridwidth = 1; getContentPane().add(B1, c); B2 = new JButton("Bleu");

CHAPITRE 18 FENTRES ET BOUTONS


B2.setFont(f); c.gridheight = GridBagConstraints.REMAINDER; getContentPane().add(B2, c); addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { L1.setForeground(new Color(0, 255, 0)); L1.repaint(); }}); setup(); setResizable(false); setVisible(true); }

779

Nous n'avons reprsent ici que le deuxime constructeur de la classe ChoixCouleur. Avec ce programme, si nous cliquons sur le premier bouton, le texte de l'tiquette est affich en rouge. Si nous cliquons sur la fentre, le texte est affich en vert. En revanche, le deuxime bouton ne modifie pas le texte. Il faut noter deux choses importantes :

Le bouton ragit toujours au clic de souris ou au clavier, en s'enfonant.

Aucun vnement souris n'est transmis qui que ce soit. En particulier,


contrairement ce qui se passe avec d'autres langages, l'lment qui contient le bouton ne reoit pas d'vnement. Vous pouvez noter qu'il en est de mme si vous cliquez sur l'tiquette. (C'est du moins le cas si vous cliquez sur le texte. Si vous cliquez entre les caractres, la fentre reoit un vnement souris. Le fond des tiquettes est donc transparent aux vnements souris.)

Le fait que les vnements soient locaux et ne soient pas transmis d'un objet son conteneur est tout fait cohrent avec le modle des vnements Java. Dans les langages qui font remonter aux vnements la hirarchie des conteneurs jusqu' en trouver un qui l'coute, celui-ci est oblig d'interroger l'vnement pour connatre son origine, ce que les concepteurs de Java ont cherch viter.

780

LE DVELOPPEUR JAVA 2
Si vous estimez ncessaire de transmettre un vnement d'un objet son conteneur, il faut le faire explicitement, en coutant l'vnement pour l'attraper et le renvoyer. Notez qu'il est ainsi possible de transmettre un vnement n'importe quel listener et pas seulement celui du conteneur. Dans le programme suivant, le second bouton coute les vnements souris et les renvoie la fentre :
ChoixCouleur(String s, int t) { addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); }}); addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { L1.setForeground(new Color(0, 255, 0)); L1.repaint(); }}); Font f = new Font("Dialog", Font.BOLD, t); getContentPane().setLayout(new GridBagLayout()); GridBagConstraints c = new GridBagConstraints(); L1 = new JLabel(s); L1.setFont(f); c.weightx = 1; c.weighty = 1; c.gridwidth = GridBagConstraints.REMAINDER; c.gridheight = 1; getContentPane().add(L1, c); B1 = new JButton("Rouge"); class AL1 extends MouseAdapter { public void mouseClicked(MouseEvent e) { L1.setForeground(new Color(255, 0, 0)); L1.repaint(); } }

CHAPITRE 18 FENTRES ET BOUTONS


B1.addMouseListener(new AL1()); B1.setFont(f); c.gridwidth = 1; getContentPane().add(B1, c); B2 = new JButton("Bleu"); B2.addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { processMouseEvent(e); }}); B2.setFont(f); c.gridheight = GridBagConstraints.REMAINDER; getContentPane().add(B2, c); setup(); setResizable(false); setVisible(true); } protected void processMouseEvent(MouseEvent e) { super.processMouseEvent(e); }

781

Dans ce programme (nous n'avons reprsent ici que le second constructeur de la classe ChoixCouleur et une nouvelle mthode), la fentre reoit un MouseListener qui affiche le texte de l'tiquette en vert. Le second bouton reoit lui aussi un MouseListener. (Il ne pourrait pas retransmettre l'vnement s'il n'tait pas en mesure de l'couter.) Celui-ci appelle simplement la mthode processMouseEvent de la classe ChoixCouleur en lui passant l'vnement. La mthode processMouseEvent mrite quelques commentaires. Celle-ci appartient en effet la classe Component, qui est un lointain anctre de ChoixCouleur par l'intermdiaire (entre autres) de JFrame. Cette mthode ne peut tre appele que si le composant a reu un MouseListener. De plus, cette mthode est protge, ce qui nous empche de l'appeler directement depuis le listener du bouton. Il nous faut donc la redfinir dans la classe ChoixCouleur. Le listener qui l'appelle tant une classe anonyme, il

782

LE DVELOPPEUR JAVA 2
fait videmment partie du mme package que celle-ci. Nous pouvons donc redfinir la mthode en la dclarant protected. Si le listener tait une classe externe appartenant un autre package, il nous faudrait la dclarer public. Une utilisation de ce principe pourrait tre d'effectuer un traitement spcifique pour certains boutons et un traitement gnral pour d'autres. Ainsi, on peut imaginer qu'un bouton donne au texte la couleur rouge, un autre la couleur bleue et qu'un troisime mette jour l'affichage. Dans l'exemple suivant, chaque bouton change la couleur du texte et du fond. Cliquer dans la fentre ne change que le style du fond :
import import import import javax.swing.*; java.awt.event.*; java.awt.*; java.util.*;

public class Boutons2 { public static void main( String[] args ) { ChoixCouleur choix = new ChoixCouleur("Choisissez une couleur"); } } class ChoixCouleur extends JFrame { JLabel L1; JButton B1, B2; Random random = new Random(); Color c; ChoixCouleur(String s) { this(s, 12); } ChoixCouleur(String s, int t) { addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); }});

CHAPITRE 18 FENTRES ET BOUTONS


addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { c = new Color(Math.abs(random.nextInt()) % 256, Math.abs(random.nextInt()) % 256, Math.abs(random.nextInt()) % 256); getContentPane().setBackground(c); B1.setBackground(c); B2.setBackground(c); repaint(); }}); Font f = new Font("Dialog", Font.BOLD, t); getContentPane().setLayout(new GridBagLayout()); GridBagConstraints c = new GridBagConstraints(); L1 = new JLabel(s); L1.setFont(f); c.weightx = 1; c.weighty = 1; c.gridwidth = GridBagConstraints.REMAINDER; c.gridheight = 1; getContentPane().add(L1, c); B1 = new JButton("Rouge"); B1.addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { L1.setForeground(new Color(255, 0, 0)); processMouseEvent(e); }}); B1.setFont(f); c.gridwidth = 1; getContentPane().add(B1, c); B2 = new JButton("Bleu"); B2.addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { L1.setForeground(new Color(0, 0, 255)); processMouseEvent(e); }});

783

784

LE DVELOPPEUR JAVA 2
B2.setFont(f); c.gridheight = GridBagConstraints.REMAINDER; getContentPane().add(B2, c); setup(); setResizable(false); setVisible(true); } protected void processMouseEvent(MouseEvent e) { super.processMouseEvent(e); } void setup() { int xB = Math.max(B1.getPreferredSize().width, B2.getPreferredSize().width); int yB = B1.getPreferredSize().height; B1.setPreferredSize(new Dimension(xB, yB)); B1.setMaximumSize(new Dimension(xB, yB)); B1.setMinimumSize(new Dimension(xB, yB)); B2.setPreferredSize(new Dimension(xB, yB)); B2.setMaximumSize(new Dimension(xB, yB)); B2.setMinimumSize(new Dimension(xB, yB)); int x = Math.max(L1.getPreferredSize().width, 2 * xB); int y = L1.getPreferredSize().height + yB; setSize((int)((x + 50) * 1.2), (int)((y + 20) * 1.8)); Dimension tailleEcran = Toolkit.getDefaultToolkit().getScreenSize(); int largeurEcran = tailleEcran.width; int hauteurEcran = tailleEcran.height; int largeur = getSize().width; int hauteur = getSize().height; int xPos = (largeurEcran - largeur) / 2; int yPos = (hauteurEcran - hauteur) / 2; setLocation(xPos, yPos); } }

Notez que pour changer la couleur du fond de la fentre, nous devons appeler la mthode setBackground de sa contentPane. Si nous modifions la

CHAPITRE 18 FENTRES ET BOUTONS

785

couleur du fond de la fentre elle-mme, nous n'obtiendrons pas le rsultat souhait car le fond de la fentre est masqu par la contentPane. La classe Component comporte une mthode processXXXEvent pour chaque type d'vnement.

Les menus
Java permet de crer trs facilement des menus, qu'ils soient lis une barre de menus ou flottants. Le programme suivant cre une fentre avec une barre de menus :
import import import import javax.swing.*; java.awt.event.*; java.awt.*; java.util.*;

public class Menus extends JFrame { public static void main( String[] args ) { MaFenetre fenetre = new MaFenetre("Exemple de menus"); } } class MaFenetre extends JFrame { MaFenetre(String s) { super(s); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); }}); setup(); JMenu menu = new JMenu("Commandes", true); JMenuItem item1 = new JMenuItem("Ouvrir"); item1.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { new BD("Affiche par Commandes/Ouvrir"); }});

786

LE DVELOPPEUR JAVA 2
menu.add(item1 ); JMenuItem item2 = new JMenuItem("Ouvrir aussi"); item2.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { new BD("Affiche par Commandes/Ouvrir aussi"); }}); menu.add(item2 ); menu.add(new JSeparator()); JMenuItem item3 = new JMenuItem("Quitter"); item3.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { System.exit(0); }}); menu.add(item3 ); JMenuBar mb = new JMenuBar(); mb.add(menu); setJMenuBar(mb); setVisible(true); } void setup() { int largeur = 400, hauteur = 300; setSize(largeur, hauteur); Dimension tailleEcran = Toolkit.getDefaultToolkit().getScreenSize(); int largeurEcran = tailleEcran.width; int hauteurEcran = tailleEcran.height; int xPos = (largeurEcran - largeur) / 2; int yPos = (hauteurEcran - hauteur) / 2; setLocation(xPos, yPos); } } class BD extends JFrame { JLabel L1; JButton B1, B2;

CHAPITRE 18 FENTRES ET BOUTONS


BD(String s) { addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { dispose(); }}); Font f = new Font("Dialog", Font.BOLD, 12); getContentPane().setLayout(new GridBagLayout()); GridBagConstraints c = new GridBagConstraints(); L1 = new JLabel(s); L1.setFont(f); c.weightx = 1; c.weighty = 1; c.gridwidth = GridBagConstraints.REMAINDER; c.gridheight = 1; getContentPane().add(L1, c); B1 = new JButton("Rouge"); B1.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { L1.setForeground(new Color(255, 0, 0)); repaint(); }}); B1.setFont(f); c.gridwidth = 1; getContentPane().add(B1, c); B2 = new JButton("Bleu"); B2.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { L1.setForeground(new Color(0, 0, 255)); repaint(); }}); B2.setFont(f); c.gridheight = GridBagConstraints.REMAINDER; getContentPane().add(B2, c); setup();

787

788
setResizable(false); setVisible(true); }

LE DVELOPPEUR JAVA 2

void setup() { int xB = Math.max(B1.getPreferredSize().width, B2.getPreferredSize().width); int yB = B1.getPreferredSize().height; B1.setPreferredSize(new Dimension(xB, yB)); B1.setMaximumSize(new Dimension(xB, yB)); B1.setMinimumSize(new Dimension(xB, yB)); B2.setPreferredSize(new Dimension(xB, yB)); B2.setMaximumSize(new Dimension(xB, yB)); B2.setMinimumSize(new Dimension(xB, yB)); int x = Math.max(L1.getPreferredSize().width, 2 * xB); int y = L1.getPreferredSize().height + yB; setSize((int)((x + 50) * 1.2), (int)((y + 20) * 1.8)); Dimension tailleEcran = Toolkit.getDefaultToolkit().getScreenSize(); int largeurEcran = tailleEcran.width; int hauteurEcran = tailleEcran.height; int largeur = getSize().width; int hauteur = getSize().height; int xPos = (largeurEcran - largeur) / 2; int yPos = (hauteurEcran - hauteur) / 2; setLocation(xPos, yPos); } }

Nous avons redonn ici le listing de la classe BD, qui est trs proche de celui du programme prcdent, en imprimant en gras le principal changement, qui consiste remplacer System.exit(0) par dispose() afin que la fermeture de la bote de dialogue n'entrane pas la fin du programme. Pour crer une barre de menus, nous commenons par crer une nouvelle instance de la classe JMenu, avec pour paramtres le nom du menu et une valeur de type boolean indiquant si le menu peut-tre dtach ou non.

CHAPITRE 18 FENTRES ET BOUTONS


JMenu menu = new JMenu("Commandes", true);

789

Nous crons ensuite un article de menu en instanciant la classe JMenuItem avec le nom de l'article pour paramtre. Nous lui ajoutons ensuite un ActionListener :
JMenuItem item1 = new JMenuItem("Ouvrir"); item1.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { new BD("Affiche par Commandes/Ouvrir"); }});

L'article est ajout au menu l'aide de l'instruction :


menu.add(item1 );

Nous crons de la mme faon le deuxime article :


JMenuItem item2 = new JMenuItem("Ouvrir aussi"); item2.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { new BD("Affiche par Commandes/Ouvrir aussi"); }}); menu.add(item2 );

puis le troisime aprs avoir insr un sparateur :


menu.add(new JSeparator()); JMenuItem item3 = new JMenuItem("Quitter"); item3.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { System.exit(0); }});

790
menu.add(item3 );

LE DVELOPPEUR JAVA 2

Enfin, nous crons une barre de menus en instanciant la classe JMenuBar, puis nous lui ajoutons le menu :
JMenuBar mb = new JMenuBar(); mb.add(menu);

Il ne reste plus qu' affecter cette barre de menus notre fentre au moyen de la mthode setJMenuBar(). (Nous ne pouvons pas utiliser la mthode add car la barre de menus est porte par la RootPane.)
setJMenuBar(mb);

Les fentres internes


La fentre que nous utilisions jusqu'ici tait une JFrame, c'est--dire un composant lourd. Java possde galement des fentres implmentes sous forme de composants lgers JInternalFrame. Ces fentres ne peuvent tre cres qu' l'intrieur d'une autre fentre. Nous pouvons modifier notre programme pour que la bote de dialogue soit une JInternalFrame et non une JFrame :
import import import import import javax.swing.*; javax.swing.event.*; java.awt.event.*; java.awt.*; java.util.*;

public class Menus2 extends JFrame { public static void main( String[] args ) { MaFenetre fenetre = new MaFenetre("Exemple de menus"); } }

CHAPITRE 18 FENTRES ET BOUTONS


class MaFenetre extends JFrame { JDesktopPane desktop; MaFenetre(String s) { super(s); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); }}); setup(); JMenu menu = new JMenu("Commandes", true); JMenuItem item1 = new JMenuItem("Ouvrir");

791

item1.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { BD bd = new BD("Affiche par Commandes/Ouvrir"); desktop.add(bd); try { bd.setSelected(true); } catch (java.beans.PropertyVetoException ve) {} }}); menu.add(item1 ); JMenuItem item2 = new JMenuItem("Ouvrir aussi"); item2.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { BD bd = new BD("Affiche par Commandes/Ouvrir aussi"); desktop.add(bd); try { bd.setSelected(true); } catch (java.beans.PropertyVetoException ve) {} }}); menu.add(item2 ); menu.add(new JSeparator()); JMenuItem item3 = new JMenuItem("Quitter");

792

LE DVELOPPEUR JAVA 2
item3.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { System.exit(0); }}); menu.add(item3 ); JMenuBar mb = new JMenuBar(); mb.add(menu); setJMenuBar(mb); desktop = new JDesktopPane(); getContentPane().add(desktop); setVisible(true); } void setup() { int largeur = 400, hauteur = 300; setSize(largeur, hauteur); Dimension tailleEcran = Toolkit.getDefaultToolkit().getScreenSize(); int largeurEcran = tailleEcran.width; int hauteurEcran = tailleEcran.height; int xPos = (largeurEcran - largeur) / 2; int yPos = (hauteurEcran - hauteur) / 2; setLocation(xPos, yPos); } } class BD extends JInternalFrame { static int compte = 0; JLabel L1; JButton B1, B2; BD(String s) { super(s, false, true, false, false); addInternalFrameListener(new InternalFrameAdapter() { public void internalFrameClosing(InternalFrameEvent e) { dispose(); }});

CHAPITRE 18 FENTRES ET BOUTONS


Font f = new Font("Dialog", Font.BOLD, 12); getContentPane().setLayout(new GridBagLayout()); GridBagConstraints c = new GridBagConstraints(); L1 = new JLabel(s); L1.setFont(f); c.weightx = 1; c.weighty = 1; c.gridwidth = GridBagConstraints.REMAINDER; c.gridheight = 1; getContentPane().add(L1, c); B1 = new JButton("Rouge"); B1.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { L1.setForeground(new Color(255, 0, 0)); repaint(); }}); B1.setFont(f); c.gridwidth = 1; getContentPane().add(B1, c); B2 = new JButton("Bleu"); B2.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { L1.setForeground(new Color(0, 0, 255)); repaint(); }}); B2.setFont(f); c.gridheight = GridBagConstraints.REMAINDER; getContentPane().add(B2, c); setup(); setResizable(false); setBackground(Color.lightGray); setVisible(true); }

793

794

LE DVELOPPEUR JAVA 2
void setup() { int xB = Math.max(B1.getPreferredSize().width, B2.getPreferredSize().width); int yB = B1.getPreferredSize().height; B1.setPreferredSize(new Dimension(xB, yB)); B1.setMaximumSize(new Dimension(xB, yB)); B1.setMinimumSize(new Dimension(xB, yB)); B2.setPreferredSize(new Dimension(xB, yB)); B2.setMaximumSize(new Dimension(xB, yB)); B2.setMinimumSize(new Dimension(xB, yB)); int x = Math.max(L1.getPreferredSize().width, 2 * xB); int y = L1.getPreferredSize().height + yB; setSize((int)((x + 50) * 1.2), (int)((y + 20) * 1.8)); int w = (10 + 10 * (compte++ % 10)); setLocation(w, w); } }

Diffrentes modifications ont t apportes au programme afin d'utiliser des JInternalFrame. Tout d'abord, nous avons import un nouveau package contenant les vnements spcifiques correspondant aux composants Swing :

import javax.swing.event.*;

La classe MaFenetre dclare une variable de type JDesktopPane. Cette classe est un conteneur qui permet de disposer les JInternalFrame en plusieurs couches.

class MaFenetre extends JFrame { JDesktopPane desktop;

Une instance de JDesktopPane est cre et ajoute la contentPane de la fentre.

CHAPITRE 18 FENTRES ET BOUTONS


desktop = new JDesktopPane(); getContentPane().add(desktop);

795

Les deux premiers articles du menu crent une instance de la classe BD (qui tend JInternalFrame). Cette instance est ajoute la JDesktopPane (appele desktop) et non la fentre principale. Puis la fentre cre est slectionne en invoquant sa mthode setSelected() avec le paramtre true. L'invocation de cette mthode doit tre place dans un bloc try de faon intercepter l'exception java.beans.PropertyVetoException.

public void actionPerformed(ActionEvent e) { BD bd = new BD("Affiche par Commandes/Ouvrir"); desktop.add(bd); try { bd.setSelected(true); } catch (java.beans.PropertyVetoException ve) {} }});

La mthode setup de la fentre principale dtermine arbitrairement une taille de 400 x 300 pixels. La classe BD reprend galement l'essentiel du programme prcdent, l'exception des points lis au fait qu'elle drive de JInternalFrame et non de JFrame :

class BD extends JInternalFrame {

Son constructeur appelle le constructeur de la classe parente avec pour arguments une chane de caractres (le titre de la fentre) et quatre boolean indiquant si la fentre peut tre redimensionne, ferme, maximise et icnifie. Un InternalFrameListener remplace le WindowListener. Sa fonction est de supprimer la fentre (dispose()) et non plus de terminer le programme :

796

LE DVELOPPEUR JAVA 2

Figure 18.19 : Laffichage des fentres et du menu.

BD(String s) { super(s, true, true, true, true); addInternalFrameListener(new InternalFrameAdapter() { public void internalFrameClosing(InternalFrameEvent e) { dispose(); }});

Enfin, sa mthode setup utilise la variable statique compte pour disposer les fentres cres en diagonale, espaces de 10 pixels. Chaque fois que dix fentres ont t affiches, la suivante est affiche de nouveau dans l'angle suprieur gauche :
int w = (10 + 10 * (compte++ % 10)); setLocation(w, w);

Si vous compilez et excutez ce programme, vous obtenez le rsultat de la Figure18.19.

Le plaf (Pluggable Look And Feel)


Vous avez pu constater que l'aspect de ces fentres est trs diffrent de celui de la fentre principale. Il s'agit l du look and feel Metal, conu

CHAPITRE 18 FENTRES ET BOUTONS

797

spcialement pour les applications Java. Grce cette conception, les applications peuvent avoir le mme aspect sur tous les systmes. Java propose galement les look & feel Windows, Motif et Macintosh. De plus, le terme pluggable indique qu'il s'agit d'un lment qui peut tre ajout un programme, et ce mme de faon dynamique, au moment de son excution. Le programme suivant reprend le prcdent en ajoutant un menu permettant l'utilisateur de choisir son plaf. (Pour des raisons de copyright, le look & feel Macintosh nest disponible que sous Mac Os.)
import import import import import javax.swing.*; javax.swing.event.*; java.awt.event.*; java.awt.*; java.util.*;

public class Menus3 extends JFrame { public static void main( String[] args ) { MaFenetre fenetre = new MaFenetre("Exemple de menus"); } } class MaFenetre extends JFrame { JDesktopPane desktop; MaFenetre(String s) { super(s); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); }}); setup(); JMenu menu = new JMenu("Commandes", true); JMenuItem item1 = new JMenuItem("Ouvrir"); item1.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { BD bd = new BD("Affiche par Commandes/Ouvrir");

798

LE DVELOPPEUR JAVA 2
desktop.add(bd); try { bd.setSelected(true); } catch (java.beans.PropertyVetoException ve) {} }}); menu.add(item1); JMenuItem item2 = new JMenuItem("Ouvrir aussi"); item2.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { BD bd = new BD("Affiche par Commandes/Ouvrir aussi"); desktop.add(bd); try { bd.setSelected(true); } catch (java.beans.PropertyVetoException ve) {} }}); menu.add(item2); menu.add(new JSeparator()); JMenuItem item3 = new JMenuItem("Quitter"); item3.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { System.exit(0); }}); menu.add(item3); JMenuBar mb = new JMenuBar(); mb.add(menu); JMenu menu2 = new JMenu("Look & Feel", true); JMenuItem item11 = new JMenuItem("Metal"); item11.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { changePlaf(1); }}); menu2.add(item11);

CHAPITRE 18 FENTRES ET BOUTONS


JMenuItem item12 = new JMenuItem("Motif"); item12.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { changePlaf(2); }}); menu2.add(item12); JMenuItem item13 = new JMenuItem("Windows"); item13.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { changePlaf(3); }}); menu2.add(item13); JMenuItem item14 = new JMenuItem("Macintosh"); item14.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { changePlaf(4); }}); menu2.add(item14); mb.add(menu2); setJMenuBar(mb); desktop = new JDesktopPane(); getContentPane().add(desktop); setVisible(true); } void setup() { int largeur = 400, hauteur = 300; setSize(largeur, hauteur);

799

Dimension tailleEcran = Toolkit.getDefaultToolkit().getScreenSize();

800

LE DVELOPPEUR JAVA 2
int largeurEcran = tailleEcran.width; int hauteurEcran = tailleEcran.height; int xPos = (largeurEcran - largeur) / 2; int yPos = (hauteurEcran - hauteur) / 2; setLocation(xPos, yPos); } void changePlaf(int s) { try { switch (s) { case 1: UIManager.setLookAndFeel ("javax.swing.plaf.metal.MetalLookAndFeel"); break; case 2: UIManager.setLookAndFeel ("com.sun.java.swing.plaf.motif.MotifLookAndFeel"); break; case 3: UIManager.setLookAndFeel ("com.sun.java.swing.plaf.windows.WindowsLookAndFeel"); break; case 4: //UIManager.setLookAndFeel //("javax.swing.plaf.mac.MacLookAndFeel"); break; } SwingUtilities.updateComponentTreeUI(this); } catch (UnsupportedLookAndFeelException e) { System.out.println ("Ce Look & Feel n'est pas disponible sur votre systme."); } catch (IllegalAccessException e) { System.out.println ("Ce Look & Feel n'est pas accessible sur votre systme."); } catch (ClassNotFoundException e) { System.out.println("Ce Look & Feel n'a pas t trouv."); }

CHAPITRE 18 FENTRES ET BOUTONS

801

catch (InstantiationException e) { System.out.println ("Ce Look & Feel ne peut tre instanci."); } catch (Exception e) { System.out.println("Erreur d'excution."); } } } class BD extends JInternalFrame { static int compte = 0; JLabel L1; JButton B1, B2; BD(String s) { super(s, false, true, false, false); addInternalFrameListener(new InternalFrameAdapter() { public void internalFrameClosing(InternalFrameEvent e) { dispose(); }}); Font f = new Font("Dialog", Font.BOLD, 12); getContentPane().setLayout(new GridBagLayout()); GridBagConstraints c = new GridBagConstraints(); L1 = new JLabel(s); L1.setFont(f); c.weightx = 1; c.weighty = 1; c.gridwidth = GridBagConstraints.REMAINDER; c.gridheight = 1; getContentPane().add(L1, c); B1 = new JButton("Rouge"); B1.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { L1.setForeground(new Color(255, 0, 0)); repaint(); }});

802
B1.setFont(f); c.gridwidth = 1; getContentPane().add(B1, c); B2 = new JButton("Bleu");

LE DVELOPPEUR JAVA 2

B2.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { L1.setForeground(new Color(0, 0, 255)); repaint(); }}); B2.setFont(f); c.gridheight = GridBagConstraints.REMAINDER; getContentPane().add(B2, c); setup(); setResizable(false); setBackground(Color.lightGray); setVisible(true); } void setup() { int xB = Math.max(B1.getPreferredSize().width, B2.getPreferredSize().width); int yB = B1.getPreferredSize().height; B1.setPreferredSize(new Dimension(xB, yB)); B1.setMaximumSize(new Dimension(xB, yB)); B1.setMinimumSize(new Dimension(xB, yB)); B2.setPreferredSize(new Dimension(xB, yB)); B2.setMaximumSize(new Dimension(xB, yB)); B2.setMinimumSize(new Dimension(xB, yB)); int x = Math.max(L1.getPreferredSize().width, 2 * xB); int y = L1.getPreferredSize().height + yB; setSize((int)((x + 50) * 1.2), (int)((y + 20) * 1.8)); int w = (10 + 10 * (compte++ % 10)); setLocation(w, w); } }

CHAPITRE 18 FENTRES ET BOUTONS

803

La principale modification apporte au programme consiste en l'ajout d'un menu et d'une mthode permettant de changer le look & feel. Le menu est trs simple :

JMenuItem item11 = new JMenuItem("Metal"); item11.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { changePlaf(1); }}); menu2.add(item11); JMenuItem item12 = new JMenuItem("Motif"); item12.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { changePlaf(2); }}); menu2.add(item12); JMenuItem item13 = new JMenuItem("Windows"); item13.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { changePlaf(3); }}); menu2.add(item13); JMenuItem item14 = new JMenuItem("Macintosh"); item14.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { changePlaf(4); }}); menu2.add(item14); mb.add(menu2);

Chaque article appelle la mthode changePlaf avec un argument numrique compris entre 1 et 4. La mthode changePlaf est plus intressante. Elle

804

LE DVELOPPEUR JAVA 2
comporte un slecteur qui, en fonction de l'argument, active un look & feel. Pour slectionner un look & feel, nous utilisons l'instruction :

UIManager.setLookAndFeel("javax.swing.plaf.motif.MotifLookAndFeel");

Ici, le plaf Motif est slectionn. Une fois la slection effectue, il faut l'appliquer au moyen de l'instruction :

SwingUtilities.updateComponentTreeUI(this);

La mthode updateComponentTreeUI est une mthode statique de la classe SwingUtilities. Elle prend pour argument un container de type Window ou Applet et modifie le look & feel de celui-ci (si c'est possible) et de tous les composants contenus. Ici, nous utilisons l'argument this car nous sommes au niveau de la fentre. Il est cependant tout fait possible d'obtenir le mme rsultat partir de n'importe quel composant en utilisant la mthode SwingUtilities.getRoot() qui renvoie le composant de type Window ou Applet le plus haut dans la hirarchie contenant le composant appelant. Il est ainsi trs facile d'implmenter dans une classe anonyme un changement de look & feel, par exemple :

JMenuItem item11 = new JMenuItem("Metal"); item11.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { UIManager.setLookAndFeel ("javax.swing.plaf.motif.MotifLookAndFeel"); SwingUtilities.updateComponentTreeUI (SwingUtilities.getRoot()); }});

Cependant, il faut tenir compte des exceptions lances par ces instructions.

CHAPITRE 18 FENTRES ET BOUTONS

805

Note : La version actuelle de Java 2 place le look Metal dans le package


javax.swing.plaf.metal et les looks Motif et Windows dans les packages com.sun.java.swing.plaf.motif et com.sun.java.swing.plaf.windows. Il

n'est pas certain que cette disposition soit conserve.

Exemples de composants
Il existe de nombreux autres lments permettant de construire des interfaces utilisateurs. Les boutons de slection offrent la particularit de pouvoir avoir deux types de comportement. En effet, ils peuvent tre associs en groupes l'intrieur desquels un seul bouton peut tre slectionn. Les boutons de slection sont des instances de la classe JCheckBox. Ils peuvent tre crs avec pour paramtres une icne, une chane de caractres et une valeur initiale. Ces boutons permettent une slection exclusive ou non exclusive selon qu'ils sont utiliss de faon indpendante ou associs un conteneur spcial, instance de ButtonGroup. Nous ne pouvons pas dtailler ici tous les composants d'interfaces utilisateurs de Java. Il faudrait pour cela y consacrer un livre entier. Ce livre (consacr essentiellement aux composants Swing) est aujourd'hui en prparation et devrait sortir d'ici quelques mois.) titre d'exemple, nous donnons ici l'exemple d'un programme permettant de slectionner une couleur pour le fond des fentres :
import import import import import import java.awt.*; java.awt.event.*; java.util.*; java.beans.*; javax.swing.*; javax.swing.event.*;

public class ExempleSwing { static AppFrame frame; public static void main( String[] args ) { frame = new AppFrame("Exemple de composants Swing", 640, 480); frame.setVisible(true); } }

806
class AppFrame extends JFrame { int xPos; int yPos; JDesktopPane desktop; Color BCouleur, FCouleur; SelecteurCouleur sc = null;

LE DVELOPPEUR JAVA 2

AppFrame(String s, int l, int h) { super(s); desktop = new JDesktopPane(); getContentPane().add(desktop); xPos = (Background.largeurEcran - l) / 2; yPos = (Background.hauteurEcran - h) / 2; addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); }}); BCouleur = new Color(255, 255, 255); FCouleur = new Color(192, 192, 192); desktop.setBackground(BCouleur); setBounds(xPos, yPos, l, h); JMenu menu = new JMenu("Commandes", true); JMenuItem item1 = new JMenuItem("Couleurs"); item1.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { sc = SelecteurCouleur.create(AppFrame.this); if (!(sc == null)) { desktop.add(sc); try { sc.setSelected(true); } catch (PropertyVetoException ve) {} } }}); menu.add(item1);

CHAPITRE 18 FENTRES ET BOUTONS

807

JMenuItem item2 = new JMenuItem("Ouvrir"); item2.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { BD bd = new BD("Bote de dialogue", AppFrame.this); desktop.add(bd); try { bd.setSelected(true); } catch (PropertyVetoException ve) {} }}); menu.add(item2); menu.add(new JSeparator()); JMenuItem item3 = new JMenuItem("Quitter"); item3.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { System.exit(0); }}); menu.add(item3); JMenuBar mb = new JMenuBar(); mb.add(menu); JMenu menu2 = new JMenu("Look & Feel", true); JMenuItem item11 = new JMenuItem("Metal"); item11.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { changePlaf(1); }}); menu2.add(item11); JMenuItem item12 = new JMenuItem("Motif"); item12.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { changePlaf(2); }}); menu2.add(item12);

808

LE DVELOPPEUR JAVA 2
JMenuItem item13 = new JMenuItem("Windows"); item13.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { changePlaf(3); }}); menu2.add(item13); JMenuItem item14 = new JMenuItem("Macintosh"); item14.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { changePlaf(4); }}); menu2.add(item14); mb.add(menu2); setJMenuBar(mb); } void changePlaf(int s) { try { switch (s) { case 1: UIManager.setLookAndFeel ("javax.swing.plaf.metal.MetalLookAndFeel"); break; case 2: UIManager.setLookAndFeel ("com.sun.java.swing.plaf.motif.MotifLookAndFeel"); break; case 3: UIManager.setLookAndFeel ("com.sun.java.swing.plaf.windows.WindowsLookAndFeel"); break; case 4: //UIManager.setLookAndFeel ("com.sun.java.swing.plaf.mac.MacLookAndFeel"); break; }

CHAPITRE 18 FENTRES ET BOUTONS


SwingUtilities.updateComponentTreeUI(this);

809

} catch (UnsupportedLookAndFeelException e) { System.out.println ("Ce Look & Feel n'est pas disponible sur votre systme."); } catch (IllegalAccessException e) { System.out.println ("Ce Look & Feel n'est pas accessible sur votre systme."); } catch (ClassNotFoundException e) { System.out.println ("Ce Look & Feel n'a pas t trouv."); } catch (InstantiationException e) { System.out.println ("Ce Look & Feel ne peut tre instanci."); } catch (Exception e) { System.out.println("Erreur d'excution."); } } } class BD extends JInternalFrame { static int compte = 0; JLabel L1; JButton B1, B2; JFrame parent; Color couleur; BD(String s, AppFrame p) { super(s, false, true, false, false); parent = p; couleur = p.FCouleur; getContentPane().setBackground(couleur); addInternalFrameListener(new InternalFrameAdapter() { public void internalFrameClosing(InternalFrameEvent e) { dispose(); }});

810

LE DVELOPPEUR JAVA 2
Font f = new Font("Dialog", Font.BOLD, 12); getContentPane().setLayout(new GridBagLayout()); GridBagConstraints c = new GridBagConstraints(); L1 = new JLabel(s); L1.setFont(f); c.weightx = 1; c.weighty = 1; c.gridwidth = GridBagConstraints.REMAINDER; c.gridheight = 1; getContentPane().add(L1, c); B1 = new JButton("Rouge"); B1.setBackground(couleur); B1.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { L1.setForeground(new Color(255, 0, 0)); repaint(); }}); B1.setFont(f); c.gridwidth = 1; getContentPane().add(B1, c); B2 = new JButton("Bleu"); B2.setBackground(couleur); B2.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { L1.setForeground(new Color(0, 0, 255)); repaint(); }}); B2.setFont(f); c.gridheight = GridBagConstraints.REMAINDER; getContentPane().add(B2, c); setup(); setResizable(false); setVisible(true); } void setup() { int xB = Math.max(B1.getPreferredSize().width, B2.getPreferredSize().width);

CHAPITRE 18 FENTRES ET BOUTONS

811

int yB = B1.getPreferredSize().height; B1.setPreferredSize(new Dimension(xB, yB)); B1.setMaximumSize(new Dimension(xB, yB)); B1.setMinimumSize(new Dimension(xB, yB)); B2.setPreferredSize(new Dimension(xB, yB)); B2.setMaximumSize(new Dimension(xB, yB)); B2.setMinimumSize(new Dimension(xB, yB)); int x = Math.max(L1.getPreferredSize().width, 2 * xB); int y = L1.getPreferredSize().height + yB; setSize((int)((x + 50) * 1.2), (int)((y + 20) * 1.8)); int w = (10 + 10 * (compte++ % 10)); setLocation(w, w); } } class SelecteurCouleur extends JInternalFrame { static int ninstances = 0; JLabel C1, C2, C3; JButton B1, B2; JSlider S1, S2, S3; JCheckBox CB1, CB2; ButtonGroup bg; JPanel panel, panel2; int cr, cv, cb, cr0, cv0, cb0; Color precBCouleur, precFCouleur; AppFrame parent; boolean fond = false; boolean contenu = true; public static SelecteurCouleur create(AppFrame p) { if (ninstances == 0) { ninstances++; return new SelecteurCouleur(p); } else { return null; } }

812

LE DVELOPPEUR JAVA 2
private SelecteurCouleur(AppFrame p) { super("Selecteur de couleur", false, true, false, false); addInternalFrameListener(new InternalFrameAdapter() { public void internalFrameClosing(InternalFrameEvent e) { ninstances = 0; dispose(); }}); parent = p; precBCouleur = parent.BCouleur; precFCouleur = parent.FCouleur; cr = precFCouleur.getRed(); cv = precFCouleur.getGreen(); cb = precFCouleur.getBlue(); cr0 = cr; cv0 = cv; cb0 = cb; C1 = new JLabel("Rouge"); C2 = new JLabel("Vert"); C3 = new JLabel("Bleu"); bg = new ButtonGroup(); CB1 = new JCheckBox("Arrire plan", false); CB1.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { cr = cr0; cv = cv0; cb = cb0; changeCouleur(); fond = true; contenu = false; cr = precBCouleur.getRed(); cv = precBCouleur.getGreen(); cb = precBCouleur.getBlue(); S1.setValue(cr); S2.setValue(cv); S3.setValue(cb); cr0 = cr; cv0 = cv; cb0 = cb; }});

CHAPITRE 18 FENTRES ET BOUTONS


CB2 = new JCheckBox("fentres", true); CB2.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { cr = cr0; cv = cv0; cb = cb0; changeCouleur(); fond = false; contenu = true; cr = precFCouleur.getRed(); cv = precFCouleur.getGreen(); cb = precFCouleur.getBlue(); S1.setValue(cr); S2.setValue(cv); S3.setValue(cb); cr0 = cr; cv0 = cv; cb0 = cb; }}); bg.add(CB1); bg.add(CB2);

813

S1 = new JSlider(SwingConstants.HORIZONTAL, 0, 255, cr); S1.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { cr = S1.getValue(); changeCouleur(); }}); S2 = new JSlider(SwingConstants.HORIZONTAL, 0, 255, cv); S2.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { cv = S2.getValue(); changeCouleur(); }}); S3 = new JSlider(SwingConstants.HORIZONTAL, 0, 255, cb); S3.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) {

814

LE DVELOPPEUR JAVA 2
cb = S3.getValue(); changeCouleur(); }}); B1 = new JButton("Appliquer"); B1.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { cr0 = cr; cv0 = cv; cb0 = cb; if (contenu) { precFCouleur = new Color(cr, cv, cb); parent.FCouleur = precFCouleur; changeCouleur(); } }}); B2 = new JButton("Annuler"); B2.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { cr = cr0; cv = cv0; cb = cb0; S1.setValue(cr); S2.setValue(cv); S3.setValue(cb); }}); panel = new JPanel(); panel.add(B1); panel.add(B2); panel2 = new JPanel(); panel2.add(CB1); panel2.add(CB2); getContentPane().setLayout(new GridBagLayout()); GridBagConstraints c = new GridBagConstraints(); c.weighty = 1; c.weightx = 1; c.gridwidth = GridBagConstraints.REMAINDER; getContentPane().add(panel2, c); c.weightx = 0;

CHAPITRE 18 FENTRES ET BOUTONS


c.anchor = GridBagConstraints.EAST; c.gridwidth = GridBagConstraints.RELATIVE; c.gridheight = 1; getContentPane().add(C1, c); c.weightx = 1; c.gridwidth = GridBagConstraints.REMAINDER; c.fill = GridBagConstraints.HORIZONTAL; getContentPane().add(S1, c); c.weightx = 0; c.fill = GridBagConstraints.NONE; c.gridwidth = GridBagConstraints.RELATIVE; c.gridheight = 1; getContentPane().add(C2, c); c.weightx = 1; c.gridwidth = GridBagConstraints.REMAINDER; c.fill = GridBagConstraints.HORIZONTAL; getContentPane().add(S2, c); c.weightx = 0; c.fill = GridBagConstraints.NONE; c.gridwidth = GridBagConstraints.RELATIVE; c.gridheight = 1; getContentPane().add(C3, c); c.weightx = 1; c.gridwidth = GridBagConstraints.REMAINDER; c.fill = GridBagConstraints.HORIZONTAL; getContentPane().add(S3, c); c.fill = GridBagConstraints.NONE; c.anchor = GridBagConstraints.CENTER; c.gridheight = GridBagConstraints.REMAINDER; getContentPane().add(panel, c); setup(); setResizable(false); changeCouleur(); setVisible(true); }

815

void setup() { int xB = Math.max(B1.getPreferredSize().width, B2.getPreferredSize().width);

816

LE DVELOPPEUR JAVA 2
int yB = B1.getPreferredSize().height; B1.setPreferredSize(new Dimension(xB, yB)); B1.setMaximumSize(new Dimension(xB, yB)); B1.setMinimumSize(new Dimension(xB, yB)); B2.setPreferredSize(new Dimension(xB, yB)); B2.setMaximumSize(new Dimension(xB, yB)); B2.setMinimumSize(new Dimension(xB, yB)); int l = Math.max(C1.getPreferredSize().width, 2 * xB); int h = C1.getPreferredSize().height + yB; l = (int)((l + 50) * 1.2); h = (int)((h + 20) * 3); int x = (int)((parent.getSize().width - l) / 2); int y = (int)((parent.getSize().height - l) / 1.8); setBounds(x, y, l, h); } void changeCouleur() { if (fond) { precBCouleur = new Color(cr, cv, cb); parent.BCouleur = precBCouleur; parent.desktop.setBackground(parent.BCouleur); } else { precFCouleur = new Color(cr, cv, cb); setBackground(precFCouleur); panel.setBackground(precFCouleur); panel2.setBackground(precFCouleur); S1.setBackground(precFCouleur); S2.setBackground(precFCouleur); S3.setBackground(precFCouleur); B1.setBackground(precFCouleur); B2.setBackground(precFCouleur); CB1.setBackground(precFCouleur); CB2.setBackground(precFCouleur); } parent.repaint(); } }

CHAPITRE 18 FENTRES ET BOUTONS

817

Ce programme utilise de nombreux lments des programmes prcdents, avec quelques amliorations. (Remarquez que le look & feel s'applique tous les composants lgers, mais pas aux composants lourds.) La fentre principale de lapplication garde donc le look & feel de la plate-forme utilise. On pourrait tre tent dutiliser une JInternalFrame pour la fentre de lapplication, en plaant celle-ci dans une JWindow de mmes dimensions. Toutefois, une JWindow ne possde pas de barre de titre et ne peut ainsi tre dplace, ce qui pose de multiples problmes. Nous utilisons donc une fentre de type JFrame, qui souvre centre sur lcran. La Figure 18.20 montre le rsultat obtenu.

Figure 18.20 : Exemples de composants Swing.

La bote de dialogue affiche un groupe de cases cocher mutuellement exclusives. Les boutons sont crs tout fait normalement en instanciant la classe JCheckBox :

818

LE DVELOPPEUR JAVA 2
CB1 = new JCheckBox("Arrire plan", false);

La valeur false indique ici que le bouton n'est pas coch.

Note : Au moment de sa cration, un groupe peut ne comporter que des boutons non cochs. Une fois qu'un bouton a t coch, il n'est plus possible de le dcocher sans en cocher un autre. Si vous crez une liste d'options et si vous voulez qu'il soit possible de n'en slectionner aucune, vous devez prvoir un bouton pour l'option Aucune. Si la dslection doit tre le rsultat d'une autre action, par exemple dans un formulaire comportant un bouton de remise zro, vous pouvez ne pas afficher le bouton Aucune et l'actionner au moyen de sa mthode doClick().
Le bouton est ajout un groupe cr spcialement en instanciant la classe Ce groupe n'a aucune autre fonction que de rendre la slection exclusive. En particulier, il ne peut pas tre utilis pour afficher en bloc le groupe. Il est cependant tout fait possible d'affecter les boutons un panel afin de les manipuler ensemble. Ils doivent alors tre ajouts individuellement au groupe et au panel, comme nous l'avons fait dans cet exemple :
ButtonGroup. bg = new ButtonGroup(); CB1 = new JCheckBox("Arrire plan", false); . . . CB2 = new JCheckBox("fentres", true); . . . bg.add(CB1); bg.add(CB2); . . . panel2.add(CB1); panel2.add(CB2);

CHAPITRE 18 FENTRES ET BOUTONS

819

Les cases cocher sont des boutons comme les autres et reoivent ce titre un ActionListener, implment ici sous la forme d'une classe anonyme :
B1.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { cr0 = cr; cv0 = cv; cb0 = cb; if (contenu) { precFCouleur = new Color(cr, cv, cb); parent.FCouleur = precFCouleur; changeCouleur(); } }});

Les glissires permettant de faire varier les valeurs de rouge, vert et bleu de la couleur sont des composants d'utilisation trs simple :

S1 = new JSlider(SwingConstants.HORIZONTAL, 0, 255, cr); S1.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { cr = S1.getValue(); changeCouleur(); }});

Ils prennent pour paramtre quatre int : une orientation (verticale ou horizontale), leur limite basse et leur limite haute, ainsi que leur valeur initiale. Ils reoivent un ChangeListener. Celui-ci a la charge d'interroger le composant auquel il appartient afin de connatre la valeur rsultant de la modification. On utilise pour cela la mthode getValue().

820

LE DVELOPPEUR JAVA 2

Rsum
Il reste de trs nombreux composants tudier. Heureusement, leur fonctionnement est trs intuitif. Une fois que vous avez compris le principe, la documentation fournie avec Java suffit pour les mettre en uvre. Au moment o ces lignes sont crites, il n'existe pas de livre en franais sur la bibliothque Swing. Si vous peinez un peu pour trouver les informations recherches, dites-vous que plus un problme se pose avec acuit, plus la solution est profitable. Et si vous tes vraiment bloqu, attendez la sortie de notre prochain livre sur le sujet !

Chapitre 19 : Le graphisme

Le graphisme

19

ANS LE CHAPITRE PRCDENT, NOUS AVONS TUDI UN CERtain nombre de composants de l'interface utilisateur de Java. Ces composants permettent, en les assemblant, de construire l'interface graphique d'un programme. Cependant, il est des cas o le programmeur doit ajouter des lments ne pouvant pas tre obtenus l'aide des composants de l'interface. Il dispose alors de trois moyens pour parvenir au rsultat recherch : les primitives graphiques, le texte et les images.

Les primitives graphiques


Les primitives graphiques sont des mthodes qui permettent de tracer des lments graphiques simples tels que lignes droites, rectangles, ellipses,

822

LE DVELOPPEUR JAVA 2
polygones ou arcs de cercle. Le programmeur utilise ces primitives pour composer un dessin complexe. Ces primitives tant des mthodes, vous vous doutez qu'elles appartiennent une classe ou une instance de classe.

Les objets graphiques


Toutes les primitives permettant d'obtenir des formes graphiques quelconques doivent tre invoques sur une instance de la classe Graphics ou de la classe Graphics2D. Cette instance est appele contexte graphique. Cependant, ces classes ne peuvent pas tre instancies car il s'agit de classes abstraites. Il existe en fait deux moyens principaux pour obtenir un contexte graphique. L'un consiste invoquer la mthode getGraphics sur un objet possdant un contexte graphique. L'autre consiste utiliser le paramtre pass la mthode paint. La classe Graphics fournit les primitives disponibles jusqu' la version 1.1 de Java. La classe Graphics2D fournit les nouvelles primitives de la version 2.

Un composant pour les graphismes


De nombreux composants de l'interface utilisateur ont un contexte graphique. Il existe toutefois un composant prvu spcialement pour fournir son contexte graphique. Il s'agit de la classe Canvas. La premire chose faire pour utiliser des fonctions graphiques est donc de crer une instance de Canvas. Le programme suivant utilise des lments des programmes des chapitres prcdents pour crer un Canvas :
import java.awt.*; import java.awt.event.*; import com.sun.java.swing.*; public class Graphisme { static Tableau frame; public static void main( String[] args ) { frame = new Tableau(); frame.setVisible(true); } }

CHAPITRE 19 LE GRAPHISME

823

class Tableau extends JFrame { static final Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); static final int largeurEcran = screenSize.width; static final int hauteurEcran = screenSize.height + 2; int l = 640; int h = 480; public Tableau() { super(); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); }}); setBounds ((largeurEcran - l) / 2, (hauteurEcran - h) / 2, l, h); getContentPane().setBackground(new Color(255, 255, 255)); } }

Ce programme affiche simplement une fentre vide sur un fond uni. Pour utiliser les mthodes graphiques, il nous faut un support. Nous pouvons utiliser pour cela un canvas. Un canvas est un composant offrant simplement une surface rectangulaire sur laquelle nous allons pouvoir dessiner. Pour pouvoir dessiner sur un canvas, il faut :

Obtenir un handle pour son contexte graphique. tre notifi du moment auquel le canvas est affich afin d'ajouter au
dessin du canvas le trac de nos primitives. Comme tout composant, un canvas est affich en invoquant sa mthode paint. Cette mthode est appele automatiquement dans les cas suivants :

Le composant doit tre affich pour la premire fois.

824
lment.

LE DVELOPPEUR JAVA 2

Le composant doit tre raffich parce qu'il a t masqu par un autre Le composant doit tre raffich parce qu'il a t masqu par programme (setVisible(false) puis setVisible(true)).
repaint().

Une demande de mise jour a t excute en invoquant sa mthode


La mthode paint() d'un canvas est trs simple :
public void paint(Graphics g) { Graphics2D g2 = (Graphics2D)g; g2.setColor(getBackground()); g2.fillRect(0, 0, width, height); }

Cette mthode utilise la primitive graphique fillRect pour dessiner un rectangle plein de la couleur dtermin par getBackground et de dimensions gales celles du canvas lui-mme. Nous n'aurons donc la possibilit de dessiner autre chose qu'un rectangle monochrome qu'en redfinissant la mthode paint.

Note : Le sous-casting de Graphics en Graphics2D n'est pas ici obligatoire

car nous n'utilisons que des mthodes dfinies dans Graphics. Cependant, l'effectuer systmatiquement nous permet d'utiliser indiffremment (grce au polymorphisme) les mthodes de Graphics et de Graphics2D.

Le programme suivant cre une nouvelle classe tendant Canvas et traant un rectangle noir :
import java.awt.*; import java.awt.event.*; import com.sun.java.swing.*; public class Graphisme2 { static Tableau frame;

CHAPITRE 19 LE GRAPHISME
public static void main( String[] args ) { frame = new Tableau(); frame.setVisible(true); } } class Tableau extends JFrame {

825

static final Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); static final int largeurEcran = screenSize.width; static final int hauteurEcran = screenSize.height + 2; int l = 640; int h = 480; public Tableau() { super(); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); }}); setBounds ((largeurEcran - l) / 2, (hauteurEcran - h) / 2, l, h); getContentPane().setBackground(new Color(255, 255, 255)); getContentPane().add(new Canvas() { public void paint(Graphics g) { Graphics2D g2 = (Graphics2D)g; int l2 = getSize().width; int h2 = getSize().height; g2.setColor(new Color(0, 0, 0)); g2.drawRect((int)(l2 / 4), (int)(h2 / 4), (int)(l2 / 2), (int)(h2 / 2)); }}); } }

826

LE DVELOPPEUR JAVA 2

Figure 19.1 : Affichage dun rectangle.

Ce programme affiche le rsultat de la Figure19.1 : Nous aurions pu tout aussi bien redfinir la mthode paint de notre JFrame, de la faon suivante :
import java.awt.*; import java.awt.event.*; import com.sun.java.swing.*; public class Graphisme3 { static Tableau frame; public static void main( String[] args ) { frame = new Tableau();

CHAPITRE 19 LE GRAPHISME
frame.setVisible(true); } }

827

class Tableau extends JFrame { static final Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); static final int largeurEcran = screenSize.width; static final int hauteurEcran = screenSize.height + 2; int l = 640; int h = 480; public Tableau() { super(); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); }}); setBounds ((largeurEcran - l) / 2, (hauteurEcran - h) / 2, l, h); getContentPane().setBackground(new Color(255, 255, 255)); } public void paint(Graphics g) { Graphics2D g2 = (Graphics2D)g; int l2 = getSize().width; int h2 = getSize().height; g2.setColor(new Color(0, 0, 0)); g2.drawRect((int)(l2 / 4), (int)(h2 / 4), (int)(l2 / 2), (int)(h2 / 2)); } }

Cependant, nous avons l un petit problme. En effet, le fond de l'cran est gris au lieu d'tre blanc comme spcifi par la ligne :

828

LE DVELOPPEUR JAVA 2
getContentPane().setBackground(new Color(255, 255, 255));

En effet, nous avons redfini la mthode paint de la fentre. Or, c'est dans cette mthode que la couleur de fond indique par setBackground est applique. Si nous voulons dessiner directement sur la fentre, nous devons, au choix, prendre notre charge l'affichage de celle-ci, ou nous en passer. C'est pourquoi il est prfrable d'utiliser un support spcial dont la mthode paint ne fait normalement pratiquement rien. La classe Canvas est idale pour cela. Il faut cependant noter qu'un canvas n'est pas transparent et masque ce qui se trouve derrire. Remarquez que la redfinition de la mthode paint rsout le problme de l'obtention d'un contexte graphique. En effet, chaque fois que cette mthode est invoque, le contexte graphique de l'objet afficher lui est pass en paramtre.

Les diffrentes primitives


La classe Graphics dispose des primitives graphiques suivantes :

void drawRect(int

x, int y, int l, int h)

Trace un rectangle vide de largeur l et de hauteur h en utilisant la couleur courante. L'angle suprieur gauche est situ la position x, y.

void fillRect(int

x, int y, int l, int h)

Remplit un rectangle de largeur l et de hauteur h en utilisant la couleur courante. L'angle suprieur gauche est situ la position x, y.

void drawRoundRect(int x, int y, int l, int h, int la, int ha)


Trace un rectangle de largeur l et de hauteur h en utilisant la couleur courante. L'angle suprieur gauche est situ la position x, y. La largeur des arcs de cercle est la et leur hauteur est ha.

void fillRoundRect(int x, int y, int l, int h, int la, int ha)

CHAPITRE 19 LE GRAPHISME

829

Remplit un rectangle de largeur l et de hauteur h en utilisant la couleur courante. L'angle suprieur gauche est situ la position x, y. La largeur des arcs de cercle est la et leur hauteur est ha.

void draw3DRect(int

x, int y, int l, int h, boolean relev)

Trace un rectangle en relief de largeur l et de hauteur h en utilisant la couleur courante. L'angle suprieur gauche est situ la position x, y. relev indique si le rectangle est saillant (true) ou en creux (false).

void fill3DRect(int

x, int y, int l, int h, boolean relev)

Trace un rectangle en relief de largeur l et de hauteur h en utilisant la couleur courante. L'angle suprieur gauche est situ la position x, y. relev indique si le rectangle est saillant (true) ou en creux (false).

void drawOval(int

x, int y, int l, int h)

Trace un ovale inscrit dans un rectangle de largeur l et de hauteur h en utilisant la couleur courante. L'angle suprieur gauche du rectangle est situ la position x, y. (Le rectangle sert de rfrence et n'est pas trac.)

void fillOval(int

x, int y, int l, int h)

Remplit un ovale inscrit dans un rectangle de largeur l et de hauteur h en utilisant la couleur courante. L'angle suprieur gauche du rectangle est situ la position x, y. (Le rectangle sert de rfrence et n'est pas trac.)

void drawArc(int x, int y, int l, int h, int angleInitial, int


angle)

Trace un arc d'ellipse dans un rectangle de largeur l et de hauteur h en utilisant la couleur courante. L'angle suprieur gauche du rectangle est situ la position x, y. (Le rectangle sert de rfrence et n'est pas trac.) L'arc est trac partir de l'angle angleInitial, mesur en degrs partir de la position trigonomtrique 0 (3 heures en langage militaire). Les angles sont mesurs pour un arc inscrit dans un carr

830

LE DVELOPPEUR JAVA 2

drawArc(x, y, 100, 100, 45, 135)

135

45

drawArc(x, y, 200, 50, 45, 135) 135 45 0

Figure 19.2 : Java mesure les angles darcs dune faon un peu particulire.

qui serait ensuite dform en un rectangle. Cela a l'air trs compliqu dit de cette faon, mais c'est trs simple sur un croquis, comme on peut le voir sur la Figure 19.2.

void fillArc(int x, int y, int l, int h, int angleInitial, int


angle)

Remplit un arc d'ellipse dans un rectangle de largeur l et de hauteur h en utilisant la couleur courante. L'angle suprieur gauche du rectangle est situ la position x, y. (Le rectangle sert de rfrence et n'est pas trac.) L'arc est trac partir de l'angle angleInitial, mesur en degrs partir de la position trigonomtrique 0.

CHAPITRE 19 LE GRAPHISME

831
xPoints, int[] yPoints, int nPoints)

void drawPolygon(int[]

Trace un polygone ferm dfini par les tableaux de coordonnes xPoints et yPoints en utilisant la couleur courante. nPoints indique le nombre de points prendre en compte.

void drawPolygon(Polygon

p)

Trace un polygone ferm dfini par l'objet p en utilisant la couleur courante. Un objet de type Polygon est cr avec les paramtres int[] xPoints, int[] yPoints et int nPoints.

void fillPolygon(int[]

xPoints, int[] yPoints, int nPoints)

Remplit un polygone ferm dfini par les tableaux de coordonnes xPoints et yPoints en utilisant la couleur courante. nPoints indique le nombre de points prendre en compte.

void fillPolygon(Polygon void drawLine(int void clearRect(int

p)

Remplit un polygone ferm dfini par l'objet p en utilisant la couleur courante.


x1, int y1, int x2, int y2)

Trace une ligne reliant les points de coordonnes x1, x2 et y1, y2 en utilisant la couleur courante.
x, int y, int width, int height)

Remplit un rectangle vide de largeur l et de hauteur h en utilisant la couleur du fond. L'angle suprieur gauche est situ la position x, y. La classe graphics2D dispose (entre autres) des primitives suivantes :

void

draw(Shape s)

Trace le contour d'une forme s en utilisant les attributs graphiques courants.

832

LE DVELOPPEUR JAVA 2

void

fill(Shape s)

Trace le contour d'une forme s en utilisant les attributs graphiques courants.

Color getBackground() void setBackground(Color Color getStroke() void setStroke(Stroke Color getPaint() void setPaint(Paint

color)

Dterminent la couleur du fond.

s)

Dterminent le type de ligne utilis pour le trac.

p)

Dterminent le type de remplissage.

AffineTransform getTransform() void setTransform(AffineTransform

Tx)

Dterminent le type de transformation appliqu. Il existe de nombreuses autres primitives dont vous trouverez la description dans la documentation en ligne de l'API de Java. Les formes (Shape) susceptibles d'tre traces l'aide des primitives de Graphics2D sont les suivantes :

Arc2D (arc de cercle) Area (union, intersection ou soustraction de formes simples) CubicCurve2D (courbe cubique ou courbe de Bziers) Dimension2D Ellipse2D

CHAPITRE 19 LE GRAPHISME

833

GeneralPath (combinaison de lignes droites et courbes) Line2D Point2D QuaCurve2D (courbe quadratique) Rectangle2D RectangularShape RoundRectangle2D
A titre d'exemple, nous allons crire un programme permettant de tracer un rectangle au contour pointill l'aide de la souris.

import import import import import import

java.awt.*; java.awt.event.*; com.sun.java.swing.*; java.beans.*; com.sun.java.swing.event.*; java.awt.geom.*;

public class Graphisme4 { static Tableau frame; public static void main( String[] args ) { frame = new Tableau(); frame.setVisible(true); } } class Tableau extends JFrame { static final Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); static final int largeurEcran = screenSize.width; static final int hauteurEcran = screenSize.height + 2;

834

LE DVELOPPEUR JAVA 2
int l = 640; int h = 480; JDialog sc = null; Color BCouleur = new Color(255, 255, 255); Color FCouleur = new Color(127, 127, 127); Tableau moi; Canvas dessin; Container pane; JButton couleur; JCheckBox plein, vide, relief, plat; ButtonGroup bg3D, bgContour; boolean is3D, isContour; JPanel panelHaut; int x1, y1, x2, y2; public Tableau() { super(); pane = getContentPane(); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); }}); setBackground(BCouleur); moi = this; setBounds ((largeurEcran - l) / 2, (hauteurEcran - h) / 2, l, h); pane.setBackground(BCouleur); bgContour = new ButtonGroup(); bg3D = new ButtonGroup(); couleur = new JButton("Couleur"); couleur.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { sc = SelecteurCouleur.create(moi); }});

CHAPITRE 19 LE GRAPHISME
vide = new JCheckBox("Contour", false); vide.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { isContour = true; }}); plein = new JCheckBox("Fond", true); plein.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { isContour = false; }}); plein.setPreferredSize(vide.getPreferredSize()); plat = new JCheckBox("2D", true); plat.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { is3D = false; }}); plat.setPreferredSize(vide.getPreferredSize()); relief = new JCheckBox("3D", false); relief.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { is3D = true; }}); relief.setPreferredSize(vide.getPreferredSize()); bgContour.add(vide); bgContour.add(plein); bg3D.add(plat); bg3D.add(relief); panelHaut = new JPanel(); panelHaut.add(vide); panelHaut.add(plein); panelHaut.add(plat); panelHaut.add(relief); panelHaut.add(couleur); pane.add(panelHaut, BorderLayout.NORTH);

835

836

LE DVELOPPEUR JAVA 2
dessin = new Canvas() { public void paint(Graphics g) { Graphics2D g2 = (Graphics2D)g; float[] motif = {5.0f}; BasicStroke pointill = new BasicStroke(2.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 5.0f, motif, 0.0f); if (x2 == 0) return; int x = Math.min(x1, x2); int y = Math.min(y1, y2); int l = Math.max(x1, x2) - x; int h = Math.max(y1, y2) - y; g.setColor(FCouleur); if (is3D) { if (isContour) { g.draw3DRect(x, y, l, h, true); } else { g.fill3DRect(x, y, l, h, true); } } else { if (isContour) { g2.setStroke(pointill); g2.draw(new Rectangle2D.Float (x, y, l, h)); } else { g.fillRect(x, y, l, h); } } }}; dessin.addMouseListener(new MouseAdapter() { public void mousePressed(MouseEvent e) { x1 = e.getX(); y1 = e.getY(); x2 = 0;

CHAPITRE 19 LE GRAPHISME
y2 = 0; }}); dessin.addMouseMotionListener(new MouseMotionAdapter() { public void mouseDragged(MouseEvent e) { x2 = e.getX(); y2 = e.getY(); dessin.repaint(); }}); pane.add(dessin, BorderLayout.CENTER); } } class SelecteurCouleur extends JDialog { static int nInstances = 0; JLabel C1, C2, C3; JButton B1, B2; JSlider S1, S2, S3; JPanel panel, panel2; int cr, cv, cb, cr0, cv0, cb0; Color precFCouleur; Tableau parent; Sample sample; public static SelecteurCouleur create(Tableau t) { if (nInstances == 0) { nInstances++; return new SelecteurCouleur(t); } else { return null; } } private SelecteurCouleur(Tableau t) { super(t, "Selecteur de couleur", true); parent = t; setDefaultCloseOperation(DISPOSE_ON_CLOSE);

837

838

LE DVELOPPEUR JAVA 2
addWindowListener(new WindowAdapter() { public void windowClosed(WindowEvent e) { nInstances = 0; }}); precFCouleur = parent.FCouleur; cr = precFCouleur.getRed(); cv = precFCouleur.getGreen(); cb = precFCouleur.getBlue(); cr0 = cr; cv0 = cv; cb0 = cb; C1 = new JLabel("Rouge"); C2 = new JLabel("Vert"); C3 = new JLabel("Bleu"); S1 = new JSlider(SwingConstants.HORIZONTAL, 0, 255, cr); S1.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { cr = S1.getValue(); changeCouleur(); }}); S2 = new JSlider(SwingConstants.HORIZONTAL, 0, 255, cv); S2.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { cv = S2.getValue(); changeCouleur(); }}); S3 = new JSlider(SwingConstants.HORIZONTAL, 0, 255, cb); S3.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { cb = S3.getValue(); changeCouleur(); }}); B1 = new JButton("Appliquer");

CHAPITRE 19 LE GRAPHISME
B1.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { cr0 = cr; cv0 = cv; cb0 = cb; precFCouleur = new Color(cr, cv, cb); parent.FCouleur = precFCouleur; changeCouleur(); parent.dessin.repaint(); dispose(); }}); B2 = new JButton("Annuler"); B2.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { cr = cr0; cv = cv0; cb = cb0; S1.setValue(cr); S2.setValue(cv); S3.setValue(cb); dispose(); }}); panel = new JPanel(); panel.add(B1); panel.add(B2); panel2 = new JPanel(); sample = new Sample(); panel2.add(sample); getContentPane().setLayout(new GridBagLayout()); GridBagConstraints c = new GridBagConstraints(); c.weighty = 1; c.weightx = 1; c.gridwidth = GridBagConstraints.REMAINDER; getContentPane().add(panel2, c); c.weightx = 0; c.anchor = GridBagConstraints.EAST; c.gridwidth = GridBagConstraints.RELATIVE;

839

840

LE DVELOPPEUR JAVA 2
c.gridheight = 1; getContentPane().add(C1, c); c.weightx = 1; c.gridwidth = GridBagConstraints.REMAINDER; c.fill = GridBagConstraints.HORIZONTAL; getContentPane().add(S1, c); c.weightx = 0; c.fill = GridBagConstraints.NONE; c.gridwidth = GridBagConstraints.RELATIVE; c.gridheight = 1; getContentPane().add(C2, c); c.weightx = 1; c.gridwidth = GridBagConstraints.REMAINDER; c.fill = GridBagConstraints.HORIZONTAL; getContentPane().add(S2, c); c.weightx = 0; c.fill = GridBagConstraints.NONE; c.gridwidth = GridBagConstraints.RELATIVE; c.gridheight = 1; getContentPane().add(C3, c); c.weightx = 1; c.gridwidth = GridBagConstraints.REMAINDER; c.fill = GridBagConstraints.HORIZONTAL; getContentPane().add(S3, c); c.fill = GridBagConstraints.NONE; c.anchor = GridBagConstraints.CENTER; c.gridheight = GridBagConstraints.REMAINDER; getContentPane().add(panel, c); setup(); setResizable(false); changeCouleur(); setVisible(true); } void setup() { int xB = Math.max(B1.getPreferredSize().width, B2.getPreferredSize().width); int yB = B1.getPreferredSize().height; B1.setPreferredSize(new Dimension(xB, yB));

CHAPITRE 19 LE GRAPHISME

841

B1.setMaximumSize(new Dimension(xB, yB)); B1.setMinimumSize(new Dimension(xB, yB)); B2.setPreferredSize(new Dimension(xB, yB)); B2.setMaximumSize(new Dimension(xB, yB)); B2.setMinimumSize(new Dimension(xB, yB)); int l = Math.max(C1.getPreferredSize().width, 2 * xB); int h = C1.getPreferredSize().height + yB; l = (int)((l + 50) * 1.2); h = (int)((h + 20) * 3); int x = (int)((parent.getSize().width - l) / 2) + parent.getLocation().x; int y = (int)((parent.getSize().height - l) / 1.1) + parent.getLocation().y; setBounds(x, y, l, h); } void changeCouleur() { precFCouleur = new Color(cr, cv, cb); sample.setForeground(precFCouleur); sample.repaint(); } } class Sample extends Canvas { Sample() { setSize(100,50); } public void paint(Graphics g) { g.fillRect(10, 10, getSize().width - 20, getSize().height - 20); } }

La partie intressante de ce programme est celle qui trace les rectangles. Au dpart, les coordonnes des angles opposs du rectangle valent 0. Le listener de souris initialise les coordonnes du premier angle x1, y1 lorsque le bouton de la souris est press :

842

LE DVELOPPEUR JAVA 2
dessin.addMouseListener(new MouseAdapter() { public void mousePressed(MouseEvent e) { x1 = e.getX(); y1 = e.getY(); x2 = 0; y2 = 0; }});

En mme temps, les coordonnes de l'angle oppos sont rinitialises. Lorsque la souris est dplace avec le bouton enfonc, le listener de mouvement de souris initialise les coordonnes de l'angle oppos et redessine le canvas :
dessin.addMouseMotionListener(new MouseMotionAdapter() { public void mouseDragged(MouseEvent e) { x2 = e.getX(); y2 = e.getY(); dessin.repaint(); }});

La mthode paint du canvas n'effectue un trac que si le deuxime angle du rectangle a t initialis. Elle dtermine alors les coordonnes de l'angle suprieur gauche et de l'angle infrieur droit en comparant les valeurs x1 et x2, d'une part, y1 et y2 d'autre part. Il est ainsi possible de tracer le rectangle en commenant par un angle quelconque :
if (x2 == 0) return; int x = Math.min(x1, int y = Math.min(y1, int l = Math.max(x1, int h = Math.max(y1,

x2); y2); x2) - x; y2) - y;

La couleur est alors applique et le rectangle trac en fonction des valeurs slectionnes au moyen des boutons :

CHAPITRE 19 LE GRAPHISME
g.setColor(FCouleur); if (is3D) { if (isContour) { g.draw3DRect(x, y, l, h, true); } else { g.fill3DRect(x, y, l, h, true); } } else { if (isContour) { g2.setStroke(pointill); g2.draw(new Rectangle2D.Float (x, y, l, h)); } else { g.fillRect(x, y, l, h); } }

843

Les rectangles sont tracs au moyen des primitives de la classe Graphics, sauf dans le cas du contour d'un rectangle en deux dimensions. On utilise alors la mthode draw de la classe Graphics2 pour tracer un rectangle pointill. La mthode setStroke est pralablement appele pour dfinir le type de ligne. Elle prend pour paramtre l'objet pointill qui est une instance de BasicStroke cre de la faon suivante :
float[] motif = {5.0f}; BasicStroke pointill = new BasicStroke(2.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 5.0f, motif, 0.0f);

Toutes les valeurs doivent tre des float, ce qui explique l'utilisation de la syntaxe 5.0f. Le premier paramtre du constructeur de BasicStroke dsigne l'paisseur de la ligne. Le deuxime et le troisime indiquent respectivement le type d'extrmit et le type d'angle. Le quatrime paramtre indi-

844

LE DVELOPPEUR JAVA 2
que la projection maximale des angles. Le cinquime paramtre est un tableau indiquant le motif utilis pour les pointills. Ici, nous n'utilisons qu'une seule valeur pour indiquer que la ligne est compose de tirets et d'espaces d'gales valeurs. Si vous voulez un schma plus complexe, essayez, par exemple :

float[] motif = {10.0f, 3.0f, 1.0f, 3.0f}

Le dernier paramtre indique le dcalage appliquer pour le dpart des pointills. Notez que nous avons utilis l'instruction :
import java.awt.geom.*;

pour importer la classe Rectangle2D. Cette classe est une classe abstraite qui ne peut pas tre instancie directement. Nous avons choisi d'instancier Rectangle2D.Float qui est une classe interne qui tend Rectangle2D. Il existe aussi une classe Rectangle2D.Double. La seule diffrence entre ces deux classes est que le constructeur de la premire prend quatre Float pour paramtres (coordonne x et y, largeur et hauteur) alors que la seconde prend des Double. Chaque nouveau trac efface le prcdent car les objets tracs ne sont pas mmoriss. Si nous souhaitions ajouter les tracs les uns aux autres, il nous suffirait de stocker les lments dans une structure et de retracer chaque lment dans la mthode paint. Nous pourrions utiliser pour cela un vecteur dans la mesure o l'ordre de trac est immuable. Si nous souhaitons pouvoir modifier facilement l'ordre de trac des lments, nous pouvons opter pour une liste lie.

L'affichage du texte
Jusqu'ici, nous n'avons affich du texte que sur la sortie standard, ou au moyen de composants prenant une chane de caractres comme paramtre.

CHAPITRE 19 LE GRAPHISME

845

maxAscent ascent Origine Ligne de base descent maxDescent

ditions javanaises

Figure 19.3 : Le texte est positionn par rapport la ligne de base.

Nous pouvons galement afficher du texte l'aide de primitives. La classe Graphics dispose des primitives suivantes pour l'affichage du texte :

void

drawString(String str, int x, int y)

Affiche la chane de caractres str la position x, y. Cette position correspond la ligne de base du premier caractre affich, comme indiqu sur la Figure 19.3.

void void

drawBytes(byte[] donnes, int d, int l, int x, int y) drawChars(char[] donnes, int d, int l, int x, int y)

Affiche l lments du tableau donnes en commenant la position d. L'affichage est effectu aux coordonnes x, y.

void

setFont(Font font)

Dtermine la police de caractres qui sera utilise pour les prochains affichages.

Font

getFont(Font font)

Renvoie la police courante.

Les polices de caractres


Pour afficher du texte, il faut disposer d'une police de caractres. Une police de caractres peut tre cre en instanciant la classe Font au moyen de son constructeur :

846

LE DVELOPPEUR JAVA 2
Font(String nom, int style, int taille)

est le nom de la police de caractres. Les polices de caractres disponibles diffrent d'un environnement un autre. Afin d'assurer une portabilit maximale, il est conseill de se limiter aux polices suivantes :

Nom

Serif SansSerif Monospaced Dialog


Il s'agit l de polices gnriques qui sont remplaces au moment de l'excution du programme par une police disponible sur le systme, par exemple :

Serif SansSerif Monospaced

Times New Roman Arial Courier New

Windows

Macintosh et Unix
Times Helvetica Courier

Tableau 19.1 : Correspondance des polices de caractres gnriques.

Les autres paramtres du constructeur de la classe Font peuvent prendre les valeurs suivantes :
style

Font.PLAIN (normal) Font.BOLD (gras) Font.ITALIC (italique) Font.BOLD | Font.ITALIC (gras et italique)

CHAPITRE 19 LE GRAPHISME
taille

847

: une valeur quelconque indiquant la taille des caractres en points.

Note : Il s'agit l du point typographique qui n'a rien voir avec les pixels
de l'affichage. La taille en points donne une ide approximative de l'encombrement vertical des caractres, ce qui est diffrent de leur hauteur. Ainsi, deux polices de mme taille peuvent prsenter des hauteurs de caractres diffrentes. C'est le cas, en particulier, des polices Times et Helvetica. Les mots Times et Helvetica sont ici affichs avec la mme taille (11 points). Une classe particulirement importante pour l'utilisation des polices de caractres est la classe FontMetrics, qui contient de nombreuses mthodes permettant d'obtenir des informations sur une police de caractres. Une instance de cette classe peut tre cre en passant pour paramtre son constructeur une instance de Font. La classe FontMetrics comporte, entre autres, les mthodes suivantes :

int int int int int

charWidth(char car) charWidth(int car)

Renvoie l'encombrement horizontal du caractre.


bytesWidth(byte[] donnes, int dpart, int longueur) charsWidth(char[] data, int off, int len)

Renvoie l'encombrement horizontal total des caractres.


getAscent()

Renvoie la hauteur des caractres hauts, comme indiqu sur la Figure 19.4. Les caractres haut sont les capitales (non accentues) et les lettres telles que b, d, f, h, etc.

int

getMaxAscent()

Renvoie la hauteur maximale des caractres, c'est--dire, par exemple, celle des capitales accentues.

848

LE DVELOPPEUR JAVA 2

maxAscent ascent Origine Ligne de base descent maxDescent

ditions javanaises
width

ascent Ligne de base descent ascent Ligne de base descent

ditions javanaises ditions javanaises


leading stringWidth

Figure 19.4 : Les mtriques des polices de caractres.

int int

getHeight()

Renvoie la hauteur normale d'une ligne de texte.


getDescent()

Renvoie la hauteur des jambages bas, comme indiqu sur la figure prcdente.

int int

getMaxDescent()

Renvoie la hauteur maximale des jambages bas.


Leading()

Renvoie la hauteur qui spare les jambages bas d'une ligne (descent) des jambages haut de la ligne suivante (ascent). Il s'agit donc l de

CHAPITRE 19 LE GRAPHISME

849

l'interligne au sens typographique du terme (l'espace ajout entre les lignes et non la distance sparant une ligne de la suivante).

int

stringWidth(String str)

Renvoie l'encombrement horizontal d'une chane de caractres dans la police utilise pour crer l'objet FontMetrics. La classe FontMetrics contient plusieurs autres mthodes getStringBounds qui permet d'obtenir le rectangle dlimitant combrement d'une chane de caractres. titre d'exemple, nous allons raliser un composant permettant d'afficher du texte en relief, pour simuler l'effet obtenu avec une pince Dymo. (Pour ceux qui ne connaissent pas la marque, il s'agit de ces petites bandes autocollantes de plastique color sur lesquelles un texte peut tre emboss l'aide d'une pince. L'embossage tire le plastique qui reste marqu du texte en relief blanc.) dont l'en-

import java.awt.*; import java.awt.event.*; import com.sun.java.swing.*; public class TexteRelief { static Tableau frame; public static void main( String[] args ) { frame = new Tableau(); frame.setVisible(true); } } class Tableau extends JFrame { static final Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); static final int largeurEcran = screenSize.width; static final int hauteurEcran = screenSize.height + 2; int l = 200; int h = 100;

850

LE DVELOPPEUR JAVA 2
Color BCouleur = new Color(128, 144, 175); Container pane; public Tableau() { super(); pane = getContentPane(); pane.setLayout(new FlowLayout()); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); }}); setBackground(BCouleur); setBounds ((largeurEcran - l) / 2, (hauteurEcran - h) / 2, l, h); pane.setBackground(BCouleur); Label3D label3D = new Label3D("Texte affich en relief", 16); pane.add(label3D); } }

class Label3D extends Canvas { int l = 0, h = 0, xt = 0, yt = 0, lt = 0; Font police; FontMetrics mtriques; Color fCouleurC = new Color(255, 255, 255), fCouleurF = new Color(63, 63, 63), String texte;

CHAPITRE 19 LE GRAPHISME
Label3D(String s, int taille) { police = new Font("SansSerif", Font.BOLD, taille); mtriques = getFontMetrics(police); texte = s; lt = mtriques.stringWidth(texte); h = mtriques.getHeight() + mtriques.getLeading(); yt = mtriques.getAscent(); l = lt + 2; setSize(l, h); } public void paint(Graphics g) { g.setFont(police); g.setColor(fCouleurF); g.drawString(texte, (xt + 1), (yt + 1)); g.setColor(fCouleurC); g.drawString(texte, xt, yt); } }

851

La Figure 19.5 montre le rsultat affich par ce programme.

Figure 19.5 : Le texte affich avec un effet de relief.

La partie intressante du programme est videmment la classe Label3D. Cette classe tend la classe Canvas. Son constructeur prend deux paramtres : une chane de caractres reprsentant le texte afficher et une valeur entire indiquant la taille de caractres employer. Une police de caractres est tout d'abord cre :

852

LE DVELOPPEUR JAVA 2
police = new Font("SansSerif", Font.BOLD, taille);

puis une instance de FontMetrics est obtenue au moyen de la mthode getFontMetrics(police). Cette mthode est disponible dans de nombreuses classes, et en particulier dans la classe Component dont drive notre classe par l'intermdiaire de Canvas :
mtriques = getFontMetrics(police);

Les diffrentes dimensions ncessaires sont obtenues partir de cette instance :


lt = mtriques.stringWidth(texte); h = mtriques.getHeight() + mtriques.getLeading(); yt = mtriques.getAscent(); l = lt + 2;

lt reprsente la longueur du texte dans la police choisie, ce qui n'appelle pas de commentaires particuliers. En revanche, le calcul de h, reprsentant la hauteur du composant, et de yt, qui reprsente la coordonne verticale du texte est un peu plus complexe. h est obtenu en additionnant la hauteur du texte et l'interligne, de faon obtenir un espace suffisant. yt est en fait gal la hauteur des jambages hauts, car il s'agit de la position de la ligne de base du texte compte depuis le haut du composant qui le contient. Si cela n'est pas clair, reportez-vous aux figures prcdentes.

La dernire ligne ajoute deux pixels la largeur du texte pour obtenir la largeur du composant. Il est ncessaire que le composant soit un peu plus large que le texte en raison du dcalage cr pour simuler un effet d'ombre. La mthode paint est trs simple :
public void paint(Graphics g) { g.setFont(police);

CHAPITRE 19 LE GRAPHISME
g.setColor(fCouleurF); g.drawString(texte, (xt + 1), (yt + 1)); g.setColor(fCouleurC); g.drawString(texte, xt, yt); }

853

La premire ligne slectionne la police choisie. La deuxime ligne slectionne la couleur fonce utilise pour l'ombre du texte. La troisime ligne affiche le texte avec un dcalage de 1 point vers la droite et vers le bas. Les deux lignes suivantes affichent le mme texte en blanc et sans dcalage. L'ensemble donne un effet de relief en simulant un clairage provenant de l'angle suprieur gauche.

Les images
Un autre aspect du graphisme consiste afficher des images. En Java, l'affichage d'images s'effectue trs simplement l'aide de la primitive drawImage. Cette primitive possde plusieurs formes, dont la plus simple est :
boolean drawImage(Image img, int x, int y, ImageObserver observer)

img est l'image afficher. x et y sont les coordonnes de l'angle suprieur gauche de l'image. observer est une instance d'ImageObserver. Nous verrons plus loin quoi servent les ImageObserver. Pour l'instant, sachez qu'il s'agit d'une interface implmente, entre autres, par la classe Component. Nous pourrons donc utiliser le composant dans lequel nous affichons l'image comme instance d'ImageObserver.

Cette mthode est une mthode asynchrone, c'est--dire qu'elle retourne immdiatement sans attendre que l'image soit affiche. Dans le cas d'une image locale, cela peut ne pas poser de problme. En revanche, dans le cas d'une image devant tre obtenue d'un serveur sur un rseau, le chargement de l'image peut demander un certain temps. Un ImageObserver permet de contrler le chargement des images.

854
Obtenir une image

LE DVELOPPEUR JAVA 2

Le paramtre le plus important est videmment l'image afficher. Une image peut tre obtenue de plusieurs faons. Le plus souvent, elle le sera au moyen de la mthode getImage. Cette mthode est implmente de faon diffrente dans la classe java.awt.ToolKit (accessible aux applications) et dans la classe java.Applet (la seule pouvant tre utilise par les applets). En effet, les applets sont conues pour fonctionner dans une page HTML partir d'un serveur. De ce fait, elles supportent de nombreuses limitations pour des raisons de scurit. Nous en apprendrons plus sur les applets dans le prochain chapitre. Pour obtenir une image dans une application, nous pouvons utiliser les mthodes :

getToolkit.getImage(String fichier) getToolkit.getImage(URL url)


La mthode getToolkit() est implmente (entre autres) dans la classe Component. Dans une applet, nous utiliserons les mthodes :

getImage(URL getImage(URL

url) url, String nom)

Ces mthodes ne crent qu'un handle d'image mais n'effectuent aucun chargement. Le chargement n'est effectu que lorsque la mthode drawImage est invoque. Le programme suivant affiche une image :
import java.awt.*; import java.awt.event.*; import com.sun.java.swing.*; public class AfficheImage { static Tableau frame;

CHAPITRE 19 LE GRAPHISME
public static void main( String[] args ) { frame = new Tableau(); frame.setVisible(true); } } class Tableau extends JFrame {

855

static final Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); static final int largeurEcran = screenSize.width; static final int hauteurEcran = screenSize.height + 2; int l = 400; int h = 300; Container pane; public Tableau() { super(); pane = getContentPane(); pane.setLayout(new FlowLayout()); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); }}); setBounds ((largeurEcran - l) / 2, (hauteurEcran - h) / 2, l, h); PhotoCanvas photo = new PhotoCanvas("voiture.jpg"); pane.add(photo); } } class PhotoCanvas extends Canvas { Image image; public PhotoCanvas(String s) { setSize(400,300);

856
} public void paint(Graphics g) { g.drawImage(image, 0, 0, this); } }

LE DVELOPPEUR JAVA 2
image = getToolkit().getImage(s);

Le nom du fichier est pass en paramtre au constructeur de la classe PhotoCanvas. Le fichier doit se trouver dans le mme rpertoire que l'application. Dans le cas contraire, il est possible de passer au constructeur un chemin d'accs complet. La Figure 19.6 montre laffichage obtenu. (le fichier voiture.jpg se trouve sur le CD-ROM accompagnant ce livre) :

Figure 19.6 : Affichage dune image.

Surveiller le chargement d'une image


Il est souvent ncessaire d'attendre que l'image soit charge pour effectuer un traitement. Cela est d'autant plus frquent lorsque l'image se trouve

CHAPITRE 19 LE GRAPHISME

857

sur un serveur accessible par l'intermdiaire d'un rseau, mais cela peut aussi tre le cas pour une image locale. Par exemple, si nous voulons connatre la taille de l'image afin de dimensionner le canvas en consquence, il faut attendre que l'image soit charge. En effet, si nous tentons d'utiliser les mthodes disponibles dans la classe Image pour connatre la hauteur et la largeur de celle-ci, nous n'obtiendrons pas le rsultat escompt. Par exemple, si nous modifions la classe PhotoCanvas de la faon suivante :
class PhotoCanvas extends Canvas { Image image; public PhotoCanvas(String s) { image = getToolkit().getImage(s); setSize(image.getWidth(this), image.getHeight(this)); } public void paint(Graphics g) { g.drawImage(image, 0, 0, this); } }

le programme n'affiche rien du tout. Pour comprendre ce qui se passe, nous pouvons ajouter les lignes :
public PhotoCanvas(String s) { image = getToolkit().getImage(s); System.out.println(image.getWidth(this)); System.out.println(image.getHeight(this)); setSize(image.getWidth(this), image.getHeight(this)); }

Le programme affiche alors sur la sortie standard :


-1 -1

858

LE DVELOPPEUR JAVA 2
La documentation nous apprend que les mthodes getHeight() et getWidth() renvoient -1 si l'image n'est pas encore disponible. Une solution consiste utiliser la classe MediaTracker. Le programme ciaprs montre les modifications apportes pour que la fentre soit redimensionne la taille de l'image :

import java.awt.*; import java.awt.event.*; import com.sun.java.swing.*; public class AfficheImage2 { static Tableau frame; public static void main( String[] args ) { frame = new Tableau(); frame.setVisible(true); } } class Tableau extends JFrame { static final Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); static final int largeurEcran = screenSize.width; static final int hauteurEcran = screenSize.height + 2; Container pane; int l, h; public Tableau() { super(); pane = getContentPane(); pane.setLayout(new FlowLayout()); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); }});

CHAPITRE 19 LE GRAPHISME

859

PhotoCanvas photo = new PhotoCanvas("voiture.jpg"); l = photo.getWidth(); h = photo.getHeight(); setBounds((largeurEcran - l) / 2, (hauteurEcran - h) / 2, l, h); pane.add(photo); } } class PhotoCanvas extends Canvas { Image image; public PhotoCanvas(String s) { image = getToolkit().getImage(s); MediaTracker tracker = new MediaTracker(this); tracker.addImage(image, 0); try { tracker.waitForAll(); } catch(InterruptedException e) { System.out.println(e); } setSize(image.getWidth(this), image.getHeight(this)); } public void paint(Graphics g) { g.drawImage(image, 0, 0, this); } }

Nous instancions la classe MediaTracker en passant pour paramtre son constructeur le composant pour le compte duquel les images doivent tre surveilles. Nous ajoutons ensuite au tracker l'image surveiller en lui attribuant un numro (ici, 0). Les images sont charges par le tracker dans l'ordre ascendant des numros. Nous utilisons ensuite la mthode waitForAll() qui, sans argument, attend que toutes les images soient charges. Nous aurions pu utiliser waitForID(), qui prend pour argument un numro d'ordre et attend que toutes les images portant ce numro soient

860

LE DVELOPPEUR JAVA 2
charges. Ces deux mthodes peuvent galement prendre un second argument indiquant le temps limite que doit durer l'attente. De cette faon, les mthodes getWidth et getHeight ne sont invoques que lorsque l'image est charge. Elles donnent donc le rsultat correct. La classe Tableau est galement modifie pour que la taille de la fentre soit adapte celle du canvas. Cette mthode prsente un inconvnient. Avec une image locale, il n'y a pas de problme. En revanche, avec une image obtenue d'un serveur sur un rseau, le dlai de chargement peut tre beaucoup plus long. Pendant ce temps, le programme est bloqu. Une autre faon de procder consiste demander au programme de nous avertir lorsque les informations sont disponibles. De cette faon, le programme peut continuer son travail en attendant que le chargement s'effectue. Une autre amlioration consisterait ne pas attendre le chargement total de l'image. En effet, il nous suffit de connatre ses dimensions. La classe ImageObserver permet d'obtenir le rsultat souhait. Comme nous l'avons vu prcdemment, la classe Component implmente l'interface ImageObserver. Cette interface dclare une seule mthode :
boolean imageUpdate(Image img, int info, int x, int y, int l, int h)

Lorsqu'un ImageObserver est associ une image, sa mthode imageUpdate est appele chaque fois que de nouvelles informations concernant l'image sont disponibles. Ces informations sont reprsentes par des valeurs entires (int) qui doivent tre utilises comme des indicateurs binaires. Elles appartiennent aux catgories suivantes :

ABORT : Le chargement a t interrompu. ALLBITS : Toutes les donnes ont t charges. ERROR : Une erreur s'est produite pendant le chargement.

CHAPITRE 19 LE GRAPHISME

861

FRAMEBITS : Une image complte supplmentaire a t charge. Cet


indicateur concerne les images composes de plusieurs images (ou frames) telles que les images GIF animes.

HEIGHT : La hauteur de l'image est disponible. PROPERTIES : Les proprits de l'image sont disponibles. SOMEBITS : Des donnes supplmentaires sont disponibles. WIDTH : La largeur de l'image est disponible.
Ces informations peuvent tre exploites en effectuant un ET logique avec le deuxime paramtre (info) pass la mthode imageUpdate. Par exemple, si nous voulons savoir si l'image est entirement charge, nous pouvons tester l'expression logique suivante :
if ((info & ALLBITS) != 0)

Chacune des constantes (ABORT, ALLBITS, ERROR, etc.) est une valeur de type int comportant un seul bit valant 1, tous les autres valant 0. (Exprim d'une autre manire, chaque constante est une puissance de 2.) L'expression info & ALLBITS vaut true si le bit qui vaut 1 dans ALLBITS vaut galement 1 dans info. Pour tester si la hauteur et la largeur sont disponibles, nous pouvons utiliser l'expression :
if ((info & (WIDTH | HEIGHT)) != 0)

Si cette formulation ne vous semble pas vidente, souvenez-vous que info, WIDTH et HEIGHT ne sont pas des boolean mais des int. Nous pouvons donc modifier notre programme de la faon suivante :
import java.awt.*; import java.awt.event.*;

862
import com.sun.java.swing.*; import java.awt.image.*; public class AfficheImage3 { static Tableau frame;

LE DVELOPPEUR JAVA 2

public static void main( String[] args ) { frame = new Tableau(); frame.setVisible(true); } } class Tableau extends JFrame { Container pane; public Tableau() { super(); pane = getContentPane(); pane.setLayout(new FlowLayout()); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); }}); PhotoCanvas photo = new PhotoCanvas("voiture.jpg"); pane.add(photo); } } class PhotoCanvas extends Canvas { static final Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); static final int largeurEcran = screenSize.width; static final int hauteurEcran = screenSize.height + 2; Image image; public PhotoCanvas(String s) { image = getToolkit().getImage(s);

CHAPITRE 19 LE GRAPHISME
prepareImage(image, this); } public void paint(Graphics g) { g.drawImage(image, 0, 0, this); }

863

public boolean imageUpdate(Image image, int info, int x, int y, int l, int h) { if ((info & (WIDTH | HEIGHT)) != 0) { setSize(l, h); getParent().getParent().getParent().getParent(). setBounds((largeurEcran - l) / 2, (hauteurEcran - h) / 2, l, h); } if ((info & (ERROR | FRAMEBITS | ALLBITS)) != 0) { repaint(); return false; } else { return true; } } }

La lecture des dimensions de l'cran a t dplace dans la classe PhotoCanvas. L'instruction :


prepareImage(image, this);

attribue l'ImageObserver l'image et lance son chargement. La mthode imageUpdate teste les indicateurs pour savoir si la hauteur et la largeur de l'image sont disponibles. Si c'est le cas, la taille du canvas est ajuste au moyen de la mthode setSize. La taille et la position de la fentre sont ensuite ajustes. Notez l'utilisation de la syntaxe :
getParent().getParent().getParent().getParent().setBounds(

864

LE DVELOPPEUR JAVA 2
En effet, le conteneur parent du canvas est la contentPane de la fentre. Le parent de celle-ci est la layeredPane, dont le parent est la JRootPane. Le parent de la JRootPane est l'instance de Tableau que notre programme a cre. La mthode teste ensuite les indicateurs ERROR, FRAMEBITS et ALLBITS pour savoir si le chargement est termin. Si c'est le cas, la mthode repaint() est appele et la mthode renvoie false, ce qui indique l'appelant que l'opration n'est pas termine et que la mthode doit de nouveau tre appele lorsque de nouvelles donnes seront disponibles. Si l'opration est termine, la mthode renvoie true pour indiquer que la mthode imageUpdate ne doit plus tre appele.

Conclusion
Il y aurait encore beaucoup dire en ce qui concerne le graphisme, et en particulier sur l'animation, la cration d'images et le filtrage. Java dispose de nombreuses classes permettant d'effectuer des traitements d'images complexes tels que renforcement, attnuation ou rotation. Par ailleurs, Sun Microsystems propose galement une API permettant de traiter les images 3D. La place nous manque malheureusement pour traiter ici ces sujets. Ils le seront dans un prochain ouvrage.

Chapitre 20 : Applets et Rseaux

Applets et rseaux

20

USQU'ICI, TOUS LES PROGRAMMES QUE NOUS AVONS RALISS taient des applications. Certaines produisaient un affichage sur la sortie standard, d'autres utilisaient une interface fentre. Toutes taient conues pour fonctionner localement sur votre ordinateur. Cependant, un des points forts de Java est son aptitude produire des programmes pouvant tre chargs et excuts depuis un serveur, travers un rseau.

Les applets
Les applets sont des programmes Java prsentant un certain nombre de particularits :

866
d'une page HTML.

LE DVELOPPEUR JAVA 2

Ils utilisent une interface fentre affiche dans une zone rectangulaire Ils disposent d'un contexte d'excution fourni par l'application qui
affiche la page HTML (en gnral, un navigateur Web).

Par mesure de scurit, ils sont soumis des restrictions d'accs aux
ressources.
init,

Ils n'excutent pas automatiquement la mthode main mais la mthode


qui n'est pas statique.

Cration d'une applet


Une applet est une classe tendant la classe java.awt.Applet ou une classe drive, comme javax.swing.JApplet. Au Chapitre 2, nous avons cr une premire applet qui tendait la classe Applet. Voici la mme applet mise jour pour utiliser les composants Swing :
import javax.swing.*; public class PremiereApplet extends JApplet { public void init() { getContentPane().add(new JLabel("a marche !")); } }

Et voici le fichier HTML permettant d'excuter l'applet :


<html> <body> <applet code="PremiereApplet" width="200" height="150"> </applet> </body> </html>

CHAPITRE 20 APPLETS ET RSEAUX

867

Vous pouvez ajouter entre les balises <applet> et </applet> un texte qui sera ignor par les navigateurs compatibles Java et affich par les navigateurs non compatibles. Par exemple :
<html> <body> <applet code="PremiereApplet" width="200" height="150"> Votre navigateur doit tre compatible Java pour voir cette page. </applet> </body> </html>

De cette faon, les utilisateurs dont le navigateur n'est pas compatible ou qui ont dsactiv Java ne seront pas dconcerts.

Problmes de compatibilit entre les versions


Autant les problmes de compatibilit entre les diffrentes versions de Java sont relativement simples grer en ce qui concerne les applications, autant c'est un vritable casse-tte amricain (les chinois n'y sont pour rien) quand il s'agit des applets. Ici, le slogan Write once, run everywhere relve de la plus pure utopie. En effet, l'immense majorit des navigateurs compatibles Java est ce jour compatible avec la version 1.0.2. Or, la version 1.0.2 n'utilise pas du tout le mme modle pour la gestion des vnements. Le modle actuel n'est disponible que depuis la version 1.1. Microsoft Explorer est compatible avec Java 1.1 depuis la version 4. En revanche, la version 4 de Netscape Navigator n'est compatible avec Java 1.1 qu'en effectuant une mise jour. Vous pensez peut-tre que vos soucis s'arrtent l ? Pas du tout ! La bibliothque Swing ne fait pas partie intgrante de Java 1.1. Elle doit tre installe sparment. Par consquent, elle n'est gnralement pas prise en charge par les navigateurs compatibles Java 1.1, sauf si leurs propritaires ont pris soin de l'installer sparment. Vous comptez peut-tre alors demander aux Internautes auxquels vous destinez vos applets d'installer la bibliothque Swing ? Bon courage ! Tout

868

LE DVELOPPEUR JAVA 2
d'abord, il est trs difficile de demander aux utilisateurs d'installer quoi que ce soit. De plus, mme avec la bibliothque Swing installe, la version 4 des deux navigateurs prcits produit immanquablement une erreur lorsqu'une applet tente d'accder la queue des vnements AWT (la queue est une structure dans laquelle sont stocks les vnements traiter). Or, les applets Swing accdent systmatiquement cette queue pour vrifier si l'accs est autoris. La documentation de Java prsente ce problme en l'attribuant une mauvaise conception des navigateurs, comme s'il tait normal, pour vrifier qu'une opration est autorise, de tenter de l'excuter. Cette mthode est pour le moins cavalire. En attendant que les navigateurs s'adaptent, il ne reste qu' modifier le comportement des applets Swing, ce que vous pouvez effectuer en ajoutant celles-ci la ligne suivante :

getRootPane().putClientProperty("defeatSystemEventQueueCheck", Boolean.TRUE);

Une fois ce problme rgl, obtenir des utilisateurs qu'ils effectuent la mise jour de leurs navigateurs n'est pas une mince affaire. Une lueur d'espoir cependant : la version 2 de Java est livre avec un programme permettant de mettre jour les navigateurs l'aide d'un plug-in qui permet ceux-ci d'utiliser le JRE install par ailleurs, plutt que leur propre JVM, ce qui est beaucoup plus simple que d'installer une nouvelle version. Malheureusement, cela ne concerne que les versions Windows des navigateurs. Si vous dveloppez des applets pour une diffusion sur Internet, nous vous conseillons de vous limiter pour l'instant la bibliothque AWT et d'ignorer les composants Swing. C'est un juste milieu provisoire qui vous empche toutefois d'atteindre les utilisateurs de navigateurs compatibles Java 1.0.2. Si vous voulez une compatibilit maximale, vous devrez apprendre utiliser l'ancien modle des vnements, que nous n'avons pas abord dans ce livre. Vous trouverez en librairie des dizaines de titres traitant de ce sujet. Vous aurez ainsi l'occasion de vrifier quel point le nouveau modle constitue un progrs pour le programmeur. Si vous dveloppez des applets pour le plaisir (on ne sait jamais !) et si vous vous contentez de les visualiser l'aide de l'AppletViewer, vous pouvez utiliser les composants Swing sans problme. Il en est de mme si vous

CHAPITRE 20 APPLETS ET RSEAUX

869

utilisez une version compatible Java 2 du navigateur HotJava ou un navigateur quip du plug-in Java 2

Avertissement
La mise au point d'applets avec un navigateur est une opration pnible. En effet, la plupart des navigateurs ne rechargent pas les classes Java lorsqu'elles ont t modifies. Il vous arrivera donc probablement plusieurs fois de vous arracher les cheveux en voyant une applet que vous venez de modifier continuer de produire toujours le mme rsultat. En effet, contrairement aux textes et aux images contenus dans les pages Web, le fait d'actualiser une page ne recharge pas les classes Java utilises par les applets. La seule faon sre de procder consiste quitter le navigateur et le relancer.

Deuxime avertissement
Ne croyez pas que vous puissiez utiliser la classe Applet provisoirement en attendant de la remplacer pas JApplet lorsque la majorit des utilisateurs seront quips d'une version compatible, sans vous soucier aujourd'hui des consquences. Par exemple, si vous utilisez dans un composant la mthode getParent(), celle-ci donnera un rsultat compltement diffrent avec une Applet et avec une JApplet. En effet, le parent d'un composant dans une Applet sera, par exemple, l'applet elle-mme. Avec une JApplet, le parent sera la contentPane de l'applet. Pour remonter du composant l'applet, il faudra utiliser la forme :

getParent().getParent().getParent().getParent()

pour remonter la contentPane, puis la layeredPane, puis la JRootPane et enfin l'applet. Bien sr, il vous faudra remplacer tous les add() par getContentPane().add, mais cela est un moindre mal. Un autre problme est que les Applet et les JApplet n'ont pas le mme layout manager par dfaut. Applet utilise le FlowLayout alors que JApplet prfre le BorderLayout. De mme, la police par dfaut des composants,

870

LE DVELOPPEUR JAVA 2
les couleurs de fond et de premier plan, la taille des caractres, sont diffrentes. Si vous voulez vous assurer une future migration sans trop de problmes, ne laissez rien au hasard et spcifiez tous les lments sans jamais vous reposer sur les valeurs par dfaut.

Fonctionnement d'une applet


Le fonctionnement d'une applet est un peu plus complexe que celui d'une application. Cependant, son modle est conforme ce que nous connaissons de Java. Une applet contient des mthodes qui sont appeles automatiquement lorsque certains vnements se produisent. Jusqu'ici, nous n'avons vu que la mthode init(), qui est appele lorsque l'applet est charge. En fait, l'invocation de la mthode init() n'est pas la seule chose qui se produise au chargement de l'applet. En effet, cette mthode, contrairement la mthode main() d'une application, n'est pas statique. Une instance de la classe charge doit donc tre cre. La liste ci-dessous dcrit les diffrents vnements qui se produisent lorsqu'une applet est charge :

La classe est charge. La classe est instancie. La mthode init() de l'instance cre est invoque. La mthode start() est invoque.
Si la page contenant l'applet est quitte :

La mthode stop() est invoque.


Si la page contenant l'applet est de nouveau affiche :

La mthode start() est invoque de nouveau.


Lorsque le navigateur est quitt :

La mthode destroy() est invoque.

CHAPITRE 20 APPLETS ET RSEAUX

871

Il n'est pas obligatoire de redfinir chacune de ces mthodes. Cependant il est des cas o il est important de le faire. Si une applet lance un thread, celui-ci continue de s'excuter lorsque la page n'est plus affiche. Cela n'est pas toujours ncessaire et occupe inutilement le temps du processeur. La mthode stop() peut donc tre employe pour stopper le thread alors que la mthode start() servira le relancer lorsque la page sera affiche de nouveau. Il n'est gnralement pas ncessaire de redfinir la mthode destroy() car le navigateur se charge du nettoyage ncessaire.

Passer des paramtres une applet


Le fichier HTML que nous avons utilis tait rduit sa plus simple expression. Les seuls paramtres que nous avons fournis l'applet taient le nom de la classe (code), la hauteur (height) et la largeur (width) de la zone qu'elle devait occuper. Ces deux paramtres sont obligatoires. En revanche, il est possible d'en prciser d'autres :
align = left | right | top | texttop | middle | absmiddle | baseline| bottom | absbottom

Ce paramtre prcise l'alignement de l'applet dans la zone qui lui est rserve.
alt = texte

Un texte qui sera affich pendant le chargement de l'applet, avant que celle-ci soit disponible. Utiliser ce paramtre permet d'indiquer aux utilisateurs ce pour quoi ils sont en train de patienter. Ce texte sera galement affich la place de l'applet si le navigateur reconnat le tag <applet> mais que Java a t dsactiv.
codebase = url du rpertoire contenant le fichier .class

872

LE DVELOPPEUR JAVA 2
Ce paramtre prcise l'URL du rpertoire contenant le fichier .class, dans le cas o celui-ci ne se trouverait pas la mme adresse que le fichier HTML.
archive = "fichier1, fichier2, ...."

spcifie les fichiers d'archives utiliss par l'applet (voir plus loin).
hspace = espace vide de chaque ct de l'applet

Ce paramtre prcise la valeur de la marge gauche dans la zone rserve l'applet.


vspace = espace vide au-dessus et au-dessous de l'applet

Ce paramtre prcise la valeur de la marge haute dans la zone rserve l'applet. Tous ces paramtres doivent tre placs l'intrieur du tag <applet>, comme dans l'exemple :
<applet code="PremiereApplet" width="200" height="150">

D'autres paramtres dfinissables par le programmeur peuvent tre placs entre les tags <applet> et </applet> sous la forme :
<param nom = paramtre1 value = valeur1> <param nom = paramtre2 value = valeur2>

Les valeurs ainsi fournies sont des chanes de caractres. Par exemple, l'applet suivante utilise un paramtre appel texte pour obtenir le texte qu'elle doit afficher :

CHAPITRE 20 APPLETS ET RSEAUX


import java.applet.*; import java.awt.*; public class DeuxiemeApplet extends Applet { public void init() { String texte = getParameter("texte"); add(new Label(texte)); } }

873

Le fichier HTML qui permet d'afficher l'applet doit fournir le paramtre :


<html> <body> <applet code="DeuxiemeApplet" width="200" height="150"> <param name = "texte" value = "Le texte afficher"> </applet> </body> </html>

La Figure 20.1 montre le rsultat obtenu avec l'AppletViewer.

Figure 20.1 : Lapplet affiche dans lAppletViewer.

874

LE DVELOPPEUR JAVA 2
L'utilisation des paramtres permet d'employer plusieurs fois une mme applet en ne la chargeant qu'une seule fois. L'exemple suivant affiche un texte dans un rectangle qui change de couleur lorsque le pointeur y entre. Il peut tre utilis comme base pour un bouton anim :
import java.applet.*; import java.awt.*; import java.awt.event.*; public class BoutonAnime extends Applet { String texte; final Color couleur1 = new Color(255, 127, 192); final Color couleur2 = new Color(255, 192, 255); final Color couleurTexte = new Color(0, 0, 0); Color couleur = couleur1; int largeur, hauteur, taille, x, y; Font police; FontMetrics mtriques; public void init() { texte = getParameter("texte"); largeur = getSize().width; hauteur = getSize().height; taille = Integer.valueOf(getParameter("taille")).intValue(); police = new Font("Dialog", Font.BOLD, taille); mtriques = getFontMetrics(police); x = (int)((largeur - mtriques.stringWidth(texte)) / 2); y = (int)(((hauteur - mtriques.getHeight()) / 2) + mtriques.getAscent()); addMouseListener(new MouseAdapter() { public void mouseEntered(MouseEvent e) { couleur = couleur2; repaint(); } public void mouseExited(MouseEvent e) { couleur = couleur1;

CHAPITRE 20 APPLETS ET RSEAUX


repaint(); } }); } public void paint(Graphics g) { g.setFont(police); g.setColor(couleur); g.fillRoundRect(0, 0, largeur, hauteur, 5, 5); g.setColor(couleurTexte); g.drawString(texte, x, y); } }

875

Ce programme est incomplet car l'applet ne fait rien d'autre que changer de couleur lorsque le pointeur y entre ou en sort. Vous pouvez utiliser cette applet dans un document HTML de la faon suivante :

<html> <body> <h2>Utilisation d'applets pour crer des boutons anims</h2> <p>Java permet de raliser des boutons anims de faon trs simple. Chaque bouton est une applet de quelques lignes insre dans le code HTML.</p> <p> Voici un exemple d'utilisation. Cliquez sur ce bouton <applet code="BoutonAnime" width="100" height="20" align = "top"> <param name = "texte" value = "Page suivante"> <param name = "adresse" value = "Page2.html"> <param name = "taille" value = "12"> </applet> pour passer la page suivante.</p> <p>Cliquez sur celui-ci

876

LE DVELOPPEUR JAVA 2
<applet code="BoutonAnime" width="110" height="20" align = "top"> <param name = "texte" value = "Page prcdente"> <param name = "adresse" value = "Page1.html"> <param name = "taille" value = "12"> </applet> pour passer la page prcdente.</p> </body> </html>

La Figure 20.2 montre le rsultat obtenu avec Netscape Navigator

Agir sur le navigateur


Une applet est en mesure d'agir sur le navigateur qui l'affiche de deux faons. La premire consiste obtenir du navigateur des informations. C'est

Figure 20.2 : Les boutons roll-over utiliss avec Netscape Navigator.

CHAPITRE 20 APPLETS ET RSEAUX

877

ce que nous avons fait jusqu'ici en utilisant les paramtres fournis dans la page HTML. L'autre type d'interaction consiste indiquer au navigateur quelle page il doit afficher. C'est ce qu'est suppos faire notre applet.

Afficher un nouveau document


Pour cela, nous devons obtenir du navigateur le contrle du contexte d'excution de l'applet. Pour que nos boutons soient fonctionnels, nous devons implmenter dans notre programme les tapes suivantes :

Lire le paramtre indiquant la destination. Crer un URL partir de ce paramtre. (Je sais bien que tout le monde

dit une URL, mais il n'y a aucune raison valable cela. URL signifie Uniform Resource Locator. Le mot locator n'a aucune raison d'tre fminin.)

Obtenir le contexte d'excution. Excuter la commande permettant de charger le document recherch.


La lecture du paramtre ne pose aucun problme. Nous savons dj faire cela :
String adresse; adresse = getParameter("adresse");

La transformation de la chane de caractres obtenue en URL valide ne pose pas de problme si vous disposez de la documentation de Java. Celle-ci nous indique que le constructeur de la classe URL peut tre invoqu avec pour paramtre un URL et une chane de caractres contenant le nom du document. Si le document afficher se trouve au mme endroit que le document contenant le bouton, il suffit d'utiliser la syntaxe suivante :
url = new URL(getDocumentBase(), adresse);

878

LE DVELOPPEUR JAVA 2
Le contexte d'excution peut tre obtenu l'aide de la mthode getAppletContext(), qui renvoie un objet du type AppletContext. Cette classe contient la mthode showDocument(URL) qui peut tre invoque pour afficher le document demand. Cependant, cette faon de procder nous oblige crer explicitement un URL, et donc intercepter l'exception MalformedURLException. Aussi, il est plus simple d'employer la forme showDocument(URL, String). C'est ce que fait le programme suivant :
import import import import java.applet.*; java.awt.*; java.awt.event.*; java.net.*;

public class BoutonAnime2 extends Applet { String texte; final Color couleur1 = new Color(255, 127, 192); final Color couleur2 = new Color(255, 192, 255); final Color couleurTexte = new Color(0, 0, 0); Color couleur = couleur1; int largeur, hauteur, taille, x, y; Font police; FontMetrics mtriques; String adresse; URL url; public void init() { texte = getParameter("texte"); adresse = getParameter("adresse"); try { url = new URL(getDocumentBase(), adresse); } catch(MalformedURLException e) { } largeur = getSize().width; hauteur = getSize().height; taille = Integer.valueOf(getParameter("taille")).intValue();

CHAPITRE 20 APPLETS ET RSEAUX

879

police = new Font("Dialog", Font.BOLD, taille); mtriques = getFontMetrics(police); x = (int)((largeur - mtriques.stringWidth(texte)) / 2); y = (int)(((hauteur - mtriques.getHeight()) / 2) + mtriques.getAscent()); addMouseListener(new MouseAdapter() { public void mouseEntered(MouseEvent e) { couleur = couleur2; repaint(); } public void mouseExited(MouseEvent e) { couleur = couleur1; repaint(); } public void mouseClicked(MouseEvent e) { getAppletContext().showDocument(url); } }); } public void paint(Graphics g) { g.setFont(police); g.setColor(couleur); g.fillRoundRect(0, 0, largeur, hauteur, 5, 5); g.setColor(couleurTexte); g.drawString(texte, x, y); } }

modifier le nom de la classe dans le fichier HTML (BoutonAnime2.class). Par ailleurs, ce programme ne traite pas les conditions d'erreur telles que texte trop long pour les boutons ou documents inexistants.

Attention : Si vous essayez ce programme tel quel, n'oubliez pas de

880
cible)

LE DVELOPPEUR JAVA 2
Note : Il existe galement une mthode showDocument(URL
url, String

qui prend comme deuxime paramtre une chane de caractres indiquant dans quelle fentre le document doit tre affich. Les valeurs possibles sont :

nom_de_fentre : le document est affich dans la fentre portant le


nom indiqu. Si elle nexiste pas, une nouvelle fentre est cre.

_blank : le document est affich dans une nouvelle fentre sans nom. _self : le document est affich dans la mme fentre ou dans le mme
cadre.

_parent : le document est affich dans la fentre ou le cadre parent.


S'il n'y a pas de fentre parente, il est affich dans la mme fentre ou le mme cadre.

_top : le document est affich dans la fentre de niveau suprieur, ou


dans le mme cadre/fentre si on se trouve dj au niveau suprieur.

Afficher un message dans la barre d'tat


La plupart des navigateurs affichent l'adresse de destination d'un lien dans la barre d'tat chaque fois que le pointeur est plac sur le lien. Nous pouvons trs facilement obtenir le mme rsultat en modifiant notre programme de la faon suivante :
public void mouseEntered(MouseEvent e) { couleur = couleur2; showStatus(url.toString()); repaint(); } public void mouseExited(MouseEvent e) { couleur = couleur1; showStatus(""); repaint(); }

CHAPITRE 20 APPLETS ET RSEAUX

881

Afficher des images


Une applet est soumise de nombreuses restrictions d'accs. En particulier, elle ne peut accder des fichiers se trouvant ailleurs qu' l'endroit d'o elle a t charge. De plus, elle ne peut obtenir librement toutes les informations concernant la machine sur laquelle elle fonctionne. En consquence, la procdure suivre pour charger une image est un peu diffrente de celle mise en uvre pour une application. En effet, l'instruction :
image = getToolkit().getImage(String nom);

doit tre remplace par :


image = getImage(URL url , String nom)

Si les images sont stockes dans le mme rpertoire que l'applet, vous n'aurez pas besoin de construire un URL et pourrez employer la syntaxe suivante :
image = getImage(getCodeBase(), "image.jpg")

Vous pouvez galement placer les images dans un sous-rpertoire et utiliser la syntaxe :
image = getImage(getCodeBase(), "images/image.jpg")

Note : Vous ne pouvez pas utiliser directement


cette mthode renvoie l'URL du document alors l'URL du rpertoire dans lequel se trouve l'applet.

getDocumentBase() car que getCodeBase() renvoie

Les sons
Les possibilits sonores de Java sont extrmement limites. Elles se bornent la lecture des fichiers aux formats au, wav, aiff, midi et rmf. La lecture

882

LE DVELOPPEUR JAVA 2
d'un fichier s'effectue en crant tout d'abord un clip. Un clip est une instance de la classe AudioClip. Vous pouvez obtenir un clip l'aide de la mthode getAudioClip de la classe Applet :
AudioClip monClip = new AudioClip(url)

ou :
AudioClip monClip = new AudioClip(url, chane)

La syntaxe est identique celle employe pour les images. Par exemple, pour crer un clip avec le fichier musique.au se trouvant dans le sousrpertoire sons du rpertoire contenant l'applet, vous utiliserez la syntaxe :
AudioClip monClip = new AudioClip(getCodeBase(), "sons/ musique.au")

Cette mthode ne peut tre invoque que depuis une instance d'Applet. Aussi, il existe galement la mthode statique newAudioClip qui permet de crer un clip depuis une application :
AudioClip monClip = Applet.newAudioClip(URL url)

Le clip obtenu peut tre jou de la faon suivante :

monClip.play() joue le clip une fois. monClip.loop() joue le clip continuellement. monClip.stop() arrte le clip.
Ces mthodes proviennent de la classe AudioClip. Il existe galement dans la classe Applet les mthodes :

CHAPITRE 20 APPLETS ET RSEAUX


play(URL url) play(URL url, String nom)

883

qui permettent de jouer un fichier son sans crer un clip. Bien que les fonctions sonores de Java soient extrmement limites, elles offrent cependant la possibilit de mixer automatiquement plusieurs clips.

Note : Si vous dcidez de jouer un clip en continu lorsque votre applet est
affich, n'oubliez pas que son excution continuera jusqu' ce que l'utilisateur quitte le navigateur. Par consquent, il est fortement conseill, dans ce cas, d'implmenter la mthode stop() de l'applet pour arrter le clip lorsque l'utilisateur change de page.

Optimiser le chargement des applets l'aidedesfichiersd'archives


Une applet peut faire appel de nombreux lments. En effet, chaque classe est place dans un fichier spar, ainsi que chaque image, chaque son, et en rgle gnrale chaque ressource. Si votre applet fonctionne partir d'un serveur, il faudra une transaction spare pour chaque lment. Cela prsente deux inconvnients. Le premier est un ralentissement global du chargement. Le second est plus subtil. En effet, les lments ne sont chargs qu'au moment o ils sont ncessaires. Cette faon de procder n'est pas forcment optimale. En effet, si votre applet excute des traitements en temps rel, il serait prfrable que tous les lments ncessaires soient chargs avant de commencer. Pour parvenir ce rsultat, il est possible de regrouper tous les lments dans un fichier de type .jar. Vous pouvez alors utiliser la syntaxe suivante dans votre fichier HTML :
<applet code = "monApplet.class" archive = "archive.jar" ...

Ainsi, Java cherchera les lments ncessaires dans le fichier archive.jar. Ce fichier sera charg une fois lors du premier accs. Si certains lments ne

884

LE DVELOPPEUR JAVA 2
sont ncessaires que dans un cas prcis, vous pouvez utiliser plusieurs fichiers .jar :
<applet code = "monApplet.class" archive = "archive1.jar, archive2.jar" ...

Pour crer un fichier d'archives, vous devez employer lutilitaire jar.exe livr avec le JDK. Par exemple, pour crer une archive contenant toutes les classes, toutes les images JPEG et tous les fichiers sons d'un rpertoire, vous devez employer la syntaxe :
jar cvf archive.jar *.class *.jpeg *.au

Les applets et la scurit


Les applets sont potentiellement dangereuses si elles sont diffuses sur un rseau largement ouvert au public comme Internet. Par scurit, Java impose des limitations draconiennes ce que peut faire une applet. Cela est extrmement gnant dans le cas d'un Intranet ou, dans une moindre mesure, d'un Extranet. Dans ces cas, en effet, l'origine des applets est parfaitement connue et il n'y a donc pas craindre les malversations de programmeurs malveillants. De la mme faon, il est regrettable qu'une applet diffuse sur Internet partir d'un site connu et auquel vous pouvez faire confiance soit soumise aux mmes limitations que les applets d'origine inconnue. Java 2 permet de rsoudre ce problme en configurant le security manager afin qu'il puisse autoriser les applets d'origine connue afficher des fentres ou accder au disque dur local. Le security manager est install par le navigateur qui affiche l'applet. Normalement, les applications ne sont donc pas soumises un tel contrle. Il est cependant possible d'installer un security manager pour les applications.

CHAPITRE 20 APPLETS ET RSEAUX

885

Mettre en place les autorisations


Pour modifier les autorisations concernant l'activit des applets, il est ncessaire de crer un fichier d'autorisations. Il s'agit d'un fichier texte qui peut tre cr l'aide de n'importe quel diteur de texte. Cependant, Java est fourni avec un utilitaire (crit en Java) permettant de simplifier cette tche. Pour lancer cet utilitaire, il suffit de taper la commande :
policytool

Ce programme affiche tout d'abord un message indiquant qu'aucun fichier d'autorisations n'a t trouv, sauf si un tel fichier a dj t cr. Sous Windows, ce fichier se trouve dans le rpertoire c:\windows\profiles\votre_nom et est nomm .java.policy. La fentre de la Figure 20.3 est alors affiche. Cette fentre permet d'ouvrir ou de crer un fichier d'autorisations. Pour crer une autorisation, cliquez sur le bouton Add Policy Entry. Une nouvelle bote de dialogue est affiche (Figure 20.4).

Figure 20.3 : Slection dun fichier dautorisations.

886

LE DVELOPPEUR JAVA 2

Figure 20.4 : Ajout dune autorisation.

La zone CodeBase permet d'indiquer la source des applets auxquelles


vous souhaitez accorder l'autorisation.

La zone SignedBy sert attribuer l'autorisation aux applets portant une

certaine signature. La signature est contrle l'aide d'une cl publique enregistre dans un certificat. Pour contrler l'origine de l'applet, Java utilise la cl publique enregistre dans le certificat et vrifie qu'elle correspond bien la cl prive fournie par l'applet. Ce type d'autorisation ncessite videmment que l'utilisateur ait pralablement obtenu une copie du certificat.

Il est parfaitement possible de remplir les deux zones pour n'autoriser que les applets provenant d'une certaine origine et portant une signature. Tapez l'URL de l'origine des applets que vous souhaitez autoriser dans la zone CodeBase, par exemple :

CHAPITRE 20 APPLETS ET RSEAUX

887

Figure 20.5 : Dfinition du type dautorisation.

http://www.volga.fr/-

Note : Le tiret aprs la barre oblique indique que l'autorisation concerne toutes les applets provenant de l'URL indiqu et de ses sous-rpertoires.
Cliquez sur Add Permission pour afficher la bote de dialogue de la Figure 20.5.

Dans la zone Permission, slectionnez le type de permission choisi.


Par exemple, pour qu'une applet puisse crire sur votre disque dur, slectionnez File permission. du fichier qui sera l'objet des autorisations.

Dans la zone de texte droite de la zone Target Name, tapez le nom Dans la zone Actions, slectionnez le type d'actions autoriser. Dans read (lecture) write (criture) delete (effacement)
le cas des autorisations concernant les fichiers, vous pouvez slectionner :

888

LE DVELOPPEUR JAVA 2

Figure 20.6 : Une nouvelle permission a t ajoute.

execute (excution) read, write, delete, execute (toutes les autorisations)


Si vous souhaitez accorder deux ou trois autorisations, vous devrez le faire en plusieurs fois. Cliquez sur OK. Vous obtenez l'affichage de la Figure 20.6. Cliquez maintenant sur Done. L'autorisation que vous venez de dfinir est affiche dans la bote de dialogue initiale. Droulez le menu File et slectionnez Save as. Naviguez jusqu' votre rpertoire user_home (sous Windows, il s'agit de c:\windows\profile\votre_nom, du moins si vous avez cr des profiles dutilisateurs) et donnez au fichier le nom :

CHAPITRE 20 APPLETS ET RSEAUX


.java.policy

889

Le nom et le chemin d'accs choisis apparaissent maintenant dans la zone Policy File. Votre fichier d'autorisations est maintenant cr.

Note : S'il existait dj un fichier .java.policy, n'enregistrez pas votre fichier sous le mme nom, sous peine de l'craser. Au contraire, reprenez la procdure depuis le dbut et modifiez le fichier existant au lieu d'en crer un nouveau.

Comment Java utilise les fichiers d'autorisations


Java utilise par dfaut le fichier d'autorisations indiqu dans le fichier :
rpertoire_de_java\lib\security\java.security

sous la forme :
policy.url.1=file:${java.home}/lib/security/java.policy policy.url.2=file:${user.home}/.java.policy

Vous pouvez mettre en uvre un nouveau fichier d'autorisations de plusieurs faons :

Crer un fichier .java.policy comme nous l'avons fait ou modifier le


pour dsigner un fichier d'autorisations supplmentaire :
policy.url.3=file:c:/java/autorisations

fichier existant. C'est la faon la plus simple et la plus sre de procder.

Ajouter une ligne au fichier java.security comme indiqu ci-dessous

Cette faon de procder peut tre plus souple si vous devez modifier souvent les autorisations. Il vous suffit de commenter une ligne pour annuler temporairement les autorisations du fichier correspondant.

890

LE DVELOPPEUR JAVA 2

Passer une rfrence au fichier d'autorisations dans la ligne de com-

mande du programme affichant les pages contenant l'applet. Dans le cas de l'AppletViewer, la syntaxe utiliser (sur une seule ligne) est :

appletviewer -J-Djava.security.policy=c:/java/autorisations nom_du_fichier.html

Cette mthode n'est cite que pour mmoire. En effet, elle est inapplicable avec les autres navigateurs.

Utiliser le security manager avec les applications


Si vous le souhaitez, vous pouvez restreindre les possibilits alloues aux applications en demandant Java d'utiliser un security manager. Pour cela, il vous suffit d'utiliser la syntaxe suivante pour lancer votre programme :

java -Djava.security.manager nom_du_programme

Votre programme est alors excut avec les mmes restrictions que s'il s'agissait d'une applet, en utilisant les autorisations indiques par le fichier java.security. Bien entendu, vous voudrez probablement dfinir un fichier d'autorisations compltement diffrent pour ce type d'utilisation.

Accder un URL partir d'une application


L'utilisation des URL n'est pas rserve aux applets. Une application peut parfaitement accder par ce moyen des ressources se trouvant sur un rseau. Le programme suivant en fait la dmonstration. Il accde l'URL http://www.volga.fr/ et affiche les donnes ainsi obtenues sur la sortie standard :

CHAPITRE 20 APPLETS ET RSEAUX


import java.net.*; import java.io.*; public class Reseau1 { static static static static URL url; InputStreamReader reader; BufferedReader entree; String ligne;

891

public static void main( String[] args ) throws Exception { url = new URL("http://www.volga.fr/"); reader = new InputStreamReader(url.openStream()); entree = new BufferedReader(reader); while ((ligne = entree.readLine()) != null) System.out.println(ligne); entree.close(); } }

Si vous excutez ce programme, vous verrez s'afficher l'cran le contenu du fichier index.html se trouvant sur le site de la socit Volga Diffusion (vous pouvez remplacer l'URL par ce que vous voulez).

Attention : Si ce programme ne fonctionne pas, il peut y avoir plusieurs


raisons :

Vous n'avez pas de connexion Internet. Dans ce cas, vous pouvez


utiliser un URL local (c'est beaucoup moins spectaculaire).

Vous avez une connexion Internet par le rseau commut (tlphone)


et l'tablissement de la connexion n'est pas automatique. Dans ce cas, tablissez la connexion manuellement avant de lancer le programme.

Vous accdez Internet travers un proxy qui n'est pas configur.


Consultez alors votre administrateur rseau. Notez que, avec une version de Windows configure normalement, la connexion est tablie automatiquement pour peu que votre fournisseur d'accs

892

LE DVELOPPEUR JAVA 2
le permette. En revanche, le programme ne ferme pas la communication tlphonique. N'oubliez donc pas de le faire manuellement si vous ne voulez pas avoir de mauvaises surprises avec votre note de tlphone. Ce programme affiche le code HTML trouv l'URL indiqu. Vous pouvez afficher le document correspondant au code en utilisant la classe JEditorPane. Ce composant est capable :

D'afficher du texte balis en interprtant son format. D'excuter des commandes d'dition sur le texte affich. De dtecter le format du texte, du moins pour les deux formats implments : HTML et RTF. Il nous suffit donc d'afficher le texte lu l'URL choisi dans une instance de JEditorPane pour obtenir un embryon de navigateur HTML. Voici un exemple de programme lisant un URL et affichant le contenu interprt. Nous avons insr l'instance de JEditorPane dans une instance de JScrollPane qui gre automatiquement l'affichage des barres de dfilement lorsque cela est ncessaire :
import import import import import import java.awt.*; java.awt.event.*; com.sun.java.swing.*; java.net.*; com.sun.java.swing.text.html.*; com.sun.java.swing.event.*;

class Navigateur extends JFrame { static final Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); static final int largeurEcran = screenSize.width; static final int hauteurEcran = screenSize.height + 2; static URL url; Container pane; JScrollPane scrollPane;

CHAPITRE 20 APPLETS ET RSEAUX


JEditorPane navPane; public Navigateur() { super(); pane = getContentPane(); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); }});

893

this.addComponentListener(new ComponentAdapter() { public void componentResized(ComponentEvent e) { scrollPane.setSize(getWidth() - 8, getHeight() - 28); }}); try { url = new URL("http://www.volga.fr/"); navPane = new JEditorPane(); navPane.setPage(url); scrollPane = new JScrollPane(navPane, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); scrollPane.setPreferredSize(new Dimension(600,450)); pane.add(scrollPane); } catch(Exception e) {System.out.println(e);} } public static void main( String[] args ) { Navigateur frame = new Navigateur(); frame.pack(); int l = frame.getWidth(); int h = frame.getHeight(); frame.setBounds((largeurEcran - l) / 2, (hauteurEcran - h) / 2, l, h); frame.setVisible(true); } }

894

LE DVELOPPEUR JAVA 2

Figure 20.7 : Un fichier HTML interprt par le composant JEditorPane.

La Figure 20.7 montre le rsultat affich par ce programme. Nous voyons que Java fournit les bases permettant de raliser assez facilement un navigateur Web. Beaucoup plus facilement, en tout cas, qu'avec tout autre langage. La gestion des liens hypertextes est en effet assure au moyen d'un HyperlinkListener dont la cration et l'exploitation sont exactement semblables celles des listeners que nous avons tudis jusqu'ici.

Conclusion
Nous terminons ici notre approche des applets et des rseaux. Java offre de nombreuses autres possibilits que nous ne pouvons pas aborder ici. Il

CHAPITRE 20 APPLETS ET RSEAUX

895

existe de nombreux ouvrages spcialiss dans la programmation d'application rseaux en Java. Si vous tes intress par ce type d'ouvrages (en anglais), nous vous conseillons de consulter le site www.amazon.com o vous pourrez ventuellement vous les procurer par correspondance.

Chapitre 21 : Prsentation des Java-Beans

Prsentation des Java-Beans

2 1

ES JAVA-BEANS NE FONT PAS PARTIE DU LANGAGE JAVA. IL S'AGIT d'une API (Interface de programmation) qui utilise le langage Java et qui a t intgre la plate-forme J2EE (Java 2 Enterprise Edition). Paralllement cette API, Sun distribue gratuitement un kit de dveloppement nomm BDK (Beans Development Kit) qui peut tre tlcharg partir de son site Web. Une copie du BDK 1.1 figure galement sur le CDROM accompagnant ce livre.

Le concept de Beans
L'API Java-Beans permet de crer des composants logiciels rutilisables. Cela n'est pas nouveau. Tous les programmeurs crent leur propre biblio-

898

LE DVELOPPEUR JAVA 2
thque de sous-programmes. Ces sous-programmes leur permettent, aprs certaines adaptations, de construire plus vite leurs applications, en vitant d'avoir rcrire plusieurs fois les mmes lments. Toutefois, les JavaBeans vont beaucoup plus loin. La premire particularit est que les JavaBeans sont conus pour tre rutiliss sous forme compile et non sous forme source, ce qui constitue un gain de temps apprciable. Par ailleurs, les Java-Beans prsentent une interface exposant leurs fonctionnalits sous une forme comprhensible aussi bien par un programmeur que par un programme. Les avantages sont multiples. Un environnement de programmation peut ainsi interroger un bean pour savoir exactement quelles sont ses caractristiques et tre en mesure de le connecter automatiquement une application, en fournissant au programmeur une structure qu'il n'a plus qu' remplir. Le gain est double : gain de temps, puisqu'une partie du travail est ralise automatiquement, et gain de fiabilit car le programmeur est guid dans son travail. Les Java-Beans permettent galement de simplifier et de scuriser totalement la maintenance des applications. Le programmeur qui utilise de tels composants n'a pas connatre leur fonctionnement interne. Seules les fonctionnalits exposes sont pertinentes. Il est ainsi possible d'optimiser sparment un composant puis de le remplacer sans immobiliser l'application. Les composants peuvent tre tests indpendamment de l'application. Ils peuvent provenir d'une quipe de programmeurs diffrente. De plus, la plupart des IDE (environnement de dveloppement intgr) Java sont capables de manipuler certains beans sans ncessiter l'criture d'une seule ligne de code. Le programmeur fait simplement glisser l'icne correspondante vers son programme et l'IDE interroge le composant pour connatre ses proprits. Ce processus est connu sous le nom d' introspection. L'introspection met en uvre la rflexion, que nous avons tudie au Chapitre 17. Une autre technique utilise une classe particulire (BeanInfo) contenant des informations sur le comportement des beans. Ainsi, les informations sur les beans peuvent provenir de deux sources : interne (introspection) et externe (BeanInfo).

CHAPITRE 21 PRSENTATION DES JAVA-BEANS

899

Les beans communiquent au moyen d'vnements, comme les composants des interfaces graphiques que nous avons tudis au Chapitre 18. Un bean intress par un certain type d'vnement s'enregistre auprs de ceux susceptibles de produire ce type d'vnement. Les environnements de dveloppement permettent de connatre la liste des vnements pouvant tre produits par un bean grce aux techniques dcrites prcdemment. Les beans prsentent des proprits qui peuvent tre personnalises l'aide d'un diteur de proprits. Une fois qu'un bean a t personnalis, son tat peut tre sauvegard grce la mise en uvre de la persistence. La persistence est base sur la srialisation (voir Chapitres 14 et 15). Du point de vue de la programmation, un bean ne prsente pas de particularit notable. Seul le respect de certaines rgles permet de faire d'un composant un bean.

Le Beans Development Kit


Le Beans Development Kit (BDK) est un ensemble de programmes fournis gratuitement par Sun Microsystems pour permettre d'exprimenter les beans. Son composant principal est la BeanBox, qui est une application minimale capable de tirer parti des beans. Crer ou utiliser des beans ne ncessite pas l'utilisation du BDK. Cette application ne permet d'ailleurs pas de crer des beans, mais seulement de les tester. Pour dvelopper des beans, vous utiliserez simplement les mmes outils que pour tout autre programme Java. Il peut s'agir d'un diteur de texte (comme UltraEdit) ou d'un environnement de dveloppement intgr (comme Forte).

Installation du BDK
Pour installer le BDK sous Windows, procdez de la faon suivante :

1. Faites un double clic sur l'icne du programme bdk1_1-win.exe. Le programme dmarre et affiche la fentre de la Figure 21.1.

900

LE DVELOPPEUR JAVA 2

Figure 21.1: Installation du BDK 1.1.

2. Cliquez sur Next pour afficher la deuxime page du programme d'installation, affichant la licence d'exploitation du programme.

3. Aprs avoir pris connaissance de vos engagements, cliquez sur Yes


pour en accepter les termes, puis sur Next pour afficher la page suivante. Ce programme comporte quelques bugs. Le premier auquel nous sommes confronts est qu'avant d'installer le BDK, le fichier bdk1_1win.exe est dcompact dans le rpertoire des fichiers temporaires de Windows. (Il s'agit d'un fichier auto-dcompactable.) Le programme d'installation propose donc d'installer le BDK dans un sous-rpertoire de celui-ci, ce qui est tout fait dconseill. Dans le cas contraire, le BDK risquerait d'tre supprim automatiquement lorsque Windows tente de rcuprer de l'espace sur le disque dur en effaant les fichiers temporaires. (Figure 21.2.)

4. Indiquez le rpertoire d'installation choisi (par exemple

c:\bdk1.1) et cliquez sur Next. Le programme propose maintenant de rechercher les JVM installes sur votre ordinateur. Malheureusement, cliquer sur Search risque de ne donner aucun rsultat. En effet, le programme ne trouve gnralement pas les JVM prsentes.

CHAPITRE 21 PRSENTATION DES JAVA-BEANS

901

Figure 21.2 : Le rpertoire d'installation propos par dfaut n'est pas correct.

5. Cliquez sur le bouton Choose Another pour slectionner une JVM.


Avec cette version du BDK, vous devez slectionner une JVM 1.2 ou 1.3. Le programme affiche une bote de dialogue de slection de fichiers. Naviguez jusqu'au rpertoire d'installation du J2SDK 1.3 et slectionnez le fichier java.exe se trouvant dans le sous-rpertoire jre\bin.

6. Cliquez sur OK pour lancer l'installation. Le reste du processus se droule automatiquement. Lorsqu'il est termin, la bote de dialogue de la Figure 21.3 est affiche. En fait, le bouton Done ne fonctionne pas. Pour terminer l'installation, cliquez sur la case de fermeture de la bote de dialogue. Le programme affiche alors un message vous demandant de confirmer l'interruption du processus d'installation. Cliquez sur Exit Install pour terminer.

Note : Si vous aviez prcdemment install une version du BDK, l'installation provoque pralablement la dsinstallation de la version prcdente, et ce de faon automatique.

902

LE DVELOPPEUR JAVA 2

Figure 21.3 : L'installation du BDK est presque termine!

Utilisation de la BeanBox
L'lment principal du BDK est la BeanBox, qui permet de tester les JavaBeans. Le BDK contient par ailleurs un certain nombre de beans de dmonstration. Nous allons donc vrifier que l'installation est correcte en dmarrant la BeanBox et en personnalisant un bean. Procdez de la faon suivante :

1. Ouvrez le sous-rpertoire BeanBox du rpertoire d'installation du BDK. 2. Excutez le fichier


run.bat en faisant un double clic sur son icne. La Figure 21.4 montre le rsultat obtenu.

La fentre centrale est appele fentre de composition. C'est dans


cette fentre que vous mettrez en uvre les Java-Beans.

La fentre de gauche est la palette affichant les diffrents beans

disponibles. Vous pouvez ajouter des beans la fentre de composition partir de cette palette. Vous pouvez galement ajouter vos propres beans la liste de la palette afin de pouvoir les

CHAPITRE 21 PRSENTATION DES JAVA-BEANS

903

Figure 21.4 : La BeanBox.

utiliser. Les beans prsents sont fournis titre de dmonstration.

La fentre en haut droite est la feuille de proprits. Elle affiche les proprits du bean slectionn.

La fentre en bas droite est le traceur de mthodes. Elle permet de suivre les mthodes appeles. La fentre de composition est elle-mme un bean qui peut tre personnalis en y plaant d'autres beans. Cette faon de procder est commune tous les IDE. Toutefois, la diffrence des autres IDE, la BeanBox ne permet pas l'criture de code.

Ajouter un bean dans la fentre de composition


Pour ajouter un bean dans la fentre de composition, procdez de la faon suivante :

904

LE DVELOPPEUR JAVA 2

Figure 21.5 : Le bean ajout la fentre de composition.

1. Dans la palette, cliquez sur le bean choisi, par exemple Juggler. Ne


vous inquitez pas s'il ne se passe rien en apparence. Rien n'indique que le bean est slectionn.

2. Cliquez dans la fentre de composition. Le bean est affich et commence immdiatement fonctionner. Ses proprits sont affiches dans la fentre des proprits (Figure 21.5). Nous pouvons voir que ce bean comporte trois proprits nommes respectivement debug, name et animationRate. Dans la fentre de composition, le bean est entour d'un trait hachur indiquant qu'il est slectionn.

Slection d'un bean dans la fentre de composition


Certains beans ragissent au clic. C'est le cas, par exemple, d'un bouton. Ces beans peuvent tre slectionns dans la fentre de composition en cliquant sur leur limite extrieure (l o se trouve le trait hachur lorsqu'ils sont slectionns). Les beans qui ne ragissent pas au clic peuvent gnralement tre slectionns en cliquant n'importe o dans leur surface. Lorsqu'un bean est slectionn, la feuille de proprit affiche les informations le concernant.

CHAPITRE 21 PRSENTATION DES JAVA-BEANS

905

Note : La fentre de composition, qui est elle-mme un bean, peut tre


slectionne en cliquant sur son fond. Ses proprits sont alors affiches normalement dans la feuille de proprits.

Dplacement d'un bean


Un bean peut tre dplac dans la fentre de composition en le faisant glisser par sa bordure hachure. Le pointeur est alors affich sous la forme d'une flche quatre pointes. Notez que mme les beans pouvant tre slectionns en cliquant n'importe o dans leur surface ne peuvent tre dplacs que par leur bordure.

Redimensionner un bean
Certains beans peuvent tre redimensionns en faisant glisser leurs angles. Le pointeur affiche alors une double flche oblique.

Note : La possibilit de redimensionner un bean dpend de sa conception. Certains beans ne peuvent pas tre redimensionns. D'autres, comme celui que nous venons de slectionner, peuvent l'tre bien que cela ne prsente aucun intrt car leur contenu n'est pas modifi. D'autres enfin voient leur contenu adapt aux nouvelles dimensions. Ces diffrences sont simplement le reflet des diffrences de fonctionnalit choisies par le programmeur.

Modifier les proprits d'un bean


Pour pouvoir modifier les proprits d'un bean, il faut qu'il soit pralablement slectionn dans la fentre de composition. Pour modifier les proprits du bean Juggler, procdez de la faon suivante :

1. Slectionnez le bean dans la fentre de composition. 2. Modifiez la proprit choisie dans la feuille de proprits. Selon le
type de proprit, cela peut tre fait en tapant une valeur dans une zone de texte, en slectionnant une valeur dans une liste, ou en cliquant sur un lment affichant une bote de dialogue. Par exemple,

906

LE DVELOPPEUR JAVA 2
modifiez la proprit animationRate en lui donnant la valeur 50. La nouvelle proprit est prise en compte lorsque vous tapez la touche Entre ou lorsque vous cliquez dans une proprit diffrente.

Installer un handler d'vnement


Disposer des composants et modifier leurs proprits ne suffit pas pour crer une application. Les composants doivent s'changer des messages. Comme nous l'avons vu dans les chapitres prcdents, les messages prennent la forme d'appels de mthodes dclenchs par des vnements. Nous allons maintenant ajouter deux boutons qui permettront de dmarrer et d'arrter l'animation. Chaque bouton doit gnrer un vnement de type ActionPerformed. Le composant Juggler doit s'enregistrer auprs des boutons afin de recevoir les vnements correspondants. Au Chapitre 18, nous avons appris faire cela au moyen de lignes de codes servant dfinir des ActionListener sous forme de classes internes. Nous allons voir que les JavaBeans permettent de mettre en uvre ces lments de faon totalement transparente. Procdez de la faon suivante :

1. Ajoutez deux fois le bean ExplicitButton en procdant exactement de la


mme faon que pour le bean Juggler. La Figure 21.6 montre le rsultat que vous devez obtenir.

2. Slectionnez le bouton de gauche et modifiez sa proprit

Label dans la feuille de proprits pour lui donner la valeur "Dmarrer" (Figure 21.7).

3. Pour le second bouton, nous procderons de faon diffrente en utilisant un customizer. Tous les beans ne possdent pas de customizer. Ceux qui en possdent un (c'est le cas du bean ExplicitButton) peuvent tre personnaliss de faon encore plus facile, comme nous allons le voir. Slectionnez le second bouton, droulez le menu Edit et choisissez l'option Customize. La bote de dialogue de la Figure 21.8 est affiche. Le customizer du bean ExplicitButton est extrmement simple et permet seulement de modifier la lgende du bouton. D'autres beans peuvent avoir des customizers beaucoup plus performants qui guident l'utilisateur pas pas dans la configuration des proprits.

CHAPITRE 21 PRSENTATION DES JAVA-BEANS

907

Figure 21.6 : Les deux boutons ajouts la fentre de composition.

Figure 21.7 : Modification de la lgende du premier bouton l'aide de la feuille de proprits.

908

LE DVELOPPEUR JAVA 2

Figure 21.8 : Modification de la lgende du premier bouton l'aide d'un customizer.

4. Remplacez le texte de la lgende par "Arrter" et cliquez sur Done. 5. Le bouton Arrter tant slectionn, droulez le menu Edit et slectionnez les options Event > ButtonPush > ActionPerformed. Nous sommes ainsi prts traiter l'vnement produit lorsque l'utilisateur clique sur le bouton (Figure 21.9). Un trait rouge relie maintenant le bouton au pointeur (Figure 21.10).

6. Cliquez sur la bordure du bean

afin de relier l'vnement celui-ci. Une bote de dialogue affiche toutes les mthodes disponibles dans ce bean (Figure 21.11).
StopJuggling et cliquez sur OK. La BeanBox gnre le code ncessaire et le compile, comme indiqu par le message Generating and compiling adaptor class. Le bouton est maintenant fonctionnel. Si vous cliquez sur celui-ci, l'animation s'arrte.

Juggler

7. Slectionnez la mthode

8. Procdez de la mme faon pour relier le premier bouton la mthode


StartJuggling.

CHAPITRE 21 PRSENTATION DES JAVA-BEANS

909

Figure 21.9 : Slection d'un vnement.

Figure 21.10 : Le bouton est reli au pointeur.

910

LE DVELOPPEUR JAVA 2

Figure 21.11 : La liste des mthodes disponibles est affiche.

Relier des proprits


En procdant de faon similaire, il est possible de relier des proprits de plusieurs beans de faon que la modification de l'une d'elles entrane la mise jour automatique des autres. Pour exprimenter cette fonctionnalit, nous allons relier la couleur de fond des deux boutons celle de la BeanBox. (Rappelons que la BeanBox est elle-mme un bean.) Procdez de la faon suivante :

1. Slectionnez la BeanBox en cliquant sur le fond de la fentre de composition.

2. Droulez le menu Edit et slectionnez l'option BindProperty. La bote


de dialogue de la Figure 21.12 est affiche. Cette bote de dialogue affiche la liste des proprits du bean qui peuvent tre source du lien.

3. Slectionnez la proprit

background (couleur de fond) et cliquez sur OK. Un trait rouge relie maintenant le centre de la BeanBox au pointeur (Figure 21.13).

CHAPITRE 21 PRSENTATION DES JAVA-BEANS

911

Figure 21.12 : La liste des proprits de la BeanBox.

Figure 21.13 : Connexion de la proprit back.ground.

912

LE DVELOPPEUR JAVA 2

Figure 21.14 : Les proprits cibles.

4. Cliquez sur la bordure du premier bouton. Une bote de dialogue affiche la liste des proprits pouvant tre utilises pour cible du lien (Figure 21.14).

5. Slectionnez la proprit background et cliquez sur OK. 6. Procdez de la mme faon pour le second bouton. 7. Cliquez dans le fond de la fentre de composition pour afficher ses
proprits dans la feuille de proprits, et cliquez dans celle-ci sur la proprit background (Figure 21.15).

8. Dans la bote de dialogue affiche, slectionnez une couleur. La couleur slectionne est automatiquement applique la BeanBox et aux deux boutons (Figure 21.16).

Enregistrer les beans


Pour enregistrer votre travail, droulez le menu Edit et slectionnez l'option Save. Une bote de dialogue est affiche pour vous permettre d'indiquer un

CHAPITRE 21 PRSENTATION DES JAVA-BEANS

913

Figure 21.15 : Configuration de la proprit background de la BeanBox.

Figure 21.16 : Les proprits background des deux boutons sont modifies en mme temps que celle de la BeanBox.

914

LE DVELOPPEUR JAVA 2

Figure 21.17 : Gnration d'une applet.

nom de fichier. Toutefois, l'enregistrement fait appel la srialisation. Aussi, seuls les lments srialisables peuvent tre enregistrs.

Gnrer une applet


La BeanBox permet de gnrer automatiquement une applet. Pour cela, il suffit de slectionner l'option MakeApplet du menu Edit de la fentre de composition. Une bote de dialogue est affiche pour vous permettre d'indiquer le nom que vous voulez donner l'applet et l'endroit o les fichiers correspondants doivent tre placs (Figure 21.17). Le programme cre automatiquement tous les lments ncessaires et compile le code. Un document HTML est cr afin de permettre la visualisation de l'applet. La Figure 21.18 montre le rsultat obtenu dans le navigateur HotJava.

CHAPITRE 21 PRSENTATION DES JAVA-BEANS

915

Figure 21.18 : L'applet affiche dans HotJava.

Cration d'un bean


L'criture d'un bean n'est pas plus complique que celle d'un programme ordinaire. Les contraintes sont peu nombreuses. Un bean n'est fondamentalement qu'une classe publique implmentant l'interface Serializable et pourvue d'un constructeur sans argument, lui aussi public. Un bean ne possde pas obligatoirement une interface graphique. Cependant, celui que nous dvelopperons en possdera une car il est beaucoup plus facile de mettre en vidence l'utilisation des beans dans un contexte graphique.

916
Codage d'un bean

LE DVELOPPEUR JAVA 2

Nous crirons un bean driv de la classe JPanel et contenant une tiquette JLabel. Le texte de cette tiquette sera simplement l'heure du systme, obtenue grce la mthode currentTimeMillis() de la classe java.lang.System et mise en forme par une instance de la classe java.util.Date. Voici le code correspondant :
package monbean; import javax.swing.*; import java.util.*; import java.io.Serializable; public class MonBean extends JPanel implements Serializable { JLabel L1; public MonBean() { L1 = new JLabel(new Date(System.currentTimeMillis()).toString()); add(L1); } }

Notez plusieurs points importants :

La classe implmente l'interface Serializable. Nous avons plac la classe MonBean dans le package monbean. Rappe-

lons que cela revient tout simplement indiquer que le fichier MonBean.class doit se trouver dans le rpertoire monbean pour pouvoir tre utilis. Dans l'exemple prsent, cela n'a pas beaucoup d'importance. Toutefois, presque tous les beans seront composs de classes multiples ainsi que de nombreux fichiers de ressources diverses. Les beans devant tre "packags" avec toutes leurs ressources, il est beaucoup plus simple de regrouper tous les lments d'un bean dans un mme rpertoire, l'exception des sources. Seule la classe du bean luimme devant tre obligatoirement publique, le plus simple est de pla-

CHAPITRE 21 PRSENTATION DES JAVA-BEANS

917

cer les sources de toutes les classes utilises par un bean dans le mme fichier et d'enregistrer ce fichier dans le rpertoire courant. Il est alors possible de compiler ce fichier l'aide de la ligne de commande suivante :
javac -d . MonBean.java

L'option -d demande au compilateur de placer les fichiers .class dans les sous-rpertoires correspondant leurs packages, partir du rpertoire indiqu. Ici, nous avons indiqu le rpertoire courant, dsign par un point. Le compilateur place donc automatiquement le fichier MonBean.class dans le sous-rpertoire monbean (le nom du package indiqu en tte du programme) du rpertoire courant. Si ce rpertoire n'existe pas, il est cr automatiquement.

Attention : Une erreur frquente consiste oublier le point aprs l'op-

tion -d. Dans ce cas, le compilateur ne fait rien. (Il n'affiche aucun message d'erreur mais ne compile pas le programme.)

La classe MonBean est publique. Cela est indispensable puisque le bean


doit tre utilis par des classes n'appartenant pas au mme package.

Le constructeur ne possde pas d'argument et est public. Il y a l une


source d'erreur potentielle. Si vous oubliez de rendre public le constructeur, la BeanBox ne se plaindra pas de ce que le constructeur n'est pas public, mais affichera le message :

WARNING: Could not instantiate bean "monbean.MonBean" from JAR "C:\BDK1.1\jars\monbean.jar" We located the class "monbean.MonBean" OK But the class did not have a zero-arg constructor. All beans must provide public zero-arg constructors.

Ce message indique que la classe n'a pas de constructeur sans argument, ce qui n'est pas tout fait exact. Le message affich par le compilateur dans un cas similaire est beaucoup plus clair :

918

LE DVELOPPEUR JAVA 2
ShowBean.java:7: MonBean() is not public in monbean.MonBean; cannot be accessed from outside package monbean.MonBean bean = new monbean.MonBean(); ^

car il indique clairement que le constructeur est inaccessible parce qu'il n'est pas public.

Cration d'un conteneur de test


La cration d'un bean est une opration simple, toutefois les piges ne manquent pas. Nous en avons dj dvoil quelques-uns et nous allons en voir d'autres. Aussi, afin de procder par tapes, nous allons tout d'abord crire un conteneur qui nous permettra de tester nos beans de manire statique. Nous serons ainsi assurs, si quelque chose ne fonctionne pas, de savoir d'o cela provient. Voici le listing d'un conteneur trs simple, utilisant une JFrame pour afficher le bean :
import javax.swing.*; import java.awt.event.*; public class ShowBean { public static void main( String[] args ) { JFrame fenetre = new JFrame(); monbean.MonBean bean = new monbean.MonBean(); fenetre.getContentPane().add(bean); fenetre.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); }}); fenetre.setBounds(300, 200, 250, 100); fenetre.setVisible(true); } }

CHAPITRE 21 PRSENTATION DES JAVA-BEANS

919

Figure 21.19 : Le bean affich dans une JFrame.

Cette classe cre simplement une fentre de 300 x 200 pixels positionne au point 250,100 sur l'cran, et affiche le bean. La fentre est quipe d'un listener permettant d'arrter le programme lorsqu'elle est ferme. La Figure 21.19 montre le rsultat obtenu. Maintenant que nous sommes certains que notre bean fonctionne correctement, nous pouvons le "packager" afin de le rendre utilisable sous cette forme dans la BeanBox.

Packager un bean
Cette opration est trs simple, mais nanmoins source de problmes en raison d'un petit bug. Le packaging d'un bean consiste le placer, avec tous les lments qu'il utilise, dans un fichier d'archive au format jar. Tous ces lments doivent tre disposs dans l'archive en respectant la structure des rpertoires correspondant aux packages. Par ailleurs, ils doivent tre accompagns d'un fichier descriptif appel manifeste. Ce fichier doit se trouver dans un rpertoire particulier nomm meta-inf.

Prparation du manifeste
Vous ne devez pas crer vous-mme le manifeste, mais un simple fichier texte contenant les informations que le manifeste doit comporter. Ces informations sont :

920

LE DVELOPPEUR JAVA 2

Le numro de version (pour l'instant, 1.0). Le nom complet du bean (avec l'indication du package). Une ligne indiquant que le contenu de l'archive constitue un bean.
Voici le contenu du fichier correspondant notre bean :
Manifest-Version: 1.0 Name: monbean.MonBean.class Java-Bean: True

La premire ligne indique le numro de version. La deuxime ligne (vide), est ignore. La troisime ligne indique le nom complet de la classe principale du bean, y compris le package. La dernire ligne indique qu'il s'agit d'un bean. (Faites attention de ne pas placer une ligne vide entre les lignes commenant par Name et Java-Bean.)

Note : Dans l'indication du package, il est possible d'utiliser le sparateur


Java (le point) ou celui du systme d'exploitation UNIX (la barre oblique), mais pas celui de MS-DOS (la barre oblique inverse). Il est toutefois beaucoup plus logique d'utiliser le sparateur Java.

Attention : Avec certains diteurs de texte, un tel fichier ne fonctionnera pas. Vous pourrez chercher pendant des heures avant d'en trouver la raison, qui n'est indique nulle part dans la documentation. Il est en fait impratif que la dernire ligne soit termine par un retour chariot. De nombreux diteurs de texte n'ajoutent pas automatiquement un retour chariot la fin de la dernire ligne du texte. La faon la plus sre de procder consiste ajouter une ligne vide aprs la dernire ligne. Enregistrez ce fichier dans le rpertoire courant (parent du rpertoire contenant le bean), sous le nom (par exemple) MonBean.mf. Ce nom n'a pas d'importance, mais il est prfrable d'adopter une stratgie cohrente.

CHAPITRE 21 PRSENTATION DES JAVA-BEANS

921

Figure 21.20 : Le contenu du fichier d'archive.

Cration de l'archive
Une fois le manifeste prpar, l'archive peut tre cre l'aide de l'utilitaire jar.exe. Pour cela, placez-vous dans le rpertoire parent du rpertoire contenant le bean et tapez la commande :
jar cfm monbean.jar MonBean.mf monbean

Cette commande cre le fichier d'archive monbean.jar dans le rpertoire courant. Cette archive contiendra le contenu du rpertoire monbean ainsi qu'un manifeste cr partir du fichier MonBean.mf. Une fois le fichier cr, vous pouvez examiner son contenu en l'ouvrant l'aide de l'utilitaire Winzip. La Figure 21.20 montre le contenu du fichier. La colonne de gauche indique les noms des fichiers prsents dans l'archive. Le manifeste est toujours plac en tte. Vous pouvez constater qu'il ne s'agit pas du fichier que vous avez cr. Par ailleurs, ce fichier est plac dans le

922

LE DVELOPPEUR JAVA 2
rpertoire meta-inf, comme cela est indiqu dans la colonne de droite. Vous pouvez ouvrir ce fichier dans un diteur de texte. Vous y trouverez le contenu suivant :
Manifest-Version: 1.0 Created-By: 1.3.0 (Sun Microsystems Inc.) Name: monbean.MonBean.class Java-Bean: True

Notez que le fichier est termin par deux lignes vides. Si vous avez oubli d'ajouter un retour chariot la fin du fichier MonBean.mf, la dernire ligne de texte (Java-Bean: True) du manifeste sera absente (mais les deux dernires lignes vides seront prsentes !) et votre bean ne fonctionnera pas.

Utilisation du bean dans la BeanBox


Pour utiliser ce bean dans la BeanBox, vous devez l'ajouter la palette. Vous pouvez procder de deux manires diffrentes. La premire consiste charger le bean l'aide de la commande LoadJar du menu Edit. De cette faon, le bean sera disponible pour la session en cours. Si vous voulez que le bean soit toujours disponible, copiez le fichier MonBean.jar dans le sousrpertoire jars du rpertoire d'installation du BDK. Une fois install, notre bean peut tre utilis de la mme faon que les autres. La seule diffrence est que son utilisation affiche un certain nombre de messages d'avertissement en raison de l'absence d'diteur de proprits pour certaines proprits. Cela n'empche toutefois pas notre bean de fonctionner. La Figure 21.21 montre le bean install dans la palette et affich dans la fentre de composition de la BeanBox.

Rsum
Nous n'avons fait ici qu'effleurer le sujet des beans. Il y aurait un livre entier crire mais nous n'avons pas la place pour cela. Par ailleurs, le bean que

CHAPITRE 21 PRSENTATION DES JAVA-BEANS

923

Figure 21.21 : Le bean install dans la BeanBox.

nous avons cr n'a aucune utilit puisqu'il affiche l'heure laquelle il est plac dans son conteneur. Avec les connaissances acquises dans les chapitres prcdents, il est toutefois facile de le transformer pour qu'il affiche l'heure en permanence. Nous vous suggrons d'effectuer cette modification titre d'exercice.

Chapitre 22 : Les servlets et les Java Server Pages

Les servlets et les Java Server Pages

22

ES APPLETS JAVA PERMETTENT D'INCLURE DANS DES PAGES HTML des programmes qui sont tlchargs depuis un serveur et excuts sur l'ordinateur de l'utilisateur. Cette faon de traiter les problmes offre de nombreux avantages. Un des avantages les plus vidents concerne la vitesse d'excution apparente. Le fait que le programme soit excut sur l'ordinateur de l'utilisateur permet d'viter de nombreuses connexions, ce qui acclre le traitement et conomise la bande passante. Une fois le programme tlcharg, l'excution se droule comme dans le cas d'une application locale. Toutefois, certains traitements ne peuvent tre effectus sur l'ordinateur de l'utilisateur. Par exemple, si celui-ci doit remplir un formulaire, la validation

926

LE DVELOPPEUR JAVA 2
de certaines donnes peut tre faite localement. Dans le cas de la saisie d'un numro de carte bancaire, par exemple, un algorithme simple permet de vrifier la cohrence du numro, afin de demander l'utilisateur de corriger les erreurs de frappe ventuelles. Mais, cela ne permet pas de s'assurer que le numro est valide et que le compte est approvisionn. La vrification se droule donc en deux tapes. La premire vrification concerne uniquement la cohrence et est effectue localement. La seconde vrification ncessite une connexion vers un serveur afin de s'assurer que le compte existe, et qu'il n'y a pas d'opposition. De la mme faon, si l'utilisateur slectionne des articles qu'il souhaite commander, une applet locale est mme de multiplier le prix unitaire par le nombre d'articles, et d'ajouter les frais de port et la TVA. Toutefois, un moment ou un autre, la commande doit tre envoye au serveur. La faon traditionnelle de traiter ces changes d'informations consiste utiliser un script CGI, mettant en uvre un programme fonctionnant sur le serveur. Le langage le plus frquemment utilis pour ce type de traitement est Perl. Ce langage est assez bien adapt ce type d'utilisation car il est interprt et ne ncessite donc pas de compilation. Par ailleurs, les donnes manipuler sont transmises sous forme de texte, et Perl est particulirement bien quip pour le traitement de ce type de donnes. Cependant, le programmeur qui dveloppe des applets Java fonctionnant sur les ordinateurs des utilisateurs aimerait pouvoir mettre ses connaissances profit pour dvelopper galement le ct serveur du programme. C'est dans cette optique que Sun Microsystems a cr l'API Servlet.

Qu'est-ce qu'une Servlet ?


Une servlet est l'quivalent, ct serveur, d'une applet. La principale diffrence est que les servlets n'ont pas d'interface utilisateur graphique. Les servlets peuvent tre excutes sur les serveurs quips d'un module spcial. Tous les serveurs courants peuvent tre quips pour excuter des servlets. Malheureusement, tous les fournisseurs d'hbergement ne sont pas encore convaincus de la ncessit de le faire.

CHAPITRE 22 LES SERVLETS ET LES JAVA SERVER PAGES

927

Les servlets constituent l'outil idal de l'implmentation du tiers mdian dans une application trois-tiers.1 Une servlet peut ainsi recevoir une requte envoye par un navigateur, ngocier les informations demandes avec une base de donnes, une application de calcul, ou toute autre application fonctionnant sur un systme distant, et renvoyer le rsultat de la requte au navigateur. Toutefois, pour des applications simples, la servlet peut parfaitement fonctionner seule et fournir elle-mme la rponse la requte. Il peut s'agir de l'criture ou de la lecture d'informations sur le disque du serveur, ou de la lecture d'informations sur le systme. Une application simple de ce schma pourrait tre une servlet permettant de synchroniser des clients en fournissant l'heure du serveur.

Structure et cycle de fonctionnement d'uneservletHTTP


Une servlet HTTP est simplement une classe drivant de la classe javax.servlet.http.HttpServlet, cette classe drivant elle-mme de la classe javax.servlet.GenericServlet qui implmente, entre autres, l'interface javax.servlet.Servlet. Cette interface dclare les mthodes suivantes :

init(), qui permet d'initialiser la servlet. Cette mthode est appele par
le serveur immdiatement aprs avoir instanci la servlet. Elle doit retourner dans un dlai fix par la configuration du serveur.

service(ServletReaquest req, ServletResponse res). Cette mthode est appele chaque requte. Si la servlet est initialise (la mthode init() a
1. Une application n-tiers est tout simplement une application dans laquelle le traitement est rparti entre n tiers, n variant gnralement entre 2 et 3. Le mot tiers est pris ici au sens de "ayant cause titre particulier" (dfinition du dictionnaire Hachette), tel qu'il est utilis dans les expressions "tiers payant", "tiers dtenteur" et n'a rien voir avec la division par trois, bien que cette confusion soit frquente du fait que les applications n-tiers sont le plus souvent 3-tiers. En effet, on ne parle pas d'application 1-tiers, considrant qu'il s'agit d'une application ordinaire ne ncessitant pas d'appellation particulire. Par ailleurs, les applications 2-tiers sont gnralement nommes d'aprs le rle des deux tiers : client-serveur. Les applications 4-tiers ou plus tant assez rares, la plupart des applications n-tiers sont des applications 3-tiers mettant en uvre un client, une base de donnes et un tiers intermdiaire, ou mdian, dont le rle revient nos servlets.

928

LE DVELOPPEUR JAVA 2
t excute et pas la mthode destroy()), cette mthode est excute immdiatement. Dans le cas contraire, la mthode init() est pralablement appele et la requte est tenue en attente pendant l'excution de celle-ci. La mthode service appelle diffrentes mthodes en fonction du type de requte traiter. Les servlets sont normalement excutes par un moteur de servlets fonctionnant sur le serveur en mode multithread. (Un serveur ne pouvant servir qu'une seule requte la fois ne serait pas trs utile.) Par consquent, tous les accs aux ressources partages, et particulirement la classe de la servlet et aux variables statiques doivent tre synchroniss. (En ce qui concerne la synchronisation, voir le Chapitre 16.)

getServletConfig(), qui permet d'obtenir des informations sur la configu-

ration de la servlet. Cette mthode renvoie un objet de type ServletConfig. La classe GenericServlet implmente cette fonctionnalit. version de la servlet, ainsi que le copyright.

getServletInfo(), qui renvoie des informations concernant l'auteur et la destroy(). Cette mthode est appele automatiquement lorsque tous les
threads dmarrs par la mthode service() sont termins ou qu'un certain dlai est coul. Elle permet de rendre les ressources qui ont t ventuellement alloues. Une fois cette mthode excute, la mthode service() ne peut plus tre appele. En cas de besoin, la servlet doit tre rinitialise.

Ce dont vous avez besoin


Pour exprimenter les servlets, Sun Microsystems fournit gratuitement le JSWDK (JavaServer Web Development Kit). Il s'agit d'un serveur minimaliste. Vous pouvez le tlcharger partir du site de Sun. Vous trouverez galement la version 1.0.1 sur le CD-ROM accompagnant ce livre. Pour mettre en uvre les servlets, vous aurez besoin d'une extension pour votre serveur Web. Si vous grez vous-mme votre serveur Web, pas de

CHAPITRE 22 LES SERVLETS ET LES JAVA SERVER PAGES

929

problme. Cette extension existe pour tous les serveurs courants. Vous trouverez tous les renseignements ce sujet sur le site de Sun. En revanche, si vous faites appel un fournisseur d'hbergement, le problme risque d'tre plus dlicat. Lorsque Microsoft propose une nouvelle spcification, les fournisseurs se battent pour tre les premiers l'implmenter, quels que soient les inconvnients de la solution propose. En revanche, s'ils sont unanimes reconnatre l'intrt des servlets, nombreux sont ceux qui hsitent proposer cette technologie, qui implique un surcrot de travail pour installer et maintenir un lment supplmentaire, de la mme faon qu'ils taient nombreux refuser l'accs aux scripts CGI il y a quelques annes. Ils changeront de point de vue le jour o une majorit d'utilisateurs l'exigera. Vous savez donc ce qu'il vous reste faire. En attendant, vous pouvez vous adresser aux sites spcialiss. Certains fournissent un hbergement complet. D'autres n'hbergent que les servlets. Vous devez alors placer vos pages Web sur un autre serveur. En choisissant un hbergement pour vos servlets, vrifiez si vous disposez d'une JVM pour vous seul ou d'une JVM partage. Un des meilleurs fournisseurs d'hbergement spcialis dans les solutions Java (Servlet, JSP, JDBC, etc.) est Servlets.net, l'adresse :
http:\\www.servlets.net\

La premire solution avec JVM prive cote environ 450 francs par mois avec 75 Mo d'espace disque et 2 Go de transfert mensuel et un service d'assistance comptent, rellement spcialis dans la technologie Java. Il est galement possible de trouver des fournisseurs gratuits, offrant gnralement un espace trs limit et aucun support technique.

Installation du JSWDK 1.0.1


Le JSWDK 1.0.1 est fourni sous la forme d'un fichier d'archive nomm jswdk1_0_1-win.zip. Ce fichier doit tre dsarchiv au moyen de l'utilitaire Winzip (ou de jar.exe) dans la racine de votre disque dur. L'extraction cre un rpertoire nomm c:\jswdk-1.0.1. Ce rpertoire contient le serveur sous

930

LE DVELOPPEUR JAVA 2
forme de fichier jar ainsi que divers sous-rpertoires contenant la documentation de l'API Servlet ainsi que des exemples. L'utilisation du serveur ncessite la prsence du J2SDK 1.2 ou 1.3 correctement configur, ainsi que la prsence de java.exe dans le chemin d'accs par dfaut. Notez que, sous Windows, une copie de java.exe est installe dans le rpertoire Windows, qui figure automatiquement dans le chemin d'accs par dfaut. Il est donc inutile de configurer le chemin d'accs.

Configuration du chemin d'accs aux classes


La variable d'environnement classpath doit indiquer les chemins d'accs aux fichiers jar (servlet.jar, webserver.jar, etc.). Toutefois, vous n'avez pas vous en proccuper car vous utiliserez les fichiers batch fournis pour dmarrer et arrter le serveur. En revanche, le fichier tools.jar du J2SDK doit se trouver dans le chemin d'accs aux classes (classpath). Par ailleurs, la taille de la mmoire d'environnement doit tre porte 2816. Il est par consquent conseill d'effectuer les oprations suivantes :

Modification du fichier startserver.bat


Le plus simple est d'ajouter, au dbut du fichier, la ligne :
set CLASSPATH=c:\jdk1.3\lib\tools.jar;%CLASSPATH%

Vous pouvez galement, si vous ne voulez pas modifier le fichier d'origine, crer un fichier batch contenant les lignes :
set CLASSPATH=c:\jdk1.3\lib\tools.jar;%CLASSPATH% startserver

Cration d'un raccourci


Il n'est pas possible d'excuter directement le fichier startserver.bat depuis l'explorateur tout en modifiant la taille de l'environnement. Aussi, il est conseill de crer un raccourci en procdant de la faon suivante :

CHAPITRE 22 LES SERVLETS ET LES JAVA SERVER PAGES

931

Dans le rpertoire du JSWDK, cliquez avec le bouton droit de la souris. Dans le menu affich, slectionnez Nouveau, puis Raccourci. Windows
affiche la fentre de l'assistant de cration d'un raccourci.

Pour la ligne de commande, tapez C:\WINDOWS\COMMAND.COM, puis


cliquez sur Suivant et enfin sur Terminer.

Cliquez avec le bouton droit sur le raccourci nouvellement cr. Dans le menu affich, cliquez sur Proprits. Cliquez sur l'onglet Programme. Pour le nom du raccourci, remplacez Commandes MS-DOS par ce que
vous voulez, par exemple Dmarrage. lement, c:\jswdk-1.0.1).

Pour le rpertoire de travail, indiquez le rpertoire du JSWDK (norma Cliquez sur l'onglet Mmoire. Dans la zone Environnement initial, remplacez Auto par 2816. Cliquez sur OK.
Le rpertoire webpages
Le rpertoire d'installation du JSWDK contient un sous-rpertoire nomm webpages. C'est ce rpertoire qui contient les pages HTML contrles par le serveur. Tous les fichiers qui devront tre accessibles par l'intermdiaire du serveur doivent tre placs dans ce rpertoire ou dans un sous-rpertoire. Le document index.html figurant dans ce rpertoire est servi par dfaut.

Note : La documentation du JSWDK se trouve dans le sous-rpertoire


docs

du rpertoire webpages. Elle peut donc tre consulte en accdant au rpertoire docs du serveur, comme nous allons le voir dans un instant.

932

LE DVELOPPEUR JAVA 2

Figure 22.1 : Le serveur est dmarr et fonctionne sur le port 8080.

Dmarrage du serveur
Pour dmarrer le serveur, faites un double clic sur le raccourci que nous avons cr. Dans la fentre MS-DOS, tapez la commande :
startserver

Le fichier batch startserver.bat est excut, configurant la variable classpath puis lanant le programme com.sun.web.shell.Startup. Une fois le serveur dmarr, il affiche un message dans une console MS-DOS, indiquant le port sur lequel il fonctionne (Figure 22.1).

Accs au serveur
Le serveur est accessible par l'intermdiaire du port 8080 de votre machine. Si celle-ci est relie un rseau et possde une adresse IP permanente,

CHAPITRE 22 LES SERVLETS ET LES JAVA SERVER PAGES

933

vous pouvez accder au serveur en envoyant une requte http cette adresse, en indiquant ce port en paramtre. Toutefois, si vous accdez au serveur partir de l'ordinateur sur lequel il fonctionne, vous pouvez utiliser le nom localhost pour dsigner l'ordinateur. Ainsi, la requte HTTP prend la forme :
http://localhost:8080/

La plupart des navigateurs utilisent le protocole http par dfaut. Par consquent, vous pouvez probablement taper simplement dans votre navigateur l'adresse :
localhost:8080

En rponse cette requte, le navigateur fournit la page html nomme index.html et situe dans son rpertoire racine, c'est--dire le rpertoire c:\jswdk-1.0.1\webpages. La Figure 22.2 montre le rsultat obtenu dans Netscape Navigator.

Note : Vous pouvez galement accder au serveur en utilisant l'adresse IP


locale : 127.0.0.1. Dans ce cas, certains navigateurs, comme Internet Explorer, vous obligent indiquer le protocole de manire explicite, sous la forme :
http://127.0.0.1:8080/

Netscape Navigator, en revanche, permet d'omettre le protocole.

Arrter le serveur
Pour arrter le serveur, vous devez taper la commande stopserver. Vous ne devez pas arrter le serveur en fermant la fentre MS-DOS.

934

LE DVELOPPEUR JAVA 2

Figure 22.2 : La page par dfaut du serveur affiche dans un navigateur.

Configuration du serveur
Lorsqu'il dmarre, le serveur lit un fichier de configuration nomm webserver.xml. Ce fichier contient diverses informations qui peuvent tre personnalises. La configuration par dfaut est la suivante :

CHAPITRE 22 LES SERVLETS ET LES JAVA SERVER PAGES


<!DOCTYPE WebServer [ <!ELEMENT WebServer (Service+)> <!ATTLIST WebServer id ID #REQUIRED adminPort NMTOKEN ""> <!ELEMENT Service (WebApplication*)> <!ATTLIST Service id ID #REQUIRED port NMTOKEN "8080" hostName NMTOKEN "" inet NMTOKEN "" docBase CDATA "webpages" workDir CDATA "work" workDirIsPersistent (false | true) "false"> <!ELEMENT WebApplication EMPTY> <!ATTLIST WebApplication id ID #REQUIRED mapping CDATA #REQUIRED docBase CDATA #REQUIRED maxInactiveInterval NMTOKEN "30"> ]>

935

<WebServer id="webServer"> <Service id="service0"> <WebApplication id="examples" mapping="/examples" docBase="examples"/> </Service> </WebServer>

Vous pouvez modifier, par exemple, le numro du port utilis par le serveur. Vous pourrez ainsi faire fonctionner plusieurs serveurs sur le mme PC. Vous pouvez galement attribuer au serveur un nom (hostname) pour remplacer le nom par dfaut (localhost).

936

LE DVELOPPEUR JAVA 2
Vous pouvez indiquer un autre rpertoire comme racine du serveur (docbase) ainsi que pour le stockage des fichiers temporaires (work). La ligne :
WebApplication id="examples" mapping="/examples" docBase="examples"/>

dfinit un "mapping", c'est--dire une correspondance entre un rpertoire virtuel (examples) et un rpertoire rel (/examples). Le rpertoire de base pour les documents est le rpertoire virtuel examples. Normalement, ce rpertoire devrait tre un sous-rpertoire du rpertoire webpages. Toutefois, ce rpertoire virtuel correspond en ralit au rpertoire rel examples se trouvant dans le rpertoire de dmarrage du serveur. Ainsi, ne vous tonnez pas si vous essayez de crer un rpertoire nomm examples dans webpage. Ce rpertoire sera inaccessible car toutes les requtes seront rediriges vers le rpertoire c:\jswdk-1.0.1\examples.

Test d'une servlet


Le JSWDK est livr avec un certain nombre de servlets de dmonstration. Afin de vrifier que tout fonctionne correctement, vous pouvez excuter ces servlets. Pour cela, procdez de la faon suivante :

1. A l'aide de votre navigateur, connectez-vous l'adresse


localhost:8080/

http://

pour afficher le document index.html.

2. Cliquez sur le lien Servlet examples. 3. Cliquez sur le lien Execute de la ligne Request header (Figure 22.3).
Le navigateur affiche alors une page HTML, comme indiqu sur la Figure 22.4. Le document HTML affich contient diverses informations concernant la requte, le serveur et le navigateur. Si vous droulez le menu Affichage du

CHAPITRE 22 LES SERVLETS ET LES JAVA SERVER PAGES

937

Figure 22.3 : Test d'une servlet.

navigateur et slectionnez l'option Sources de la page, vous obtenez le rsultat de la Figure 22.5. Vous pouvez constater qu'il s'agit d'une page HTML tout fait ordinaire. Toutefois, les informations qu'elle contient sont spcifiques la session en cours et n'ont donc pu tre rassembles que de faon dynamique. Si vous

938

LE DVELOPPEUR JAVA 2

Figure 22.4 : L'affichage de la servlet.

examinez l'adresse du lien utilis, visible dans la barre d'tat de la fentre du navigateur, sur la Figure 22.4, vous constaterez qu'elle a la valeur suivante :
http://localhost:8080/examples/servlet/RequestHeaderExample

Le rpertoire servlet est virtuel. Les servlets sont normalement installes dans le rpertoire webpages\web-inf\servlets. Toutefois, cause du mapping dcrit prcdemment, la servlet utilise dans l'exemple ci-dessus se trouve dans le rpertoire examples\web-inf\servlets.

CHAPITRE 22 LES SERVLETS ET LES JAVA SERVER PAGES

939

Figure 22.5 : Les sources de la page affiche.

Attention : Veillez ne pas faire de confusion entre le nom du rpertoire rel (web-inf\servlets, au pluriel) et le nom virtuel utilis dans les url des servlets (servlet, au singulier).

Cration d'une servlet


Nous allons maintenant crer notre premire servlet. Toutefois, avant de pouvoir la compiler, nous devons configurer la variable d'environnement classpath. En effet, les packages dont nous avons besoin ne font pas partie de l'API standard.

940

LE DVELOPPEUR JAVA 2

Configuration de la variable d'environnement classpath


Nous devons inclure dans notre chemin d'accs aux classes le fichier servlet.jar. Pour cela, modifiez votre fichier de configuration autoexec.bat en ajoutant la ligne suivante :
set classpath=.;c:\jswdk1.0.1\lib\servlet.jar

N'oubliez pas le point ! Dans le cas contraire, les classes standard ne seraient plus accessibles. (Pour plus de dtails, voir le Chapitre 1.)

Codage de la servlet
Notre premire servlet sera rudimentaire et affichera simplement une chane de caractres. Dans votre diteur, saisissez le code suivant :
import java.io.*; import javax.servlet.*; import javax.servlet.http.*;

public class PremiereServlet extends HttpServlet { public void doGet( HttpServletRequest requete, HttpServletResponse reponse) throws IOException, ServletException { reponse.setContentType("text/html"); PrintWriter pw = reponse.getWriter(); pw.print("<html>"); pw.print("<body bgcolor=\"white\">"); pw.print("<head>"); pw.print("<title>Ma premire servlet</title>"); pw.print("</head>"); pw.print("<body>");

CHAPITRE 22 LES SERVLETS ET LES JAVA SERVER PAGES


pw.print("<h1>Ca marche !</h1>"); pw.print("</body>"); pw.println("</html>"); } }

941

Enregistrez ce programme sous le nom PremiereServlet.java, dans le rpertoire webpages\web-inf\applets. Compilez le programme et corrigez les erreurs de frappe ventuelles. Une fois que le programme est compil sans erreur, vous devez obtenir dans le rpertoire webpages\web-inf\applets un fichier PremiereServlet.class.

Utilisation de la premire servlet


Pour utiliser cette servlet, il suffit de taper son url dans la zone d'adresse du navigateur, en n'oubliant pas qu'il s'agit de son url virtuel :
http://localhost:8080/servlet/PremiereServlet

Si vous tapiez son url rel, le serveur essaierait de l'afficher comme un document au lieu de l'excuter, et produirait donc une erreur 404 puisque le fichier webpages\web-inf\applets\PremiereServlet n'existe pas. (Le fichier porte l'extension .class.) La Figure 22.6 montre le rsultat obtenu.

Description de la premire servlet


Dans la section prcdente, nous avons crit une servlet sans comprendre tous les aspects de son fonctionnement. Nous allons maintenant dcrire en dtail ses lments.
import java.io.*; import javax.servlet.*; import javax.servlet.http.*;

942

LE DVELOPPEUR JAVA 2

Figure 22.6 : Notre premire servlet affiche dans le navigateur.

Nous commenons par importer les packages ncessaires. Le package java.io est ncessaire car notre servlet doit renvoyer des informations en rponse la requte qu'elle reoit. Cela n'est pas absolument indispensable, mais c'est souvent le cas lorsqu'une servlet travaille seule. Il est rare d'utiliser une servlet pour effectuer un traitement sans envoyer une certaine rponse l'utilisateur qui est l'origine de la requte. Le package javax.servlet est ncessaire car notre servlet lance une exception de type ServletException, dfini dans ce package.

CHAPITRE 22 LES SERVLETS ET LES JAVA SERVER PAGES

943

Tous les autres lments spcifiques aux servlets dont nous avons besoin proviennent du package javax.servlet.http.

Note : Rappelons que javax signifie "java extension" et dsigne les packages qui sont introduits en tant qu'extensions. Ces packages conservent normalement leur nom s'ils sont ensuite intgrs la bibliothque de classes standard, devenant ainsi des extensions standard.
public class PremiereServlet extends HttpServlet {

Notre servlet est une classe qui tend la classe HttpServlet. Celle classe, qui tend la classe javax.servlet.GenericServlet et implmente l'interface Serializable, est une classe abstraite conue pour servir de cadre aux servlets devant rpondre aux requtes http. Elle dclare les mthodes spcifiques suivantes :

doDelete(HttpServletRequestreq, HttpServletResponseresp), qui traite les


requtes HTTP DELETE.

doGet(HttpServletRequestreq, HttpServletResponseresp), qui traite les re-

qutes HTTP GET. Cette mthode traite galement les requtes de type HEAD, qui renvoient uniquement l'en-tte de la rponse. Les requtes de type HEAD peuvent tre utilises par les navigateurs pour dterminer si les donnes qu'ils dtiennent sont jour ou non.

doOptions(HttpServletRequestreq, HttpServletResponseresp), qui traite les


requtes HTTP OPTIONS.

doPost(HttpServletRequestreq, HttpServletResponseresp), qui


requtes HTTP POST. qutes HTTP PUT.

traite les

doPut(HttpServletRequestreq, HttpServletResponseresp), qui traite les re doTrace(HttpServletRequestreq, HttpServletResponseresp), qui traite les
requtes HTTP TRACE.

944

LE DVELOPPEUR JAVA 2

getLastModified(HttpServletRequestreq), qui renvoie la date et l'heure de


la dernire modification de l'objet req, permettant ainsi un navigateur de savoir si l'objet qu'il possde en cache est jour ou non.

service(HttpServletRequestreq, HttpServletResponseresp), qui reoit les


Les mthodes qui nous intresseront le plus souvent sont GET et POST.

requtes http et les distribue aux mthodes intresses, en fonction de leur type.

Note : Il est prfrable de redfinir directement les mthodes qui traitent les types de requtes dont nous avons besoin plutt que de redfinir la mthode service.
public void doGet( HttpServletRequest requete, HttpServletResponse reponse) throws IOException, ServletException {

Ici, nous traiterons des requtes de type GET, qui sont les requtes http les plus frquemment employes. Un navigateur Web emploie la mthode GET pour accder un url sauf si nous prcisons un autre type de requte. Le choix ne se prsente pratiquement que dans les formulaires, comme nous le verrons plus loin. La mthode doGET est appele automatiquement par la mthode service chaque fois que la servlet reoit une requte de type GET. Comme nous l'avons dit plus haut, cette mthode traite galement les requtes de type HEAD. En fait, une requte de type HEAD renvoie le mme en-tte qu'une requte de type GET (mais les donnes ne sont pas envoyes). Il est de la responsabilit du programmeur de configurer convenablement les entits de l'en-tte avant d'envoyer la rponse. En effet, les en-ttes sont dtruits immdiatement aprs l'envoi des donnes. La seule entit obligatoire est le type de contenu (contentType), qui doit tre configur pour indiquer le type MIME du contenu de la rponse. Toutefois, d'autres entits peuvent tre particulirement importantes. C'est le cas, en

CHAPITRE 22 LES SERVLETS ET LES JAVA SERVER PAGES

945

particulier, de la longueur des donnes (contentLength), qui permet, si elle est prsente, de maintenir active la connexion tant que la quantit de donnes indique n'a pas t transmise. Ce point ncessite quelques explications.

Maintenir une connexion active : Lorsque la rponse une


requte a t envoye, la connexion est automatiquement termine. Cette rponse peut tre compose, par exemple, de code HTML qui sera interprt par le navigateur. Ce code peut contenir un nombre quasiment illimit d'url, par exemple des rfrences des images, ou des applets Java. Chaque image et chaque applet sera charge grce l'tablissement d'une nouvelle connexion. De plus, une applet peut contenir plusieurs classes qui seront charges de manire indpendante (sauf si elles sont incluses dans une archive). L'affichage de la page Web dynamique correspondant la rponse la requte peut ainsi ncessiter l'tablissement de cinquante connexions, voire plus. Si nous avons la possibilit d'indiquer la longueur du contenu, la connexion utilise pour la rponse est maintenue ouverte jusqu' ce que la quantit de donnes correspondante ait t transmise, ce qui acclre considrablement la transmission.
reponse.setContentType("text/html");

Ici, nous indiquons le type de contenu. Dans le cas de l'utilisation d'un PrintWriter pour la transmission de la rponse, il est impratif que le type du contenu soit indiqu avant la dclaration de celui-ci.
PrintWriter pw = reponse.getWriter();

Ici, nous obtenons le PrintWriter de l'objet reponse grce la mthode getWriter de celui-ci.
pw.print("<html>"); pw.print("<body bgcolor=\"white\">"); pw.print("<head>");

946

LE DVELOPPEUR JAVA 2
pw.print("<title>Ma premire servlet</title>"); pw.print("</head>"); pw.print("<body>"); pw.print("<h1>Ca marche !</h1>"); pw.print("</body>"); pw.println("</html>"); } }

Il ne nous reste plus qu' envoyer le code HTML en utilisant la mthode print du PrintWriter. Nous utilisons ici la mthode print car HTML ne ncessite pas de fins de lignes. Toutefois, il est prudent de s'assurer que toutes les donnes seront bien transmises et non conserves dans un tampon. La faon normale de procder consiste appeler la mthode flush(). Ici, nous utilisons, pour la dernire ligne, la mthode println(), qui donne le mme rsultat sous une forme plus compacte.

Attention : Dans la plupart des cas, il n'est pas ncessaire de vider ainsi
les tampons. Toutefois, il est plus sr de prendre cette prcaution. Notez par ailleurs que l'inclusion d'un caractre fin de ligne ("\n") dans les donnes ne garantit pas que les tampons seront vids car ce caractre est diffrent d'une plate-forme une autre. La mthode println, en revanche, offre cette garantie en utilisant le caractre fin de ligne de la plate-forme sur laquelle fonctionne le programme. Utiliser un caractre "fin de ligne" est la meilleure faon d'obtenir un bug difficile traquer lorsque la servlet sera installe sur une plate-forme diffrente.

Note : La disposition choisie ici n'offre d'avantages que du point de vue du programmeur. Les neuf dernires lignes du programme pourraient aussi bien tre remplaces par la ligne suivante :
pw.println("<html><body bgcolor=\"white\"><head><title>Ma premire servlet</title></head><body><h1>Ca marche !</h1></ body></html>");

HTML tant un langage trs laxiste, vous pouvez aussi obtenir le mme rsultat avec la forme suivante :

CHAPITRE 22 LES SERVLETS ET LES JAVA SERVER PAGES


pw.println("<html><body bgcolor=\"white\"><head><title>Ma premire servlet</title></head><body><h1>Ca marche !");

947

ce qui n'est tout de mme pas conseiller !

Attention : Si vous utilisez le serveur du JSWDK pour tester vos servlets,


n'oubliez pas que, contrairement aux serveurs rellement conus pour tre utiliss en production, celui-ci n'est destin qu'aux tests et est sujet certaines limitations. L'une des plus importantes est qu'il ne recharge pas les servlets modifies. Ne vous arrachez donc pas les cheveux si vous essayez de mettre au point une servlet et ne constatez aucun changement aprs une recompilation. Pour que la modification soit prise en compte, il faut imprativement arrter le serveur et le redmarrer. Cela constituerait bien sr un handicap insurmontable pour un vritable serveur, mais cela n'est pas trop gnant ici tant donn la rapidit de l'opration.

Une servlet pour exploiter un formulaire


Une servlet affichant une page HTML statique n'offre pas beaucoup d'avantages ! Le principal intrt des servlets est de pouvoir fournir des rponses dynamiques, c'est--dire dont le contenu dpend de la requte et ne peut pas tre prcalcul. Les applications sont innombrables. Elles se justifient chaque fois que des ressources ncessaires sont inaccessibles localement. Il peut s'agir d'une slection dans une base de donnes, de l'criture dans un fichier, ou d'un traitement dont les lments doivent rester totalement confidentiels. Dans le premier cas, le traitement local pourrait tre envisag condition de transmettre un copie entire de la base de donnes. Imaginons, par exemple, un programme de test proposant dix questions tires au hasard parmi mille. Ce type de traitement peut tre effectu localement dans un navigateur, grce une applet, condition de transmettre la totalit des questions, ce qui peut poser des problmes de temps de transmission. Dans le deuxime cas (criture sur disque), le traitement local est impossible pour deux raisons. La premire concerne la scurit, cette opration tant interdite une applet, sauf si elle est signe. La seconde raison est que

948

LE DVELOPPEUR JAVA 2
les donnes qui serait crites sur un disque local ne pourraient tre ensuite exploites que localement, ce qui n'est pas toujours le but recherch. Dans le troisime cas, un programme peut tre ncessaire pour vrifier la cohrence d'un code d'accs. Ce type de traitement peut parfaitement tre excut localement, mais cela offre un programmeur moyennement malin la possibilit de dcompiler le code et d'accder ainsi l'algorithme de validation, ce qui peut tre trs dangereux. Et s'il s'agit de vrifier qu'un utilisateur figure bien dans la liste des personnes autorises, on comprend aisment qu'il n'est pas souhaitable de tlcharger cette liste pour effectuer la vrification localement. On voit dans ces trois exemples que le choix d'excuter un traitement sur le serveur et non sur le client peut tre dict par de multiples considrations. Nous allons maintenant crire une servlet un peu plus complexe qui rcuprera le contenu d'un formulaire et l'enregistrera dans un fichier.

Codage de la servlet
Le code de la servlet est donn ci-aprs. Il ne prsente pratiquement rien de nouveau par rapport ce que nous avons appris jusqu'ici, dans ce chapitre et dans le Chapitre 14, consacr aux entres/sorties :
import import import import java.io.*; java.util.*; javax.servlet.*; javax.servlet.http.*;

public class DeuxiemeServlet extends HttpServlet { public void doPost( HttpServletRequest requete, HttpServletResponse reponse) throws IOException, ServletException { String donnes = ""; FileWriter fichier; for (Enumeration e = requete.getParameterNames();

CHAPITRE 22 LES SERVLETS ET LES JAVA SERVER PAGES

949

e.hasMoreElements() ;) { donnes = donnes + requete.getParameter( (String)e.nextElement()) + "\t"; } donnes = donnes + "\n"; fichier = new FileWriter("donnes.txt", true); fichier.write(donnes); fichier.close(); reponse.setContentType("text/html"); PrintWriter pw = reponse.getWriter(); pw.print("<html>"); pw.print("<body bgcolor=\"white\">"); pw.print("<head>"); pw.print("<title>Confirmation de commande</title>"); pw.print("</head>"); pw.print("<body>"); pw.print("<p>Nous avons bien enregistr votre commande et vous en remercions.</p>"); pw.print("</body>"); pw.println("</html>"); } }

Nous importons le package java.util car nous avons besoin de l'interface Enumeration. Nous utilisons cette interface plutt qu'un Iterator car nous aurons traiter un tableau.
import java.util.*;

Nous dclarons deux nouvelles variables : donnes, de type String, qui recevra les donnes qui doivent tre enregistres dans le fichier, et fichier, de type FileWriter :

950
String donnes = ""; FileWriter fichier;

LE DVELOPPEUR JAVA 2

Notez que la chane donnes est initialise la valeur "". La chane contenant les donnes est construite dans une boucle parcourant une numration construite sur la valeur de retour de la mthode getParametetNames, qui renvoie un tableau de chanes de caractres contenant les noms des paramtres.
for (Enumeration e = requete.getParameterNames(); e.hasMoreElements();) { donnes = donnes + requete.getParameter( (String)e.nextElement()) + "\t"; } donnes = donnes + "\n";

Pour chaque lment de l'numration, nous appelons la mthode getParameter de l'objet requete. Cette mthode renvoie une chane de caractres qui est ajoute la fin de la chane donnes. Nous ajoutons galement une tabulation pour sparer les donnes. A la fin du traitement, nous ajoutons un saut de ligne afin de sparer les enregistrements.

Note : Le programme ainsi conu offre l'avantage de continuer fonctionner si le nombre de champs du formulaire est modifi, car les champs sont traits de manire anonyme. Bien entendu, il est galement possible de traiter les champs de manire nominative, grce la mthode getParameter(), qui prend pour argument une chane de caractres correspondant au nom du champ et renvoie une chane contenant sa valeur. Nous ouvrons ensuite le fichier donnes.txt en mode ajout de donnes (indiqu par l'argument true). Nous y crivons la ligne de donnes et nous refermons le fichier. Le reste du programme est semblable la version prcdente.

CHAPITRE 22 LES SERVLETS ET LES JAVA SERVER PAGES


fichier = new FileWriter("donnes.txt", true); fichier.write(donnes); fichier.close();

951

Note : Dans ce programme, nous avons implment la mthode

doPost

uniquement pour montrer les deux cas. Nous aurions pu tout aussi bien implmenter la mthode doGet. Les deux diffrences essentielles entre les deux mthodes sont les suivantes :

La mthode POST ne limite pas la quantit de donnes qui peuvent


tre transmises au serveur. La mthode GET impose une limite, gnralement de 250 caractres.

Avec la mthode GET, les donnes transmises sont affiches dans la


Note : De nombreux formulaires utilisent la mthode GET alors qu'il

ligne d'adresse du navigateur, ce n'est pas le cas avec la mthode POST.

semblerait plus logique d'employer la mthode POST. Il peut y avoir cela plusieurs raisons :

Une page affiche avec la mthode GET peut tre recharge. Cela peut

tre un avantage (plus pratique) ou un inconvnient (manque de scurit). Pour une scurit maximale, il faut que le formulaire utilise la mthode POST et soit lui-mme non rechargeable.

La mthode GET permet d'enregistrer l'url dans les favoris ou sous la

forme d'un raccourci, ou encore dans un lien HTML. Cela peut tre un avantage ou un inconvnient, pour les mmes raisons que prcdemment.

Avec la mthode GET, les donnes sont visibles dans la zone d'adresse
du navigateur. Cela peut tre un inconvnient si le formulaire comporte des champs que l'utilisateur ne doit pas voir. (Ils sont toutefois visibles dans les sources de la page.)

Certains serveurs peuvent n'autoriser qu'une de ces deux mthodes.

952
Le formulaire

LE DVELOPPEUR JAVA 2

Le formulaire est un formulaire HTML normal. Il utilise videmment la mthode POST puisque c'est ce que notre servlet attend. Voici le code HTML du formulaire :
<html><head> <title>Formulaire d'enregistrement</title> </head> <body bgcolor="white"> <form method="post" action="http:/servlet/DeuxiemeServlet"> <ul><ul> <p>M/Mme/Mlle&nbsp;: <select name="titre"> <option>M <option>Mme <option>Mlle </select><br><br> Nom&nbsp;:<br> <input type="text" name="nom" size="50"><br> Prnom&nbsp;:<br> <input type="text" name="prenom" size="50"><br> Socit&nbsp;:<br> <input type="text" name="societe" size="50"><br> E-mail&nbsp;:<br> <input type="text" name="email" size="50"><br> Adresse&nbsp;:<br> <input type="text" name="adresse1" size="50"><br> <input type="text" name="adresse2" size="50"><br> <input type="text" name="adresse3" size="50"><br> Code postal&nbsp;:<br> <input type="text" name="codepostal" size="10"><br> Ville&nbsp;:<br> <input type="text" name="ville" size="50"><br><br> <br>Je commande <input type="text" name="nombre" size="3" value="1"> exemplaire(s) de votre housse de cathdrale taille XS

CHAPITRE 22 LES SERVLETS ET LES JAVA SERVER PAGES

953

au prix de 59,50&nbsp;francs plus 15&nbsp;francs de frais de port et d'emballage)<input type="text" name="montant" size="7" value="74,50">&nbsp;francs. <br><br> Rfrences carte Visa :<br> Numro :<br> <input type="text" maxlength="4" name="ncb1" size="4"> <input type="text" maxlength="4" name="ncb2" size="4"> <input type="text" maxlength="4" name="ncb3" size="4"> <input type="text" maxlength="4" name="ncb4" size="4"><br> Nom du porteur :<br> <input type="text" name="porteur" size="50"><br> Date d'expiration :<br> <select name="expirmonth"> <option> <option>01 <option>02 <option>03 <option>04 <option>05 <option>06 <option>07 <option>08 <option>09 <option>10 <option>11 <option>12 </select> <select name="expiryear"> <option> <option>00 <option>01 <option>02 <option>03 </select> <br> <br> <br>

954

LE DVELOPPEUR JAVA 2
<input type="reset" value="Remettre zro"> <input type="submit" value="Commander"></p> </ul></ul> <input type="hidden" name="paiement" size="50" value="Carte Bancaire France"><br> <input type="hidden" name="commande" size="50" value="Internet"><br> </form> </body></html>

Vous trouverez une copie de ce formulaire dans le fichier formulaire.html, sur le CD-ROM accompagnant ce livre.

Utilisation du formulaire
Le formulaire doit tre plac dans le rpertoire webpages du serveur. Pour l'utiliser, tapez l'url suivant dans la ligne d'adresse de votre navigateur :
http://localhost:8080/formulaire.html

La Figure 22.7 montre le formulaire rempli. Une fois le formulaire rempli, cliquez sur le bouton Commander. Le navigateur affiche une page de confirmation (Figure 22.8). Pour vrifier que le programme a t excut correctement, ouvrez le rpertoire d'installation du serveur (c:\jswdk-1.0.1). Vous devez y trouver un fichier nomm donnes.txt. Si vous ouvrez ce fichier dans un diteur de texte, vous constaterez qu'il contient toutes les donnes du formulaire (Figure 22.9). Notez que les donnes ne sont pas dans l'ordre dans lequel elles ont t transmises.

Note : Dans le cas, plus frquent, o les donnes auraient tre transmises une base de donnes, nous aurions tenir compte de cette particula-

CHAPITRE 22 LES SERVLETS ET LES JAVA SERVER PAGES

955

Figure 22.7 : Le formulaire rempli.

956

LE DVELOPPEUR JAVA 2

Figure 22.8 : La commande a t enregistre.

Figure 22.9 : Le fichier contient les donnes du formulaire.

CHAPITRE 22 LES SERVLETS ET LES JAVA SERVER PAGES

957

rit. Il nous suffirait de baser notre programme sur la transmission de paires nom/valeur pour qu'il reste indpendant de l'ordre des champs.

Problmes de synchronisation
En ce qui concerne les variables, nous n'avons pas ici tenir compte de la synchronisation car toutes les ressources que nous utilisons sont locales la mthode doGet. Chaque thread qui accde cette mthode utilise donc sa propre copie de ces ressources. Ce ne serait pas le cas, par exemple, si la variable donnes avait t dclare comme membre de la classe DeuxiemeServlet. Utiliser des variables locales est souvent une solution simple pour viter les problmes de synchronisation. Une autre solution consiste configurer le serveur pour qu'une nouvelle instance de la servlet soit cre pour chaque requte. Pour acclrer le fonctionnement du programme, on peut galement utiliser un pool d'instances. Dans ces deux cas, seules les variables statiques doivent tre synchronises. Toutefois, il reste le problme des ressources externes, en l'occurrence le fichier donnes.txt. Si nous laissons les choses en l'tat, deux threads peuvent accder au fichier simultanment, ce qui risque de poser des problmes. Une solution consiste synchroniser le bloc :
synchronized(this) { fichier = new FileWriter("donnes.txt", true); fichier.write(donnes); fichier.close(); }

Il faut pour cela que la variable fichier soit dclare comme membre de la classe. (Dans le cas contraire, la synchronisation ne sert rien puisque chaque thread utilise sa propre copie des variables locales.) Toutefois, cette mthode prsente un inconvnient. Si vous n'administrez pas le serveur vous-mme, vous ne pouvez pas tre assur qu'il n'y aura qu'une seule instance de la classe en fonctionnement. Si plusieurs instances sont cres, la synchronisation ne fonctionnera qu' l'intrieur de chaque instance. Le FileWriter de chaque instance sera bien synchronis, mais pas le fichier cor-

958

LE DVELOPPEUR JAVA 2
respondant. Pour viter ce problme, il faut dclarer la variable fichier comme membre statique de la classe. De cette faon, nous sommes certains qu'il n'existera toujours qu'un seul FileWriter synchronis et que les donnes ne pourront tre perdues.

Attention : Cette mthode ne garantit pas les donnes contre les problmes de synchronisation avec d'autres applications. Si le fichier doit tre accessible d'autres applications, le problme est plus complexe et doit tre gr au moyen d'exceptions qui sont lances par Java lorsque le systme indique que le fichier est dj en cours d'utilisation. Le traitement d'une telle exception consiste gnralement attendre quelques millisecondes et ressayer. Au bout d'un certain nombre d'essais infructueux, on considre que l'opration a chou. Cette mthode devra tre mise en uvre, par exemple, si plusieurs JVM fonctionnent simultanment et grent des accs au fichier.

Comparaison avec Perl


Une servlet comme celle-ci est suppose remplacer un script CGI, gnralement crit en Perl. A titre de comparaison, voici le bloc qui dcode les paramtres de la requte, en Perl et en Java :

Perl
read(STDIN,$in,$ENV{'CONTENT_LENGTH'}); $donnes=""; @in = split(/&/,$in); foreach $i(0..$#in){ $in[$i] =~ s/\+/ /g; ($key, $val) = split(/=/,$in[$i],2); $UnURLed = &unescape($val); $donnes="$donnes\t$UnURLed"; } $donnes="$donnes\n"; sub unescape{

CHAPITRE 22 LES SERVLETS ET LES JAVA SERVER PAGES


local ($_)=@_; tr/+/ /; s/%(..)/pack("c",hex($1))/ge; $_; }

959

Java
for (Enumeration e = requete.getParameterNames(); e.hasMoreElements() ;) { donnes = donnes + requete.getParameter( (String)e.nextElement()) + "\t"; } donnes = donnes + "\n";

Est-il utile d'argumenter ?

Les servlets pour remplacer les SSI


Les servlets ont d'autres utilits. Elles permettent, par exemple, de remplacer les SSI, ou Server Side Include. Les SSI sont des commandes places dans le code HTML et qui sont excutes par le serveur. Elles ressemblent en cela aux applets, qui sont en quelque sorte des commandes incluses dans le code HTML et excutes par le navigateur. Une commande SSI peut tre employe, par exemple, pour afficher l'heure du systme, ou un compteur d'accs. Les commandes SSI sont insres dans les pages HTML au moyen de la balise <SERVLET>, qui utilise la syntaxe suivante :
<SERVLET CODE="nom_servlet" CODEBASE="url" param1=valeur1 parma2=valeur2> <PARAM NAME="nom_paramtre1" VALUE="valeur_paramtre1"> <PARAM NAME="nom_paramtre2" VALUE="valeur_paramtre2">

960

LE DVELOPPEUR JAVA 2
Texte affich si le serveur ne prend pas en charge la balise SERVLET. </SERVLET>

L'utilisation de la balise <SERVLET> est intressante lorsque la page HTML est essentiellement statique. Parmi les utilisations courantes des SSI, on trouve l'affichage de l'heure du systme, l'indication de la date de dernire mise jour de la page ou l'affichage d'un compteur d'accs.

Attention : Pour raliser l'exemple suivant, vous devez disposer d'un


serveur compatible avec les SSI. Ce n'est pas le cas du serveur du JSWDK. Les exemples suivants ont t raliss avec le Java Web Server de Sun Microsystems. Il s'agit d'une application commerciale, vendue environ 300 dollars, ce qui est un prix trs faible pour ce type d'application. Par ailleurs, vous pouvez tlcharger une version d'valuation limite 30 jours, l'adresse :
http://www.sun.com/software/jwebserver/

A titre d'exemple, nous utiliserons une servlet drive de notre Java-Bean du chapitre prcdent, affichant l'heure du systme. Il s'agit d'une servlet vraiment minimale. Nous verrons un exemple plus intressant un peu plus loin. Le code ne ncessite pas d'explication particulire.
import import import import java.io.*; java.util.*; javax.servlet.*; javax.servlet.http.*;

public class Heure extends HttpServlet { public void doGet (HttpServletRequest requete, HttpServletResponse reponse) throws IOException, ServletException {

CHAPITRE 22 LES SERVLETS ET LES JAVA SERVER PAGES


reponse.setContentType("text/html"); PrintWriter pw = reponse.getWriter();

961

pw.print(new Date(System.currentTimeMillis()).toString()); } }

Enregistrez ce programme dans le fichier Heure.java et compilez-le. Le fichier Heure.class rsultant doit tre plac dans le rpertoire servlets du serveur. Voici un fichier HTML minimal utilisant cette servlet/SSI :
<html> <body bgcolor="white"> <h1>Date et heure du serveur : <servlet code="Heure">.</h1> </body></html>

Ce fichier doit tre enregistr avec l'extension .shtml dans le rpertoire public_html du serveur. L'extension shtml indique au serveur qu'il doit parcourir le code HTML avant de l'envoyer afin de dtecter les lments <SERVLET>. Lorsqu'il rencontre un tel lment, le serveur le remplace (ainsi que son contenu) par le rsultat de la servlet rfrence. Le document (que nous avons nomm heure.shtml) est maintenant accessible au moyen de l'url :
http://localhost:8080/heure.shtml

La Figure 22.10 montre le rsultat affich par le navigateur.

Un compteur d'accs
Le programme suivant permet de crer une SSI affichant le nombre d'accs une page. Il n'est gure plus complexe que les prcdents :

962

LE DVELOPPEUR JAVA 2

Figure 22.10 : Le document contenant une SSI affich par Netscape Navigator.

import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class Compteur extends HttpServlet { static RandomAccessFile fichier; public void doGet (HttpServletRequest requete, HttpServletResponse reponse) throws IOException, ServletException {

CHAPITRE 22 LES SERVLETS ET LES JAVA SERVER PAGES


String compteur = requete.getParameter("page"); long compte = -1;

963

fichier = new RandomAccessFile("compteurs\\" + compteur, "rw"); synchronized (this) { compte = fichier.readLong(); fichier.seek(0); fichier.writeLong(++compte); fichier.close(); } reponse.setContentType("text/html"); PrintWriter pw = reponse.getWriter(); pw.print(compte); } }

Ce programme ouvre un fichier en accs direct. Le nom du fichier est donn par le paramtre "page" de la balise <SERVLET>. De cette faon, la mme servlet peut grer plusieurs compteurs, pour des pages diffrentes. Le fichier est ouvert en mode lecture/criture (rw) et une valeur y est lue grce la mthode readLong. L'index de lecture est ensuite rinitialis l'aide de la mthode seek(0), puis la valeur incrmente est crite dans le fichier et le fichier est referm. La valeur du compteur est ensuite affiche dans la page HTML. Notez qu'il est parfaitement possible de supprimer l'affichage du compteur si vous ne voulez pas que les visiteurs soit informs du compte. Vous pouvez par ailleurs crire une servlet affichant seulement le compteur et l'utiliser dans une page de statistiques. L'exemple suivant montre un document HTML utilisant cette servlet :
<html> <body bgcolor="white"> <h3>Nombre de visiteurs : <servlet code="Compteur">

964

LE DVELOPPEUR JAVA 2
<param name="page" value="page1"></servlet>.</h3> </body></html>

Ce document est plac dans le fichier compteur.shtml. Avec Java Web Server, ce fichier est plac dans le rpertoire public_html. La servlet est plac dans le rpertoire servlets. Le compteur est un fichier nomm page1. Il doit tre plac dans un sous-rpertoire compteurs du rpertoire principal du serveur. Pour une installation standard, il s'agira de :
C:\JavaWebServer2.0\compteurs

Bien entendu, vous pouvez utiliser n'importe quel rpertoire, en modifiant la servlet en consquence. Sachez simplement que le rpertoire racine pour les servlets est simplement le rpertoire du serveur (et non le sous-rpertoire servlets). Le document HTML peut tre visualis l'aide de l'url :
http://localhost:8080/compteur.shtml

La Figure 22.11 montre le rsultat obtenu.

Note : Au dpart, vous devez disposer d'un fichier contenant un

long.

Vous en trouverez un exemple sur le CD-ROM accompagnant ce livre (le fichier page1). Pour crer un tel fichier, vous pouvez tout simplement commenter la ligne qui lit la valeur du compteur et compiler puis excuter le programme :
// compte = fichier.readLong();

De cette faon, le programme peut tre excut sans produire d'erreur mme si le fichier n'existe pas. Comme la valeur de compte est initialise -1, le fichier est automatiquement cr avec la valeur 0. A titre d'exercice, vous pouvez modifier le programme pour que la ligne ci-dessus ne soit excute que si le fichier existe. Ainsi, la servlet pourra tre insre dans une page HTML sans qu'il soit ncessaire de crer le fichier du compteur.

CHAPITRE 22 LES SERVLETS ET LES JAVA SERVER PAGES

965

Figure 22.11 : Utilisation du compteur.

Les Java Server Pages


Avec la version 1 du Java Web Server, Sun avait introduit une technologie appele Page Compilation qui permettait de placer du code source Java directement dans une page HTML. Cette technologie a t remplace par la technologie JSP (Java Server Pages) qui permet d'obtenir le mme rsultat. L'ide est que le code d'une servlet est compos d'une partie utilitaire (la dclaration) qui est toujours identique, et d'une partie personnalise (votre programme). La technologie JSP permet de ne placer dans la page HTML que la partie personnalise. Lorsque le serveur rencontre cet lment, il le complte pour en faire le code source d'une servlet, le compile et l'excute. Il s'ensuit un dlai supplmentaire (pour la compilation), mais cela n'a pas d'importance car la servlet compile est conserve pour les accs futurs. Elle n'est recompile que si la page qui la contient est modifie.

966

LE DVELOPPEUR JAVA 2
L'exemple du compteur prcdent peut tre rcrit en utilisant les JSP de la faon suivante :
<html> <body bgcolor="white"> <h3>Nombre de visiteurs : <%@ page import = "java.io.*"%> <%! static RandomAccessFile fichier; %> <% long compte = -1; String compteur = "page1"; fichier = new RandomAccessFile("compteurs\\" + compteur, "rw"); synchronized(this) { compte = fichier.readLong(); fichier.seek(0); fichier.writeLong(++compte); fichier.close(); } %> <%= compte %> </h3> </body></html>

Ce fichier contient plusieurs lments spcifiques aux JSP. Tous les lments JSP commencent par les caractres <% et sont termins par les caractres %>.

Directives
<%@ page import = "java.io.*"%>

Les directives sont signales par les caractres @ page. Ici, la directive indique que le package java.io doit tre import. Les directives servent galement

CHAPITRE 22 LES SERVLETS ET LES JAVA SERVER PAGES

967

indiquer le type de contenu, ainsi que diffrentes options concernant l'excution de la servlet.

Dclarations
<%! static RandomAccessFile fichier; %>

Les dclarations commencent par le caractre !. Elles permettent de dclarer des variables en dehors du code proprement dit. Ici, nous utilisons une dclaration pour indiquer que la variable fichier est statique.

Scriplets
<% ...code... %>

Une scriplet est compose de code Java excutable. Ce code est ajout au code de la mthode de service de la servlet gnre.

Expressions
<%= compte %>

Une expression permet d'inclure le contenu d'une variable dans le code HTML. Ici, nous demandons que la valeur de compte soit affiche dans la page. Notez qu'il est possible de placer du code dans une dclaration, par exemple :
<%= ++compte %>

968
Le code gnr

LE DVELOPPEUR JAVA 2

Lorsque le serveur rencontre les lments JSP, il cre une servlet et la compile. Cette servlet est place dans un rpertoire temporaire. Avec Java Web Server, ce rpertoire est :
<rpertoire d'installation>\tmpdir\default\pagecompile\jsp

Dans ce rpertoire, on trouve le fichier _compteur.java qui contient le code source suivant :
package pagecompile.jsp; import import import import import import import import import import import import javax.servlet.*; javax.servlet.http.*; javax.servlet.jsp.*; java.io.PrintWriter; java.io.IOException; java.io.FileInputStream; java.io.ObjectInputStream; java.util.Vector; com.sun.server.http.pagecompile.jsp.runtime.*; java.beans.*; com.sun.server.http.pagecompile.jsp.JspException; java.io.*;

public class _compteur extends HttpJspBase { static char[][] _jspx_html_data = null; // begin [file="C:\\JavaWebServer2.0\\public_html\\compteur.jsp";from=(5,3);to=(5,38)] static RandomAccessFile fichier; // end public _compteur( ) { }

CHAPITRE 22 LES SERVLETS ET LES JAVA SERVER PAGES


private static boolean _jspx_inited = false;

969

public final void _jspx_init() throws JspException { ObjectInputStream oin = null; int numStrings = 0; try { FileInputStream fin = new FileInputStream("C:\\JavaWebServer2.0\\tmpdir\\default\\pagecompile\\jsp\\pagecompile.jspcompteur.dat"); oin = new ObjectInputStream(fin); _jspx_html_data = (char[][]) oin.readObject(); } catch (Exception ex) { throw new JspException("Unable to open data file"); } finally { if (oin != null) try { oin.close(); } catch (IOException ignore) { } } } public void _jspService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { boolean _jspx_cleared_due_to_forward = false; JspFactory _jspxFactory = null; PageContext pageContext = null; HttpSession session = null; ServletContext application = null; ServletConfig config = null; JspWriter out = null; Object page = this; String _value = null; try { if (_jspx_inited == false) { _jspx_init(); _jspx_inited = true; } _jspxFactory = JspFactory.getDefaultFactory(); response.setContentType("text/html");

970

LE DVELOPPEUR JAVA 2
pageContext = _jspxFactory.getPageContext(this, request, response, "", true, 8192, true); application = pageContext.getServletContext(); config = pageContext.getServletConfig(); session = pageContext.getSession(); out = pageContext.getOut(); out.print(_jspx_html_data[0]); out.print(_jspx_html_data[1]); out.print(_jspx_html_data[2]); // begin [file="C:\\JavaWebServer2.0\\public_html\\compteur.jsp";from=(7,2);to=(17,0)] long compte = -1; String compteur = "page1"; fichier = new RandomAccessFile("compteurs\\" + compteur, "rw"); synchronized(this) { compte = fichier.readLong(); fichier.seek(0); fichier.writeLong(++compte); fichier.close(); } // end out.print(_jspx_html_data[3]); // begin [file="C:\\JavaWebServer2.0\\public_html\\compteur.jsp";from=(18,3);to=(18,11)] out.print( compte ); // end out.print(_jspx_html_data[4]); } catch (Throwable t) { if (out.getBufferSize() != 0) out.clear(); throw new JspException("Unknown exception: ", t); } finally { if (!_jspx_cleared_due_to_forward)

CHAPITRE 22 LES SERVLETS ET LES JAVA SERVER PAGES


out.flush(); _jspxFactory.releasePageContext(pageContext); } } }

971

Dans ce code source, les lments qui proviennent de notre fichier compteur.jsp sont indiqus entre les commentaires //begin et //end. La position des lments dans le fichier source est galement indique.

Note : Avec le JSWDK, le code source n'est pas accessible.

Rsum
Nous n'avons fait ici qu'effleurer le sujet des servlets et des JSP. Nous n'avons malheureusement pas la place d'entrer dans les dtails. Vous trouverez dans la documentation du JSWDK de trs nombreux exemples qui vous permettront d'approfondir vos connaissances.

Annexe A : Les classes String et StringBuffer

Les classes String et StringBuffer

U CHAPITRE 4, NOUS AVONS TUDI LES CHANES DE CARACTRES en tant qu'objets. Nous avons vu qu'il n'existe pas de primitives pour reprsenter les chanes de caractres. Par consquent, il n'existe pas d'oprateurs pour le traitement du texte, l'exception, toutefois, d'un cas particulier.

L'oprateur +
L'oprateur + peut tre en quelque sorte surcharg pour oprer sur les chanes de caractres. Appliqu des chanes de caractres, l'oprateur +

974

LE DVELOPPEUR JAVA 2
reprsente la concatnation, c'est--dire la mise bout bout des oprandes. Ainsi :
System.out.println("Le programme " + "est termin");

est quivalent :
System.out.println("Le programme est termin");

Notez la prsence d'un espace aprs le mot programme. La concatnation n'ajoute aucun espace entre les lments. Vous devez donc les prendre en compte vous-mme. L'oprateur + est un raccourci qui permet d'viter l'utilisation de la mthode concat(). Celle-ci permet d'obtenir le mme rsultat de la faon suivante :
String chane1 = "Le programme " String chane2 = "est termin."; System.out.println(chane1.concat(chane2));

Le principal avantage de l'oprateur +, outre la simplicit d'criture, est qu'il peut s'appliquer des chanes littrales.

Les constructeurs de la classe String


Une chane de caractres peut tre cre de multiples faons. La plus simple consiste affecter une chane littrale un handle dclar de type String :
String chane1 = "Une chane";

ANNEXE A LES CLASSES STRING ET STRINGBUFFER

975

On peut galement initialiser une chane l'aide d'un autre handle de chane dj initialise :
String chane1 = "Une chane"; String chane2 = chane1;

Attention, cependant, au fait que les objets de type String sont statiques. S'agissant d'objet, on peut imaginer que, dans le cas prcdent, chane1 et chane2 pointent vers un seul et mme objet. C'est vrai. Cependant, si nous modifions chane1, la valeur de chane2 ne sera pas modifie, comme le montre l'exemple suivant :
public class Chaines { public static void main( String[] args ) { String chane1 = "FR2"; String chane2 = chane1; chane1 = "TF1"; System.out.println(chane1); System.out.println(chane2); } }

qui affiche :
TF1 FR2

En effet, l'instruction chane1 = "TF1" ne modifie pas la valeur de chane1 mais cre un nouvel objet de type String et lui affecte la valeur littrale indique. Cela peut paratre vident, mais a ne l'est pas du tout ! String est la seule classe qui permet de crer ainsi des objets sans s'en rendre compte. Ici, la valeur prcdente de chane1 continue d'exister car un autre handle pointe vers cet objet. En revanche, lorsqu'un seul handle pointe vers une chane, chaque fois que la valeur de la chane est modifie, l'an-

976

LE DVELOPPEUR JAVA 2
cien objet est abandonn et un nouvel objet est cr. Du point de vue des performances, ce processus peut paratre peu optimis. Ce n'est pas le cas car, le plus souvent, les chanes ne sont pas modifies. Il est ainsi possible de leur attribuer une longueur fixe et non une taille dynamique, ce qui rend leur manipulation beaucoup plus rapide ( l'exception, bien sr, du changement de leur valeur). Toutes les mthodes qui renvoient un objet de type chane reproduisent ce type de comportement. Il n'existe pas de mthodes modifiant le contenu d'une chane de caractres. Ainsi, l'instruction suivante :
chane1.replace('a', 'A');

renvoie une chane de caractres identique chane1 mais dans laquelle les a sont remplacs par des A. Elle ne modifie pas la chane chane1. Dans l'instruction :
chane1 = chane1.replace('a', 'A');

l'objet vers lequel pointait chane1 est remplac par la valeur retourne par la mthode. L'ancien objet point par chane1 est abandonn si aucun autre handle ne pointe vers lui. Dmonstration :
public class Chaines2 { public static void main( String[] args ) { String chane1 = "FR3"; String chane2 = chane1; chane1 = chane1.replace('3', '2');; System.out.println(chane1); System.out.println(chane2); } }

Ce programme produit le rsultat suivant :

ANNEXE A LES CLASSES STRING ET STRINGBUFFER


FR2 FR3

977

Une chane de caractres peut galement tre cre de la faon normale, en utilisant l'oprateur new. Le constructeur de la classe String peut prendre pour argument :

Une chane de caractres, sous la forme d'une valeur littrale, d'un


handle, d'un objet retourn par une mthode, ou d'une expression quelconque s'valuant comme une chane :

String String String String

chane1 chane2 chane3 chane4

= = = =

new new new new

String("une chane"); String(chane1); String(objet.toString()); String("une chane" + chane2 + objet.toString());

Un tableau de byte ou de char, ventuellement accompagn d'un indice de dbut et d'un nombre de caractres, ainsi que de l'indication d'un type d'encodage :
String(byte[] String(byte[] String(byte[] String(byte[]

bytes) bytes, String enc) bytes, int dbut, int longueur) bytes, int dbut, int longueur, String encodage) String(char[] cars) String(char[] cars, int dbut, int longueur)

Une instance de StringBuffer (voir plus loin) :


String(StringBuffer buffer)

978

LE DVELOPPEUR JAVA 2

La classe StringBuffer
La classe StringBuffer permet de crer des chanes de caractres dynamiques, c'est--dire dont le contenu peut tre modifi. La manipulation de telles structures est plus lente car l'espace qui leur est allou doit tre modifi en fonction des besoins. Cependant, la possibilit de modifier le contenu permet d'viter la cration d'une nouvelle instance chaque modification. Pour optimiser les performances, il est prfrable d'utiliser StringBuffer lorsque le contenu d'une chane doit tre modifi, puis de crer une instance de String avec ce contenu une fois les modifications termines. Une instance de StringBuffer peut tre cre de trois faons :

StringBuffer() cre une chane vide en rservant la mmoire pour


16 caractres.

StringBuffer(int
pour l caractres.

l)

cre une chane vide en rservant la mmoire


chane)

StringBuffer(String

cre une chane dont le contenu est identique celui de la chane statique chane.

La classe StringBuffer contient diverses mthodes permettant de modifier le contenu d'une chane dynamique. La mthode append, par exemple, permet d'ajouter la fin de la chane sur laquelle elle est invoque une reprsentation sous forme de chane de son argument. La mthode insert fait la mme chose une position quelconque de la chane sur laquelle elle est invoque. D'autres mthodes permettent d'obtenir le caractre correspondant une position donne, de remplacer un caractre, ou d'obtenir une sous-chane en indiquant la position de dbut et la position de fin de celle-ci.

Annexe B : Les mots rservs

Les mots rservs

B
break cast const double final

L
char

ES MOTS SUIVANTS SONT RSERVS EN JAVA ET NE PEUVENT DONC pas tre employs pour des identificateurs. Notez que certains de ces mots (apparaissant ici en italique) ne sont pas utiliss par Java.

abstract byvalue

boolean case class do false

byte catch continue else finally

default extends

980
float goto inner long operator protected short switch throws var widefp for if instanceof native outer public static synchronized transient void

LE DVELOPPEUR JAVA 2

future implements int new package rest strictfp this true volatile

generic import interface null private return super throw try while

Note : Les mots en gras ne sont pas encore dfinitivement adopts. Ils
sont donc susceptibles de disparatre dans une prochane version.

Annexe C : Configuration de UltraEdit

Configuration de UltraEdit

OMME NOUS L'AVONS DIT AU DBUT DE CE LIVRE, VOUS POUVEZ utiliser, pour crire des programmes Java, de trs nombreux outils allant de l'diteur le plus simple, comme le bloc-notes de Windows, l'environnement de dveloppement intgr le plus sophistiqu. Quels que soient les avantages des environnement intgrs, ils ne sont pas adapts l'apprentissage d'un langage, dont ils ont tendance masquer les particularits. D'un autre ct, un diteur trop simple tel que le bloc-notes vous fera perdre beaucoup de temps (et probablement de cheveux). Les qualits d'un bon diteur sont sa simplicit, sa rapidit, son ergonomie, et la disponibilit de certaines fonctions qui simplifient beaucoup le travail des programmeurs :

982

LE DVELOPPEUR JAVA 2

La numrotation des lignes : cette fonction est absolument indispensable car toutes les rfrences renvoyes par le compilateur le sont sous forme de numros de lignes

La capacit mettre la syntaxe en vidence l'aide de couleurs diff-

rentes est utile, mais pas indispensable. Disons que c'est un lment de confort et de productivit trs apprciable qui vous aidera de deux faons : en mettant en vidence vos fautes de frappe, et surtout en acclrant la lecture des programmes.

La capacit excuter des commandes du systme hte est trs intres La possibilit de crer des macros et de configurer des commandes par

sante. Elle vous permet de lancer la compilation et l'excution du programme depuis l'diteur.

menu ou au clavier est trs utile pour simplifier encore la compilation et l'excution des programmes.

La possibilit de passer des paramtres ces macros vous permet de

compiler le programme se trouvant dans la fentre active sans avoir spcifier son nom.

La possibilit de capturer la sortie du compilateur ou du programme

excut est inestimable. En effet, dans une fentre DOS, vous ne pouvez pas faire dfiler l'affichage. Si la compilation de votre programme produit un nombre important d'erreurs, vous ne pourrez pas les voir affiches. Par ailleurs, le compilateur Java envoie les erreurs sur la sortie Standard error qui n'est pas redirigeable vers un fichier. Il n'y a donc aucune solution simple pour l'utilisateur.

La capacit de grer un ensemble de fichiers correspondant un mme


projet est trs apprciable.

La possibilit de slectionner un bloc dlimit par deux caractres tels


que parenthses ou accolades est d'une aide prcieuse pour dtecter certaines erreurs.

La possibilit d'ajouter ou de retirer automatiquement une tabulation


au dbut de chaque ligne d'un bloc slectionn est trs utile.

ANNEXE C CONFIGURATION DE ULTRAEDIT

983

La possibilit de commenter automatiquement une partie du code est


souhaitable. Il existe de nombreux diteurs sur le march, mais aucun ne remplit toutes ces conditions. Il en existe un, cependant, qui les remplit toutes la perfection sauf la dernire. Il s'agit de UltraEdit, produit disponible en shareware pour un prix drisoire. La dernire version de UltraEdit est disponible sur le CD-ROM accompagnant ce livre.

UltraEdit n'est pas gratuit!


Attention : Le dveloppement d'un tel produit reprsente un travail considrable. Sa mise jour rgulire est un gage de productivit maintenue pour tous ses utilisateurs. En consquence, vous devez imprativement acheter la licence si vous dcidez de l'utiliser au-del de la priode d'essai. Vous trouverez tous les renseignements vous permettant d'acqurir la licence sur le site de son diteur, l'adresse www.idmcomp.com. Le prix est de 30$ et vous pouvez rgler par carte bancaire. La mise jour est gratuite pendant une priode d'un an et cote ensuite 15$ par an.

Installation et configuration de UltraEdit


L'installation de UltraEdit est extrmement simple. Il vous suffit de lancer le programme excutable Uedit32i.exe et de rpondre aux questions poses. Une fois le programme install, il vous faudra le configurer pour pouvoir compiler et excuter des programmes Java. Pour cela, il vous suffit de drouler le menu Advanced et de slectionner Tool Configuration. La bote de dialogue de la page suivante est affiche. Dans la zone Command line, tapez :
c:\jdk1.2\bin\javac "%n%e"

984

LE DVELOPPEUR JAVA 2

Figure C.1 : Configuration des commandes de compilation et dexcution.

%n %e

reprsente le nom du fichier en cours d'dition dans la fentre active et reprsente son extension. Attention bien utiliser des lettres minuscules, qui dsignent la version longue des nom et extension du fichier, alors que les lettres majuscules dsignent la version courte (format 8.3).

Attention : La documentation indique que vous pouvez employer %f


pour dsigner le nom du fichier avec son extension. Cependant, si vous utilisez cette possibilit, vous obtiendrez un message derreur si le chemin daccs comporte des espaces. Dans la zone Working Directory, tapez %p. Dans la zone Menu Item Name, tapez le nom que vous voulez donner la commande dans le menu, par exemple :
Compile

Cochez la case Save all files first de faon que UltraEdit enregistre les fichiers avant de les compiler.

ANNEXE C CONFIGURATION DE ULTRAEDIT

985

Dans la zone Command Output (DOS Commands), slectionnez l'option qui vous convient. Vous pouvez :

Ajouter les messages du compilateur un fichier existant, Remplacer un fichier existant, Crer un nouveau fichier chaque compilation, Afficher les messages dans une fentre (List Box).
La quatrime option est la plus utile. Cochez ensuite la case Capture Output puis cliquez sur Insert. Recommencez la procdure pour crer une commande excutant le programme et une autre lanant l'AppletViewer. Les lignes de commande indiquer sont les suivantes :

Excuter : c:\jdk1.2\bin\java

"%n" "%n".html

AppletViewer : c:\jdk1.2\bin\AppletViewer

Indiquez le chemin daccs au rpertoire du document dans la zone Working Directory, sous la forme :

%p

(sans guillemets). Dans le cas contraire, vous obtiendrez un message derreur NoClassDefFound lors de lexcution. Il ne vous reste plus maintenant qu' configurer des quivalents clavier pour ces commandes. Droulez le menu Advanced et slectionnez l'option Configuration. Dans la bote de dialogue affiche, cliquez sur l'onglet Key Mapping pour obtenir l'affichage de la page suivante.

986

LE DVELOPPEUR JAVA 2

Figure C.2 : Configuration des raccourcis clavier.

Dans la zone Commands, slectionnez AdvancedUserTool1. Cliquez dans la zone Press New Key et tapez la touche ou la combinaison de touches que vous voulez affecter la commande.

Cliquez sur Assign. Recommencez


pour les commandes AdvancedUserTool1 et AdvancedUserTool2.

UltraEdit est maintenant configur. Vous pouvez alors compiler et excuter un programme en slectionnant simplement une commande dans le menu Advanced ou en tapant une touche, comme indiqu sur la Figure C.3.

ANNEXE C CONFIGURATION DE ULTRAEDIT

987

Figure C.3 : Compilation dun programme.

Figure C.4 : Les erreurs de compilation sont affiches dans une fentre dfilante.

988

LE DVELOPPEUR JAVA 2
Si le programme comporte une erreur, celle-ci sera affiche dans la fentre rserve cet effet (si vous avez choisi l'option Output to List box), comme dans l'exemple de la Figure C.4. Notez qu'il vous suffit de faire un double clic sur le message d'erreur pour placer le point d'insertion au dbut de la ligne correspondante. (Si la fentre n'est pas affiche, droulez le menu View et slectionnez Output Window.)

Index

A
abstract 334, 363, 367 Accs direct (fichiers ) 594 Accesseurs 298, 323, 338, 414 Accessibilit 128, 137, 297 et rfrences 467 ActionEvent 775 ActionListener 379, 775 actionPerformed() 380, 774 ActiveX xxv, 30, 40 Adapter 525 add() 68 Adobe PhotoDeluxe 18 Adresse IP 932 locale 933 Affectation des handles 97 oprateur d' 205 Affichage de texte 844 des images 853 d'un message dans la barre d'tat 880 aiff (format de fichier sons) 881 Alatoires (nombres) 401 Algorithme 77 Allocation de la mmoire 94, 102, 454, 487 de ressources 472 America On Line xxviii

990
Anonymes classes 124, 522 constructeurs des 185, 325 initialisateurs des 524 rfrences aux 525 instances externes 506 interfaces 524 objets 93 dans les tableaux 392 et garbage collector 458 threads 655 primitives 117 threads 649 ANSI 133 AOL xxviii API 3D 864 ActiveX xxv documentation 29, 58, 832 dossier 12, 29, 58 append() 978 Apple 25 Applets 63, 717, 865, 866 affichage des images dans les 881 contexte d'excution des 878 cration partir de Java-Beans 914 cration d' 866 et autorisations d'accs 885 et fichiers compresss 883 fonctionnement des 870 layout managers des 869 limitations des 63 optimisation des 585 paramtres des 871 signes 9 AppletViewer 2, 8, 36, 40, 65, 71, 476, 868, 873, 890, 985 Applications accs un URL partir d'une 890 affichage d'images dans les 854

LE DVELOPPEUR JAVA 2
arrt des, en cas de boucle infinie 270 botes de dialogue des 749 compatibilit des xxv conception d' 154 contexte d'excution des 866 cration de clips dans les 882 dploiement d' 23 dveloppement d' xxxii, 153 rapide 37 diffusion d' xxvii, xxxvii, xxxviii, 24 change de donnes entre 569 et hritage 382 fentres 379, 721 fonctionnement des 870 lancement des 36 maintenance des 486, 711 multifentres 731 multimdia 464, 738 optimisation des 585 portabilit des 738 protection contre la copie 23 rseaux 895 scurit des 890 structure des 56 test des 26 Archives excutables 27 fichiers d' 316, 919 cration de 585 manifeste 27 Arguments constructeurs sans 179, 180, 201, 700, 710 de la mthode main() 56 de la mthode println() 196 de l'instruction if 108 de type tableaux 406 dclars final 330 des constructeurs 87, 91 des mthodes 88 surchargs 191 handles 219, 226

INDEX
passage des 407, 414, 601 au constructeur de la classe parente 655 ArithmeticException 534, 551 Arithmtique binaire (oprateurs d') 229, 240 Arithmtiques (oprateurs) 207 Arrays 452 Arrt des applications en boucle infinie 270 Arrondi dans la division 208 dans les castings 122 ASCII 134 Assembleur xxix, 101 Asynchrone 632 mthode 853 mode du garbage collector 454, 486 processus 95 Atomes 637 Atomicit 637 apparente 641 au (format de fichier sons) 881 Audio (formats) 11 AudioClip 882 Auto-dcrmentation 214 Auto-incrmentation 214 autoexec.bat 15, 50 Autorisations d'accs (pour les applets) 885 Awt 718

991
de titre 718 d'espacement (pour la slection des boutons) 775 d'tat 880 d'outils 38 Base de registre 27 BasicStroke 843 BDK 897 installation du 899 BeanBox 902 BeanInfo 898 Beans 897 cration de 915 customizers 906 dplacement des, dans la BeanBox) 905 enregistrement des 912 et handler d'vnements 906 installation dans des packages 916 instrospection 898 packaging des 919 persistence 899 proprits des 899 lies 910 redimensionnement des, dans la BeanBox 905 rflexion 898 srialisation 899 transformation en applets 914 Beans Development Kit 897 installation du 899 BigDecimal 105 BigInteger 105 Binaires (oprateurs) 207 Binding 361 BitSet 418 Blackdown xxiv, 44 Botes de dialogue dans les applications 749 Boolean 105 boolean 118, 233, 242 BorderLayout 738, 741, 742, 743

B
Bags 423 Barre de dfilement 714, 892 de menus 719, 744, 785 cration d'une 788 de progression 568 de tches (de Windows) 20

992
Borland 40, 45 Boucles for 267 infinies 270 sortie par return 278 while 282 Boutons 770 radio 805 BoxLayout 744 Branchements 254 vers des tiquettes 281 break 276, 279 BufferedReader 544, 578 ButtonGroup 805 Byte 104 Bytecode xxiv, xxxvi, 23 compilateurs de xxxvii compilation la vole xxxviii interprteur de xxxvi portabilit du xxxvii

LE DVELOPPEUR JAVA 2
d'un flottant en entier 122 explicite 120 oprateurs de 244 catch 536, 556 CD-ROM xxxvii hybride xxxvii CGI 926 Chane de caractres cration de 974 Chanes de caractres 57, 132, 973 concatnation de 974 dynamiques 133, 978 littrales 134 ChangeListener 819 char 104, 117, 238 Character 433 checkError() 570 Checksum (contrle de) 569 Chemins d'accs aux classes Java 17, 58 configuration des 15, 17 par dfaut 49 sparateurs de niveaux 61 Class 697 Classes abstraites 334 anonymes 124, 185, 522 et constructeurs 185, 325, 524 et initialiseurs 524 rfrences aux 525 chemin d'accs aux 17 dclaration des 54 dfinition des 54 dfinitions des 78 extension des 69, 83 externes rfrences aux 508 finales 331 imbriques 489 importation des 497 noms des 496 instances de 83

C
C (langage) xxx, 204 C++ xxx, 204 Canvas 822 Caractres autoriss dans les identificateurs 122 autoriss dans les noms 56 chanes de 57, 132 codage des 105, 133 de fin de lignes 583 jeu de 71 polices de 737, 845 streams de 570 Case cocher 805 Casting des primitives 119 des valeurs littrales 246 d'un entier en flottant 121

INDEX
internes 489 rfrences aux 519 locales 515 et handles 517 noms des 517 rfrences aux 517 membres 84, 497 et hritage 508 rfrences aux 502 parentes 154 invocation des constructeurs des 163 rfrence aux 161 prives 338 protges 337 publiques 337 reprsentation de la hirarchie des 79 sans constructeurs 185 synchronises 332 classpath 17, 18, 304 Client-Serveur 927 Clonage 606 en profondeur 612 en surface 612 et hritage 616 interdire le 619 clone() 606 Cloneable 609, 617 CloneNotSupportedException 609, 619 Codage des caractres 105, 133 Code ANSI 133 ASCII 133 UNICODE 105, 110 Collections 422, 439 Color 93 COM 40 command.com 21 Commentaires 54 syntaxe des 53, 62, 67 Communication entre les threads 685 stream de 571

993
Compactage de la mmoire 455 Comparable 427, 430 Comparateurs 421, 427, 430, 448 Compatibilit des applications xxv entre les versions de Java 24 Compilateurs de bytecode xxxvii JIT xxxviii natifs xxxvi Compilation 48 options de 307 Complment ( 1 et 2) 235 Component 69, 718 Composants lgers 715 lourds 714 Composition 144 et hritage 381 fentre de (dans le BDK) 904 Compression 584 Compteur d'accs (aux pages HTML) 961 Concatnation de chanes de caractres 974 Configuration de l'environnement 14 des chemins d'accs 15, 17 Connexion HTTP maintenir active une 945 Consoles de jeu xxxviii Constantes 136, 368, 382 globales 370 Constructeurs 69, 158 accessibilit des 339 arguments des 87, 91 classes sans 185 dfinition 86 des classes parentes 163 et classes anonymes 185, 325, 524 exceptions dans les 562 explicites 141

994
par dfaut 141, 174, 180 sans argument 179, 180, 201, 700, 710 signatures des 171 surcharger les 171 Container 69 contentLENGTH 945 contentPane 719 contentType 944 Contexte d'excution 866, 878 graphique 822 continue 280 Contrle de checksum 569 des threads 650 structure de 253 Conversion des caractres de fin de lignes 583 Copie de tableaux 409 d'objets 606 Corel xxv Courbes cubiques 832 de Bziers 832 quadratiques 833 Cration de fichiers d'archives 585 de Java-Beans 915 de threads 641 d'exceptions 549 Crationnisme 80 Cross-compilation xxxviii Customizer (pour les Java-Beans) 906

LE DVELOPPEUR JAVA 2
d'une variable numrique 122 Dcalage (oprateurs de) 231 Dclaration des classes 54 des handles 97 des mthodes 55, 88 des primitives 105 des tableaux 388 JSP 967 Dcompression de fichiers 590 default 289 Dfinition des classes 54 des mthodes 55, 88 DeflaterOutputStream 584 DELETE (requte http) 943 Dmons 637, 682 Dmonstration de Java 5, 10 des composants Swing 10 Dmos (de Java) 5 Dploiement d'applications 23 Deprecated 484 Dsallocation de la mmoire 94, 454 de ressources 472 Dsrialisation 568 destroy() 870 Dveloppement d'applications 153 rapide 37 environnements de 37 Dialog 718 Diffusion des applications xxvii, xxxvii, xxxviii, 24 Diminution des ressources xxxiv Directives JSP 966 dispose() 795 Division 208 arrondi dans la 208 et dcalage 231

D
Dbordement de la pile xxxiv, 102 d'un tableau xxxiv

INDEX
par zro 534, 555 reste de la 208 do...while 285 doClick() 818 Documentation du JDK 12 en ligne (utilisation de la) 58 installation de la 27 Donnes compression de 584 change entre applications 569 DOS 133, 303, 316, 630, 982 Double 105 double 118 drawImage() 853 Dymo 849 test de l' 222, 252 else 260 elseif 264 Embossage 849 Encapsulation 298 Enregistrement des Java-Beans 912 Ensembles 426 ordonns 427 Entte d'une requte HTTP 943 Entres/sorties 566 Enumration 949 Enumrations 439 Enveloppeurs 103, 111 avec les vecteurs 414 de primitives 222 Environnement 1 configuration de l' 14 de dveloppement 37 intgr 8 taille de l' 20 variables d' 16, 304 equals() 217, 426 quivalence de handles 216 de primitives 216 Erreurs surveilles 532, 535 traitement des 530 et finalisation 486 Errors 562 tiquettes (pour les branchements) 282 Evnements handler d', dans les java-Beans 906 vnements 379, 723 de souris 777 volutionnisme 80 Exceptions 182, 534 cration d' 549 dans les constructeurs 562 et hritage 562

995

E
Early binding 361 changes de donnes entre applications 569 criture dans un tampon index 569 de donnes, avec vrification 569 de donnes binaires 569 des donnes en mmoire xxxiii d'un fichier 575 squentiel 569 sur la sortie standard 61 Editeur de proprits 899 de texte 899 diteur (choix d'un) 33 Effets de bord 55 galit dfinition de l' 219 des objets 217 des primitives 206 valuation de l' 219 oprateur d' 97

996
handlers d' 537 Explicite (casting) 120 Expressions JSP 967 extends 66, 69 Extension de classes 69, 83, 297 et hritage 508 de signe 231 de zro 231, 236 des noms de classes 303 des noms des fichiers sources 337 du code ASCII 134 packages constituant une 725, 770 slective, des arbres 770

LE DVELOPPEUR JAVA 2
Finalisation des objets 455 et hritage 484 et traitement d'erreur 486 Finaliseurs 458 finalize() 456, 459, 479 finally 554, 556, 562 Fins de ligne 53 Float 105, 219 float 104, 118 FlowLayout 743 utilisation du 746 Fonctions 226 FontMetrics 849 for 267 Format des identificateurs 122 des valeurs littrales 117 Formatage du code Java 54 Formulaires et servlets 947 forName() 697 Forte 40, 899 Forth 204 FORTRAN 76 fr.comp.java 45 Frame 718 friendly 337 Fuites de mmoire xxxiv, 94, 456

F
Fentre de composition (du BDK) 904 Fentres 717 d'applications 721 DOS 48 hirarchie des 717 internes 790 structure des 718 Fermeture d'une fentre 723 Feuille de proprits (dans le BDK) 903 Fichiers accs direct 594 d'archives 316, 585, 919 dcompression de 590 lecture et criture de 575 longueur des 599 pointeurs de 599 File 575, 595 FileDescriptor 575 fillInStackTrace() 540 FilterOutputStream 584 final 136, 327, 361

G
Garbage collector xxxiv, 454 contrle du 463, 477 et objets anonymes 458 et paramtres de l'interprteur 486 optimisation du 456 Gnrateur de nombres alatoires 401

INDEX
getAppletContext() 878 getAudioClip() 882 getClass() 701 getCodeBase() 881 getDocumentBase() 881 getHeight() 858 getMaximumSize() 750 getMinimumSize() 750 getName() 674 getPreferredSize() 750 getRuntime() 484 getSize() 750 getStringBounds() 849 getWidth() 858 glassPane 718 Glissires 819 Granularit de la synchronisation 640 Graphics 822 Graphics2D 822 GridBagConstraints 756 GridBagLayout 741, 742, 755 utilisation du 755 GridLayout 743, 752 Groupes de boutons 805 de composants 745 de nouvelles 45 de threads 653 GZIP 585 GZIPInputStream 568 GZIPOutputStream 569 cration des 97 de tableaux 393 dclaration des 97 dfinition 95 quivalence de 216 et classes locales 517 initialisation des 97 tableaux de 398 HashTable 421 HEAD (requte http) 943 Header C 12 Hritage 144, 155 et classes membres 508 et clonage 616 et composition 381 et exceptions 562 et finalisation 484 multiple 370, 375, 381 HotJava 30, 869 HotSpot xxxix, 13 HTML 2, 24, 29, 63, 71, 770, 854, 866 <SERVLET> 959 HTTP 927 http 943 Hybride (CD-ROM) xxxvii HyperlinkListener 894

997

I
IBM xxiv, 39 IDE 8, 37 Identificateurs caractres autoriss dans les 122 format des 122 masquage des 127 porte des 125 visibilit des 125 Identit des objets 217 if 257 if...else 243

H
Handlers d'vnements dans les Java-Beans 906 d'exceptions 537, 551, 556, 558 Handles 101, 448 affectation des 97

998
ImageObserver 853 Images affichage des 853 dans les applets 881 imageUpdate() 860 Imbrication de boucles 272 de classes 489, 502 import 311 Importation de classes imbriques 497 explicite 313 implicite 313 Incrmentation 89 d'une boucle for 267, 270 Indices (des boucles for) 272 Informations perte d', dans les castings 122 init() 66, 870 Initialisation des handles 97 des instances 157 des objets 159, 350 des primitives 106 des tableaux 389 automatique 390 des variables 157 finales 328 statiques 324 d'une boucle for 267 oprateur d' 107 ordre d' 186 Initialiseurs 182 dans les classes anonymes 524 de variables d'instances 183 d'instances 184 statiques 325 InPrise 39, 45 InputStream 578 InputStreamReader 571, 578 insert() 978

LE DVELOPPEUR JAVA 2
Installation de la documentation en ligne 27 des sources de Java 5 du BDK 899 du J2RE (Windows) 26 du J2SDK 3 du JSWDK 929 InstallShield xxv, 26 instanceof 700 Instances 83 externes anonymes 506 initialisation des 157 initialiseurs d' 184 initialiseurs de variables d' 183 variable d' 159 Instanciation 89 d'une classe l'aide de son objet Class 700 int 87, 104, 118 Integer 103, 104, 217 Integrated Development Environment 8, 37 Interfaces 336, 367 anonymes 524 et polymorphisme 371 prives 338 protges 337 publiques 337 InternalFrameAdapter 525 InternalFrameListener 525, 795 Internationalisation avec le J2RE 25 Internet xxvii Internet Explorer xxvi, 32 pour Macintosh xxviii Interprteur allocation de la mmoire par l' 487 de bytecode xxxvi Introspection 898 IOException 547, 570, 576 IP (adresse) 932 isInstance() 700

INDEX
Itrateurs 427 de listes 429 Iterator 423, 425, 427 persistence 899 proprits des 899 lies 910 redimensionnement des, dans la BeanBox 905 rflexion 898 srialisation 899 transformation en applets 914 java.exe 7 java.lang.System 58 javac.exe 2, 8, 50 javadoc.exe 2, 9 javah.exe 9 javap.exe 9 JavaServer Web Development Kit 928 JavaWorld 43 JBuilder 39 JCheckBox 805 JDC 42 JDesktopPane 794 Jetables (objets) 562 JFrame 718, 738, 790 JInternalFrame 718, 790 JIT (compilateurs) xxxviii JLabel 750 JMenuBar 719 jre.exe 13 JRootPane 718 JSP 925, 965 Dclarations 967 Directives 966 expressions 967 Srciplets 967 JSWDK 928 installation du 929 Just In Time xxxviii JVM xxiv, 13, 23, 149, 454 choix d'une, pour le BDK 901 partage 929

999

J
J2EE 897 J2RE xxvii, 2 installation du 26 internationalisation 25 J2SDK (contenu du) 7 JApplet 866 JAR 9, 58, 316, 568, 569, 585, 883 Jar (fichiers) 919 jar.exe 9 JarInputStream 568 JarOutputStream 569 jarsigner.exe 9 Java 1.0 xxvi Java 1.1 xxvi Java 2 Enterprise Edition 897 Java Boutique 43 Java Developer Connection 42 Java Developers Journal 43 Java Runtime Environment xxvii, 2 Java Server Pages 925, 965 Java Virtual Machine xxiv, xxxvi, 23 Java Web Server 960 Java-Beans 795, 897 cration de 915 customizers 906 dplacement des, dans la BeanBox 905 enregistrement des 912 et handler d'vnements 906 et les environnements de dveloppement 38 installation dans des packages 916 introspection 898 packaging des 919

1000
L
Lancement des applications 36 Lanceur 631 Langages Assembleur xxix, 101 C xxx, 204 C++ xxx, 204 de prototypage xxxi Forth 204 FORTRAN 76 Lisp 77 machine xxix, 76 Pascal xxx Perl 958 PostScript 204 Smalltalk xxx, 204 structurs xxx Late binding 361 layeredPane 718 Layout managers 721, 736 affichage sans 765 Lecture d'un fichier 575 Libration de la mmoire 94, 455 ressources 472 Ligne de commande 2 Lignes (caractres de fin de) 583 LineNumberReader 572 Linux xxiv, xxvii, 1, 4, 44 Lisp 77 List 424, 429 Listeners 380, 524, 724 Listes 424 itrateurs de 429 ListIterator 429 Littrales (valeurs) 117 Localhost 932 Logiques (oprateurs) 225 Long 104, 118, 219

LE DVELOPPEUR JAVA 2
Longueur des fichiers 599 des noms en Java 56 Look & feel xxxii, 714, 796 loop() 882

M
Mac Os Runtime for Java 25 Machine virtuelle Java xxiv, 23 Macintosh xxiv, xxvii, xxviii, xxxii, 2, 25, 583, 713 MacOS xxiv, 2, 737 main() 55 Maintenance des applications 486, 711 MalformedURLException 878 Manifestes 27, 919 Map 419 Mapping 936 Marquage des objets, par le garbage collector 455 Masquage des identificateurs 127 de type static 145 Masques binaires 241 Math 315 Mdian (tiers) 927 MediaTracker 858 Membres dfinition 84 rfrence aux 109 statiques 85 Mmoire allocation de la 487 compactage de la 455 dsallocation de la 454 diminution des ressources 455 fuites de xxxiv, 94, 456

INDEX
libration de la 94, 455 pile 102 rcupration de la 454 tas 102 Memory leaks 94 Menus barre de 719 cration de 785 Metal (look & feel) 716, 796 Mthodes abstraites 334 arguments des 88 asynchrones 853 branchement par appel de 254 dclaration de 55, 88 dfinition des 55, 84, 88 finales 329 natives 204, 333, 715 paramtres de 55, 88, 160 porte des identificateurs de 130 prives 338 protges 337 publiques 337 redfinition de 161 retour des 196 signatures des 165 statiques 323 surcharge des 164, 353 avec un type diffrent 192 surcharges arguments des 191 synchronises 331 traceur de (dans le BDK) 903 valeurs de retour des 188 Microsoft xxv MIDI 11 midi (format de fichier sons) 881 MIME 944 Mise en forme du code Java 54 Mnmoniques xxix Motif 713, 737, 797 Mots rservs 979 MouseEvent 777 MouseListener 777 MRJ 25 MS-DOS 62, 583, 585 sparateurs 920 Multi-processus 95 Multimdia 738 applications 464 Multithreading 295 Mutateurs 299, 338

1001

N
Natif (compilateur) xxxvi native 333 Natives (mthodes) 204 Navigateur 866 choix d'un 29 Navigator xxvi, xxvii, 30, 867, 876 Ngatifs (reprsentation des nombres) 235 NetBeans 40 Netscape xxvi, xxvii, 30, 867, 876 new 90, 95, 246 newAudioClip() 882 newLine() 584 Newsgroups 45 Nombres alatoires 401 Noms caractres autoriss dans les 56 de variables 56 des classes anonymes 525 des classes imbriques 496 des classes locales 517 des objets 96 des packages 318 des polices de caractres 846 des threads 649

1002
longueur des 56 Notation des nombres ngatifs 235 infixe 205 notifyAll() 685 null 457

LE DVELOPPEUR JAVA 2
deux oprandes 207 trois oprandes 243 un oprande 213 affectation 205 arithmtiques 207 binaires 207 d'arithmtique binaire 229, 240 d'auto-dcrmentation 214 d'auto-incrmentation 214 de casting 244 de dcalage 231 d'intialisation 107 et sur-casting automatique 208 instanceof 246 logiques 225 new 246 priorit des 210, 247 raccourcis 211 relationnels 216 unaires 207 Oprations atomiques 637 Optimisation 167 des applications 585 du garbage collector 456 OPTIONS (requte http) 943 Ordre d'initialisation 186 naturel 430 partiel 430 total 430 OutOfMemoryError 471 OutputStreamWriter 573 Outrepasser. Voir Redfinition de mthode OverlayLayout 744

O
ObjectOutputStream 593 Objets anonymes 93 dans les tableaux 392 et garbage collector 458 threads 655 banque d' 341 copie d' 606 dfinition 78 galit des 217 finalisation des 455 graphiques 822 identit des 217 inaccessibles 452 initialisation des 159, 350 jetables 562 marquage, par le garbage collector 455 noms des 96 porte des 131 rfrences d' 447 sous-casting des 359 sur-casting des 349 automatique 352 explicite 357 implicite 352, 358 synchroniss 332 tableaux d' 398 verrouillage des 667 Oprandes 207, 213 oprateur trois 243 Oprateurs 203

P
pack() 751 Packages 204, 301, 305, 338

INDEX
accessibles par dfaut 315 et Java-Beans 916 noms des 318 par dfaut 493 Packaging des Java-Beans 919 Page Compilation 965 Pages de code 134 paint() 774 Panel 68 Paramtres des applets 871 des mthodes 55, 88, 160 ordre des, dans la signature des mthodes 165 passage des 601 Pascal xxx Passage des paramtres 601 Path 49, 303 PC xxxii, 133, 713 Perl 926, 958 Persistence 899 Perte d'informations dans les castings 122 PhantomReference 472 PhotoDeluxe 18 Pile 102, 417 dbordement de la xxxiv, 102 Pkzip 316 Plaf 796 Plantage xxxiii play() 882 Pluggable Look And Feel 796 Point d'entre d'un programme 631 Point-virgule 62 Pointeurs 148 de fichiers 599 Polices de caractres 737, 845 gnriques 846 noms des 846 policytool 885 Polymorphisme 330, 331, 347, 385, 394, 474, 534, 824

1003
des exceptions 551 et hritage 376 et interfaces 371 et traitement des exceptions 536 vs rflexion 702 Port 932 Portabilit des applications 738 des sources xxxvii du bytecode xxxvii Porte des identificateurs 125 des identificateurs de mthodes 130 des indices des boucles for 275 des objets 131 POST (requte http) 943 Post-dcrmentation 214 Post-incrmentation 214 PostScript 204 PowerJ 40 Pr-dcrmentation 214 Pr-incrmentation 214 preferredLayoutSize() 751 Primitives 101, 103 anonymes 117 casting des 119 dclaration des 105 enveloppeurs de 222 quivalence des 216 graphiques 821 initialisation des 106 tableaux de 393 valeur par dfaut des 109 print() et fonctionnement du tampon d'affichage 61 println() 57 arguments de 196 PrintStream 58, 570 PrintWriter 570 Priorit des oprateurs 210, 247

1004
des threads 662 private 137 Procdures 226 Processeur manipulation des registres du 101 Processus 95, 630. Voir aussi Threads Processus asynchrone 632 Profondeur (clonage en) 612 Programme point d'entre d'un 631 PropertyVetoException 795 Proprits des Java-Beans 899 diteur de 899 feuille de (dans le BDK) 903 lies (des Java-Beans) 910 Protection des applications, contre la copie 23 Prototypage xxxi public 55, 337 Pure Java xxxvi, xxxvii PushBackReader 571 PUT (requte http) 943

LE DVELOPPEUR JAVA 2
Reader 570 readFloat() 599 readInt() 599 readLine() 544, 584 readShort() 599 Rcupration de la mmoire 94, 454 des ressources 472 Redfinition de mthodes 161 Reference 464 ReferenceQueue 472 Rfrences aux classes anonymes 525 aux classes externes 508 aux classes internes 519 aux classes locales 517 aux classes membres 502 aux classes parentes 161 aux membres d'une classe 109 circulaires 455 d'objets 447 en avant 186, 518 et accessibilit 467 faibles 469 limites 464 passage par 602 Rflexion 702, 898 et classes internes 708 repaint() 774 Requtes HTTP 943 reshape() xxvii Ressources dsallocation de 472 diminution des xxxiv resume() 652 Retour d'une mthode 196 valeur de 188, 257 return 189, 257 pour sortir d'une boucle 278 RMF (format audio) 11 rmf (format de fichier sons) 881

Q
Queues 472

R
Raccourcis (pour les oprateurs) 211 RAD 37 Random 401 RandomAccessFile 595 Rapid Application Development 37 read() 584, 598 readBoolean() 599 readChar() 599

INDEX
round() 122 rt.jar 14, 58, 316 RTF 770 RTTI 521, 693 run (mthode) 636 run() 641 runFinalization() 479 runFinalizersOnExit() 482 Runnable 641, 676 Runtime 484 RunTime Type Identification 521, 695 RuntimeException 536, 556 gnres par JSP 968 synchronisation dans les 957 Set 426 setBounds() xxvii, 722 setStroke() 843 setVisible() 722, 750 Short 104 Signatures 9 des constructeurs 171 des mthodes 165 Signe (extension de) 231 Simulation 3D xxxvi Sites Web Java Boutique 43 Java Developer Connection 42 Java Developers Journal 43 java.sun.com 41 JavaWorld 43 relatifs Java 41 Smalltalk xxx, 204 SoftReference 464 Solaris 1, 270 Sons 881 SortedMap 421 SortedSet 427 Sources de Java 5, 14, 58 portabilit des xxxvii Sources de Java 5 Souris (vnements de) 777 Sous-castings 119 des objets 359 Sous-classes 83 Sous-typage. Voir Sous-casting src.jar 58 SSI 959 Stack 417 Standard error 61, 982 Standard output 61 start (mthode) 636 start() 642, 870 static 55, 85, 320

1005

S
Sacs 423 Scalability xl scr.jar 66 Scriplets JSP 967 Scripts CGI 926 ScrollPaneLayout 743 Scurit xxxiii, 9, 884 des applications 890 Security manager 890 Slecteurs 288 Sparateur dans les chemins d'accs 920 Sparateurs 53 de niveaux dans les chemins d'accs 61 dans les packages 303 Squences 254 Srialisation 333, 570, 591, 620, 899 des Java-Beans 914 Serializable 593, 620 Srializable 916 Server Side Include 959 <SERVLET> 959 Servlets 925 et formulaires 947

1006
Statiques membres 85 variables 85 stop() 652, 870, 882 Streams 566 de caractres 570 de communication 567, 569, 571, 572, 573 de donnes binaires 567 de sortie 568, 572 de traitement 567, 569, 571, 573, 576 d'entre 567, 571 String 132, 973 StringBuffer 133, 573, 978 Structures de contrle 253 super 179, 485, 614 super() 163 Sur-casting 693. Voir aussi Casting automatique, avec les oprateurs 208 automatique, des primitives 236 des objets 349 automatique 352 explicite 357 implicite 352, 358 implicite des tableaux 393 Sur-typage. Voir Sur-casting Surcharger les constructeurs 171 les mthodes 164, 353 une mthode avec un type diffrent 192 Surface (clonage en) 612 suspend() 652 Swing xxxii, 718, 770 dmonstration des composants 10 Swingset 10 switch 288 Sybase 40 Symantec 40 Synchroisation dans les servlets 957 Synchrone garbage collector 95

LE DVELOPPEUR JAVA 2
mode du garbage collector 454, 486 Synchronisation 637 des classes 332 des mthodes 331 des objets 332 des threads 648, 664 du garbage collector 95 granularit de la 640 synchronized 295, 331, 668 System 57

T
Tableaux 388 copie de 409 de handles 398 de primitives 393 de tableaux 405 dbordement de xxxiv dclaration des 388 d'objets 398 anonymes 392 handles de 393 initialisation des 389 automatique 390 littraux 391 multidimensionnels 404 sur-casting implicite des 393 taille des 389, 400 utiliss comme arguments 406 utiliss comme valeurs de retour 406 Tables 419 ordonnes 421 Tabulations 54 Tche de fond 95 Taille de la contentPane 720 de la pile 102 de l'cran 737 de l'environnement 20

INDEX
de tampon 571 par dfaut 573 des botes de dialogue 738 des caractres 737, 738, 762, 847 des cellules des layout managers 752 des composants xxvii, 743 des fentres 737, 860 des images 857 des tableaux 389, 400 des vecteurs 413 du tas 102 Tampons avec la mthode print 61 pour l'accs aux fichiers 582 tailles des 571 Tas 102, 447, 487 Tlchargement du J2SDK 3 Temps rel xxxiv, xxxvi, 95 Test dans les boucles for 268 des applications 26 this 173, 503 Thread 630 ThreadGroup 650 Threads 95, 331, 630 anonymes 649 communication entre les 685 contrle des 650 cration de 641 groupes de 653 noms des 649 priorit des 662 sous formes d'objets anonymes 655 synchronisation des 648, 664 Throw 539 throw 551 Throwable 540, 562 Tiers (applications n- 927 ToolKit 854 toString() 141 TRACE (requte http) 943 Traceur de mthodes 903 Traitement asynchrone 632 Traitement d'erreur 530 et finalisation 486 transient 333, 593 Transtypage. Voir Casting TreeSet 437, 448 Tris 441 Trois-tiers 927 Troncage (dans les castings) 122 try 182, 536 Types des indices des boucles for 273 non signs 238 Typographie dans l'criture des programmes 84

1007

U
UltraEdit 35, 48, 52, 899, 981 Unaires (oprateurs) 207 UNICODE 105, 110, 133 Uniform Resource Locator 877 UNIX sparateurs 920 Unix xxiv, xxxii, 303, 583, 585, 651, 663, 713 URL 877 accs un 890

V
Valeurs de retour 188, 257 littrales 57, 117 syntaxe des 117 par dfaut, des primitives 109

1008
passage par 605 Variables dfinition 84 d'environnement 16, 304 d'instances 159 initialiseurs de 183 finales 327 initialisation des 328 initialisation des 157 prives 338 protges 337 publiques 337 statiques 85, 137, 320 initialisation des 324 transitoires 333 volatiles 333 Vecteurs 413 Vector 413 Verrouillage des objets 667 ViewportLayout 743 Visibilit (des identificateurs) 125 Visual Caf 40 Visual J++ 40 VisualAge for Java 39 void 55, 89, 105, 189 volatile 333

LE DVELOPPEUR JAVA 2
Window 717 WindowAdapter 379, 727 WindowListener 379, 727 Windows xxiv, xxxiv, 133, 303, 316, 585, 651, 663, 713, 737, 797 base de registre 27 base de registre de 27 jeu de caractres de 71 Windows 2000 3 Windows 3.1 4 Windows 95 270 Windows 98 1, 3 Windows NT 270 Windows NT4.0 3 WindowsEvent 727 Winzip 316 Wrapper 103 write() 598 Writer 570

Y
yield() 661

W
wait() 685 waitForAll() 859 wav (format de fichier sons) 881 WeakReference 471 Web 866 while 282

Z
Zro division par 534 extension de 231, 236 Zip 316, 585 ZipInputStream 568 ZipOutputStream 569

INDEX
SUN MICROSYSTEMS, INC. BINARY CODE LICENSE AGREEMENT

1009

READ THE TERMS OF THIS AGREEMENT AND ANY PROVIDED SUPPLEMENTAL LICENSE TERMS (COLLECTIVELY AGREEMENT) CAREFULLY BEFORE OPENING THE SOFTWARE MEDIA PACKAGE. BY OPENING THE SOFTWARE MEDIA PACKAGE, YOU AGREE TO THE TERMS OF THIS AGREEMENT. IF YOU ARE ACCESSING THE SOFTWARE ELECTRONICALLY, INDICATE YOUR ACCEPTANCE OF THESE TERMS BY SELECTING THE ACCEPT BUTTON AT THE END OF THIS AGREEMENT. IF YOU DO NOT AGREE TO ALL THESE TERMS, PROMPTLY RETURN THE UNUSED SOFTWARE TO YOUR PLACE OF PURCHASE FOR A REFUND OR, IF THE SOFTWARE IS ACCESSED ELECTRONICALLY, SELECT THE DECLINE BUTTON AT THE END OF THIS AGREEMENT. 1. LICENSE TO USE. Sun grants you a non-exclusive and non-transferable license for the internal use only of the accompanying software and documentation and any error corrections provided by Sun (collectively Software), by the number of users and the class of computer hardware for which the corresponding fee has been paid. 2. RESTRICTIONS Software is confidential and copyrighted. Title to Software and all associated intellectual property rights is retained by Sun and/or its licensors. Except as specifically authorized in any Supplemental License Terms, you may not make copies of Software, other than a single copy of Software for archival purposes. Unless enforcement is prohibited by applicable law, you may not modify, decompile, or reverse engineer Software. You acknowledge that Software is not designed, licensed or intended for use in the design, construction, operation or maintenance of any nuclear facility. Sun disclaims any express or implied warranty of fitness for such uses. No right, title or interest in or to any trademark, service mark, logo or trade name of Sun or its licensors is granted under this Agreement. 3. LIMITED WARRANTY. Sun warrants to you that for a period of ninety (90) days from the date of purchase, as evidenced by a copy of the receipt, the media on which Software is furnished (if any) will be free of defects in materials and workmanship under normal use. Except for the foregoing, Software is provided AS IS. Your exclusive remedy and Suns entire liability under this limited warranty will be at Suns option to replace Software media or refund the fee paid for Software. 4. DISCLAIMER OF WARRANTY. UNLESS SPECIFIED IN THIS AGREEMENT, ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A

1010

LE DVELOPPEUR JAVA 2
PARTICULAR PURPOSE OR NON-INFRINGEMENT ARE DISCLAIMED, EXCEPT TO THE EXTENT THAT THESE DISCLAIMERS ARE HELD TO BE LEGALLY INVALID. 5. LIMITATION OF LIABILITY. TO THE EXTENT NOT PROHIBITED BY LAW, IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR SPECIAL, INDIRECT, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF OR RELATED TO THE USE OF OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. In no event will Suns liability to you, whether in contract, tort (including negligence), or otherwise, exceed the amount paid by you for Software under this Agreement. The foregoing limitations will apply even if the above stated warranty fails of its essential purpose. 6. Termination. This Agreement is effective until terminated. You may terminate this Agreement at any time by destroying all copies of Software. This Agreement will terminate immediately without notice from Sun if you fail to comply with any provision of this Agreement. Upon Termination, you must destroy all copies of Software. 7. Export Regulations. All Software and technical data delivered under this Agreement are subject to US export control laws and may be subject to export or import regulations in other countries. You agree to comply strictly with all such laws and regulations and acknowledge that you have the responsibility to obtain such licenses to export, re-export, or import as may be required after delivery to you. 8. U.S. Government Restricted Rights. If Software is being acquired by or on behalf of the U.S. Government or by a U.S. Government prime contractor or subcontractor (at any tier), then the Governments rights in Software and accompanying documentation will be only as set forth in this Agreement; this is in accordance with 48 CFR 227.7201 through 227.7202-4 (for Department of Defense (DOD) acquisitions) and with 48 CFR 2.101 and 12.212 (for non-DOD acquisitions). 9. Governing Law. Any action related to this Agreement will be governed by California law and controlling U.S. federal law. No choice of law rules of any jurisdiction will apply. 10. Severability. If any provision of this Agreement is held to be unenforceable, this Agreement will remain in effect with the provision omitted, unless omission would frustrate the intent of the parties, in which case this Agreement will immediately terminate.

INDEX

1011
11. Integration. This Agreement is the entire agreement between you and Sun relating to its subject matter. It supersedes all prior or contemporaneous oral or written communications, proposals, representations and warranties and prevails over any conflicting or additional terms of any quote, order, acknowledgment, or other communication between the parties relating to its subject matter during the term of this Agreement. No modification of this Agreement will be binding, unless in writing and signed by an authorized representative of each party. For inquiries please contact: Sun Microsystems, Inc. 901 San Antonio Road, Palo Alto, California 94303

1012

LE DVELOPPEUR JAVA 2
JAVATM 2 SOFTWARE DEVELOPMENT KIT STANDARD EDITION VERSION 1.3 SUPPLEMENTAL LICENSE TERMS These supplemental license terms (Supplemental Terms) add to or modify the terms of the Binary Code License Agreement (collectively, the Agreement). Capitalized terms not defined in these Supplemental Terms shall have the same meanings ascribed to them in the Agreement. These Supplemental Terms shall supersede any inconsistent or conflicting terms in the Agreement, or in any license contained within the Software. 1. Internal Use and Development License Grant. Subject to the terms and conditions of this Agreement, including, but not limited to, Section 2 (Redistributables) and Section 4 (Java Technology Restrictions) of these Supplemental Terms, Sun grants you a non-exclusive, non-transferable, limited license to reproduce the Software for internal use only for the sole purpose of development of your JavaTM applet and application (Program), provided that you do not redistribute the Software in whole or in part, either separately or included with any Program. 2. Redistributables. In addition to the license granted in Paragraph 1 above, Sun grants you a non-exclusive, non-transferable, limited license to reproduce and distribute, only as part of your separate copy of JAVA(TM) 2 RUNTIME ENVIRONMENT STANDARD EDITION VERSION 1.3 software, those files specifically identified as redistributable in the JAVA(TM) 2 RUNTIME ENVIRONMENT STANDARD EDITION VERSION 1.3 README file (the Redistributables) provided that: (a) you distribute the Redistributables complete and unmodified (unless otherwise specified in the applicable README file), and only bundled as part of the JavaTM applets and applications that you develop (the Programs:); (b) you do not distribute additional software intended to supersede any component(s) of the Redistributables; (c) you do not remove or alter any proprietary legends or notices contained in or on the Redistributables; (d) you only distribute the Redistributables pursuant to a license agreement that protects ! Suns interests consistent with the terms contained in the Agreement, and (e) you agree to defend and indemnify Sun and its licensors from and against any damages, costs, liabilities, settlement amounts and/or expenses (including attorneys fees) incurred in connection with any claim, lawsuit or action by any third party that arises or results from the use or distribution of any and all Programs and/or Software. 3. Separate Distribution License Required. You understand and agree that you must first obtain a separate license from Sun prior to reproducing or modifying any

INDEX

1013
portion of the Software other than as provided with respect to Redistributables in Paragraph 2 above. 4. Java Technology Restrictions. You may not modify the Java Platform Interface (JPI, identified as classes contained within the java package or any subpackages of the java package), by creating additional classes within the JPI or otherwise causing the addition to or modification of the classes in the JPI. In the event that you create an additional class and associated API(s) which (i) extends the functionality of a Java environment, and (ii) is exposed to third party software developers for the purpose of developing additional software which invokes such additional API, you must promptly publish broadly an accurate specification for such API for free use by all developers. You may not create, or authorize your licensees to create additional classes, interfaces, or subpackages that are in any way identified as java, javax, sun or similar convention as specified by Sun in any class file naming convention. Refer to the appropriate version of the Java Runtime Envir! onment binary code license (currently located at http://www.java.sun.com/jdk/index.html) for the availability of runtime code which may be distributed with Java applets and applications. 5. Trademarks and Logos. You acknowledge and agree as between you and Sun that Sun owns the Java trademark and all Java-related trademarks, service marks, logos and other brand designations including the Coffee Cup logo and Duke logo (Java Marks), and you agree to comply with the Sun Trademark and Logo Usage Requirements currently located at http://www.sun.com/policies/trademarks. Any use you make of the Java Marks inures to Suns benefit. 6. Source Code. Software may contain source code that is provided solely for reference purposes pursuant to the terms of this Agreement. 7. Termination. Sun may terminate this Agreement immediately should any Software become, or in Suns opinion be likely to become, the subject of a claim of infringement of a patent, trade secret, copyright or other intellectual property right.

1014

LE DVELOPPEUR JAVA 2
Sun Microsystems, Inc. Binary Code License Agreement

READ THE TERMS OF THIS AGREEMENT AND ANY PROVIDED SUPPLEMENTAL LICENSE TERMS (COLLECTIVELY AGREEMENT) CAREFULLY BEFORE OPENING THE SOFTWARE MEDIA PACKAGE. BY OPENING THE SOFTWARE MEDIA PACKAGE, YOU AGREE TO THE TERMS OF THIS AGREEMENT. IF YOU ARE ACCESSING THE SOFTWARE ELECTRONICALLY, INDICATE YOUR ACCEPTANCE OF THESE TERMS BY SELECTING THE ACCEPT BUTTON AT THE END OF THIS AGREEMENT. IF YOU DO NOT AGREE TO ALL THESE TERMS, PROMPTLY RETURN THE UNUSED SOFTWARE TO YOUR PLACE OF PURCHASE FOR A REFUND OR, IF THE SOFTWARE IS ACCESSED ELECTRONICALLY, SELECT THE DECLINE BUTTON AT THE END OF THIS AGREEMENT. 1. LICENSE TO USE. Sun grants you a non-exclusive and non-transferable license for the internal use only of the accompanying software and documentation and any error corrections provided by Sun (collectively Software), by the number of users and the class of computer hardware for which the corresponding fee has been paid. 2. RESTRICTIONS Software is confidential and copyrighted. Title to Software and all associated intellectual property rights is retained by Sun and/or its licensors. Except as specifically authorized in any Supplemental License Terms, you may not make copies of Software, other than a single copy of Software for archival purposes. Unless enforcement is prohibited by applicable law, you may not modify, decompile, reverse engineer Software. Software is not designed or licensed for use in on-line control of aircraft, air traffic, aircraft navigation or aircraft communications; or in the design, construction, operation or maintenance of any nuclear facility. You warrant that you will not use Software for these purposes. You may not publish or provide the results of any benchmark or comparison tests run on Software to any third party without the prior written consent of Sun. No right, title or interest in or to any trademark, service mark, logo or trade name of Sun or its licensors is granted under this Agreement. 3. LIMITED WARRANTY. Sun warrants to you that for a period of ninety (90) days from the date of purchase, as evidenced by a copy of the receipt, the media on which Software is furnished (if any) will be free of defects in materials and workmanship under normal use. Except for the foregoing, Software is provided AS IS. Your exclusive remedy and Suns entire liability under this limited warranty will be at Suns option to replace Software media or refund the fee paid for Software.

INDEX

1015
4. DISCLAIMER OF WARRANTY. UNLESS SPECIFIED IN THIS AGREEMENT, ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT, ARE DISCLAIMED, EXCEPT TO THE EXTENT THAT THESE DISCLAIMERS ARE HELD TO BE LEGALLY INVALID. 5. LIMITATION OF LIABILITY. TO THE EXTENT NOT PROHIBITED BY LAW, IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR SPECIAL, INDIRECT, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF OR RELATED TO THE USE OF OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. In no event will Suns liability to you, whether in contract, tort (including negligence), or otherwise, exceed the amount paid by you for Software under this Agreement. The foregoing limitations will apply even if the above stated warranty fails of its essential purpose. 6. Termination. This Agreement is effective until terminated. You may terminate this Agreement at any time by destroying all copies of Software. This Agreement will terminate immediately without notice from Sun if you fail to comply with any provision of this Agreement. Upon Termination, you must destroy all copies of Software. 7. Export Regulations. All Software and technical data delivered under this Agreement are subject to US export control laws and may be subject to export or import regulations in other countries. You agree to comply strictly with all such laws and regulations and acknowledge that you have the responsibility to obtain such licenses to export, re-export, or import as may be required after delivery to you. 8. U.S. Government Restricted Rights. Use, duplication, or disclosure by the U.S. Government is subject to restrictions set forth in this Agreement and as provided in DFARS 227.7202-1 (a) and 227.7202-3(a) (1995), DFARS 252.227-7013 (c)(1)(ii)(Oct 1988), FAR 12.212 (a) (1995), FAR 52.227-19 (June 1987), or FAR 52.227-14(ALT III) (June 1987), as applicable. 9. Governing Law. Any action related to this Agreement will be governed by California law and controlling U.S. federal law. No choice of law rules of any jurisdiction will apply. 10. Severability. If any provision of this Agreement is held to be unenforceable, This Agreement will remain in effect with the provision omitted, unless omission would frustrate the intent of the parties, in which case this Agreement will immediately terminate.

11. Integration. This Agreement is the entire agreement between you and Sun relating to its subject matter. It supersedes all prior or contemporaneous oral or written communications, proposals, representations and warranties and prevails over any conflicting or additional terms of any quote, order, acknowledgment, or other communication between the parties relating to its subject matter during the term of this Agreement. No modification of this Agreement will be binding, unless in writing and signed by an authorized representative of each party. For inquiries please contact: Sun Microsystems, Inc. 901 San Antonio Road, Palo Alto, California 94303

INDEX
JAVATM 2 RUNTIME ENVIRONMENT VERSION 1.3 SUPPLEMENTAL LICENSE TERMS

1017

These supplemental license terms (Supplemental Terms) add to or modify the terms of the Binary Code License Agreement (collectively, the Agreement). Capitalized terms not defined in these Supplemental Terms shall have the same meanings ascribed to them in the Agreement. These Supplemental Terms shall supersede any inconsistent or conflicting terms in the Agreement, or in any license contained within the Software. 1. License to Distribute. Subject to the terms and conditions of this Agreement, including, but not limited to, Section 2 (Redistributables) and Section 3 (Java Technology Restrictions) of these Supplemental Terms, Sun grants you a non-exclusive, non-transferable, limited license to reproduce and distribute the Software in binary code form only, provided that you (i) distribute the Software complete and unmodified, only as part of, and for the sole purpose of running your Java applet or application (Program) into which the Software is incorporated, (ii) do not distribute additional software intended to replace any component(s) of the Software, (iii) do not remove or alter any proprietary legends or notices contained in the Software, (iv) only distribute the Program subject to a license agreement that protects Suns interests consistent with the terms contained in this Agreement, and (v) agree to defend and indemnify Sun and its licensors from and against any damages, costs, ! liabilities, settlement amounts and/or expenses (including attorneys fees) incurred in connection with any claim, lawsuit or action by any third party that arises or results from the use or distribution of any and all Programs and/or Software. 2. Redistributables. In addition to the license granted in Paragraph 1 above, Sun grants you a non-exclusive, non-transferable, limited license to reproduce and distribute, only as part of Software, those files specifically identified as redistributable in the Software README file (the Redistributables) provided that: (a) you distribute the Redistributables complete and unmodified (unless otherwise specified in the applicable README file), and only bundled as part of the JavaTM applets and applications that you develop (the Programs:); (b) you do not distribute additional software intended to supersede any component(s) of the Redistributables; (c) you do not remove or alter any proprietary legends or notices contained in or on the Redistributables; (d) you only distribute the Redistributables pursuant to a license agreement that protects Suns interests consistent with the terms contained in the

1018

LE DVELOPPEUR JAVA 2
Agreement; and (e) you agree to defend and indemnify Sun and its licensor! s from and against any damages, costs, liabilities, settlement amounts and/or expenses (including attorneys fees) incurred in connection with any claim, lawsuit or action by any third party that arises or results from the use or distribution of any and all Programs and/or Software. 3. Java Technology Restrictions. You may not modify the Java Platform Interface (JPI, identified as classes contained within the java package or any subpackages of the java package), by creating additional classes within the JPI or otherwise causing the addition to or modification of the classes in the JPI. In the event that you create an additional class and associated API(s) which (i) extends the functionality of a Java platform, and (ii) is exposed to third party software developers for the purpose of developing additional software which invokes such additional API, you must promptly publish broadly an accurate specification for such API for free use by all developers. You may not create, or authorize your licensees to create additional classes, interfaces, or subpackages that are in any way identified as java, javax, sun or similar convention as specified by Sun in any class file naming convention. 4. Trademarks and Logos. You acknowledge and agree as between you and Sun that Sun owns the Java trademark and all Java-related trademarks, service marks, logos and other brand designations including the Coffee Cup logo and Duke logo (Java Marks), and you agree to comply with the Sun Trademark and Logo Usage Requirements currently located at http://www.sun.com/policies/trademarks. Any use you make of the Java Marks inures to Suns benefit. 5. Source Code. Software may contain source code that is provided solely for reference purposes pursuant to the terms of this Agreement. Source code may not be redistributed. 6. Termination. Sun may terminate this Agreement immediately should any Software become, or in Suns opinion be likely to become, the subject of a claim of infringement of a patent, trade secret, copyright or other intellectual property right.

INDEX
PROGRAM DEVELOPMENT AND SERVLET JAR FILE DISTRIBUTION SUPPLEMENTAL LICENSE TERMS

1019

These supplemental license terms (Supplement) add to or modify the terms of the Binary Code License Agreement (collectively, the Agreement). Capitalized terms not defined in this Supplement shall have the same meanings ascribed to them in the Agreement. These Supplement terms shall supersede any inconsistent or conflicting terms in the Agreement, or in any license contained within the Software. 1. License to Develop. Sun grants you a non-exclusive, non-transferable, royalty-free limited license to use the Software for the development of Java TM compatible servlets (the Programs) and reproduce and distribute the Programs to third party end users provided that you: (i) do not redistribute the Software in whole or in part, either separately or included in any Program except as authorized for the Servlet JAR Files as specified in Section 2 below, and (ii) agree to indemnify, hold harmless, and defend Sun and its licensors from and against any claims or lawsuits, including attorneys fees, that arise or result from the use or distribution of any and all Programs. 2. License to Distribute. Sun grants you a non-exclusive, non-transferable, royalty-free limited license to reproduce and distribute the servlet classes contained in the Software in the archive files server.jar, jspengine.jar, webserver.jar and servlet.jar (Servlet JAR Files) to third party end users solely as a component of your Programs provided that you: (i) distribute the Servlet JAR Files complete and unmodified in their original Java Archive file; (ii) do not distribute additional software intended to replace any component(s) of the Servlet JAR Files; (iii) do not remove or alter any proprietary legends or notices contained in or on the Software; (iv) only distribute the Servlet JAR Files pursuant to a license agreement that protects Suns interests consistent with the terms contained in the Agreement; (v) agree to incorporate the most current version of the Servlet JAR Files that was available from Sun no later than 180 days prior to each production release of your Program; and (vi) agree to indemnify, hold harmless, and defend Sun and its licensors from and against any claims or lawsuits, including attorneys fees, that arise or result from the use or distribution of any and all Programs.

3. Trademarks and Logos. You acknowledge as between you and Sun that Sun owns the Java trademark and all Java-related trademarks, logos and icons including the Coffee Cup and Duke (Java Marks) and agree to comply with the Java Trademark Guidelines at http:// java.sun.com/trademarks.html.

INDEX
BDK 1.1 LICENSE

1021

This license (License) contains rights and restrictions associated with use of the accompanying software. Limited License Grant. Sun grants to you (Licensee) a nonexclusive, nontransferable, worldwide, royaltyfree license to use this release of Beans Development Kit Version 1.1 (the Software). Licensee agrees that except as provided in Section 2 below, it shall use the Software provided hereunder solely for Licensees internal evaluation purposes and not for computer operations of any critical nature. This Software is subject to change which may cause applications developed using the Software to be incompatible with subsequent versions. Redistribution of Demonstration Files. Sun grants Licensee the right to use, modify and redistribute the Beans example and demonstration code, including the BeanBox (Demos), in both source and binary code form provided that (i) Licensee does not utilize the Demos in a manner which is disparaging to Sun; and (ii) Licensee indemnifies and holds Sun harmless from all claims relating to any such use or distribution of the Demos. Such distribution is limited to the source and binary code of the Demos and specificallyexcludes any rights to modify or distribute any graphical images contained in the demos. Restrictions. The Software is confidential, copyrighted proprietary information of Sun and title to all copies is retained by Sun and/or its licensors. Licensee shall not make copies of Software, other than a single copy of Software in machine-readable format for backup or archival purposes and, if applicable, Licensee may print one copy of on-line documentation, in which event all proprietary rights notices on Software and online documentation shall be reproduced and applied to all copies. Licensee shall not, decompile, disassemble, decrypt, extract, or otherwise reverse engineer Software. Except as provided in Section 2 above, Software may not be transferred, leased, assigned, or sublicensed, in whole or in part. No right, title or interest in and to any trademarks or trade names of Sun or Suns licensors is granted hereunder. Software is not designed or intended for use in on-line control of aircraft, air traffic, aircraft navigation or aircraft communications; or in the design, construction, operation or maintenance of any nuclear facility. Licensee represents and warrants that it will not use or redistribute the Software for such purposes.

1022
Trademarks and Logos.

LE DVELOPPEUR JAVA 2

Licensee acknowledges that Sun owns the Java trademark and all Java-related trademarks, logos and icons including the Coffee Cup and Duke (Java Marks) and agrees to: (i) to comply with the Java Trademark Guidelines at http://java.sun.com/ trademarks.html; (ii) not do anythingharmful to or inconsistent with Suns rights in the Java Marks; and (iii) assist Sun in protecting those rights, including assigning to Sun any rights acquired by Licensee in any Java Mark. Disclaimer of Warranty. Software is provided AS IS, without a warranty of any kind. ALL EXPRESS OR IMPLIED REPRESENTATIONS AND WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. Limitation of Liability. SUN AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE OR ANY THIRD PARTY AS A RESULT OF USING OR DISTRIBUTING SOFTWARE. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. Termination. Licensee may terminate this License at any time by destroying all copies of Software. This License will terminate immediately without notice from Sun if Licensee fails to comply with any provision of this License. Upon such termination, Licensee must destroy all copies of Software. Export Regulations. Software, including technical data, is subject to U.S.export control laws, including the U.S. Export Administration Act and its associated regulations, and may be subject to export or import regulations in other countries. Licensee agrees to comply strictly with all such regulations and acknowledges that it has the responsibility to obtain licenses to export, re-export, or import Software. Software may not be downloaded, or otherwise exported or re-exported (i) into, or to a national or resident of, Cuba, Iraq, Iran, North Korea, Libya, Sudan, Syria or any country to which the U.S. has

INDEX

1023
embargoed goods; or (ii) to anyone on the U.S. Treasury Departments list of Specially Designated Nations or the U.S. Commerce Departments Table of Denial Orders. Restricted Rights. Use, duplication or disclosure by the United States government is subject to the restrictions as set forth in the Rights in Technical Data and Computer Software Clauses in DFARS 252.227-7013(c) (1) (ii) and FAR 52.227-19(c) (2) as applicable. Governing Law. Any action related to this License will be governed by California law and controlling U.S. federal law. No choice of law rules of any jurisdiction will apply.

Anda mungkin juga menyukai