Vous êtes sur la page 1sur 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(){ ---------