Initiation à la Programmation Java

publicité
Initiation à la Programmation Java
(4GI)
Michel Cubero-Castan
8/10/2006
Copyright (c) 2003 Michel Cubero-Castan
Tous droits réservés.
Table des matières
1 Java Computing .....................................................................................................................
1.1 Introduction ....................................................................................................................
1.2 Evolution du modèle informatique ................................................................................
1.2.1 Le modèle centralisé ..............................................................................................
1.2.2 Le modèle réseau local ..........................................................................................
1.3 Les avantages de Java ...................................................................................................
1.4 Le modèle d'exécution ...................................................................................................
1.5 Environnement multi-plateforme (matérielle) ...............................................................
1.6 Plateforme Java ..............................................................................................................
2
2
2
2
3
4
4
5
6
2 Système de développement JDK ........................................................................................... 8
2.1 Classe, Instance, Variable, Méthode .............................................................................. 8
2.2 Hiérarchie, Héritage, Interface, Paquetage .................................................................... 9
2.3 Exemple de programme Java ......................................................................................... 11
2.4 Classpath ........................................................................................................................ 12
2.5 Les outils de JDK .......................................................................................................... 14
2.5.1 javac ....................................................................................................................... 15
2.5.2 java ......................................................................................................................... 16
2.5.3 javadoc ................................................................................................................... 16
2.6 L'application ANT (Apache) ......................................................................................... 17
2.6.1 Fichier XML de description ................................................................................... 17
2.6.2 Lancement de ANT ................................................................................................ 18
3 Les bases de Java ..................................................................................................................
3.1 Les bases traditionnelles ................................................................................................
3.1.1 Lexicographie .........................................................................................................
3.1.2 Commentaires .........................................................................................................
3.1.3 Identificateurs .........................................................................................................
3.1.4 Les variables ..........................................................................................................
3.1.5 Les types primitifs .................................................................................................
3.1.5.1 Les nombres entiers ........................................................................................
3.1.5.2 Les nombres flottants .....................................................................................
3.1.5.3 Les booléens ...................................................................................................
3.1.5.4 Les caractères .................................................................................................
3.1.6 Les chaînes de caractères .......................................................................................
3.1.7 Les tableaux ...........................................................................................................
3.1.8 Les opérateurs ........................................................................................................
3.1.8.1 Opérations arithmétiques ................................................................................
3.1.8.2 Concaténation de chaînes de caractères .........................................................
3.1.8.3 Opérations de comparaisons ...........................................................................
20
20
20
20
20
21
21
22
22
22
22
23
23
24
24
25
25
3.1.8.4 Opérations logiques ........................................................................................
3.1.8.5 Opérations de manipulation binaire ...............................................................
3.1.8.6 Affectation ......................................................................................................
3.1.8.7 Conversion ......................................................................................................
3.1.8.8 Précédence des opérateurs ..............................................................................
3.1.9 Les structures de contrôle ......................................................................................
3.1.9.1 Le bloc d'instructions .....................................................................................
3.1.9.2 Branchement conditionnel ..............................................................................
3.1.9.3 Choix multiple ................................................................................................
3.1.9.4 Boucles ...........................................................................................................
3.1.10 Les exceptions ......................................................................................................
3.2 Les bases de l'orientation objet .....................................................................................
3.2.1 Les paquetages .......................................................................................................
3.2.2 Les classes ..............................................................................................................
3.2.3 Les méthodes ..........................................................................................................
3.2.4 Les constructeurs ....................................................................................................
3.2.5 Les interfaces .........................................................................................................
3.2.6 Les variables ..........................................................................................................
3.3 Exemple de classe, l'Heure ............................................................................................
3.4 Variations sur le polymorphisme ...................................................................................
3.4.1 Le problème initial .................................................................................................
3.4.2 Polymorphisme de méthode (surcharge) ................................................................
3.4.3 Méthode abstraite ...................................................................................................
3.4.4 Interface ..................................................................................................................
3.4.5 Interface et méthode abstraite ................................................................................
3.4.6 Classe membre statique ..........................................................................................
3.4.7 Classe membre .......................................................................................................
3.4.8 Classe locale ...........................................................................................................
3.4.9 Classe anonyme ......................................................................................................
3.5 Allocation dynamique d'objets ......................................................................................
3.5.1 Clone ......................................................................................................................
3.5.2 Libération ...............................................................................................................
3.5.3 Options (non standards) de la commande java ......................................................
25
26
27
27
28
28
29
29
30
30
33
35
35
36
38
42
45
46
48
49
50
50
51
52
52
53
54
55
56
57
57
59
61
4 API standard ..........................................................................................................................
4.1 Utilisation de l'API ........................................................................................................
4.2 java.lang.* ......................................................................................................................
4.2.1 Classes liées au langage .........................................................................................
4.2.2 Classes liées à la machine virtuelle .......................................................................
4.3 java.util.* ........................................................................................................................
4.3.1 java.util.StringTokenizer ........................................................................................
4.3.2 java.util.Vector .......................................................................................................
4.3.3 java.util.Enumeration .............................................................................................
4.3.4 java.util.Hashtable ..................................................................................................
4.3.5 java.util.Observer et java.util.Observable ..............................................................
62
62
63
63
65
66
66
67
68
69
70
4.4 java.io.* .......................................................................................................................... 72
4.4.1 Entrées-sorties standard .......................................................................................... 72
4.4.2 Entrées-sorties sur fichiers ..................................................................................... 73
5 Réflexion ...............................................................................................................................
5.1 java.lang.Class ...............................................................................................................
5.2 Création d'instance .........................................................................................................
5.3 Appel de méthode ..........................................................................................................
5.4 Traitement des variables ................................................................................................
76
76
78
79
80
6 Les Threads ...........................................................................................................................
6.1 Utilisation des Threads ..................................................................................................
6.1.1 Classe java.lang.Thread ..........................................................................................
6.1.2 Interface java.lang.Runnable ..................................................................................
6.2 Cycle de vie des Threads ..............................................................................................
6.3 Ordonnancement des Threads ........................................................................................
6.4 Synchronisation des Threads .........................................................................................
6.5 Groupe de Threads ........................................................................................................
6.6 Les processus .................................................................................................................
81
82
82
82
83
84
85
88
90
Initiation à la Programmation Java (4GI)
Polycopié du cours de Java
Java est le dernier né des langages de programmation par objets, résultat de plusieurs années de
recherche et de développement pour Sun. C'est le premier langage qui offre une solution complète,
robuste et indépendante des plates-formes au problème de la programmation d'applications pour l'Internet.
Ce cours est une initiation à la programmation Java, en particulier la programmation répartie en Java.
Si vous trouvez une erreur dans cette version du polycopié, vérifiez d'abord si l'erreur n'a pas déjà été
corrigée dans la version en ligne. Si ce n'est pas le cas, merci d'en faire part par courrier électronique
à l'adresse
[email protected].
Page 1 sur 92
Initiation à la Programmation Java (4GI)
Chapitre 1
Java Computing
1.1 Introduction
En Décembre 1990, le constructeur Sun Microsystems monte une petite équipe de programmeurs
chargée de concevoir un environnement de programmation pour des appareils d'électronique grand
public, en particulier les récents PDA (Assistants Numériques Personnels) tel le Newton d'Apple, ou
encore les futurs décodeurs pour la télévision intéractive.
Dirigée par Bill Joy, cette équipe comprend notamment James Gosling, un ancien étudiant du département d'informatique de l'université Carnegie Mellon, spécialiste des logiciels en réseau, dont le rêve
était de créer un langage simple et robuste, capable de piloter toutes sortes d'appareils du téléphone
portatif au grille-pain.
Dès sa création, l'équipe met au point le langage de programmation OAK. Le premier programme
écrit en OAK s'exécute en Août 1991 grâce à un interprète écrit en C (le premier compilateur OAK
était également écrit en C). Trois ans plus tard, suite aux déboires des pionners du PDA et au manque
d'enthousiasme que suscitaient les décodeurs de télévision interactive, Sun oriente son équipe vers les
aspects Internet alors en plein essor (Le programme Mosaic a été lancé en 1993). Le nouveau langage
est alors baptisé Java et devient véritablement célèbre après l'annonce par Netscape le 23 Mai 1995
que son butineur Navigator, qui représentait alors 85% du trafic sur le Web, allait l'incorporer.
Java est plus qu'un simple langage de programmation, c'est une véritable révolution dans la manière
d'aborder l'informatique d'entreprise. Cette philosophie s'appelle le Java Computing.
1.2 Evolution du modèle informatique
1.2.1 Le modèle centralisé
Au début de l'informatique, les systèmes informatiques étaient basés sur un modèle centralisé (Figure
1.1). Le système était composé d'une grosse machine (mainframe) sur laquelle étaient connectés plusieurs terminaux.
Page 2 sur 92
Initiation à la Programmation Java (4GI)
Fig 1.1 : Modèle informatique centralisé
Cette organisation présentait plusieurs avantages. Par exemple, l'utilisateur pouvait avoir accès à l'ensemble de ses fichiers et programmes depuis n'importe quel terminal. Ou encore, s'il était le seul utilisateur du système à un instant donné, il bénéficiait de l'ensemble des ressources de la machine, en
particulier, l'unité centrale. Pour l'ingénieur système, le fait de gérer une seule machine était un point
positif : un seul système d'exploitation à connaître, un seul matériel à connaître.
Malheureusement, ces avantages pouvaient devenir facilement des inconvénients. Par exemple, à partir du moment où l'utilisateur n'est plus seul, il faut partager la puissance de la machine. Ce partage
se réduit à zéro en cas de panne de la machine. Côté évolution, elle ne peut être que globale, dans le
sens où augmenter la puissance de la machine implique un changement complet.
1.2.2 Le modèle réseau local
Après le modèle centralisé, sont apparus les réseaux locaux. Plusieurs postes de travail étaient connectés sur un réseau local, comprenant également un ou plusieurs serveurs (Fig 1.2 ).
Fig 1.2 : Modèle informatique réseau local
Avec une telle organisation, il est possible d'avoir un poste de travail adapté à l'utilisateur. Dans la
mesure où ce dernier ne nécessite qu'une machine de petite puissance, on arrive à des coûts réduits,
du moins pour le matériel.
Cependant, la multiplication des postes de travail dans une entreprise conduit à la multiplication des
ressources logicielles : chaque machine doit avoir son système d'exploitation (pas forcément le même
Page 3 sur 92
Initiation à la Programmation Java (4GI)
sur tous les postes de travail), ses outils de bureautique (courrier électronique, navigateur intranet, traitement de texte, tableur, ...). Tous ces logiciels introduisent des coûts supplémentaires (maintenance
pour les logiciels achetés, développement plus important pour les logiciels maison), sans compter les
coûts indirects liés à l'administration de ces machines (coûts proportionnels à l'incompatibilités des
postes de travail et des systèmes).
Le coût annuel d'exploitation d'un simple poste de travail peut devenir très élevé. Le Gartner Group
l'a évalué à 11.900$ par poste et par an (Gartner Group, Strategic Planning Research Note, SPA-14022, 26 avril 1996). De son côté, Sun estime le coût d'exploitation de postes de travail Java inférieur
à 2.500$;.
1.3 Les avantages de Java
L'approche Java (plate-forme de développement et environnement d'exécution) offre beaucoup
d'avantages :
• Il permet de créer des applications offrant une meilleure sécurité au niveau de l'exécution. Cette
sécurité est possible surtout par la vérification de tout le code avant son exécution. Elle vient aussi
de l'absence, dans le langage, de la notion de pointeur.
• Il permet de créer des applications adaptables à des environnements changeants, simplement par
le fait que le code peut être téléchargé avant l'exécution, à partir de n'importe quel point serveur
d'un réseau.
• Cette notion d'adaptabilité doit être reliée à la notion de portabilité assurée par la machine virtuelle
Java (dans la mesure où celle-ci existe pour chaque type de machine). Cette portabilité est clairement mise en avant dans le leitmotiv de Java : write once, run everywhere .
• Du point de vue du programmeur, Java permet de réduire le temps de développement d'une application grâce à la réutilisabilité du code développé.
• L'apprentissage de Java est facilité par la ressemblance avec les langages C et C++.
Le succès de Java autant au niveau des programmeurs (en Octobre 1996, selon une estimation de Sun,
ils étaient plus de 200~000 à utiliser ce langage) que des constructeurs (la machine virtuelle Java existe
pour un grand nombre de systèmes, Apple, IBM, Microsoft, Novel, Silicon Graphics, ..., quelque
fois directement intégrée dans le système d'exploitation) laisse penser que, dans un proche avenir, la
plateforme logicielle qui sera la plus diffusée ne sera plus Window mais la machine virtuelle Java.
1.4 Le modèle d'exécution
La clé du modèle d'exécution Java repose sur un langage machine intermédiaire unique pour toutes les
machines virtuelles Java. La mémorisation d'un programme en code machine intermédiaire s'effectue
dans un fichier ayant .class pour extension. Le format des données dans ce fichier est unique. Ainsi,
le même fichier peut être lu indifféremment par l'une ou l'autre des machines virtuelles Java, que ce
soit dans un environnement Window, Macintosh ou Unix (Fig 1.3 ).
Page 4 sur 92
Initiation à la Programmation Java (4GI)
Fig 1.3 : Modèle d'exécution Java
Au niveau de la compilation, cela implique que les différents compilateurs disponibles produisent
tous un fichier exécutable dans un format commun. De cette manière, il est possible de compiler un
programme Java dans un environnement unix sur une machine Sun, puis de porter le fichier obtenu
sur un Macintosh et l'exécuter par la machine virtuelle propre à Apple. C'est ce qui permet, entre autre,
son utilisation sur internet.
1.5 Environnement multi-plateforme (matérielle)
Pour s'exécuter, un programme Java dispose d'un environnement multi-plateforme. Il en existe pour
plus de 34 systèmes d'exploitation différents, tournant sur plus de 10 processeurs différents. La figure
1.4 présente les diverses composantes de cette plateforme.
Page 5 sur 92
Initiation à la Programmation Java (4GI)
Fig 1.4 : Environnement multiplateforme Java
Tout d'abord, il convient de distinguer deux sortes de programmes Java : l'application Java et l'appliquette (Applet en anglais) Java. L'application est un programme comme ceux que nous avons l'habitude de créer. Pour s'exécuter, il a besoin d'une machine virtuelle Java. Une appliquette est un programme (en général de taille réduite) qui ne peut pas s'exécuter tout seul, mais à l'intérieur d'une autre
application (par exemple le navigateur de Netscape).
Appliquette et application s'exécutent grâce à une machine virtuelle (JVM : Java Virtual Machine).
Cette machine dispose d'un chargeur (pour charger le code exécutable à partir du réseau internet), d'un
vérifhcateur chargé de contrôler le code (pour la sécurité), et d'un interprète chargé d'exécuter le code.
Le code peut faire appel à des fonctions disponibles dans une bibliothèque : l'API Java (Application
Programming Interface) dont seule une petite partie est dépendante du système d'exploitation (classes
PEER).
L'exécution d'un programme Java n'est pas des plus rapides puisque interprété. La machine virtuelle
peut toutefois s'accompagner d'un compilateur JIT (Just In Time), un compilateur à la volée. Ce type
de compilateur transforrme le code machine Java en code machine pour l'unité centrale hôte, pendant
l'exécution du programme. Cette approche permet de conserver la nature portable du programme
écrit en Java, tout en bénéficiant des avantages de la compilation classique au niveau de la vitesse
d'exécution.
1.6 Plateforme Java
Page 6 sur 92
Initiation à la Programmation Java (4GI)
La plateforme Java désigne l'environnement qui permettra à un programme java de s'exécuter. Alors
que la plupart des plateformes possèdent des composantes matérielles et logicielles, la plateforme
Java est purement logicielle . Elle comprend principalement deux éléments:
• la machine virtuelle
• la bibliothèque Java
En fait, il n'y a pas une mais trois plateformes Java:
• J2ME : Java 2 Micro Edition
• J2SE : Java 2 Standard Edition
• J2EE : Java 2 Enterprise Edition
La plateforme Micro Edition est celle utilisée pour l'utilisation de Java sur des appareils du type carte
à puce, téléphone, etc. La plateforme Standard Edition est la plateforme généralement utilisée pour
le développement d'application Java. La plateforme Enterprise Edition est utilisée pour le développement d'applications modulaires de grande taille, grâce à un ensemble de services intégrés pour le
courier électronique, l'accès aux bases de données, etc.
Page 7 sur 92
Initiation à la Programmation Java (4GI)
Chapitre 2
Système de développement JDK
2.1 Classe, Instance, Variable, Méthode
Java est avant tout un langage de programmation orientée objet. Mais, qu'est-ce qu'un objet ? Pour
mieux cerner les différents concepts de la programmation objet en Java, regardons l'exemple d'un
gestionnaire d'emploi du temps. La figure 2.1 montre un exemple de page qui pourrait être fournie
par un tel gestionnaire.
Fig 2.1 : Page d'emploi du temps
On constate qu'il existe, dans cette page, beaucoup d'éléments différents :
•
•
•
•
Un cadre avec titre
Un cadre par jour
Un cours à 9h35
...
Page 8 sur 92
Initiation à la Programmation Java (4GI)
Mais, il y a également beaucoup d'éléments semblables! Par exemple, l'élément cours multimédia par
Mr C. Chaplin du Jeudi 18/09/1997 n'est pas tellement différent de l'élément correspondant au cours
ayant lieu le Vendredi 19/9/97 à 11h15, si ce n'est l'indication de la salle et la date!
On appellera classe, un modèle pour plusieurs objets semblables, partageant des caractéristiques communes. Dans notre exemple, nous pouvons avoir la classe des TD, représentée graphiquement par le
schéma:
La classe TD nous décrit des éléments qui possèdent les caractéristiques suivantes :
•
•
•
•
•
•
une créneau horaire,
un numéro de séance,
un nom d'enseignement,
un nom d'enseignant,
une salle,
un groupe d'élèves (indiqué implicitement par la position dans la colonne du jour: groupe A pour
la position à gauche, groupe B pour la position à droite).
Une instance de la classe TD n'est autre qu'un objet issu de cette classe. La classe est une notion
abstraite, tandis que l'objet est la concrétisation de la classe. Dans notre page de la figure 2.1 , nous
avons deux objets instances de la classe TD : un TD pour le groupe A, le mardi 16/09/1997 à 9h15,
et un TD pour le groupe B, le mardi 16/09/97 à 9h30.
On appelle variable, un élément (ou une information) associé à un objet ou bien à une famille d'objets.
Par exemple, dans notre classe TD, chaque caractéristique (créneau horaire, numéro de séance, nom
d'enseignement, ...) est une variable.
Enfin, nous appellerons méthode, la description d'une action ou d'un comportement qui permet de
modifier une ou plusieurs valeurs associées aux variables d'un objet d'une classe donnée. La méthode
est définie au niveau de la classe et pourra s'appliquer sur toutes les instances d'une classe. Dans notre
exemple du gestionnaire d'emploi du temps, nous pourrions définir la méthode déplacer, qui nous
permettra de changer la position d'un TD dans le planning. Cette méthode sera appliquée sur l'instance
TD du groupe A du mardi 16/09/1997 à 9h15, pour éventuellement le déplacer au vendredi matin à 8H.
2.2 Hiérarchie, Héritage, Interface, Paquetage
Page 9 sur 92
Initiation à la Programmation Java (4GI)
Poursuivons l'examen de la figure 2.1 . Nous avons deux éléments n'appartenant pas à la même classe,
mais possédant des caractéristiques communes, l'instance de la classe Note et l'instance de la classe
Cours:
Ces deux classes présentent, en effet, des points communs. Elles possèdent la même variable (créneau
horaire) et la même forme de représentation (un grand rectangle). Elles peuvent posséder des méthodes
communes, par exemple la méthode déplacer. Appelons créneau, la classe correspondant à l'ensemble
de ces points communs. Nous pouvons maintenant redéfinir la classe Note en disant que c'est un
créneau auquel nous avons rajouté de nouvelles variables et de nouvelles méthodes. Nous pouvons
faire de même pour la classe Cours. Nous avons défini ainsi une hiérarchie entre les classes :
Nous dirons que créneau est la super-classe de Note et Cours, et récriproquement, Note et Cours sont
des sous-classe de Créneau. Une sous-classe hérite des variables et des méthodes de sa super-classe.
Pour définir la classe Cours, il suffit d'indiquer qu'elle va hériter de la classe Créneau, et d'indiquer
les nouvelles variables et méthodes. L'effort de programmation est ainsi diminué, tout ce qui a été
fait n'est plus à faire.
Un des points particulièrement intéressant de l'héritage réside dans la possibilité de voir un objet
sous plusieurs visages. Par exemple, une instance de la classe Cours peut aussi être vue comme une
instance de la classe Créneau (dans ce cas, on ne peut utiliser que les méthodes utilisables par la classe
Créneau).
Certains langages de programmation orientée objet permettent la définition d'une classe à partir de
plusieurs classes n'ayant aucun lien entres elles. On parle alors d'héritage. Le langage Java ne le
permet pas. Java n'offre que l'héritage simple. Une classe ne peut avoir qu'une seule super-classe.
De même, cette super-classe ne peut avoir qu'une seule super-classe qui elle même ne peut avoir ...
Où arrêter le raisonnement ? Sur une classe racine. Là aussi, Java simplifie les choses en n'ayant
qu'une seule classe racine possible. Cette classe est la classe Objet. De ce fait, tout objet Java hérite,
directement ou indirectement, de cette classe unique.
Il n'y a pas de possibilité d'héritage multiple en Java. Ceci n'est pas en soi un problème. L'héritage
multiple permet à un objet d'être vu comme un objet issu de l'une de ses super-classes. Avec l'héritage
simple de Java, il est possible de voir un objet comme appartenant à une classe différente de la classe
Page 10 sur 92
Initiation à la Programmation Java (4GI)
dont il est issu directement ou indirectement, grâce à la notion d'interface. Une interface décrit la
possibilité d'un comportement particulier.
La bibliothèque standard Java (API) contient un grand nombre de classes et interfaces (211 pour
JDK 1.0.2, 477 pour JDK 1.1.5). Trouver un nom unique pour chacune de ces classes ou interfaces
serait une tâche ardue s'il n'existait pas la notion de paquetage. Un paquetage est un regroupement de
classes, d'interfaces et même de paquetages. Une classe particulière CL faisant partie du paquetage
P2, lequel fera partie du paquetage P1 sera désignée par P1.P2.CL.
La bibliothèque standard Java se trouve dans le paquetage java. Dans l'API 1.0.2, nous pouvons
trouver les paquetages suivants:
• java.lang
• java.util
• java.io
• java.net
• java.awt
Le paquetage java.util contient lui même les classes:
• java.util.Enumeration
• java.util.HashTable
• java.util.Date
2.3 Exemple de programme Java
Le programme ci-dessous est un exemple de petit programme Java permettant de calculer le nombre
de jours qui nous séparent du premier janvier de l'an 2000.
package ch2;
import java.util.Calendar;
/**
* Programme calculant le nombre de jours depuis l'an 2000
* @author
Michel Cubero-Castan
* @version
1.2
* @since
JDK1.1
*/
public class An2000 {
private static Calendar calendrier = Calendar.getInstance();
/**
* Méthode principale du programme calculant le nombre de jours
* qui nous séparent du premier janvier de l'an 2000.
*
* @param args les paramètres du programme (non utilisés)
*/
public static void main(String[] args) {
long aujourdhui = calendrier.getTime().getTime();
calendrier.clear();
calendrier.set(2000,0,1,0,0,0); // 1er janvier 2000, 0h00
long an2000 = calendrier.getTime().getTime();
long nbrSecDepuis2000 = (aujourdhui - an2000) / 1000;
Page 11 sur 92
Initiation à la Programmation Java (4GI)
long nbrJoursDepuis2000 = nbrSecDepuis2000 / (24*60*60);
System.out.println("==> Il est passé " + nbrJoursDepuis2000
+ " jours, depuis l'an 2000.");
}
}
Ce programme se présente sous la forme d'une classe ayant pour nom An2000 et faisant partie du
paquetage ch2 (Attention aux lettres majuscules et minuscules, Java est sensible aux lettres capitales).
Toute définition de classe publique (nous verrons plus tard ce que signifie ce terme) doit être mémorisée dans un fichier source ayant pour nom, le nom de la classe, et .java pour extension. De plus, si
cette classe fait partie d'un paquetage, le fichier source doit se trouver (pour pouvoir être retrouvé par
le compilateur) dans un répertoire ayant le même nom que le paquetage. Ainsi, le fichier source du
programme An2000 aura pour nom An2000.java et sera placé dans le répertoire ch2 :
Lorsque nous compilerons ce fichier, nous obtiendrons un fichier par classe compilé. Le nom de ce
fichier sera le nom de la classe, avec .class pour extension. Si la classe appartient à un paquetage, le
fichier compilé devra résider dans un répertoire ayant comme nom, le nom du paquetage. Pour notre
programme An2000, nous aurons donc :
2.4 Classpath
La seule contrainte imposée par Java sur la position d'un fichier (.java ou .class) est d'être dans un
répertoire du même nom que le paquetage de la classe. Il n'y a pas de contrainte sur la position du
Page 12 sur 92
Initiation à la Programmation Java (4GI)
paquetage racine. Comment la machine virtuelle va-t-elle trouver son chemin d'accès? Elle utilise
la variable d'environnement CLASSPATH pour retrouver ce chemin. Cette variable donne les différents répertoires dans lesquels la machine virtuelle Java va rechercher les différentes classes qui ne
font pas partie du paquetage standard java (l'ensemble des classes du paquetage standard java se
trouvent dans une archive java connue de la machine virtuelle java; il n'est pas besoin d'en indiquer
l'emplacement). Ces répertoires sont séparés par le caractère : . Si la classe recherchée appartient à
un paquetage, par exemple la classe P1.P2.P3.CL, un des répertoires indiqués par la variable CLASSPATH doit contenir le répertoire P1 et non pas la classe CL.
Par défaut, si cette variable n'est pas positionnée, la machine virtuelle Java utilise le répertoire courant.
Bien entendu, les répertoires contenant les fichiers ".class" peuvent être différents des répertoires
contenant les fichiers ".java". Par exemple, nous pouvons répartir ces deux types de fichiers dans
deux répertoires, Sources pour les fichiers ".java" et Classes pour les fichiers ".class", en utilisant un
paquetage différent par chapitre. Nous aurons ainsi, des répertoires organisés comme suit (le répertoire
/home/Gringo pouvant être le répertoire de login, contenant les répertoires Sources et Classes) :
Il existe deux manières de spécifier le chemin de recherche des classes: en utilisant la variable d'environnement CLASSPATH, ou par l'intermédiaire du paramètre -classpath d'une commande d'exécution d'un des outils du système de développement. Par exemple, pour compiler, puis exécuter le
programme An2000, si les répertoires Sources et Classes se trouvent dans le répertoire de login de
Page 13 sur 92
Initiation à la Programmation Java (4GI)
l'utilisateur (désigné ici par /home/gringo), nous utiliserons l'une des deux séquences de commandes (type unix) suivantes :
variable d'environexport CLASSPATH=/home/gringo/Classes:/home/gringo/Sources
nement
javac -d /home/gringo/Classes An2000.java
java ch2.An2000
paramètre de commande
cd /home/gringo/Sources/ch2
javac -classpath /home/gringo/Classes:/home/gringo/Sources/
-d /home/gringo/Classes An2000.java
java -classpath /home/gringo/Classes ch2.An2000
2.5 Les outils de JDK
JDK (Java Development Kit) est le système de développement de référence fourni par Sun. Il en
existe plusieurs versions, dénotées par JDKx.y.z où
• x est un numéro de version correspondant à une évolution majeure du langage.
• y est un numéro de version correspondant à une évolution au niveau de l'API.
• z est un numéro de version correspondant à une correction d'erreur.
La première version publique accessible fut la version JDK1.0.0. Il y a une compatibilité ascendante
entre les différentes versions. L'avant dernière version (JDK1.1.8) est maintenant acceptée par la plupart des principaux navigateurs. C'est celle utilisée pour les différents exercices. Dans la suite de ce
polycopié, nous indiquerons les éléments de JDK1.1, nouveaux par rapport à la première version. La
version actuelle est la version JDK1.2.2.
Le premier système de développement JDK1.0 comprenait les outils suivants:
Le compilateur Java. Il produit du code intermédiaire (le bytecode).
javac
java
L'interprète de bytecode.
jdb
Le débogueur Java.
javah
Un générateur de fichier d'en-tête C (".h") et de programme de conversion des
arguments, utilisé appeler des procédures C à partir de programme Java.
javap
Un programme desassembleur permettant de visualiser les fichiers de bytecode
Java.
javadoc
Le générateur de documentation en HTML à partir des commentaires contenus
dans le programme Java.
appletviewer
Un visualiseur d'applet (principalement utilisé pour les tests).
Le système de développement JDK1.1 comprend, en plus des outils précédents, les outils suivants:
L'interprète de bytecode destiné à être utilisé en mode production (lorsque le
jre
processus de développement est terminé).
jar
Un programme permettant de combiner plusieurs fichiers de bytecode Java, ainsi
que d'autres types de fichiers (images, son, ...) en un seul fichier archive.
javakey
Un outil pour les signatures digitales.
Page 14 sur 92
Initiation à la Programmation Java (4GI)
native2ascii
Un outil pour convertir un fichier utilisant des caractères spéciaux en un fichier
ASCII, en utilisant la notation unicode \udddd.
rmic
Un outil pour générer les interfaces de conversion pour les classes contenant des
objets distants.
rmiregistry
L'outil d'enregistrement des objets distants.
serialver
Outil permettant d'obtenir la version de sérialisation d'une classe.
updateAWT
Outil de conversion pour le passage de l'AWT 1.0 à l'AWT 1.1.
Le plus récent système de développement, JDK1.2, contient un beaucoup plus grand nombre de nouvelles classes. On trouve ainsi également de nouveaux outils, comme des outils pour l'internationalisation des programmes, des outils pour la sécurité des applications, ainsi que des outils liés à CORBA.
Examinons plus attentivement, les outils les plus importants.
2.5.1 javac
Le compilateur javac s'appelle avec la commande de même nom. Il est possible de compiler une ou
plusieurs classes. Plusieurs paramètres optionnels peuvent être utilisés avec cette commande. Nous
ne décrirons ici que les plus utiles pour un apprentissage java.
$ javac [option] nomFichier1.java nomFichier2.java ...
La commande javac compile chaque nom de fichier passé comme paramètre à la commande.
Pour chaque classe contenue dans les fichiers sources, le compilateur produit un fichier de bytecode
java ayant pour nom le nom de la classe compilée et pour extension ".class". Ces fichiers de bytecode
sont placés dans le même répertoire que le fichier source sauf si l'option -d est utilisée. Dans ce cas,
les fichiers de bytecode Java sont placés dans le répertoire indiqué par l'option.
Si la classe compilée fait partie d'un paquetage, il est préférable d'utiliser cette option et d'indiquer
dans l'option, le répertoire racine de la hiérarchie de classe.
Lors de la compilation d'une classe faisant référence à une deuxième classe, le compilateur recherche
(grâce à la variable d'environnement CLASSPATH ou à l'option -classpath) le fichier source et le
fichier bytecode correspondant à cette deuxième classe. Une compilation de la seconde classe est
effectuée si, soit le fichier bytecode de la seconde classe n'existe pas, soit la date de modification du
fichier source est postérieure à la date de modification du fichier bytecode. Cette vérification n'est
pas reconduite pour les classes qui seraient référencées par cette deuxième classe, sauf si l'option
-depend est utilisée. Dans ce cas, le processus de compilation est effectué pour toute classe référencée
directement ou indirectement par la classe compilée.
Pour faciliter l'utilisation de l'outils de mise au point jdb, il faut utiliser l'option -g qui permet au
compilateur de générer dans le bytecode, les informations pouvant servir à la mise au point (comme
les noms de variables, la numérotation des lignes sources, ...).
Enfin, il existe, depuis la version JDK1.1, un certain nombre de méthodes marquées deprecated
c'est à dire des méthodes dont l'usage n'est pas recommandé. Ce sont des méthodes de la première
version qui ont été conservées dans la version 1.1 pour assurer une compatibilité ascendante, mais
Page 15 sur 92
Initiation à la Programmation Java (4GI)
qui peuvent être remplacées par de nouvelles méthodes de cette dernière version. En principe, ces
méthodes dépréciées pourront être retirées dans les versions futures.
Le compilateur JDK1.1 indique à la fin de la compilation si la classe compilée utilise de telles
méthodes dépréciées. Il est possible d'obtenir un message d'avertissement pour chaque méthode utilisée, avec l'option de compilation -deprecation.
Exemple: Pour compiler notre programme An2000, nous devons exécuter la commande unix suivante
(dans le répertoire ch2 contenant le fichier source):
$ javac -g -d ../../Classes/ An2000.java
2.5.2 java
La commande java permet d'exécuter la méthode main de la classe dont le nom complet (nom du
paquetage inclu) est passé en argument. La méthode main doit avoir la signature suivante:
public static void main(String argv[]){ ... }
La classe à exécuter doit avoir été compilé, puisque la commande java interprète le bytecode obtenu
à l'issue de la compilation. De plus, le fichier contenant le bytecode doit être accessible via la variable
d'environnement CLASSPATH.
Comme la commande javac, la commande java peut contenir des options. La première option -classpath donne le chemin d'accès aux différentes classes.
Une deuxième option -cs ou -checksource permet de vérifier si les dates de dernière modification
des fichiers de bytecode des classes utilisées sont bien postérieure aux dates des fichiers sources
correspondants. Dans la négative, une compilation est automatiquement effectuée avant le chargement
de la classe.
Une troisième option -debug permet de relier l'exécution du programme au débogueur, pour une mise
au point du programme.
D'autres options permettent de modifier certains paramètres de la machine virtuelle Java, ou d'obtenir
des statistiques lors de l'exécution.
Il est possible de transmettre des paramètres à un programme Java, via la ligne de commande:
$ java [option] nomClasse paramètre1 paramètre2 ...
Ces paramètres sont accessibles dans la méthode main, via le tableau de chaînes de caractères passé
en paramètre.
Il est également possible de transmettre un certain nombre de propriétés par la commande java, en
utilisant l'option -D suivie du nom de propriété et de sa valeur (-Dnom=valeur ).
Voici la commande unix et le résultat obtenu pour l'exécution de notre programme An2000 :
$ java -classpath ../../Classes ch2.An2000
==> Il reste 606 jours, avant l'an 2000.
$
2.5.3 javadoc
Page 16 sur 92
Initiation à la Programmation Java (4GI)
L'outil javadoc permet de produire la documentation au format html, à partir des commentaires contenus dans le fichier source de la classe à documenter. Les commentaires utilisés par cet outil sont indiqués par /** ... */. Ils comprennent plusieurs lignes:
• la première ligne débute par /** suivi de la fin de ligne.
• plusieurs lignes de commentaire permettent de décrire la classe (ou la méthode) documentée.
• à la suite des lignes de commentaire, nous avons des lignes contenant une balise par ligne (une
balise est un mot clé commençant par le caractère @)
• la liste de lignes contenant une balise se termine par l'indication de fin de commentaire */.
Les balises suivantes peuvent être utilisées :
pour indiquer l'auteur de la classe (une ligne par auteur).
@author
@version
pour indiquer le numéro de version de la classe documentée.
@param
une balise par paramètre de la méthode documentée (le nom du paramètre doit
suivre la balise).
@return
cette balise permet de spécifier le type du résultat renvoyé par la méthode documentée (le type suit la balise).
@exception
cette balise doit être utilisée pour chaque exception pouvant être levée par la
méthode documentée.
@since
cette balise permet d'indiquer la première version du système de développement
pouvant utiliser cette méthode.
Pour obtenir la documentation du paquetage ch2 contenant le programme An2000, nous utiliserons
la commande unix suivante:
$ javadoc -sourcepath ../Sources ch2
Des informations plus complètes sur la documentation de programme java avec l'outil javadoc peuvent
être lues sur
http://java.sun.com/products/jdk/javadoc/writingdoccomments.html
2.6 L'application ANT (Apache)
Le système ANT est une application Java développée par la fondation Apache. En peu de mots, on
peut dire qu'il s'agit de l'équivalent d'un makefile portable, puisque écrit en Java. En fait, il s'agit
d'un outil très puissant utilisable dans tout développement de logiciels, et pas seulement en Java.
2.6.1 Fichier XML de description
Le fichier de description du travail devant être réalisé par ANT est un fichier XML. Voici celui utilisé
pour compiler ou exécuter le programme An2000 précédent:
<?xml version="1.0" encoding="UTF-8"?>
<project name="An2000" default="help" basedir=".">
<property name="classpath" value="Classes"/>
<target name="init">
<tstamp/>
</target>
Page 17 sur 92
Initiation à la Programmation Java (4GI)
<target name="compil" depends="init">
<echo message="Compilation de An2000"/>
<echo message="Version java : ${ant.java.version}"/>
<javac srcdir="Sources"
destdir="Classes"
classpath="${classpath}"
includes="ch2/An2000.java"
target="1.1"
debug="on"/>
</target>
<target name="help" depends="init">
<echo message="pour compiler : ant compil"/>
<echo message="pour exécuter : ant run"/>
</target>
</project>
Examinons les différents éléments constituant ce fichier. La deuxième ligne
<project name="An2000" default="help" basedir=".">
correspond à la racine du document XML. Elle donne :
• le nom du projet (attribut name) : An2000; ce peut-être n'importe quelle chaîne de caractères.
• la cible utilisée par défaut (attribut default) : help; C'est la cible sélectionnée si on n'indique
aucune cible lors du lancement du système ANT.
• le répertoire de base pour toute chemin d'accès relatif (attribut basedir) : ici on fait référence
au répertoire courant.
La ligne suivante
<property name="classpath" value="Classes"/>
permet de définir une propriété de nom classpath qui sera utilisé, dans le reste du fichier, pour
remplacer les occurrences de ${classpath} par la valeur associée.
Nous avons ensuite un certain nombre de cibles (target) permettant d'identifier le ou les travaux
à exécuter. Chaque cible ne sera exécutée qu'une seule fois, après l'exécution des cibles dont elle
dépend. Par exemple, la cible nommée compil dépend de la cible nommée init; lorsqu'elle sera
exécutée, il y aura au préalable, exécution de la cible init :
<target name="init">
<tstamp/>
</target>
Cette cible compil comprend plusieurs tâches : deux tâches de nom echo, et une tâche de nom
javac. La tâche echo permet simplement d'afficher un message. La tâche javac permet de lancer
une compilation sur le ou les fichiers sources java indiqués par l'attribut includes. On pourrait ainsi
compiler tous les fichiers sources java du paquetage ch2 en utilisant:
<javac ... includes="ch2/*.java" .../>
2.6.2 Lancement de ANT
Page 18 sur 92
Initiation à la Programmation Java (4GI)
Si le système ANT a été correctement installé, on lancera l'exécution d'une cible de nom xxx soit par
la commande
$ ant xxx
si le fichier XML possède le nom build.xml et se trouve dans le répertoire courant, sinon, il faut
indiquer le nom de ce fichier par l'option -buildfile suivie du chemin d'accès au fichier xml :
$ ant
-buildfile nomFichier.xml xxx
Si aucune cible n'est indiquée lors du lancement, le système choisit la cible par défaut, c'est à dire
ici, la cible help :
$ ant
Buildfile: build.xml
init:
help:
[echo] pour compiler : ant compil
[echo] pour exécuter : ant run
BUILD SUCCESSFUL
Total time: 2 seconds
$
Il est possible de lancer plusieurs cibles. Par exemple pour compiler puis exécuter, on aura la trace
suivante (on suppose que le répertoire courant contient les répertoires Sources et Classes, ainsi
que le fichier build.xml) :
$ ant compil run
Buildfile: build.xml
init:
compil:
[echo] Compilation de An2000
[echo] Version java : 1.4
[javac] Compiling 1 source file to Classes
init:
run:
[java] ==> Il est passé 1091 jours, depuis l'an 2000.
BUILD SUCCESSFUL
Total time: 2 seconds
$
Page 19 sur 92
Initiation à la Programmation Java (4GI)
Chapitre 3
Les bases de Java
Dans le chapitre précédent, nous avons utilisé le programme An2000 qui est défini comme une classe
n'ayant qu'une seule méhode, la méthode main. Nous allons voir maintenant, les principaux éléments
du langage , éléments que l'on retrouve dans la plupart des langages de programmation (impératifs
ou objets).
3.1 Les bases traditionnelles
3.1.1 Lexicographie
Un programme Java utilise l'ensemble des caractères défini par la norme Unicode . Tout caractère
pourra être écrit sous la forme de la séquence de caractères \u suivie de quatre chiffres hexadécimaux.
Exemple: Le caractère unicode \u2203 correspond au caractère il existe, le caractère unicode \u2248
correspond au caractère à peu prés =.
La machine virtuelle Java divise la séquence de caractères Unicode en lignes. Une fin de ligne correspond soit au caractère ASCII LF (newline), soit au caractère ASCII CR (return), soit au caractère
ASCII CR immédiatement suivi du caractère ASCII LF.
3.1.2 Commentaires
Il y a deux types de commentaires dans un programme Java : les commentaires traditionnels et les
commentaires de documentation. Les commentaires traditionnels peuvent soit commencer par // pour
se terminer sur la fin de ligne, soit par /* pour se terminer sur la même ligne ou une ligne ultérieure,
par */. Les commentaires de documentation utilisés par l'outil javadoc, commence par /** pour se
terminer sur la même ligne ou une ligne ultérieure, par */.
Exemple:
/** ceci est un commentaire
* pour la documentation du programme
*/
/* celui-ci est un exemple de commentaire classique */
// comme celui-ci
3.1.3 Identificateurs
Page 20 sur 92
Initiation à la Programmation Java (4GI)
Les identificateurs sont des chaînes de caractères, de longueur quelconque, comprenant des lettres
Java ou des chiffres Java, avec comme contrainte d'avoir un lettre Java premier caractère. Une lettre
java comprend les lettres latines (A-Z, a-z), le caractère souligné _ , le dollar $ , ou encore les
caractères unicodes (supérieurs à 0x00C0). Les caractères unicodes offrent la possibilité d'utiliser les
caractères natifs dans l'écriture des programmes.
Exemple : les identificateurs suivants sont valides en Java élément EnTête numéro_1 Le langage Java
comprend une liste de mots-clés réservés qui ne peuvent pas être utilisés comme identificateurs :
abstract
boolean
break
byte
case
catch
char
class
const
continue
default
do
double
else
extends
final
finally
float
for
goto
if
implements
import
instanceof
int
interface
long
native
new
package
private
protected
public
return
short
static
super
switch
synchronized
this
throw
throws
transient
try
void
volatile
while
A ces mots-clés interdits, il faut ajouter également les littéraux true, false et null.
3.1.4 Les variables
Comme dans la plupart des langages de programmation, une variable ne permet de manipuler qu'une
seule sorte de donnée à la fois. La variable est typée. Le type indique la catégorie des données pouvant
être manipulées par la variable. Lors de sa déclaration, une variable est précédée par le nom du type de
la donnée à laquelle elle (la variable) pourra être associée. Plusieurs variables d'un même type peuvent
être déclarées ensemble. Elles sont, dans ce cas, séparées par une virgule. La déclaration d'une variable
peut apparaître n'importe où, là où il peut y avoir une instruction, mais toujours avant d'être utilisée.
Par exemple, dans le programme An2000, nous avions plusieurs déclarations de variables :
Date aujourdhui = new Date();
Date an2000
= new Date (2000-1900, 0, 1, 0, 0);
long nbrMillisecondesAvant2000;
long nbrJoursAvant2000;
String résultat;
3.1.5 Les types primitifs
L'ensemble des données pouvant être manipulées par un programme java peut se décomposer en
deux catégories : les types primitifs et les objets dérivés de la classe java.lang.Object. Dans l'exemple
précédent, le type long est un type primitif, alors que les types Date et String sont des types dérivés.
Il y a 4 sortes de types primitifs : les nombres entiers, les nombres flottants, les booléens, et les
caractères.
Page 21 sur 92
Initiation à la Programmation Java (4GI)
3.1.5.1 Les nombres entiers
Quatre formats peuvent être utilisés pour les nombres entiers, suivant la taille de la représentation (8
bits, 16 bits, 32 bits ou 64 bits):
Nom
byte
short
int
long
Intervale
[ -128, +127 ]
[ -32768, +32767 ]
[ -2147483648, +2147483647 ]
[
-9223372036854775808,
+9223372036854775807 ]
Exemples
12, -5000
octal : 0623, 0777
hexadécimal : 0x12Af, 0X2F
long : 214748364800, 5L, 45l
Comme le montre les exemples ci-dessus, il est possible d'utiliser des littéraux de nombres entiers
dans la base octale ou la base hexadécimale. Un littéral en base 8 commence par le chiffre 0. Un
littéral en base 16 commence par le chiffre 0 suivi de la lettre x ou X. Un littéral de nombre entier
dénote une valeur sur 32 bits (type int) sauf si le nombre se termine par la lettre l ou L qui indique
un littéral sur 64 bits (type long).
3.1.5.2 Les nombres flottants
Deux formats sont utilisés pour la représentation des nombres flottants, sur 32 bits et sur 64 bits. Ces
représentations utilisent la norme IEEE754. Un littéral flottant utilise un préfixe indiquant le format
choisi : f ou F pour le format 32 bits, d ou D pour le format 64 bits.
Nom
float
double
Intervale
IEEE754 32 bits
IEEE754 64 bits
Exemples
1.32f, 0.56F, .56f, 112.3e-4f
1.32d, 0.56D, .56, 112.3e-4d
Les valeurs particulières de la norme IEEE754 sont fournies par les classes java.lang.Float et
java.lang.Double. Pour chacune de ces deux classes, nous avons : POSITIVE_INFINITY (infini),
NEGATIVE_INFINITY (-infini) et NaN (Not A Number).
3.1.5.3 Les booléens
Nom
boolean
Intervale
[ false, true ]
Exemples
3.1.5.4 Les caractères
La taille de la représentation d'un caractère (char) est de 16 bits, de manière à pouvoir coder tous le
caractères unicode possibles. La représentation littérale d'un caractère débute par le caractère apostrophe ' suivi soit du caractère ASCII, soit du codage unicode, soit de la valeur octale pour les caractères compris entre 000 8 et 377 8 (cette valeur octale de 1 à 3 chiffre est préfixée par le caractère \),
soit enfin l'une des représentations suivantes:
\b
Retour Arrière (BS)
Page 22 sur 92
Initiation à la Programmation Java (4GI)
\t
\n
\f
\r
\"
\'
\\
Tabulation Horizontale (HT)
Nouvelle Ligne (LF)
Saut de Page (FF)
Retour Charriot (CR)
Guillemet (")
Apostrophe (')
Slash Inversé (\)
La représentation littérale se termine par la caractère apostrophe.
Nom
char
Intervale
[ '\u0000', '\uffff' ]
Exemples
'x', '\u03a9', '\13', '\n'
3.1.6 Les chaînes de caractères
La chaîne de caractères (String) n'est pas un type primitif mais un type dérivé de la classe racine
java.lang.Object. C'est toutefois un type dérivé particulier puisque sa représentation littérale doit être
prise en compte par le langage. Cette représentation est simplement une suite de littéraux caractères
(sans les deux caractères apostrophe du début et de la fin), suite qui débute et finit avec le caractère
guillemet ("). Exemple :
"Une
"Une
"Une
"Une
chaîne toute simple"
chaîne sur \ndeux lignes"
cha\u00EEne avec un caractère unicode"
cha\356ne avec un caractère octal"
3.1.7 Les tableaux
Il n'y a seulement que des tableaux à UNE dimension. Le premier indice est TOUJOURS 0. Pour
déclarer un tableau d'un certain type, il suffit de faire suivre, soit le nom du type, soit le nom de
la variable, d'une paire de crochets vides. Par exemple, la déclaration suivante permet de définir un
tableau de valeurs entières :
int[] tab;
Cette seconde définition est équivalente à la précédente :
int tab[];
L'une ou l'autre de ces deux déclarations ne font que définir une variable; il n'y a pas de création d'un
objet Tableau. Pour que l'on puisse utiliser des tableaux, il faut les créer soit par l'opérateur new
, soit en donnant directement le contenu. Ainsi, pour créer un tableau de 10 nombres entiers, nous
utiliserons:
tab = new int[10];
ou bien, en initialisant à partir des 10 premiers nombres entiers:
tab = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
L'accès aux différents éléments d'un tableau à l'aide d'un indice. Par exemple, tab[2] correspondra au
troisième élément du tableau tab (puisque le premier indice est toujours 0).
Page 23 sur 92
Initiation à la Programmation Java (4GI)
Les tableaux sont toujours des tableaux à 1 seul indice. Il est toutefois possible de se ramener à
des tableaux à plusieurs indices en utilisant des tableaux de tableaux. Par exemple, pour définir une
matrice carré, 2 lignes par 2 colonnes, nous pourrons utiliser l'une ou l'autre des ces deux instructions :
int[][] mat1 = new int[2][2];
int[][] mat2 = { {11, 12}, {21, 22} };
L'accès au deuxième élément de la première ligne se fera par mat1[0][1].
Une variable de type tableau peut référencer des tableaux de tailles quelconques. Pour connaître la
taille d'un tableau, c'est à dire le nombre d'éléments qu'il contient, nous utiliserons le mot-clé length
associée à l'objet tableau. Par exemple, "tab.length" nous donnera la taille du tableau pointé par tab.
"mat1.length" nous donnera le nombre de lignes de la matrice mat1, tandis que "mat1[1].length" nous
donnera le nombre d'éléments de la ligne indicée par 1 (c'est à dire la deuxième ligne).
Exemple: Le programme suivant
class TestTableau {
public static void main(String arg[ ]) {
String [ ][ ] mat = {{"00"},{"10", "11"},
{"20","21", "22"},
{"30", "31", "32", "33"}};
for (int i=0; i<mat.length; i++) {
System.out.print("=>");
for (int j=0; j<mat[i].length; j++) {
System.out.print(" "+mat[i][j]);
}
System.out.println();
}
}
}
produira l'exécution suivante:
$ java TestTableau
=> 00
=> 10 11
=> 20 21 22
=> 30 31 32 33
$
3.1.8 Les opérateurs
3.1.8.1 Opérations arithmétiques
Les opérateurs arithmétiques utilisent des nombres entiers ou des nombres flottants comme arguments.
Nous retrouvons la plupart des opérateurs classiques, comme l'addition (+), la soustraction (-), la multiplication (*), la division (/) et le modulo (%). L'opération modulo donne le reste de la division entre
ses deux opérandes. Il est à noter que ces opérations sont effectuées sans détection d'éventuels débordements. Seule la division par zéro peut provoquer la levée d'une exception ArithmeticException .
Comme c'est le cas pour le langage C ou C++, nous avons la possibilité d'utiliser des opérateurs unaires
préfixés ou postfixés pour l'incrémentation ou la décrémentation.
Page 24 sur 92
Initiation à la Programmation Java (4GI)
+
*
/
%
Addition
Soustraction
Multiplication
Division
Modulo
++nomVar
nomVar++
--nomVar
nomVar--
pré-incrémentation
post-incrémentation
pré-décrémentation
post-décrémentation
Opérateurs Arithmétiques
3.1.8.2 Concaténation de chaînes de caractères
L'opérateur d'addition est utilisé comme opérateur de concaténation de chaînes de caractères. Si des
données de types différents de la chaîne de caractères sont utilisées dans une opération de concaténation, elles sont au préalable converties en chaîne de caractères. Ainsi, le programme suivant:
int i = 5;
System.out.println("=> La variable i donne "+i+"!");
produira la sortie:
=> La variable i donne 5!
3.1.8.3 Opérations de comparaisons
Nous retrouvons les opérateurs classiques:
==
!=
<
>
<=
>=
instanceof
Egal
Différent
Inférieur à
Supérieur à
Inférieur ou égal à
Supérieur ou égal à
appartenance à une
classe
Opérateurs de Comparaison
Ces opérateurs peuvent s'appliquer sur tous les types de base que nous venons de voir. Le dernier,
instanceof , permet de déterminer si un objet passé en premier opérande est issue d'une classe passée
en deuxième opérande.
3.1.8.4 Opérations logiques
Les opérateurs logiques suivants s'appliquent soit sur des valeurs entières, soit sur des valeurs booléennes, sauf pour les opérateurs notés ``non strict'' qui, eux, ne s'appliquent que sur des données
Page 25 sur 92
Initiation à la Programmation Java (4GI)
booléennes et pour l'opérateur conditionnel qui est un opérateur ternaire. Lorsqu'un opérateur logique
s'applique sur des données entières, l'opération est réalisée sur chacun des bits des opérandes.
&
&&
|
||
^
!
?
Et (strict)
Et (non strict)
Ou (strict)
Ou (non strict)
Ou Exclusif
Non
Condition
Opérations Logiques
Un opérateur non strict est un opérateur qui peut ne pas évaluer son deuxième argument si la valeur du
premier argument suffit à déterminer la valeur du résultat. Ainsi, l'évaluation de x && y se fera de la
manière suivante: si x est faux, alors le résultat sera faux, sinon le résultat sera donné par l'évaluation
de y. L'opérateur strict aurait d'abord évalué x et y avant d'effectuer le ET logique.
Ainsi, dans l'exemple suivant, la ligne 3 donnera un résultat correct tandis que la ligne 4 provoquera
une exception due à la division par 0:
int i = 10;
int j = 0;
boolean test1 = (j > 0) && (i / j > 2);
boolean test2 = (j > 0) & (i / j > 2);
L'opérateur conditionnel ? est un opérateur ternaire dont le premier opérande est de type booléen
tandis que les deux autres, séparés par le caractère : sont du même type quelconque. Le résultat
retourné par cet opérateur dépend du premier opérande: si celui-ci donne la valeur true, alors le résultat est fourni par l'évaluation du deuxième opérande, sinon le résultat est fourni par l'évaluation du
troisième opérande. Notons que l'opérande qui n'a pas été sélectionné par l'opérateur conditionnel,
n'est pas évalué.
Exemple: Après exécution du programme
int i = 10;
int j = (i>5) ? 2 : 4;
int k = (i<5) ? 2 : 4;
la variable j aura la valeur 2, et la variable k aura la valeur 4.
3.1.8.5 Opérations de manipulation binaire
Les trois premiers opérateurs ci-dessous permettent d'effectuer des opérations de décalage sur les bits
de représentation de nombres entiers. Deux types de décalage à droite sont autorisés : le décalage
arithmétique qui conserve le signe de l'opérande (sur le bit de fort poids de la représentation) et le
décalage logique (qui injecte un 0 sur le bit de fort poids). Le dernier opérateur effectue le complément
binaire, c'est à dire l'inversion de tous les bits de la représentation du nombre. il est différent de
l'opérateur NOT désigné par le caractère ! qui ne s'applique que sur des opérandes booléennes, pour
fournir une valeur booléenne.
Page 26 sur 92
Initiation à la Programmation Java (4GI)
<<
>>
>>>
~
Décalage à gauche
Décalage arithmétique à droite
Décalage logique à
droite
Complément à 1
Opérations de manipulation binaire
3.1.8.6 Affectation
L'opérateur d'affectation est le caractère = . Il permet de lier une variable à un objet ou une valeur
primitive. En Java, l'affectation joue le rôle d'un opérateur binaire qui modifie son premier opérande
avec la valeur obtenue à partir de son deuxième opérande, puis renvoie cette valeur comme résultat.
Les deux lignes d'instructions suivantes sont donc équivalentes (l'utilisation de plusieurs affectations
dans une même expression n'est toutefois pas recommandée) :
i = 5; j = i + 1;
j = (i = 5) + 1;
L'opérateur d'affectation peut se combiner avec la plupart des opérateurs binaires pour spécifier une
affectation dont la valeur est obtenue en appliquant l'opérateur binaire sur la variable qui sera initialisée et une expression.
x += y
x -= y
x *= y
x /= y
x %= y
--> x = x + y
--> x = x - y
--> x = x * y
--> x = x / y
--> x = x % y
x &= y
x ^= y
x |= y
x <<= y
x >>= y
x >>>= y
--> x = x & y
--> x = x ^ y
--> x = x | y
--> x = x << y
--> x = x >> y
--> x = x >>> y
...
Opérateurs d'affectation
3.1.8.7 Conversion
Comme dans la plupart des langages de programmation, il est possible de convertir des valeurs appartenant à un type, dans un autre type. Le langage Java impose toutefois cette conversion d'apparaître
explicitement dans le programme. Une conversion de type se note pas le nom de type destinataire,
entre parenthèses, devant l'expression à convertir.
Exemples:
Page 27 sur 92
Initiation à la Programmation Java (4GI)
int i = 17;
char c; long l; float f;
c = (char) i;
l = (long) i;
f = (float) i;
Certaines conversions peuvent se faire en entrainant une perte d'information (par exemple, la conversion d'un nombre flottant en entier implique la perte de la partie décimale).
3.1.8.8 Précédence des opérateurs
Lorsqu'il y a plusieurs opérateurs dans une expression, on le traite dans l'ordre imposé par leurs précédences: d'abord ceux qui ont la plus forte précédence, ensuite ceux qui ont la plus faible précédence.
En cas d'opérateurs de même précédence, l'ordre suivi est celui de l'apparition des opérateurs, de la
gauche vers la droite. La table ci-dessous montre la précédence des différents opérateurs, la première
ligne ayant la plus forte précédence, la dernière ligne, la plus faible. Les opérateurs étant sur une
même ligne offre la même précédence:
opérateurs postfixés
opérateurs unaires
création ou conversion
opérateurs multiplicatifs
opérateurs additifs
décalage
relationnels
équalité
ET binaire
OU Exclusif binaire
OU Inclusif binaire
ET logique
OU logique
opérateur de condition
affectation
[ ] . (params) expr++ expr- ++expr --expr +expr -expr ~ !
new (type)expr
* /
+ << >> >>>
<> <= >= instanceof
== !=
&
^
|
&&
||
? :
= += -= *= /= = &= ^= |= <<= >>= >>>=
3.1.9 Les structures de contrôle
La figure 3.1 suivante résume les différentes structures de contrôle que permet le langage Java.
Page 28 sur 92
Initiation à la Programmation Java (4GI)
Fig 3.1 : Les structures de contrôle
3.1.9.1 Le bloc d'instructions
Un bloc d'instructions est un groupe d'instructions entre accolades ( { } ). Il exprime la séquencialité
dans l'ordre d'exécution des différentes instructions. Il définit également le domaine de visibilité des
variables dont il contient la déclaration. Un bloc d'instructions peut apparaître partout où peut apparaître une instruction.
Les instructions, comme les blocs d'instructions peuvent être identifiées par une étiquette. Cette étiquette apparaîtra avant l'instruction ou le bloc d'instructions, et sera suivi du caractère :.
Exemple:
bloc1 : { inst11 ;
inst12 ;
}
Nous verrons, lors de la description des instructions de boucles, comment nous pouvons utiliser les
étiquettes d'instruction.
3.1.9.2 Branchement conditionnel
Le branchement conditionnel (if) peut apparaître sous deux formes : avec ou sans clause else . La
forme
if ( expression ) inst;
s'exécute de la manière suivante: l'expression est évaluée; si le résultat renvoie le booléen true, alors
l'instruction inst est exécutée, sinon le branchement conditionnel est terminé.
La forme
if ( expression ) inst1; else inst2;
Page 29 sur 92
Initiation à la Programmation Java (4GI)
s'exécute de la manière suivante: l'expression est évaluée; si le résultat renvoie le booléen true, alors
l'instruction inst1 est exécutée, sinon l'instruction inst2 est exécutée.
Il est possible d'imbriquer plusieurs instructions if. On peut ainsi avoir:
if ( expression1 )
if ( expression2 ) inst2;
else instFin;
Dans ce cas, la clause else instFIN se rapporte à l'instruction if la plus imbriquée, c'est à dire l'instruction if ( expression2 ) inst2;. Ce qui est différent du programme:
if ( expression1 )
{if ( expression2 ) inst2;}
else instFin;
3.1.9.3 Choix multiple
L'instruction de choix multiple switch permet d'effectuer un aiguillage sur plusieurs instructions, suivant la valeur retournée par une expression d'un des types suivants: char, byte, short ou int. Lorsque
la valeur de l'expression est connue, elle est comparée à chacune des valeurs indiquées par les clauses
case, en séquence dans l'ordre d'apparition des clauses. Dès qu'une valeur indiquée par une clause est
égale à la valeur calculée, le contrôle d'exécution est donnée à la séquence d'instructions qui suit cette
clause. Si aucune valeur ne correspond, le contrôle est donné à la clause default , si celle-ci existe,
ou bien, l'instruction de choix multiple est considérée comme terminée.
L'instruction break permet d'arrêter l'instruction de choix multiple. Elle sera en général utilisée à la
fin de la séquence d'instructions de chaque clause.
Par exemple, la détermination d'une valeur paire ou impaire pourrait se faire par l'instruction suivante:
switch (i % 2) {
case 0 : System.out.println("C'est un nombre pair"); break;
case 1 : System.out.println("C'est un nombre impair"); break;
}
3.1.9.4 Boucles
Le langage Java offre trois possibilités de répétition : l'itération, boucle tantque, boucle
répéter_jusqu'à.
L'instruction for (init; test_fin; itération) inst; permet de répéter l'instruction inst en utilisant plusieurs valeurs pour certaines variables. Ces variables sont déclarées et initialisées dans l'expression
init (plusieurs variables peuvent être séparées par des virgules). La fin de l'itération est indiquée par
l'expression test_fin. L'expression itération est exécutée au debut de chaque nouvelle itération (après
la première). Ainsi, le programme
final public class ExecIter {
public static void main(String arg[]) {
for (int i=1 ; i<=4 ; i++) {
for (int j=1; j<=i ; j++) System.out.print("*");
System.out.println();
}
Page 30 sur 92
Initiation à la Programmation Java (4GI)
}
}
donnera la sortie suivante:
*
**
***
****
L'instruction while (cond) inst ; permet d'exécuter l'instruction inst, tant que la condition cond est
vraie. L'instruction inst peut ne jamias s'exécuter si la condition n'est pas vérifié dès l'entrée de la
boucle. Le programme précédent pourrait se re-écrire comme suit:
final public class ExecWhile {
public static void main(String arg[]) {
int i=1;
while (i<=4) {
int j=i;
while (j<=4) {
System.out.print("*");
j++;
}
System.out.println();
i++;
}
}
}
L'instruction do inst; while (cond) ; est similaire à la précédente à la différence que l'instruction inst
est exécutée au moins une fois. A nouveau, le programme précédent pourrait se re-écrire comme suit:
final public class ExecDoWhile {
public static void main(String arg[]) {
int i=1;
do {
int j=i;
do {
System.out.print("*");
j++;
} while(j<=4);
System.out.println();
i++;
} while (i<=4);
}
}
Il est possible de terminer une itération ou la boucle entière grâce à deux instructions : continue et
break. L'instruction continue permet de sauter toutes les instructions qui suivent et revenir au début
de la boucle. Ainsi, le programme suivant:
final public class ExecContinue {
public static void main(String arg[]) {
for (int i=1; i<=5; i++) {
System.out.println("=> i="+i);
if ((i % 2)==0) continue;
System.out.println("=> i est impair!");
}
}
}
donnera la sortie suivante:
Page 31 sur 92
Initiation à la Programmation Java (4GI)
=>
=>
=>
=>
=>
=>
=>
=>
i=1
i est impair!
i=2
i=3
i est impair!
i=4
i=5
i est impair!
L'instruction break arrête la boucle complètement. Ainsi, le programme suivant:
final public class ExecBreak {
public static void main(String arg[]) {
for (int i=1; i<=5; i++) {
System.out.println("=> i="+i);
if ((i % 2)==0) break;
System.out.println("=> i est impair!");
}
}
}
donnera la sortie suivante:
=> i=1
=> i est impair!
=> i=2
Les instructions continue et break peuvent avoir un paramètre qui correspond au label d'une instruction de boucle externe. C'est alors cette instruction de boucle qui sera terminée ponctuellement
ou complètement.
Par exemple, le programme suivant
final public class ExecBreakNomme {
public static void main(String arg[]) {
boucle1:
for (int i=0; i<5; i++) {
for (int j=0; j<5; j++) {
System.out.println("=> "+i+"+"+j+"="+(i+j));
if (i==2) continue boucle1;
if (i==3) break boucle1;
}
}
}
}
donnera la sortie suivante:
=>
=>
=>
=>
=>
=>
=>
=>
=>
=>
=>
0+0=0
0+1=1
0+2=2
0+3=3
0+4=4
1+0=1
1+1=2
1+2=3
1+3=4
1+4=5
2+0=2
Page 32 sur 92
Initiation à la Programmation Java (4GI)
=> 3+0=3
3.1.10 Les exceptions
Les exceptions sont des situations exceptionnelles pouvant intervenir dans le déroulement normal d'un
programme. Il est possible de les capturer grâce à l'instruction try (nous verrons plus tard comment
nous pouvons créer de nouvelles exceptions).
L'instruction try ressemble à l'instruction switch . Il permet d'exécuter normalement un bloc d'instructions. Si une exception est levée pendant l'exécution de ce bloc d'instructions, le contrôle est donné
au bloc d'instructions de la première clause catch dont le paramètre correspond à l'exception. Par
exemple, soit le programme suivant:
class ExecExceptionA {
public static void main(String arg[]) {
int i = 10;
int j = 0;
System.out.println("=> Début du Test!");
try {
System.out.println("=> Calcul de "+i+"/"+j);
i = i / j;
System.out.println("=> On obtient "+i);
} catch (NullPointerException e) {
System.out.println("=> Erreur sur pointeur null");
} catch (ArithmeticException e) {
System.out.println("=> Division par 0");
}
System.out.println("=> Fin du test!");
}
}
Lorsque la machine Java exécute la division avec j égal à 0, l'exception ArithmeticException est
levée. L'instruction suivant la division (println) n'est pas exécutée et le contrôle est donné à l'instruction de la clause catch (ArithmeticException e). On obtiendra alors la sortie suivante:
=>
=>
=>
=>
Début du Test!
Calcul de 10/0
Division par 0
Fin du test!
Il est possible d'utiliser une clause finally dont les instructions associées seront exécutées à la fin de
l'instruction try, qu'une exception ait été levée ou non. Ainsi, l'exécution du programme:
class ExecExceptionB {
public static void main(String arg[]) {
int i = 10;
int j = 0;
System.out.println("=> Début du Test!");
try {
System.out.println("=> Calcul de "+i+"/"+j);
i = i / j;
System.out.println("=> On obtient "+i);
} catch (NullPointerException e) {
System.out.println("=> Erreur sur pointeur null");
} catch (ArithmeticException e) {
System.out.println("=> Division par 0");
} finally {
Page 33 sur 92
Initiation à la Programmation Java (4GI)
System.out.println("=> Clause finally");
}
System.out.println("=> Fin du test!");
}
}
donnera la sortie:
=>
=>
=>
=>
=>
Début du Test!
Calcul de 10/0
Division par 0
Clause finally
Fin du test!
tandis que le programme:
class ExecExceptionC {
public static void main(String arg[]) {
int i = 10;
int j = 2;
System.out.println("=> Début du Test!");
try {
System.out.println("=> Calcul de "+i+"/"+j);
i = i / j;
System.out.println("=> On obtient "+i);
} catch (NullPointerException e) {
System.out.println("=> Erreur sur pointeur null");
} catch (ArithmeticException e) {
System.out.println("=> Division par 0");
} finally {
System.out.println("=> Clause finally");
}
System.out.println("=> Fin du test!");
}
}
donnera la sortie:
=>
=>
=>
=>
=>
Début du Test!
Calcul de 10/2
On obtient 5
Clause finally
Fin du test!
Dans les exemples précédents, l'exception levée était capturée par l'une des clauses de l'instruction
try. Que ce passe-t-il si aucune clause ne peut capturer une exception? Alors, le contrôle d'exécution
est donné à la clause finally qui se terminera en propageant l'exception initialement levée. Ainsi,
le programme
class ExecExceptionD {
public static void main(String arg[]) {
int i = 10;
int j = 0;
System.out.println("=> Début du Test!");
try {
System.out.println("=> Calcul de "+i+"/"+j);
i = i / j;
System.out.println("=> On obtient "+i);
} catch (NullPointerException e) {
System.out.println("=> Erreur sur pointeur null");
Page 34 sur 92
Initiation à la Programmation Java (4GI)
} finally {
System.out.println("=> Clause finally");
}
System.out.println("=> Fin du test!");
}
}
donnera la sortie:
=> Début du Test!
=> Calcul de 10/0
=> Clause finally
java.lang.ArithmeticException: / by zero
at ExecExceptionD.main(ExecExceptionD.java:8)
Exception in thread "main"
On constate, dans ce dernier exemple, que la propagation de l'exception a conduit à l'arrêt complet
du programme!
3.2 Les bases de l'orientation objet
Nous venons de voir la partie, disons impérative, du langage Java. En ce sens, on retrouve beaucoup
de similitudes avec les langages tels que C ou pascal. Ce qui fait la puissance de ce langage réside
dans l'approche objet, par la définition de classes et de méthodes. Nous avons déjà introduit la notion
de classe et d'objet dans le chapitre précédent. Regardons maintenant, comment nous pouvons les
définir en Java.
3.2.1 Les paquetages
Nous avons vu précédemment ce qu'est un paquetage: un regroupement de classes, d'interfaces et
même de paquetages. Pour déclarer un paquetage, il suffit de débuter la définition d'une classe par la
déclaration package suivie du nom du paquetage et terminée par un point-virgule. Par convention, on
fera en sorte d'utiliser des noms de paquetage commencant par une minuscule. Le nom du paquetage
doit décrire complètement le paquetage. Ainsi, si le paquetage R appartient à un paquetage Q qui luimême appartient à un paquetage P, la déclaration de R doit se faire en décrivant la hiérarchie complète
permettant de l'atteindre à partir du paquetage de plus haut niveau. Ainsi nous aurons: package P.Q.R;
Il existe une convention de nommage de paquetage permettant d'avoir un nom de paquetage unique.
Il consiste à utiliser, comme paquetage englobant, le nom de domaine internet inversé (nom qui par
définition est unique). Ainsi, nous pourrions utiliser comme préfixe, le nom du paquetage suivant:
FR.insaTlse.roques.castan.coursJava.
Comme la désignation d'un nom de classe doit se faire de manière complète, utiliser de tels noms de
paquetage deviendrait vite fastidieux. Il est possible de simplifier l'écriture de ces noms en utilisant
l'instruction import , après la déclaration du paquetage. Cette instruction permet alors de n'utiliser
pour référence à une classe importée, que le nom de la classe. L'instruction import aura alors pour
paramètre, le nom de la classe complet. Cette simplification ne peut évidemment se faire que s'il n'y
a aucune ambiguïté. Lorsqu'une ambiguïté apparait, le compilateur génère une erreur.
Page 35 sur 92
Initiation à la Programmation Java (4GI)
L'instruction import peut aussi s'appliquer sur plusieurs classes appartenant à un même paquetage.
On utilise alors le caractère * , à la place des noms des classes. Par exemple, si on dispose des classes
P.C1, P.C2, P.C3, nous pouvons les importer toutes grâce à l'instruction import P.*;.
Il est possible de ne pas indiquer de paquetage au moyen de l'instruction package lors de la déclaration d'une classe. On dit alors que la classe appartient à un paquetage anonyme. Cette option ne se
justifie que pour des raisons de commodités, notamment pendant l'apprentissage du langage. Il est
recommandé, dans ce cas, de ne pas déclarer les classes publiques.
3.2.2 Les classes
Une classe définit un modèle pour plusieurs objets partageant des caractéristiques communes. Il est
d'usage de commencer le nom d'une classe par une lettre majuscule, de manière à le différencier du
nom de paquetage. La déclaration d'une classe peut avoir plusieurs formes, présentées dans le tableau
ci-dessous:
class nomClasse corpsClasse
modificateur class nomClasse corpsClasse
modificateurs class nomClasse extends SuperClasse corpsClasse
modificateurs class nomClasse implements Interface1 Interface2 ... corpsClasse
modificateurs class nomClasse extends ... implements ... corpsClasse
La première forme permet de définir une classe n'ayant qu'une seule super-classe (par défaut), la classe
java.lang.Object. Le nom de la classe est suivi du corps de la classe, lequel est constitué d'une liste,
éventuellement vide, de définitions de variables, de classes ou de méthodes. La forme la plus simple
d'une déclaration est la suivante:
class Inutile {}
Comme son nom l'indique, cette classe n'est guère utile.
Dans l'exemple de l'Emploi du temps, une structure de donnée souvent utilisée est l'heure. Une classe
Heure pourrait se définir de la manière suivante:
package ch3;
class Heure {
int h;
int m;
}
Le problème avec cette définition est que la portée du nom de la classe, c'est à dire "Heure", n'est
que dans le paquetage de définition, c'est à dire "ch3". Cela signifie que l'on ne pourra pas utiliser la
classe ch3.Heure, en dehors du paquetage ch3! Son domaine de visibilité est le paquetage dans lequel
la classe est définie. Ainsi, le programme suivant conduira à une erreur de compilation :
package ch3_blabla;
import ch3;
class UtiliseHeure {
Heure heureDébut;
Heure heureFin;
}
Tandis que le programme suivant se compilera sans erreur;
Page 36 sur 92
Initiation à la Programmation Java (4GI)
package ch3;
class UtiliseHeure {
Heure heureDébut;
Heure heureFin;
}
Il est possible d'étendre le domaine de visibilité d'une classe en utilisant le modificateur public placé
avant le mot-clé class dans la déclaration de la classe. On obtient ainsi la deuxième forme de déclaration d'une classe, présentée dans le tableau précédent. Avec le modificateur public, la classe devient
accessible par tout code Java ayant accès au paquetage. Ainsi, si nous utilisons maintenant la définition de la classe "ch3.Heure" suivante:
package ch3;
public class Heure {
int h;
int m;
}
alors la compilation de la classe "ch3_blabla.UtiliseHeure" se fera maintenant sans erreur de compilation.
La troisième forme de définition d'une classe présentée précédemment fait apparaître après le nom
de la classe, le mot-clé extends suivi d'un nom de classe. C'est la manière de définir une nouvelle
classe à partir d'une classe existante. La nouvelle classe est dite sous-classe tandis que la classe
existante est dite super-classe . Une sous-classe hérite des variables et méthodes de sa super-classe.
Exemple: Dans le chapitre précédent, nous avions trouvé une hiérarchie de classe pouvant être utilisée
dans notre programme de gestion d'emploi du temps: nous avions une super-classe appelée Créneau
à partir de laquelle nous avions défini deux sous-classes, la classe Note et la classe Cours. Ces trois
classes pourraient être définies de la manière suivante:
package ch3.p1;
class Créneau {
Heure heureDébut;
Heure durée;
int jourDeLaSemaine;
}
class Note extends Créneau {
String information;
}
class Cours extends Créneau {
String nom;
String enseignant;
String numéro;
String salle;
}
class Heure {
int h;
int m;
}
Page 37 sur 92
Initiation à la Programmation Java (4GI)
Comme on vient de le voir, il est très facile de créer une nouvelle classe à partir d'une ancienne.
Dans certains cas de figure, on peut souhaiter interdire ce type de création. Java le permet grâce au
modificateur final placé avant le mot-clé class de la déclaration de classe (si un autre modificateur
est déjà utilisé, par exemple public, le modificateur final peut être placé avant ou après ce dernier).
Le modificateur final interdit la création de sous-classe pour une classe donnée. Ainsi, le programme
suivant doit conduire à une erreur de compilation:
package ch3.p2;
final class Créneau {
Heure heureDébut;
Heure durée;
int jourDeLaSemaine;
}
class Note extends Créneau {
String information;
}
class Cours extends Créneau {
String nom;
String enseignant;
String numéro;
String salle;
}
class Heure {
int h;
int m;
}
La quatrième forme de déclaration d'une classe utilise le mot-clé implements suivi d'une liste d'interfaces. Ce mot-clé suivi de la liste d'interfaces se place avant le corps de la classe. Cela permet d'indiquer que la classe ainsi définie aura le comportement défini par chaque interface. Nous reviendrons
plus loin sur cette notion.
Enfin la dernière forme de déclaration mixte les deux formes précédentes: création d'une sous-classe
ayant le comportement de plusieurs interfaces.
3.2.3 Les méthodes
La méthode est la description d'une action ou d'un comportement permettant de modifier ou de consulter les différents éléments d'un objet appartenant à une classe. Une méthode peut se définir de plusieurs manières. Le tableau ci-dessous en présente quelques-unes:
Page 38 sur 92
Initiation à la Programmation Java (4GI)
La première forme permet de définir une méthode utilisable uniquement à l'intérieur du paquetage de
définition. La définition commence par le type du résultat retourné par la méthode. L'exécution d'une
telle méthode doit alors se terminer par l'instruction return avec pour argument une expression du
type indiqué par la déclaration.
Par exemple, pour notre classe ch3.Heure, nous pourrions définir la méthode nombreDeMinutes permettant d'obtenir le nombre de minutes d'un objet de type Heure, de la manière suivante:
int nombreDeMinutes() {
return h * 60 + m;
}
Nous avons ainsi défini une méthode sans paramètre. Une méthode peut éventuellement ne pas avoir à
retourner de résultat. Dans la déclaration, nous utiliserons alors le mot-clé void à la place du type du
résultat. Une telle méthode devra se terminer normalement par une instruction return sans paramètre
(cette instruction est implicite à la fin du corps de la méthode). Ainsi, la méthode permettant d'ajouter
un certain nombre de minutes à un objet de type Heure, se définira comme suit:
void ajouterMinutes(int nbrMinutes) {
m = (m+nbrMinutes) % 60;
h = h + ((m+nbrMinutes) / 60);
}
On définit la signature d'une méthode par son nom, le nombre de paramètres et les types des paramètres. Deux méthodes ne peuvent pas avoir la même signature. Cela signifie que deux méthodes
peuvent avoir le même nom, dans la mesure où le nombre et le type des paramètres permettent de les
identifier sans ambiguïté! La deuxième forme diffère de la première par l'ajout du mot-clé throws
suivi d'une liste d'exceptions, après la liste de paramètres. Cette liste d'exceptions, sous-classes issues
de la classe java.lang.Throwable, énumère les différentes exceptions qui peuvent être levées durant
l'exécution de la méthode, autres que les exceptions issues de la classe Error ou de la classe RuntimeException . Cela permet au compilateur de vérifier qu'un code faisant appel à cette méthode puisse
capturer ces exceptions.
La troisième forme et la dernière forme reprennent les deux précédentes en faisant apparaître en début
de la déclaration, un ou plusieurs modificateurs parmis les suivants: public, protected, private, abstract, static, final, synchronized et native. Dans le cas de plusieurs modificateurs, l'ordre est sans
importance quoique l'ordre habituel est celui indiqué précédemment. Les modificateurs public, protected, private sont exclusifs. Une méthode déclarée avec le modificateur abstract, ne peut être
déclarée private, static, final, synchronized ou native. Examinons en détail ces modificateurs.
public
L'accès à la méthode est autorisé en dehors du paquetage de définition.
Page 39 sur 92
Initiation à la Programmation Java (4GI)
private
L'accès à la méthode est autorisé uniquement à l'intérieur de la classe de définition.
protected
L'accès à la méthode est autorisée uniquement à l'intérieur du paquetage de définition, ou d'une sous-classe de la classe de définition.
abstract
La déclaration ne contient que le type retourné par la méthode, la signature ainsi
que les exceptions pouvant être levées par la méthode. Il n'y a donc pas de bloc
décrivant l'implémentation de la méthode.\\\\ Une classe ayant une méthode abstraite devient une classe abstraite et donc doit avoir le modificateur abstract dans
sa déclaration. Une classe abstraite ne peut être utilisée directement, elle servira
de super-classe pour une classe fournissant l'implémentation de chaque méthode
abstraite.
static
La méthode est utilisée sans faire référence à un objet particulier de la classe. On
dit que c'est une méthode de classe.
final
La méthode ne peut être redéfinie par une sous-classe de la classe de déclaration.
synchronized
Il ne pourra pas y avoir plusieurs exécutions simultanées de la méthode. Ces
exécutions seront automatiquement séquentialisées.
native
Une méthode native est une méthode implémentée dans le code machine dépendant de la plateforme d'exécution. Dans ce cas, le bloc décrivant l'implémentation est omis.
L'appel d'une méthode de classe se fait de plusieurs manières:
nomClasse.nomMéthode(par1, par2, ...);
variable.nomMéthode(par1, par2, ...);
nomMéthode(par1, par2, ...);
this.nomMéthode(par1, par2, ...);
super.nomMéthode(par1, par2, ...);
La première manière est pour les méthodes de classes. Eventuellement, le nom de la classe peut ne pas
être indiqué si l'appel est fait dans une méthode (de classe ou non) de la même classe. Par exemple,
le programme suivant est correct:
package ch3;
class TestMéthodeA {
static void méthodeDeClasse() { }
static void appelMéthodeDeClasseA() {
// par une autre méthode de classe version courte:
méthodeDeClasse();
// par une autre méthode de classe version longue:
TestMéthodeA.méthodeDeClasse();
}
void appelMéthodeDeClasseB() {
// par une autre méthode version courte:
Page 40 sur 92
Initiation à la Programmation Java (4GI)
méthodeDeClasse();
// par une autre méthode version longue:
TestMéthodeA.méthodeDeClasse();
}
}
class TestMéthodeB {
static void appelMéthodeDeClasseA() {
// par une autre méthode de classe d'une autre classe
TestMéthodeA.méthodeDeClasse();
}
void appelMéthodeDeClasseB() {
// par une autre méthode d'une autre classe
TestMéthodeA.méthodeDeClasse();
}
}
Les trois manières suivantes sont pour les méthodes n'ayant pas le modificateur static. Le nom de la
méthode peut être seul, ou préfixé par le nom d'une variable ou encore par le mot clé this . Dans le cas
du préfixage par le nom de la variable, la méthode s'appliquera sur cette variable. Sinon, la méthode
s'applique sur l'objet courant. Par exemple, le programme suivant est correct:
package ch3.p3;
class TestMéthodeA {
void méthodeDeInstance() {
}
void appelMéthodeDeInstanceB() {
// par une autre méthode version courte:
méthodeDeInstance();
// par une autre méthode version longue:
this.méthodeDeInstance();
}
}
class TestMéthodeB {
TestMéthodeA x;
void appelMéthodeDeInstanceB() {
// par une autre méthode d'une autre classe
x.méthodeDeInstance();
}
}
La dernière manière utilise le mot clé super . Ce mot clé permet de faire référence à la méthode de
la super-classe, dont le nom suit le mot clé super. Par exemple, le programme suivant:
package ch3;
class SuperClasse {
void méthode() {
System.out.println("=> SuperClasse");
}
}
class SousClasse extends SuperClasse {
void méthode() {
Page 41 sur 92
Initiation à la Programmation Java (4GI)
System.out.println("=> Début SousClasse");
super.méthode();
System.out.println("=> Fin SousClasse");
}
public static void main (String arg[ ]) {
SousClasse x = new SousClasse();
x.méthode();
}
}
donnera la sortie suivante:
$ java ch3.SousClasse
=> Début SousClasse
=> SuperClasse
=> Fin SousClasse
$
Enfin, il existe une méthode de classe particulière, de nom main, ayant comme paramètre un tableau
de chaîne de caractères, et définie avec les modificateurs public et static. C'est la méthode qui est
lancée par la commande java. Le paramètre contient alors les différents arguments de la ligne de
commande: Par exemple, le programme:
package ch3;
class ArgMain {
public static void main (String arg[]) {
System.out.println(
"=> Voici les "+arg.length+" arguments :\n");
for (int i=0; i < arg.length ; i++) {
System.out.println(
"=>
Argument "+i+" : "+arg[i]);
}
}
}
donnera la sortie suivante:
$ java ch3.ArgMain un "ou deux" arguments
=> Voici les 3 arguments :
=>
=>
=>
$
Argument 0 : un
Argument 1 : ou deux
Argument 2 : arguments
3.2.4 Les constructeurs
Le constructeur est une méthode particulière destinée à créer une instance de classe. La déclaration
d'un constructeur diffère de la déclaration d'une méthode au niveau de l'absence de type retourné,
puisque celui-ci est implicitement la classe de définition. De même, le nom du constructeur est obligatoirement celui de la classe de définition.
Le tableau ci-dessous présente plusieurs manières de définir un constructeur:
nomClasse ( type1 nom1, ... ) { ... }
nomClasse ( type1 nom1, ... ) throws listeClasseThrowable { ... }
Page 42 sur 92
Initiation à la Programmation Java (4GI)
modificateurs nomClasse ( type1 nom1, ... ) { ... }
modificateurs nomClasse ( type1 nom1, ... ) throws ... { ... }
Les modificateurs des constructeurs peuvent être : public, protected et private. Les autres modificateurs utilisés pour les méthodes ne peuvent être utilisés.
L'appel à un constructeur se fait par l'instruction new suivie du nom du constructeur et des paramètres.
Par exemple, le programme suivant:
package ch3;
class MaClasse {
MaClasse() {
System.out.println("=>
}
MaClasse sans paramètre");
MaClasse(int i) {
System.out.println("=>
}
MaClasse avec paramètre entier: "+i);
MaClasse(int i, int j) {
System.out.println("=>
}
MaClasse avec paramètres entiers: "+i+" et "+j);
MaClasse(String str) {
System.out.println("=>
}
MaClasse avec paramètre chaîne: "+str);
public static void main (String arg[ ]) {
System.out.println("=> Appel: new MaClasse()");
MaClasse x = new MaClasse();
System.out.println("\n=> Appel: new MaClasse(4)");
x = new MaClasse(4);
System.out.println("\n=> Appel: new MaClasse(4, 5)");
x = new MaClasse(4, 5);
System.out.println("\n=> Appel: new MaClasse(\"La chaîne\")");
x = new MaClasse("La chaîne");
}
}
donnera la sortie suivante:
$ java ch3.MaClasse
=> Appel: new MaClasse()
=>
MaClasse sans paramètre
=> Appel: new MaClasse(4)
=>
MaClasse avec paramètre entier: 4
=> Appel: new MaClasse(4, 5)
=>
MaClasse avec paramètres entiers: 4 et 5
=> Appel: new MaClasse("La chaîne")
=>
MaClasse avec paramètre chaîne: La chaîne
$
L'appel d'un constructeur d'une sous-classe débute par un appel, par défaut, au constructeur de la
super-classe. Par exemple, le programme
Page 43 sur 92
Initiation à la Programmation Java (4GI)
package ch3;
class SuperClasse {
SuperClasse() {
System.out.println("=>
}
SuperClasse(int i) {
System.out.println("=>
}
}
Constructeur SuperClasse sans paramètre");
Constructeur SuperClasse avec paramétre: "+i);
class SousClasse extends SuperClasse {
SousClasse() {
System.out.println("=>
}
Constructeur SousClasse sans paramètre");
SousClasse(int j) {
System.out.println("=>
}
Constructeur SousClasse avec paramétre: "+j);
public static void main (String arg[ ]) {
System.out.println("=> Appel: new SousClasse()");
SousClasse x = new SousClasse();
System.out.println("\n=> Appel: new SousClasse(4)");
x = new SousClasse(4);
}
}
donnera la sortie suivante:
$ java ch3.SousClasse
=> Appel: new SousClasse()
=>
Constructeur SuperClasse sans paramètre
=>
Constructeur SousClasse sans paramètre
=> Appel: new SousClasse(4)
=>
Constructeur SuperClasse sans paramètre
=>
Constructeur SousClasse avec paramétre: 4
$
Il est toutefois possible de faire appel, de manière explicite, à un constructeur de la superclasse en
utilisant uniquement comme première instruction du constructeur, le mot-clé super suivi des paramètres du constructeur. Par exemple, le programme
package ch3;
class SuperClasse {
SuperClasse() {
System.out.println("=>
}
SuperClasse(int i) {
System.out.println("=>
}
}
Constructeur SuperClasse sans paramètre");
Constructeur SuperClasse avec paramétre: "+i);
class SousClasse extends SuperClasse {
SousClasse() {
Page 44 sur 92
Initiation à la Programmation Java (4GI)
super(2);
System.out.println("=>
Constructeur SousClasse sans paramètre");
}
SousClasse(int j) {
super(j);
System.out.println("=>
}
Constructeur SousClasse avec paramétre: "+j);
public static void main (String arg[ ]) {
System.out.println("=> Appel: new SousClasse()");
SousClasse x = new SousClasse();
System.out.println("\n=> Appel: new SousClasse(4)");
x = new SousClasse(4);
}
}
donnera la sortie suivante:
$ java ch3.SousClasse
=> Appel: new SousClasse()
=>
Constructeur SuperClasse avec paramétre: 2
=>
Constructeur SousClasse sans paramètre
=> Appel: new SousClasse(4)
=>
Constructeur SuperClasse avec paramétre: 4
=>
Constructeur SousClasse avec paramétre: 4
$
3.2.5 Les interfaces
Une interface s'apparente à une classe n'ayant que des méthodes abstraites. Une définition d'interface
peut prendre l'une des formes suivantes:
interface nomInterface corpsInterface
modificateur interface nomInterface corpsInterface
modificateurs interface nomInterface extends SuperInterface corpsInterface
Nous retrouvons les formes déjà utilisées pour la définition de classe. Comme pour les classe, on
peut définir une hiérarchie d'interfaces. Deux modificateurs peuvent être utilisés: public et abstract.
Ils sont cependant superflus, puisque toutes les méthodes de l'interface sont à la fois implicitement
publiques et abstraites. Une interface ne peut avoir des constructeurs.
Le corps de l'interface diffère du corps de la classe. Il ne peut contenir que des définitions de contantes
(variables préfixées par le modificateur final) et des méthodes abstraites.
Le programme suivant montre le polymorphisme de Java, à travers l'utilisation des interfaces:
package ch3.p4;
interface InterfaceA {
public void méthodeA();
}
interface InterfaceB {
public void méthodeB();
Page 45 sur 92
Initiation à la Programmation Java (4GI)
}
class MaClasse implements InterfaceA, InterfaceB {
public void méthodeA() { }
public void méthodeB() { }
}
class TestMaClasse {
public static void Main(String[] args) {
MaClasse x = new MaClasse();
// l'objet référencé par x peut aussi être vu comme un objet de type InterfaceA:
InterfaceA y = (InterfaceA) x;
// l'objet référencé par x peut aussi être vu comme un objet de type InterfaceB:
InterfaceB z = (InterfaceB) x;
}
}
3.2.6 Les variables
La déclaration d'une variable peut être placée partout où l'on peut mettre une instruction. De plus, la
déclaration d'une variable peut être préfixée par un modificateur. Les modificateurs sont les mêmes
que ceux utilisés pour les méthodes. Par exemple, nous pourrons définir une constante en préfixant
le nom de la constante par final . Le tableau ci-dessous montre plusieurs types de variables dans un
programme:
La durée de vie de ces variables dépend du type de la variable. Une variable de classe est liée à une
classe; elle cesse d'exister lorsque la classe n'est plus chargée. Une variable locale a une durée de
vie liée au bloc de définition (boucle, méthode, clause catch, ...). Une variable d'instance est liée à
l'instance. Lorsque celle-ci disparait (recyclée par le récupérateur de mémoire), la variable disparait
en même temps.
L'accès à une variable se fait en général par son nom uniquement. Les noms des variables de classes
doivent être préfixés par le nom de la classe, lorsque celles-ci sont utilisées en dehors de la classe
de définition.
Il est possible d'accéder à une variable d'instance, en préfixant le nom de la variable par le nom de
l'instance (quoique cette manière n'est pas recommandée car elle rend visible l'implantation d'une
classe; pour accéder à une variable d'instance, il est préférable de définir une méthode d'accés). Le
programme suivant donne quelques exemples:
Page 46 sur 92
Initiation à la Programmation Java (4GI)
package ch3;
class MonObjet {
static int varClasse;
int varInstance;
public void println(String nom) {
System.out.println("=> Objet : "+nom);
System.out.println("=>
Variable de classe : "+varClasse);
System.out.println("=>
Variable d'instance: "+varInstance);
System.out.println();
}
}
class TestMonObjet {
public static void main(String[]
MonObjet x = new MonObjet();
MonObjet y = new MonObjet();
// modification de la variable
MonObjet.varClasse = 2;
// autre forme : x.varClasse =
// modification de la variable
x.varInstance = 4;
// modification de la variable
y.varInstance = 5;
// visualisation des objets:
x.println("=> x");
y.println("=> y");
// modification de la variable
x.varClasse = 3;
// visualisation des objets:
x.println("=> x");
y.println("=> y");
}
}
args) {
de classe
2;
d'instance de x
d'instance de y
de classe
Son exécution donnera la sortie suivante:
$ java ch3.TestMonObjet
=> Objet : x
=>
Variable de classe : 2
=>
Variable d'instance: 4
=> Objet : y
=>
Variable de classe : 2
=>
Variable d'instance: 5
=> Objet : x
=>
Variable de classe : 3
=>
Variable d'instance: 4
=> Objet : y
=>
Variable de classe : 3
=>
Variable d'instance: 5
$
Page 47 sur 92
Initiation à la Programmation Java (4GI)
3.3 Exemple de classe, l'Heure
Revenons à l'exemple de la gestion d'un emploi du temps. Une structure de donnée souvent utilisée
est l'heure. Plusieurs opérations peuvent être faites sur cette structure de donnée:
•
•
•
•
•
•
créer une heure : 8h25
ajouter ou soustraire des heures
comparer des heures
déplacer une heure
donner une représentation externe (chaîne de caractères) d'une heure
...
Il est donc judicieux de définir une classe Heure pour représenter cette donnée. Cette classe sera
définie dans le paquetage ch3. Nous utiliserons un objet définie dans la bibliothèque standard:
java.util.StringTokenizer. Cet objet nous permettra d'accéder très simplement aux différents mots
(tokens) d'une chaîne de caractères. La classe Heure sera définie comme publique. Elle sera définie
dans le fichier de nom Heure.java, qui débutera donc par:
package ch3;
import java.util.StringTokenizer;
public class Heure {
Nous avions déjà défini une classe Heure avec deux variables, une pour le nombre d'heures, une autre
pour le nombre de minutes. Un tel choix complique la comparaison de deux heures entre elles. Nous
choisirons plutôt d'implémenter un objet heure par le nombre total de minutes. Nous utiliserons la
variable nombreDeMinutes avec le modificateur private de manière à cacher l'implémentation de
la classe Heure:
private int nombreDeMinutes;
Pour créer un objet Heure, nous utiliserons deux constructeurs, le premier ayant en paramètre les
deux valeurs définissant une heure (nombre d'heures et nombre de minutes), le deuxième ayant en
paramètre la chaîne de caractères représentant une heure donnée:
public Heure(int H, int M) {
nombreDeMinutes = H * 60 + M;
}
public Heure(String str) {
StringTokenizer listeTokens = new StringTokenizer(str, "hH");
int H = Integer.valueOf(listeTokens.nextToken()).intValue();
int M = Integer.valueOf(listeTokens.nextToken()).intValue();
nombreDeMinutes = H * 60 + M;
}
Nous pouvons maintenant créer les différentes méthodes. La première, ajouter, permet d'additionner
des heures. L'objet initial n'est pas modifié, il y a plutôt création d'un nouvel objet Heure:
public Heure ajouter(Heure H) {
return new Heure(0, H.nombreDeMinutes+nombreDeMinutes);
}
La méthode suivante, déplacer, permet de modifier l'objet. Elle agit par effet de bord:
public void déplacer(Heure H) {
nombreDeMinutes += H.nombreDeMinutes;
}
Les deux suivantes permettent d'effectuer des comparaisons (supérieur ou égalité):
public boolean plusGrandQue(Heure H) {
return this.nombreDeMinutes > H.nombreDeMinutes;
}
Page 48 sur 92
Initiation à la Programmation Java (4GI)
public boolean egal(Heure H) {
return this.nombreDeMinutes == H.nombreDeMinutes;
}
Enfin, la dernière renvoie une représentation de l'heure sous forme de chaîne de caractères. Le nom
est celui de la méthode correspondante de la super-classe java.lang.Object. L'intérêt de conserver
ce nom au lieu d'un nom nouveau réside dans le fait que le débogueur utilise la méthode toString()
pour visualiser un objet.
public String toString() {
int M = nombreDeMinutes % 60;
if (M < 10)
return ""+(nombreDeMinutes / 60)+"H0"+M;
else
return ""+(nombreDeMinutes / 60)+"H"+M;
}
Le programme suivant va nous permettre de tester la classe Heure. Il est défini dans un paquetage
différent pour mieux tester la visibilité des différentes méthodes.
package test.ch3;
import ch3.Heure;
final public class TestHeure {
public static void main(String arg[]) {
Heure H1, H2;
System.out.println("==> Test de la classe Heure");
H1 = new Heure(1,15);
H2 = new Heure("2H50");
System.out.println("==> H1 = "+H1);
System.out.println("==> H2 = "+H2);
System.out.println("==> H1+H2 = "+H1.ajouter(H2));
System.out.println("==> H1 = "+H1);
H1.déplacer(H2);
System.out.println("==> Après H1 déplacé de H2 on a H1 = "+H1);
Heure H3 = new Heure("2H0");
System.out.println("==> 2H0 donne "+H3);
System.out.println("==> Fin du test!");
}
}
Son exécution donnera:
$ java test.ch3.TestHeure
==> Test de la classe Heure
==> H1 = 1H15
==> H2 = 2H50
==> H1+H2 = 4H05
==> H1 = 1H15
==> Après H1 déplacé de H2 on a H1 = 4H05
==> 2H0 donne 2H00
==> Fin du test!
$
3.4 Variations sur le polymorphisme
Le polymorphisme est la possibilité d'avoir pour plusieurs objets différents, des méthodes possédant toutes la même signature et ayant un comportement différent pour chaque objet. Le choix de la
méthode utilisée se fait au moment de l'exécution et non pas au moment de la compilation.
Page 49 sur 92
Initiation à la Programmation Java (4GI)
3.4.1 Le problème initial
On veut réaliser la somme des éléments d'un tableau, sachant que tous les éléments du tableau sont du
même type, et que ce type peut être n'importe quel objet pouvant être converti en float. Par exemple:
• somme() appliquée à {new Integer(1), new Integer(2), new Integer(3)}
donnera 6.0
• somme() appliquée à {"1.0", "2.0", "3.0"} donnera 6.0
• ...
public class TabMain {
public static void main(String[] args)
{
Integer[] t1 = {new Integer(1), new Integer(2), new Integer(3)};
Tab1 o1 = new Tab1(t1);
String[] t2 = {"1.0", "2.0", "3.0"};
Tab1 o2 = new Tab1(t2);
System.out.println("Objet : "+o1);
System.out.println("Somme : "+o1.somme());
System.out.println("Objet : "+o2);
System.out.println("Somme : "+o2.somme());
}
}
En programmation classique, nous pourrions avoir:
public class Tab1 {
Object[] objLst;
public Tab1 (Object[] tab) { objLst = tab; }
public float somme() {
float s = 0.0f;
for (int i=0; i<objLst.length; i++) s += floatVal(i);
return s;
}
public float floatVal(int i) {
Object o = objLst[i];
if (o instanceof java.lang.String)
return new Float((String)o).floatValue();
if (o instanceof java.lang.Integer)
return (float) ((Integer)o).intValue();
return 0.0f;
}
public static void main(String[] args)
{
Tab1 o1 = new Tab1(new Integer[] {new Integer(1), new Integer(2), new Integer(3)})
Tab1 o2 = new Tab1(new String[] {"1.0", "2.0", "3.0"});
System.out.println("Somme Int: "+o1.somme());
System.out.println("Somme Str: "+o2.somme());
}
}
3.4.2 Polymorphisme de méthode (surcharge)
Page 50 sur 92
Initiation à la Programmation Java (4GI)
public class Tab2 {
public static float somme(String[] strLst) {
float s = 0.0f;
for (int i=0; i<strLst.length; i++)
s += new Float(strLst[i]).floatValue();
return s;
}
public static float somme(Integer[] intLst) {
float s = 0.0f;
for (int i=0; i<intLst.length; i++)
s += (float) intLst[i].intValue();
return s;
}
public static void main(String[] args)
{
Integer[] t1 = {new Integer(1), new Integer(2), new Integer(3)};
String[] t2 = {"1.0", "2.0", "3.0"};
System.out.println("Somme Int: "+Tab2.somme(t1));
System.out.println("Somme Str: "+Tab2.somme(t2));
}
}
3.4.3 Méthode abstraite
public abstract class Tab3 {
Object[] objLst;
public float somme() {
float s = 0.0f;
for (int i=0; i<objLst.length; i++) s += floatVal(i);
return s;
}
abstract float floatVal(int i);
}
class Tab3Str extends Tab3 {
public Tab3Str (String[] tab) { objLst = tab; }
public float floatVal(int i) {
return new Float((String)objLst[i]).floatValue();
}
}
class Tab3Int extends Tab3 {
public Tab3Int (Integer[] tab) { objLst = tab; }
public float floatVal(int i) {
return (float) ((Integer)objLst[i]).intValue();
}
}
class Tab3Main {
public static void main(String[] args) {
Page 51 sur 92
Initiation à la Programmation Java (4GI)
Tab3 tab = new Tab3Int(new Integer[] {new Integer(1), new Integer(2), new Integer(
System.out.println("Somme Int: "+tab.somme());
tab = new Tab3Str(new String[] {"1.0", "2.0", "3.0"});
System.out.println("Somme Str: "+tab.somme());
}
}
3.4.4 Interface
public interface Tab4 {
float somme();
float floatVal(int i);
}
class Tab4Str implements Tab4 {
Object[] objLst;
public Tab4Str (String[] tab) { objLst = tab; }
public float floatVal(int i) {
return new Float((String)objLst[i]).floatValue();
}
public float somme() {
float s = 0.0f;
for (int i=0; i<objLst.length; i++) s += floatVal(i);
return s;
}
}
class Tab4Int implements Tab4 {
Object[] objLst;
public Tab4Int (Integer[] tab) { objLst = tab; }
public float floatVal(int i) {
return (float) ((Integer)objLst[i]).intValue();
}
public float somme() {
float s = 0.0f;
for (int i=0; i<objLst.length; i++) s += floatVal(i);
return s;
}
}
class Tab4Main {
public static void main(String[] args) {
Tab4 tab = new Tab4Int(new Integer[] {new Integer(1), new Integer(2), new Integer(
System.out.println("Somme Int: "+tab.somme());
tab = new Tab4Str(new String[] {"1.0", "2.0", "3.0"});
System.out.println("Somme Str: "+tab.somme());
}
}
3.4.5 Interface et méthode abstraite
public interface Tab5 {
float somme();
Page 52 sur 92
Initiation à la Programmation Java (4GI)
float floatVal(int i);
}
abstract class Tab5Abs
Object[] objLst;
implements Tab5 {
public float somme() {
float s = 0.0f;
for (int i=0; i<objLst.length; i++) s += floatVal(i);
return s;
}
public abstract float floatVal(int i);
}
class Tab5Str extends Tab5Abs {
public Tab5Str (String[] tab) { objLst = tab; }
public float floatVal(int i) {
return new Float((String)objLst[i]).floatValue();
}
}
class Tab5Int extends Tab5Abs {
public Tab5Int (Integer[] tab) { objLst = tab; }
public float floatVal(int i) {
return (float) ((Integer)objLst[i]).intValue();
}
}
class Tab5Main {
public static void main(String[] args) {
Integer[] t1 = {new Integer(1), new Integer(2), new Integer(3)};
String[] t2 = {"1.0", "2.0", "3.0"};
Tab5 tab = new Tab5Int(t1);
System.out.println("Somme : "+tab.somme());
tab = new Tab5Str(t2);
System.out.println("Somme : "+tab.somme());
}
}
3.4.6 Classe membre statique
C'est une classe définie comme un membre statique d'une autre classe, c'est à dire dont la définition
apparait au même niveau qu'une définition de variable de classe.
Une classe membre statique a accès à tous les membres statiques de la classe englobante, y compris
ceux déclarés private.
Il est également possible de déclarer de la même manière des interfaces membres statiques.
public class Tab6 {
Object[] objLst;
Page 53 sur 92
Initiation à la Programmation Java (4GI)
static class Tab6Sta {
static float somme(Object[] objLst) {
float s = 0.0f;
for (int i=0; i<objLst.length; i++)
if (objLst[i] instanceof String) s += floatVal((String)objLst[i]);
else s += floatVal((Integer) objLst[i]);
return s;
}
static float floatVal(String str) {
return new Float(str).floatValue();
}
static float floatVal(Integer i) {
return (float) i.intValue();
}
}
public Tab6 (Object[] tab) {
objLst = tab;
}
public static void main(String[] args) {
Tab6 tab = new Tab6(new Integer[] {new Integer(1), new Integer(2), new Integer(3)}
System.out.println("Somme Int: "+Tab6Sta.somme(tab.objLst));
tab = new Tab6(new String[] {"1.0", "2.0", "3.0"});
System.out.println("Somme Str: "+Tab6Sta.somme(tab.objLst));
}
}
3.4.7 Classe membre
C'est une classe définie comme un membre d'une autre classe, c'est à dire dont la définition apparait
au même niveau qu'une définition de variable de classe.
Une classe membre a accès à tous les membres statiques de la classe englobante, y compris ceux
déclarés private.
Il n'est pas possible de déclarer de la même manière des interfaces membres. Si on déclare une interface
comme membre d'une classe, elle est automatiquement statique.
public class Tab7 {
abstract class Tab7Abs {
Object[] objLstAbs;
abstract float floatVal(int i);
}
Tab7Abs objLst;
class Tab7Str extends Tab7Abs {
Tab7Str (String[] tab) { objLstAbs = (Object[]) tab; }
float floatVal(int i) {
return new Float((String)objLstAbs[i]).floatValue();
}
}
Page 54 sur 92
Initiation à la Programmation Java (4GI)
class Tab7Int extends Tab7Abs {
private Tab7Int (Integer[] tab) { objLstAbs = (Object[]) tab; }
float floatVal(int i) {
return (float) ((Integer)objLstAbs[i]).intValue();
}
}
public Tab7(String[] tab) {
objLst = new Tab7Str(tab);
}
public Tab7(Integer[] tab) {
objLst = new Tab7Int(tab);
}
public float somme() {
float s = 0.0f;
for (int i=0; i<objLst.objLstAbs.length; i++)
s += objLst.floatVal(i);
return s;
}
public static void main(String[] args) {
Tab7 tab = new Tab7(new Integer[] {new Integer(1), new Integer(2), new Integer(3)}
System.out.println("Somme Int: "+tab.somme());
tab = new Tab7(new String[] {"1.0", "2.0", "3.0"});
System.out.println("Somme Str: "+tab.somme());
}
}
3.4.8 Classe locale
C'est une classe définie dans un bloc de code java.
Elle n'est visible que dans le bloc de définition et ne peut donc être utilisée hors de ce bloc. Elle a
accès à tous les membres de la classe englobante, ainsi qu'à toutes les variables locales du bloc, ainsi
qu'aux paramètres de méthode situés dans la portée de la définition de la classe.
public class Tab8 {
abstract class Tab8Abs {
Object[] objLstAbs;
abstract float floatVal(int i);
}
Tab8Abs objLst;
public Tab8(String[] tab) {
class Tab8Str extends Tab8Abs {
Tab8Str (String[] tab) { objLstAbs = (Object[]) tab; }
float floatVal(int i) {
return new Float((String)objLstAbs[i]).floatValue();
Page 55 sur 92
Initiation à la Programmation Java (4GI)
}
}
objLst = new Tab8Str(tab);
}
public Tab8(final Integer[] tab) {
class Tab8Int extends Tab8Abs {
Tab8Int () { objLstAbs = (Object[]) tab; }
float floatVal(int i) {
return (float) ((Integer)objLstAbs[i]).intValue();
}
}
objLst = new Tab8Int();
}
public float somme() {
float s = 0.0f;
for (int i=0; i<objLst.objLstAbs.length; i++)
s += objLst.floatVal(i);
return s;
}
public static void main(String[] args) {
Tab8 tab = new Tab8(new Integer[] {new Integer(1), new Integer(2), new Integer(3)}
System.out.println("Somme Int: "+tab.somme());
tab = new Tab8(new String[] {"1.0", "2.0", "3.0"});
System.out.println("Somme Str: "+tab.somme());
}
}
3.4.9 Classe anonyme
C'est une classe locale sans nom définie et instancié dans une expression unique. Une classe anonyme
peut s'utiliser à la place de toute classe locale ayant un usage unique dans le code
public class Tab9 {
abstract class Tab9Abs {
Object[] objLstAbs;
abstract float floatVal(int i);
}
Tab9Abs objLst;
public Tab9(final String[] tab) {
objLst = new Tab9Abs() {
{ objLstAbs = (Object[]) tab; } // initialiseur d'instance
float floatVal(int i) {
return new Float((String)objLstAbs[i]).floatValue();
}
};
}
public Tab9(final Integer[] tab) {
Page 56 sur 92
Initiation à la Programmation Java (4GI)
objLst = new Tab9Abs() {
{ objLstAbs = (Object[]) tab; } // initialiseur d'instance
float floatVal(int i) {
return (float) ((Integer)objLstAbs[i]).intValue();
}
};
}
public float somme() {
float s = 0.0f;
for (int i=0; i<objLst.objLstAbs.length; i++)
s += objLst.floatVal(i);
return s;
}
public static void main(String[] args) {
Tab9 tab = new Tab9(new Integer[] {new Integer(1), new Integer(2), new Integer(3)}
System.out.println("Somme Int: "+tab.somme());
tab = new Tab9(new String[] {"1.0", "2.0", "3.0"});
System.out.println("Somme Str: "+tab.somme());
}
}
3.5 Allocation dynamique d'objets
Cycle de vie d'un objet :
par l'opérateur new devant un constructeur
Création
Copie
invocation de la méthode clone() à condition que la classe de l'objet réalise
l'interface cloneable().
Libération
lorsque l'objet n'est plus référencé. La libération intervient à un moment non
déterminé, lors de l'exécution du récupérateur
3.5.1 Clone
• allocation de la mémoire
• copie des membres
Le clonage des membres de l'objet peut s'obtenir via la redéfinition de la méthode clone() héritée
de la classe java.lang.Object.
public class TestClone {
public static void main (String[] args) {
try {
MonObjet x = new MonObjet(1, new MonString("premier"));
MonObjet y = (MonObjet) x.clone();
x.dump();
y.dump();
} catch (CloneNotSupportedException e) {
System.out.println("Cet objet ne peut être cloné!");
}
Page 57 sur 92
Initiation à la Programmation Java (4GI)
}
}
class MonObjet implements Cloneable{
int no;
MonString nom;
MonObjet (int no, MonString nom) {
this.no = no;
this.nom = nom;
}
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
void dump() {
System.out.println(""+this);
System.out.println(" no : "+no);
System.out.println(" nom: "+nom);
System.out.println();
}
}
class MonString {
String nom;
MonString (String nom) {
this.nom = nom;
}
}
$ java TestClone
MonObjet@20c10f
no : 1
nom: MonString@62eec8
MonObjet@2a9835
no : 1
nom: MonString@62eec8
$
public class TestClone2 {
public static void main (String[] args) {
try {
MonObjet2 x = new MonObjet2(1, "premier");
MonObjet2 y = (MonObjet2) x.clone();
x.dump();
y.dump();
} catch (CloneNotSupportedException e) {
System.out.println("Cet objet ne peut être cloné!");
}
}
}
Page 58 sur 92
Initiation à la Programmation Java (4GI)
class MonObjet2 implements Cloneable{
int no;
MonString2 nom;
MonObjet2 (int no, String nom) {
this.no = no;
this.nom = new MonString2(nom);
}
public Object clone() throws CloneNotSupportedException {
MonObjet2 copie = (MonObjet2) super.clone();
copie.nom = (MonString2) nom.clone();
return copie;
}
void dump() {
System.out.println(""+this);
System.out.println(" no : "+no);
System.out.println(" nom: "+nom);
System.out.println();
}
}
class MonString2
String nom;
implements Cloneable{
MonString2 (String nom) {
this.nom = nom;
}
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
$ java TestClone2
MonObjet2@3179c3
no : 1
nom: MonString2@310d42
MonObjet2@5d87b2
no : 1
nom: MonString2@77d134
$
3.5.2 Libération
• récupération ds objets non référencés (ramasse-miettes)
• faciliter la diminution des références par l'affectation des variables de type référence avec la valeur
null.
• lancement implicite du récupérateur par l'appel de la méthode java.lang.System.gc().
• finalisation des objets : exécution de la méthode finalize() de l'objet
Page 59 sur 92
Initiation à la Programmation Java (4GI)
• pour forcer la finalisation des objets avant de quitter la machine virtuelle Java, utiliser la méthode
addShutdownHook(thread) de la classe java.lang.Runtime.
public class TestFinalize {
public static void main(String[] args) {
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
System.out.println("Thread d'arrêt finaliseur!");
}
});
Runtime.getRuntime().gc();
System.out.println("Mémoire libre......................:
Runtime.getRuntime().freeMemory());
Runtime.getRuntime().gc();
System.out.println("Mémoire libre......................:
Runtime.getRuntime().freeMemory());
Runtime.getRuntime().gc();
System.out.println("Mémoire libre......................:
Runtime.getRuntime().freeMemory());
MonObjet x = new MonObjet();
MonObjet y = new MonObjet();
// on réalise un cycle dans les références.
x.variable = y;
y.variable = x;
Runtime.getRuntime().gc();
System.out.println("Mémoire libre après allocation.....:
Runtime.getRuntime().freeMemory());
Runtime.getRuntime().gc();
System.out.println("Mémoire libre après allocation.....:
Runtime.getRuntime().freeMemory());
Runtime.getRuntime().gc();
System.out.println("Mémoire libre après allocation.....:
Runtime.getRuntime().freeMemory());
// on déréference les variables x et y
x = null;
y = null;
Runtime.getRuntime().gc();
System.out.println("Mémoire libre après récupération...:
Runtime.getRuntime().freeMemory());
Runtime.getRuntime().gc();
System.out.println("Mémoire libre après récupération...:
Runtime.getRuntime().freeMemory());
Runtime.getRuntime().gc();
System.out.println("Mémoire libre après récupération...:
Runtime.getRuntime().freeMemory());
}
static class MonObjet {
MonObjet variable;
protected void finalize() throws Throwable
{
System.out.println("Finalisation de "+this);
}
}
Page 60 sur 92
"+
"+
"+
"+
"+
"+
"+
"+
"+
Initiation à la Programmation Java (4GI)
}
$ java TestFinalize
Mémoire libre......................: 1931776
Mémoire libre......................: 1932384
Mémoire libre......................: 1932384
Mémoire libre après allocation.....: 1932416
Mémoire libre après allocation.....: 1932416
Mémoire libre après allocation.....: 1932416
Finalisation de TestFinalize$MonObjet@6ec612
Finalisation de TestFinalize$MonObjet@dd1f7
Mémoire libre après récupération...: 1931344
Mémoire libre après récupération...: 1932576
Mémoire libre après récupération...: 1932576
Thread d'arrêt finaliseur!
$
3.5.3 Options (non standards) de la commande java
-Xmsn
Pour spécifier la taille initiale de l'allocation mémoire, exprimée en octets (ou
kilo-octets ou méga-octets). Taille multiple de 1024, supérieure à 1MB (par
défaut : 2Mb).
Par exemple, une mémoire initiale de 6 méga-octets s'obtient avec les options suivantes : -Xms6291456 ou -Xms6144k ou -Xms6144K ou -Xms6m ou -Xms6M
-Xmxn
Pour spécifier la taille maximale de l'allocation mémoire, exprimée en octets
(ou kilo-octets ou méga-octets). Taille multiple de 1024, supérieure à 2MB (par
défaut : 64Mb).
Par exemple, une mémoire maximale de 6 méga-octets s'obtient avec les options
suivantes : -Xms6291456 ou -Xms6144k ou -Xms6144K ou -Xms6m ou Xms6M
Page 61 sur 92
Initiation à la Programmation Java (4GI)
Chapitre 4
API standard
Le système de développement Java JDK comprend des outils (compilateur, machine virtuelle, ...), un
langage de programmation (sémantique, syntaxe), ainsi qu'une API (Application Programming Interface), c'est à dire une bibliothèque standard comprenant des classes, méthodes et interfaces permettant
une programmation indépendante de la plateforme utilisée. L'API définit en quelque sorte la machine
virtuelle pour le programmeur. Celui-ci trouvera la ou les classes nécessaires à la programmation de
son application. C'est la boîte à outils du programmeur.
L'API standard entière est regroupée dans le paquetage java. Suivant la version du système de développement, nous y retrouverons plus ou moins d'éléments. L'API 1.0.* est découpée en 8 paquetages;
l'API 1.1.* est découpée en 22 paquetages. Nous ne décrirons pas ici chaque classe de l'API; nous
montrerons plutôt comment l'API s'organise, de manière à pouvoir l'utiliser facilement.
Chaque paquetage d'importance fera l'objet d'un chapitre particulier. Nous ne présenterons, ici, que
les paquetages java.lang et java.util ainsi que certains points d'utilisation liés au paquetage java.io (par
exemple comment lire ou écrire dans un fichier).
4.1 Utilisation de l'API
En général, le système de développement JDK contient la documentation relative à l'API qu'il supporte. Elle se trouve alors dans le répertoire docs qui lui-même se trouve dans le répertoire de chargement du JDK. Une documentation est également accessible aux adresses suivantes:
API 1.0: http://java.sun.com/products/jdk/1.0.2/api/
API 1.1: http://java.sun.com/products/jdk/1.1/docs/api/packages.html
La version API 1.0 contient les éléments suivants:
L'essentiel des classes et interfaces de base de Java (numériques, chaînes de
java.lang
caractères, objet, compilateur, runtime, sécurité, thread ...). Il est automatiquement importé par tout programme Java.
java.util
Des utilitaires pratiques (Date, Hashtable, Vector, Properties).
java.io
Classes et interfaces pour la gestion de entrés/sorties et des fichiers.
java.net
Classes et interfaces pour les opérations sur réseau (Socket, URL, ...).
java.awt
La boîte à outils pour le fenêtrage (Fenêtre, Menus, Boutons, Dessin, ...).
java.awt.image
Sous-paquetage de awt, spécialisé dans le traitement d'image Bitmap.
Page 62 sur 92
Initiation à la Programmation Java (4GI)
java.awt.peer
Sous-paquetage de awt servant d'interface entre l'AWT et les composants AET
des plates-formes particulières (Motif, Macintosh, Window95). Le programmeur
Java n'utilise ces classes qu'indirectement.
java.applet
Pour la création d'applications s'exécutant dans l'environnement d'un navigateur.
La version API 1.1 contient, outre les éléments de la version 1.0 (complétés par de nouvelles classes),
les éléments suivants:
Classes permettant la définition de composants logiciels réutilisables.
java.beans
java.math
Ce paquetage permet d'effectuer des opérations sur des nombres (entiers ou réels)
avec une précision arbitraire.
java.rmi
Classes permettant une programmation répartie sur un ensemble de machines
connectées à travers un réseau.
java.security
L'ensemble des classes utilisées pour assurer une certaine sécurité au niveau de
l'utilisation des programmes (en particulier les appliquettes).
java.sql
Classes de base pour l'accès aux bases de données, à travers un programme Java.
java.text
Classes permettant la manipulation de texte de manière variable, en fonction de
la localité de l'utilisateur (pays, langue).
La documentation en ligne (format html) fournie l'ensemble des paquetages, classes, interfaces,
méthodes, constructeurs, exceptions et variables, publiques. Le navigateur (HotJava, Netscape, Internet Explorer, ...) est un outil quasi-indispensable pour le programmeur Java. C'est lui qui lui permettra
de surfer dans la documentation, pour rechercher un élément particulier de la bibliothèque standard.
La documentation API a été générée par l'outil javadoc.
4.2 java.lang.*
Le paquetage java.lang contient principalement des classes liées au langage Java, et des classes liées
à la machine virtuelle Java.
4.2.1 Classes liées au langage
La plupart des classes liées au langage sont essentiellement des wrappers des types de base, c'est à dire
des classes fournissant les méthodes utiles pour la manipulations de ces types. Par exemple, la classe
java.lang.Integer contient des méthodes pour transformer une chaîne de caractères représentant une
valeur entière en un objet Integer. A partir de cet objet, nous pourrons ensuite obtenir la valeur de
type primitif int correspondant. Le programme suivant calcule la valeur entière d'un nombre passé
en argument (sous la forme d'une chaîne de caractères) à un programme, et l'imprime en binaire et
en hexadécimal:
public class TestInteger {
public static void main(String[] args) {
Integer i = new Integer(args[0]);
int v
= i.intValue();
System.out.println("La chaîne "+args[0]+" donne la valeur entière "+v
Page 63 sur 92
Initiation à la Programmation Java (4GI)
+"\n binaire
: "+ Integer.toBinaryString(v)
+"\n hexadécimal : " + Integer.toHexString(v) );
System.out.println("parseInt(\"-240\") donne : "
+Integer.parseInt("-240"));
System.out.println("parseInt(\"a0\", 16) donne : "
+Integer.parseInt("a0", 16));
System.out.println("decode(\"0xa0\") donne : "
+Integer.decode("0xa0").intValue());
System.out.println("parseInt(\"a0\") donne : "+Integer.parseInt("a0"));
}
}
L'exécution de ce programme donne:
$ java ch4.TestInteger 17
La chaîne 17 donne la valeur entière 17
binaire
: 10001
hexadécimal : 11
$ java ch4.TestInteger -17
La chaîne -17 donne la valeur entière -17
binaire
: 11111111111111111111111111101111
hexadécimal : ffffffef
$
Une autre classe très utile est la classe java.lang.string. Elle contient beaucoup de méthodes pour faire
des manipulations de chaînes de caractères (extraction de sous-chaînes, comparaison de chaînes, ...).
Java est multithread, c'est à dire qu'un programme peut être découpé en plusieurs processus légers
(thread) pouvant s'exécuter de manière quasi-parallèles. La classe java.lang.Thread offre toutes les
méthodes liées au contrôle d'un thread.
Un objet de type Thread peut être créé soit en étendant la classe java.lang.Thread, soit en implémentant l'interface java.lang.Runnable, qui ne demande que la création d'une méthode run. Le lancement d'un thread se fait alors par l'exécution de la méthode start(). Le programme suivant montre
l'utilisation de plusieurs threads dans un programme: deux threads sont créés, le premier imprime la
lettre 'a' puis attend pendant une durée aléatoire, le deuxième imprime la lettre 'b' puis attend également pendant une durée aléatoire.
package ch4;
class TestThread extends Thread{
char c;
TestThread(char c) {
this.c = c;
}
public void run() {
for (int i = 0; i < 20; i++) {
System.out.print(""+c);
try {
sleep((int)(Math.random() * 100));
} catch (InterruptedException e) {}
}
}
public static void main (String[] args) {
TestThread threadA = new TestThread('a');
Page 64 sur 92
Initiation à la Programmation Java (4GI)
TestThread threadB = new TestThread('b');
System.out.print("==> ");
threadA.start();
threadB.start();
try {threadA.join();} catch (InterruptedException e) {};
try {threadB.join();} catch (InterruptedException e) {};
System.out.println();
}
}
On constate alors que plusieurs exécution de ce programme fourniront des résultats différents:
$ java ch4.TestThread
==> abbaaabbaaabbaababababbababababaabaabbbb
$ java ch4.TestThread
==> abababababbabbaabbbabaaabbabbbbaaabbaaaa
$ java ch4.TestThread
==> ababbabababbbaabbaabbaababababaababaabab
$ java ch4.TestThread
==> abbaababaaabaabbaaabbababababbababbababb
$
4.2.2 Classes liées à la machine virtuelle
Parmi les classes liées à la machine virtuelle, nous trouvons les outils permettant de gérer le chargement des classes, compiler les classes, assurer la sécurité de l'exécution, ... L'une de ces classes souvent
utilisée est la classe java.lang.System. Elle fournie, entre autre, l'entrée et la sortie standard. Grâce
à sa méthode getProperties(), elle permet de transmettre au programme Java, certaines propriétés
de la plate-forme utilisée. Le programme ci-dessous permet de visualiser l'ensemble des propriétés
disponibles:
package ch4;
import java.util.Properties;
class TestProperties {
public static void main(String[] args) {
Properties prop = System.getProperties();
prop.list(System.out);
}
}
Parmi ces propriétés, Java impose de trouver au moins les propriétés suivantes:
Clé
Description
java.version
Numéro de version de la machine virtuelle Java
java.vendor
Nom du fournisseur de la machine virtuelle Java
java.vendor.url URL du fournisseur de la machine virtuelle Java
java.home
Répertoire d'installation de Java
java.class.versionVersion du format des classes
java.class.path Valeur du CLASSPATH
os.name
Nom du système d'exploitation
os.arch
Nom de l'architecture utilisée
Page 65 sur 92
Initiation à la Programmation Java (4GI)
os.version
file.separator
path.separator
line.separator
user.name
user.home
user.dir
Version du système d'exploitation
Caractère séparateur de fichiers ("/" pour UNIX)
Caractère séparateur de chemin (":" pour UNIX)
Caractère séparateur ligne ("\n" pour UNIX)
Nom de login de l'utilisateur
Répertoire de login de l'utilisateur
Répertoire courant
D'autres propriétés peuvent exister, soit par défaut, soit en utilisant l'option -D de la ligne de commande de lancement du programme java:
java -Dclé1=valeur1 -Dclé2=valeur2 monProgramme
Attention, Il ne faut pas confondre les propriétés et les variables d'environnement.
4.3 java.util.*
Le paquetage java.util contient beaucoup de classes diverses et variées. Nous avons déjà utilisé la
classe java.util.Date permettant de manipuler des dates (année, mois, jour, heure, minute, seconde),
les comparer entre-elles, ...
Parmi toutes ces classes, nous allons voir brièvement, à travers quelques exemples d'utilisation, les
classes StringTokenizer, Vector, Hashtable ainsi que l'interface Enumeration. Nous terminerons
ce tour d'horizon par l'interface Observer et la classe Observable qui vont de pair.
4.3.1 java.util.StringTokenizer
La classe StringTokenizer permet de découper une chaîne de caractères en une suite de syntagmes
(unités lexicales). Une unité lexicale est délimitée par un caractère délimiteur. Par défaut, les caractères délimiteurs sont " \t\n\r", c'est-à-dire le caractère espace, le caractère de tabulation, le caractère
retour-chariot et le caractère de fin de ligne. Il est possible de définir ses propres caractères délimiteurs, soit lors de la création d'une instance de la classe, soit lors de la recherche d'une unité lexicale.
Les principales méthodes sont :
donne le nombre d'unités syntaxiques restantes.
countTokens()
hasMoreTokens()
indique s'il y a encore des unités syntaxiques à extraire.
nextToken()
retourne l'unité syntaxique suivante.
Le programme ci-dessous donne le nombre de mots contenus dans une phrase passée en paramètre,
puis liste ces mots, un par un (le programme considère que les caractères " ,." sont des séparateurs
de mots).
package ch4;
import java.util.StringTokenizer;
class TestStringTokenizer {
public static void main (String[] args) {
Page 66 sur 92
Initiation à la Programmation Java (4GI)
StringTokenizer lstMots = new StringTokenizer(args[0], " ,.");
int nombreDeMots = lstMots.countTokens();
System.out.println("==> Dans la phrase \""+args[0]+
"\", il y a "+nombreDeMots+" mots qui sont:");
int i = 0;
while (i++ < nombreDeMots) {
System.out.println("==>
"+i+" -> "+lstMots.nextToken());
}
}
}
L'exécution de ce programme pourra donner:
$ java ch4.TestStringTokenizer "Voila ma phrase, un peu courte."
==> Dans la phrase "Voila ma phrase, un peu courte.", il y a 6 mots qui sont:
==>
1 -> Voila
==>
2 -> ma
==>
3 -> phrase
==>
4 -> un
==>
5 -> peu
==>
6 -> courte
$
4.3.2 java.util.Vector
Un objet de type tableau d'objets (java.lang.Object[ ]) permet de mémoriser un certain nombre d'objets. Cependant, la taille d'un tel tableau est fixe, définie au moment de sa création. De plus, la seule
information que l'on peut obtenir, est la capacité totale du tableau.
La classe Vector correspond à un tableau dont la taille varie en fonction des éléments qu'il contient.
Les principales méthodes de cette classe sont:
permet de rajouter un objet à la fin du vecteur.
addElement(Object obj)
indexOf(Object obj)
renvoie l'indice de la première occurrence d'un objet.
elementAt(int indice)
renvoie l'objet se trouvant à l'index indiqué.
removeElement(Object obj)
retire un objet du vecteur.
size()
renvoie le nombre d'éléments contenu dans le vecteur.
isEmpty()
indique si le vecteur contient ou non des éléments.
Le programme ci-dessous donne la liste de mots différents contenus dans une phrase passée en paramètre, puis liste ces mots, un par un (le programme considère que les caractères " ,." sont des séparateurs de mots).
package ch4;
import java.util.StringTokenizer;
import java.util.Vector;
class TestVector {
public static void main (String[] args) {
StringTokenizer lstMots = new StringTokenizer(args[0], " ,.");
Page 67 sur 92
Initiation à la Programmation Java (4GI)
Vector vecteur = new Vector();
while (lstMots.hasMoreTokens()) {
String mot = lstMots.nextToken();
boolean déjàVu = false;
for (int i=0; i<vecteur.size(); i++) {
String motMémorisé = (String) vecteur.elementAt(i);
déjàVu = motMémorisé.equals(mot);
if (déjàVu) break;
}
if (!déjàVu) vecteur.addElement(mot);
}
System.out.println("==> Dans la phrase \""+args[0]+
"\",\n
il y a "+vecteur.size()+" mots différents qui sont:");
for (int i=0; i<vecteur.size(); i++) {
System.out.println("==>
"+i+" -> "+vecteur.elementAt(i));
}
}
}
L'exécution de ce programme pourra donner:
$ java ch4.TestVector "Voila ma phrase, un peu courte, tres courte cette phrase."
==> Dans la phrase "Voila ma phrase, un peu courte, très courte cette phrase.",
il y a 8 mots différents qui sont:
==>
0 -> Voila
==>
1 -> ma
==>
2 -> phrase
==>
3 -> un
==>
4 -> peu
==>
5 -> courte
==>
6 -> très
==>
7 -> cette
$
4.3.3 java.util.Enumeration
java.util.Enumeration est une interface. Tout objet implémentant cette interface doit implémenter
les deux méthodes suivantes:
qui permet de détecter si l'énumération contient encore des
hasMoreElements()
éléments,
nextElement()
qui renvoie l'élément suivant d'une énumération.
La classe java.util.StringTokenizer implémente cette interface. La classe java.util.Vector possède
une méthode elements() permettant d'obtenir l'énumération des objets contenus dans le vecteur. En
utilisant cette méthode, l'exemple précédent pourrait se programmer comme suit :
package ch4;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.Enumeration;
class TestVectorEnum {
Page 68 sur 92
Initiation à la Programmation Java (4GI)
public static void main (String[] args) {
StringTokenizer lstMots = new StringTokenizer(args[0], " ,.");
Vector vecteur = new Vector();
while (lstMots.hasMoreTokens()) {
String mot = lstMots.nextToken();
boolean déjàVu = false;
for (Enumeration e = vecteur.elements(); e.hasMoreElements(); ) {
String motMémorisé = (String) e.nextElement();
déjàVu = motMémorisé.equals(mot);
if (déjàVu) break;
}
if (!déjàVu) vecteur.addElement(mot);
}
System.out.println("==> Dans la phrase \""+args[0]+
"\",\n
il y a "+vecteur.size()+" mots différents qui sont:");
for (Enumeration e = vecteur.elements(); e.hasMoreElements(); ) {
System.out.println("==>
"+e.nextElement());
}
}
}
4.3.4 java.util.Hashtable
Dans l'exemple précédent, nous avons utilisé un vecteur pour mémoriser des objets. Une fois mémorisé, nous avons effectué une recherche de l'objet dans le vector. Ce genre de traitement correspond à
celui d'un dictionnaire (un dictionnaire étant un ensemble d'associations clé-valeur). L'API standard
contient une telle classe : java.util.Dictionary. C'est cependant une classe abstraite et donc non utilisable directement.
La classe java.util.Hashtable est une sous-classe de java.util.Dictionary utilisable directement. Elle
permet de mémoriser tout objet associé à une clé, et retrouver l'objet mémorisé, à partir de sa clé. Les
principales méthodes de cette classe sont les suivantes :
permet de mémoriser le couple clé-obj.
put(Object clé, Object obj)
get(Object clé)
permet de retouver l'objet associé à une clé.
containsKey(Object clé)
permet de déterminer si la clé est associée à un objet de la
table.
contains(Object obj)
permet de déterminer si l'objet est mémorisé dans la table.
elements()
renvoie l'énumération des objets contenus dans la table.
keys()
renvoie l'énumération des clés associées aux objets contenus
dans la table.
size()
renvoie le nombre de clés mémorisées dans la table.
remove(Object clé)
retire la clé et l'objet associé de la table.
Page 69 sur 92
Initiation à la Programmation Java (4GI)
Le programme suivant permet de déterminer le nombre d'occurences de chaque mot d'une phrase
passée en paramètre (le programme utilise la chaîne de caractères comme clé, et le nombre d'occurrences trouvées comme valeur) :
package ch4;
import java.util.StringTokenizer;
import java.util.Enumeration;
import java.util.Hashtable;
class TestHashtable{
public static void main (String[] args) {
StringTokenizer lstMots = new StringTokenizer(args[0], " ,.");
Hashtable table = new Hashtable();
while (lstMots.hasMoreTokens()) {
String mot = lstMots.nextToken();
if (!table.containsKey(mot))
table.put(mot, new Integer(1));
else {
Integer nbre = (Integer) table.get(mot);
table.put(mot, new Integer(1+nbre.intValue()));
}
}
System.out.println("==> Dans la phrase \""+args[0]+
"\",\n
il y a "+table.size()+" mots différents qui sont:");
for (Enumeration e = table.keys(); e.hasMoreElements(); ) {
String mot = (String) e.nextElement();
System.out.println("==>
"+mot+" ("+table.get(mot)+" fois)");
}
}
}
L'exécution de ce programme pourra donner:
$ java ch4.TestHashtable "Voila ma phrase, un peu courte, tres courte cette phrase."
==> Dans la phrase "Voila ma phrase, un peu courte, tres courte cette phrase.",
il y a 8 mots différents qui sont:
==>
un (1 fois)
==>
phrase (2 fois)
==>
ma (1 fois)
==>
courte (2 fois)
==>
cette (1 fois)
==>
peu (1 fois)
==>
Voila (1 fois)
==>
tres (1 fois)
$
4.3.5 java.util.Observer et java.util.Observable
java.util.Observer est une interface permettant de décrire un objet qui souhaite être informé du changement d'état d'objets issus de la classe java.util.Observable. L'interface java.util.Observer n'introduit qu'une seule méthode : public void update(Observable o, Object arg) qui est appelée lorsque
l'objet observé o change d'état.
Page 70 sur 92
Initiation à la Programmation Java (4GI)
Un objet observable, issu de la classe java.util.Observable, dispose, entre autre, des méthodes suivantes :
permet de rajouter un observateur.
addObserver(Observer o)
setChanged()
indique un changement d'état de l'objet.
notifyObservers(Object arg)
si l'état de l'objet a été changé, l'appel à cette méthode permet de notifier les observateurs potentiels de ce changement, par l'appel de la méthode update avec pour arguments
l'objet observé et l'argument arg.
L'exemple suivant montre comment utiliser le modèle Observateur/Observé en Java dans le programme précédent: chaque fois qu'un nouveau mot est introduit dans la table, un observateur en est
informé et imprime alors le nouveau mot.
package ch4;
import
import
import
import
import
java.util.StringTokenizer;
java.util.Enumeration;
java.util.Hashtable;
java.util.Observable;
java.util.Observer;
class TableObservable extends Observable {
Hashtable table = new Hashtable();
public synchronized Object put(Object clé, Object valeur) {
setChanged();
notifyObservers(clé);
return table.put(clé, valeur);
}
public synchronized Object get(Object clé) {
return table.get(clé);
}
public synchronized boolean containsKey(Object clé) {
return table.containsKey(clé);
}
public synchronized Enumeration keys() {
return table.keys();
}
public int size() { return table.size(); }
}
class ObservateurDeTable implements Observer {
public ObservateurDeTable(Observable o) {
o.addObserver(this);
}
public void update(Observable o, Object arg) {
System.out.println("J'observe l'objet : "+o+"\nqui m'envoie : "+arg);
}
}
class TestObserver{
public static void main (String[] args) {
Page 71 sur 92
Initiation à la Programmation Java (4GI)
StringTokenizer lstMots = new StringTokenizer(args[0], " ,.");
TableObservable table = new TableObservable();
ObservateurDeTable observateur = new ObservateurDeTable(table);
while (lstMots.hasMoreTokens()) {
String mot = lstMots.nextToken();
if (!table.containsKey(mot))
table.put(mot, new Integer(1));
else {
Integer nbre = (Integer) table.get(mot);
table.put(mot, new Integer(1+nbre.intValue()));
}
}
System.out.println("==> Dans la phrase \""+args[0]+
"\",\n
il y a "+table.size()+" mots différents qui sont:");
for (Enumeration e = table.keys(); e.hasMoreElements(); ) {
String mot = (String) e.nextElement();
System.out.println("==>
"+mot+" ("+table.get(mot)+" fois)");
}
}
}
L'exécution de ce programme pourra donner:
$ java ch4.TestObserver "Voila ma phrase, ma courte phrase."
J'observe l'objet : ch4.TableObservable@80ca7b7
qui m'envoie : Voila
J'observe l'objet : ch4.TableObservable@80ca7b7
qui m'envoie : ma
J'observe l'objet : ch4.TableObservable@80ca7b7
qui m'envoie : phrase
J'observe l'objet : ch4.TableObservable@80ca7b7
qui m'envoie : ma
J'observe l'objet : ch4.TableObservable@80ca7b7
qui m'envoie : courte
J'observe l'objet : ch4.TableObservable@80ca7b7
qui m'envoie : phrase
==> Dans la phrase "Voila ma phrase, ma courte phrase.",
il y a 4 mots différents qui sont:
==>
phrase (2 fois)
==>
ma (2 fois)
==>
courte (1 fois)
==>
Voila (1 fois)
$
4.4 java.io.*
Le paquetage java.io contient beaucoup de classes liées à la gestion des fichiers ou des pipelines (pour
la communication entre processus). Nous n'examinerons, ici, que les entrées-sorties standards et les
entrées-sorties sur fichiers.
4.4.1 Entrées-sorties standard
Pour les entrées-sorties standard on utilise les objets System.in (instance de la classe InputStream) et
System.out (instance de la classe PrintStream). Le problème de la classe InputStream vient du fait
Page 72 sur 92
Initiation à la Programmation Java (4GI)
qu'une lecture ne peut remplir que des tableaux d'octets. Pour obtenir des chaînes de caractères, lors
d'une lecture, il convient d'utiliser la classe DataInputStream (pour l'API 1.0) ou mieux, la classe
BufferedReader (pour l'API 1.1).
Le programme suivant montre comment lire des lignes de caractères sur l'entrée standard:
package ch4;
import java.io.*;
class TestStdIn {
public static void main(String[] args) {
DataInputStream d = new DataInputStream(System.in);
System.out.println("==> Taper deux lignes");
try {
System.out.print("?");
String ligne1 = d.readLine();
System.out.print("?");
String ligne2 = d.readLine();
System.out.println("==> Les deux lignes lues sont:\n==>
} catch (java.io.IOException e) {
System.out.println("Il y a une erreur de lecture");
}
}
}
"+ligne1+"\n==>
L'exécution de ce programme pourra donner:
$ java ch4.TestStdIn
==> Taper deux lignes
?Ma premiere ligne
?et ma seconde
==> Les deux lignes lues sont:
==>
Ma premiere ligne
==>
et ma seconde
$ java ch4.TestStdIn
==> Taper deux lignes
?une seule ligne suivie de ctrD
?==> Les deux lignes lues sont:
==>
une seule ligne suivie de ctrD
==>
null
$
On peut remarquer, dans la deuxième exécution du programme, que si on termine la première ligne
par ctrl-D (équivalent de fin de fichier pour l'entrée standard), la deuxième lecture de ligne retourne
la valeur null.
4.4.2 Entrées-sorties sur fichiers
L'utilisation de fichiers ne diffère guère des entrées-sorties standards puisqu'on utilise, en fait, la classe
DataInputStream qui nous sert d'interface avec le système de gestion des fichiers. Pour un programme Java, un fichier peut être référencé et manipulé à travers la classe java.io.File. A partir de
cette classe, on peut créer une instance de la classe java.io.FileInputStream, laquelle sera utilisée
pour obtenir une instance de la classe DataInputStream. A partir de là, l'utilisation en entrée se fait
comme pour l'entrée standard. L'exemple suivant montre comment lire les lignes de caractères contenues dans un fichier de nom "essai" :
Page 73 sur 92
"+ligne2
Initiation à la Programmation Java (4GI)
package ch4;
import java.io.*;
class TestFichIn {
public static void main(String[] args) {
File fichier;
FileInputStream streamFich=null;
DataInputStream d;
try {
fichier = new File("essai");
streamFich = new FileInputStream(fichier);
d = new DataInputStream(streamFich);
String ligne = "";
while ((ligne = d.readLine()) != null) {
System.out.println("==> "+ligne);
}
} catch (FileNotFoundException e) {
System.out.println("Fichier essai inexistant");
} catch (java.io.IOException e) {
System.out.println("Il y a une erreur de lecture");
} finally {
if (streamFich!=null)
try {streamFich.close();} catch (IOException e) {}
}
}
}
L'exécution de ce programme pourra donner:
$ java ch4.TestFichOut
==> Taper des lignes terminées par ctrl-D
?ligne 1
?ligne 2
?ligne 3
?
$ cat essaiout
ligne 1
ligne 2
ligne 3
$
L'utilisation d'un fichier en sortie n'est guère plus compliquée. Le programme suivant permet de transférer les lignes introduites sur l'entrée standard vers le fichier de nom "essaiout".
package ch4;
import java.io.*;
class TestFichOut {
public static void main(String[] args) {
File fichier;
FileOutputStream streamFich=null;
DataOutputStream d;
PrintStream out=null;
DataInputStream stdIn = new DataInputStream(System.in);
try {
fichier = new File("essaiout");
streamFich = new FileOutputStream(fichier);
d = new DataOutputStream(streamFich);
Page 74 sur 92
Initiation à la Programmation Java (4GI)
out = new PrintStream(d);
String ligne = "";
System.out.println("==> Taper des lignes terminées par ctrl-D");
System.out.print("?");
while ((ligne = stdIn.readLine()) != null) {
out.println(""+ligne);
System.out.print("?");
}
System.out.println();
} catch (java.io.IOException e) {
System.out.println("Il y a une erreur de lecture ou écriture");
} finally {
if (streamFich!=null)
{try {streamFich.close();} catch (IOException e) {}}
if (out != null) out.close();
}
}
}
L'exécution de ce programme pourra donner:
$ java ch4.TestFichOut
==> Taper des lignes terminées par ctrl-D
?ligne 1
?ligne 2
?ligne 3
?
$ cat essaiout
ligne 1
ligne 2
ligne 3
$
Page 75 sur 92
Initiation à la Programmation Java (4GI)
Chapitre 5
Réflexion
5.1 java.lang.Class
Classe représentant les classes ou les interfaces
Plusieurs manières d'obtenir une instance de java.lang.class:
• méthode getClass() héritée de java.lang.object.
• méthode de classe java.lang.Class.forName("nom de la classe")
• variable de classe TYPE des classes associées aux types primitifs (INTEGER.TYPE,
BOOLEAN.TYPE, ...)
• litéral de classe: nom-de-classe.class
class TstReflect {
public static void main(String[] args)
throws java.lang.ClassNotFoundException
{
TstReflect o = new TstReflect();
Class c1 = o.getClass();
try {
Class c2 = Class.forName("TstReflect");
} catch (java.lang.ClassNotFoundException e) {
System.out.println("Classe non trouvée");
}
Class c3 = TstReflect.class;
// un exemple de tableau
Class c4 = TstReflect[].class;
}
}
méthodes de java.lang.Class (tests)
•
•
•
•
•
boolean isArray()
boolean isInterface()
boolean isPrimitive()
boolean isInstance(Object obj)
Class getComponentType()
package pk1.pk2;
Page 76 sur 92
Initiation à la Programmation Java (4GI)
public class TestReflect1 {
public static void main(String[] args)
{
Object o = new TestReflect1[3];
Class c1 = o.getClass();
if (c1.isArray()) {
System.out.println("c'est un tableau de "+
((Object[])o).length+" éléments de type "+
c1.getComponentType().getName());
}
System.out.println("Paquetage de définition : "
+c1.getComponentType().getPackage());
}
}
$ java pk1.pk2.TestReflect1
c'est un tableau de 3 éléments de type pk1.pk2.TestReflect1
Paquetage de définition : package pk1.pk2
$
méthodes de java.lang.Class (éléments)
•
•
•
•
•
•
•
•
•
•
•
•
•
String getName()
int getModifiers()
Class getSuperclass()
Package getPackage()
Classes[] getClasses()
Classes[] getInterfaces()
Classes[] getDeclaredClasses()
java.lang.reflect.Constructor[] getConstructors()
java.lang.reflect.Constructor[] getDeclaredConstructors()
java.lang.reflect.Method[] getMethods()
java.lang.reflect.Method[] getDeclaredMethods()
java.lang.reflect.Field[] getFields()
java.lang.reflect.Field[] getDeclaredFields()
méthodes de java.lang.reflect.Modifiers
•
•
•
•
•
•
•
•
•
•
•
•
static
static
static
static
static
static
static
static
static
static
static
static
boolean
boolean
boolean
boolean
boolean
boolean
boolean
boolean
boolean
boolean
boolean
boolean
isAbstract(int mod)
isFinal(int mod)
isInterface(int mod)
isNative(int mod)
isPrivate(int mod)
isProtected(int mod)
isPublic(int mod)
isStatic(int mod)
isStrict(int mod)
isSynchronized(int mod)
isTransient(int mod)
isVolatile(int mod)
Page 77 sur 92
Initiation à la Programmation Java (4GI)
import java.lang.reflect.Modifier;
public class TstReflect {
public static void main(String[] args)
throws java.lang.ClassNotFoundException
{
TstReflect o = new TstReflect();
Class c1 = o.getClass();
int m = c1.getModifiers();
if (Modifier.isPublic(c1.getModifiers()))
System.out.println("classe publique");
if (Modifier.isPrivate(c1.getModifiers()))
System.out.println("classe privée");
}
}
5.2 Création d'instance
Constructeur par défaut: méthode newInstance() d'une instance de java.lang.Class:
try {
Class c = Class.forName("TstReflect");
TstReflect obj = (TstReflect) c.newInstance();
System.out.println("nouvelle instance : "+obj);
} catch (java.lang.InstantiationException e) {
System.out.println("Erreur : "+e);
} catch (java.lang.IllegalAccessException e) {
System.out.println("Erreur : "+e);
} catch (java.lang.ClassNotFoundException e) {
System.out.println("Erreur : "+e);
} catch (java.lang.InstantiationException e) {
System.out.println("Erreur : "+e);
} catch (java.lang.IllegalAccessException e) {
System.out.println("Erreur : "+e);
}
Constructeur
quelconque:
méthode
java.lang.reflect.Constructor:
newInstance()
d'une
public class TstReflect {
String nom;
int valeur;
public TstReflect(String str, Integer val) {
nom=str;
valeur=val.intValue();
System.out.println("Constructeur avec "+str+" - "+valeur);
}
public static void main(String[] args)
throws java.lang.ClassNotFoundException
{
Page 78 sur 92
instance
de
Initiation à la Programmation Java (4GI)
try {
Class[] paramFormels = {String.class, Integer.class};
java.lang.reflect.Constructor construc = Class.forName("TstReflect").getConstr
Object[] paramEffectifs = {"la chaîne", new Integer(17)};
TstReflect obj = (TstReflect) construc.newInstance(paramEffectifs);
System.out.println("nouvelle instance : "+obj);
} catch (java.lang.NoSuchMethodException e) {
System.out.println("Erreur : "+e);
} catch (java.lang.InstantiationException e) {
System.out.println("Erreur : "+e);
} catch (java.lang.IllegalAccessException e) {
System.out.println("Erreur : "+e);
} catch (java.lang.reflect.InvocationTargetException e) {
System.out.println("Erreur : "+e);
}
}
}
5.3 Appel de méthode
méthode
Object
invoke(Object
java.lang.reflect.Method
obj,
Object[]
args)
de
...
public String ajouterEtTransformer(Integer val) {
return nom+"-"+(valeur+val.intValue());
}
...
try {
TstReflect tstObj = new TstReflect("encore", new Integer(12));
Class[] paramFormels = {Integer.class};
java.lang.reflect.Method méthode = Class.forName("TstReflect").getMethod("ajou
Object[] paramEffectifs = {new Integer(20)};
System.out.println("appel méthode : "+méthode.invoke(tstObj,
paramEffectifs));
} catch (java.lang.NoSuchMethodException e) {
System.out.println("Erreur : "+e);
} catch (java.lang.IllegalAccessException e) {
System.out.println("Erreur : "+e);
} catch (java.lang.reflect.InvocationTargetException e) {
System.out.println("Erreur : "+e);
}
}
}
Méthode de classe : premier argument d'invoke ignoré (peut être null)
try {
TstReflect tstObj = new TstReflect("encore", new Integer(12));
Class[] paramFormels = {Integer.class};
java.lang.reflect.Method méthode = Class.forName("TstReflect").getMethod("méth
Object[] paramEffectifs = {new Integer(20)};
System.out.println("appel méthode : "+méthode.invoke(null,
Page 79 sur 92
Initiation à la Programmation Java (4GI)
paramEffectifs));
} catch ...
Paramètres de type primitif
une convertion ClasseWrapper <-> valeur primitive est effectuée
public static String méthodeInt(int val) {
return "avec int : "+val*val;
}
...
try {
TstReflect tstObj = new TstReflect("encore", new Integer(12));
Class[] paramFormels = {Integer.class};
java.lang.reflect.Method méthode = Class.forName("TstReflect").getMethod("méth
Object[] paramEffectifs = {new Integer(20)};
System.out.println("appel méthode : "+méthode.invoke(null,
paramEffectifs));
} catch ...
5.4 Traitement des variables
méthode
Object
invoke(Object
java.lang.reflect.Method
obj,
Object[]
args)
...
try {
TstReflect tstObj = new TstReflect("encore", new Integer(12));
Field var = Class.forName("TstReflect").getField("nom");
var.set(tstObj, var.get(tstObj) + " et encore");
System.out.println(" *** "+tstObj.nom);
} catch (java.lang.NoSuchFieldException e) {
System.out.println("Erreur : "+e);
} catch (java.lang.IllegalAccessException e) {
System.out.println("Erreur : "+e);
}
...
Page 80 sur 92
de
Initiation à la Programmation Java (4GI)
Chapitre 6
Les Threads
Jusqu'à présent, les programmes que nous avons écrits n'utilisent, du moins en apparence, qu'un seul
Thread. Le thread, appelé aussi processus léger, correspond à l'exécution d'un flot séquentiel d'instructions dans un espace adressable. Le processus classique unix (processus lourd) correspond à la
même chose. La différence n'apparait que lorsqu'on examine plusieurs objets: dans un environnement
multiprocessus, chaque processus dispose de son propre espace adressable; dans un environnement
multithread, tous les threads partagent le même espace adressable. Un changement de thread est donc
beaucoup plus efficace qu'un changement de processus (pour passer d'un thread à un autre, il suffit
de changer le compteur ordinal et le pointeur sur la pile d'évaluation).
Le thread est une première approche du parallélisme. Il permet la programmation parallèle au sein
d'une machine mono-processeur.
A quoi peut bien servir ce parallélisme virtuel? Prenons l'exemple d'une application permettant à une
image (représentant par exemple un fantôme) de se déplacer, avec une vitesse aléatoire, d'un côté de
la fenêtre à l'autre. En utilisant une bibliothèque adéquate, le programme pourrait être le suivant:
package fantome;
public class Prog1V1 {
public static void main(String arg[]) {
FrameFantomes fenêtre = new FrameFantomes("fantome.Prog1V1");
Fantome f1 = fenêtre.créerFantôme();
f1.go();
}
}
Modifions ce programme de manière à obtenir 3 fantômes faisant la course. La première idée venant
à l'esprit serait de définir le programme suivant:
package ch7;
public class Prog1V2 {
public static void main(String arg[]) {
FrameFantomes fenêtre = new FrameFantomes("ch7.Prog1V1");
Fantome f1 = fenêtre.créerFantôme();
Fantome f2 = fenêtre.créerFantôme();
Fantome f3 = fenêtre.créerFantôme();
f1.go();
f2.go();
f3.go();
}
}
Page 81 sur 92
Initiation à la Programmation Java (4GI)
Ce nouveau programme permet bien d'obtenir nos 3 fantômes. Le problème est qu'ils ne font pas
la course; ils effectuent le trajet, les uns après les autres. Pour obtenir une course effective, il faut
que les trois exécutions f i.go(); se fassent en parallèle, ce qui est possible grâce au fonctionnement
multithread de la machine Java.
6.1 Utilisation des Threads
Il y a deux manières de créer une Thead : soit en créant une instance de la classe java.lang.Thread,
soit en implémentant l'interface java.lang.Runnable. Cette dernière ne demande que la création d'une
méthode de nom run().
6.1.1 Classe java.lang.Thread
Le programme ci-dessous permet de créer un thread par fantôme grâce à la classe ThreadFantome
(interne à Prog1V3). L'exécution du thread est lancée par l'appel de la méthode start() héritée de la
super-classe java.lang.Thread. Cette méthode appelera ensuite la méthode run.
package ch7;
public class Prog1V3 {
static class ThreadFantome extends Thread {
Fantome fantome;
public ThreadFantome(Fantome fantome) {
this.fantome = fantome;
}
public void run() {
fantome.go();
}
}
public static void main(String arg[]) {
FrameFantomes fenêtre = new FrameFantomes("ch7.Prog1");
Fantome f1 = fenêtre.créerFantôme();
Fantome f2 = fenêtre.créerFantôme();
Fantome f3 = fenêtre.créerFantôme();
ThreadFantome tf1 = new ThreadFantome(f1);
ThreadFantome tf2 = new ThreadFantome(f2);
ThreadFantome tf3 = new ThreadFantome(f3);
tf1.start();
tf2.start();
tf3.start();
}
}
6.1.2 Interface java.lang.Runnable
Page 82 sur 92
Initiation à la Programmation Java (4GI)
En implémentant l'interface java.lang.Runnable, une classe indique qu'elle possède la méthode run()
et pourra alors être passée en paramètre lors de la création d'un Thread qui utilisera alors cette méthode
pour s'exécuter. Le programme suivant motre comment utiliser cette interface.
package ch7;
public class Prog1V4 implements Runnable {
Fantome fantome;
public Prog1V4(Fantome fantome) {
this.fantome = fantome;
}
public void run() {
fantome.go();
}
public static void main(String arg[]) {
FrameFantomes fenêtre = new FrameFantomes("ch7.Prog1");
Fantome f1 = fenêtre.créerFantôme();
Fantome f2 = fenêtre.créerFantôme();
Fantome f3 = fenêtre.créerFantôme();
Prog1V4 tf1 = new Prog1V4(f1);
Prog1V4 tf2 = new Prog1V4(f2);
Prog1V4 tf3 = new Prog1V4(f3);
new Thread(tf1).start();
new Thread(tf2).start();
new Thread(tf3).start();
}
}
6.2 Cycle de vie des Threads
Un thread possède un cycle de vie illustré par le diagramme de la figure .
Page 83 sur 92
Initiation à la Programmation Java (4GI)
Fig 6.1 : Cycle de vie des Threads
La création d'une instance de la classe Thread correspond à la création des données associées au thread.
Pour qu'un thread exécute une tâche (décrite par la méthode run()), il faut exécuter la méthode start().
Alors, il passe à l'état Alive, et peut utiliser la ressource unité centrale pour exécuter sa méthode
run(). Il quittera cet état à la fin de l'exécution de run ou par l'exécution de la méthode stop(). Il est
possible de détecter cet état Alive grâce à la valeur booléenne renvoyée par la méthode isAlive().
Lorsque le thread est vivant (état Alive), il exécute sa méthode run(). On pourra alors distinguer
deux sous-états : suspendu ou non suspendu. Le passage de l'un à l'autre s'effectue par l'exécution des
méthodes resume() et suspend(). Dans l'état suspendu, le thread ne peut plus utiliser la ressource
unité centrale de la machine. Il n'y a pas de méthodes permettant de distinguer l'un ou l'autre de ces
deux sous-états.
Lorsqu'il n'est pas suspendu, nous distinguerons deux autres sous-états suivant que le thread peut disposer de la ressource unité centrale (on dit alors runnable) ou devra attendre l'apparition d'un évènement particulier (not runnable). Trois évènements peuvent conduire un thread à l'état not runnable:
• sa méthode sleep() a été appelée, le tread n'utilisera pas la machine pendant un délai fixé par le
paramètre de cette méthode;
• le thread attend un autre thread (grâce à la méthode join());
• le thread attend la fin d'une instruction d'entrée-sortie bloquante.
6.3 Ordonnancement des Threads
Nous venons de voir comment exécuter plusieurs threads à la fois. La question qui se pose maintenant
est de savoir dans quel ordre ils vont d'exécuter. La machine virtuelle java possède une politique
d'ordonnancement très simple, basèe sur des priorités. C'est le thread non suspendu qui a la plus grande
priorité qui s'exécute à tout moment. C'est une politique préemptive, c'est à dire qu'un thread actif
peut perdre la main dès qu'un autre thread plus prioritaire devient actif (Exception: dans certaines
conditions, pour éviter un blocage entre thread, la machine virtuelle peut donner la main à un thread
de plus basse priorité).
Lors de sa création, un thread prend la même priorité que le thread qui l'a créé. Il est possible de
modifier cette priorité grâce à la méthode setPriority(int). Ainsi, en reprenant le programme précédent, nous pouvons influencer scandaleusement le résultat de la course en modifiant les priorités des
concurrents de la manière suivante:
package ch7;
public class Prog1V5 implements Runnable {
Fantome fantome;
public Prog1V5(Fantome fantome) {
this.fantome = fantome;
}
public void run() {
fantome.go();
}
public static void main(String arg[]) {
FrameFantomes fenêtre = new FrameFantomes("ch7.Prog1");
Page 84 sur 92
Initiation à la Programmation Java (4GI)
Fantome f1 = fenêtre.créerFantôme();
Fantome f2 = fenêtre.créerFantôme();
Fantome f3 = fenêtre.créerFantôme();
Thread thread1 = new Thread(new Prog1V5(f1));
Thread thread2 = new Thread(new Prog1V5(f2));
Thread thread3 = new Thread(new Prog1V5(f3));
thread1.setPriority(Thread.NORM_PRIORITY-3);
thread2.setPriority(Thread.NORM_PRIORITY-2);
thread3.setPriority(Thread.NORM_PRIORITY-1);
thread1.start();
thread2.start();
thread3.start();
}
}
Avec cette nouvelle version, le fantôme numéro 3 terminera la course avant même que les deux premiers aient pu démarrer, tandis que le fantôme numéro 1 ne commencera sa course que lorsque les
deux autres auront fini.
Lorsque plusieurs threads de même priorité deviennent actifs, ils reçoivent la main suivant une politique ciculaire (round-robin) non-préemptive. Chaque thread prend la main à tour de rôle. Dès qu'un
thread a la main, il l'a garde jusqu`à ce qu'il se mettent en attente d'un évènement. Ceci peut conduire
un thread à monopoliser les ressources de la machine. Pour éviter cela, tout thread peut donner la
main à un autre thread de même priorité en appelant la méthode yield(). Grâce à cette méthode, nous
pouvons donner au programme, un fonctionnement similaire au temps partagé.
Certaine implémentation de machine virtuelle Java peuvent bénéficier d'un véritable temps partagé au
niveau du multi-thread. Dans ce cas, le système d'exploitation de la machine (et non pas la machine
virtuelle Java) partagera le temps entre les threads de même priorité, sans qu'il soit nécessaire de
l'introduire artificiellement grâce à la méthode yield().
6.4 Synchronisation des Threads
Reprenons notre programme multithread simulant une course de fantôme. Comme nous avons pu le
constater, chaque fantôme poursuit sa course jusqu'au bout.
Nous voulons maintenant mettre en évidence le premier fantôme en modifiant sa couleur. Cette opération peut être réalisée en utilisant la méthode setPremier() de la classe Fantome. Il reste à déterminer quand utiliser cette méthode.
Il faut qu'à tout moment, chaque fantôme puisse avoir accès au fantôme de tête. Pour cela, nous utiliserons la variable de classe premier. A chaque avancée, un fantôme devra examiner et éventuellement modifier cette variable. Ceci sera fait en re-écrivant la méthode run() de la manière suivante:
public void run() {
while (!fantôme.arrivé()) {
fantôme.avance();
if (fantôme.position() > premier.position()) {
premier.resetPremier();
Page 85 sur 92
Initiation à la Programmation Java (4GI)
fantôme.setPremier();
premier = fantôme;
}
}
}
Remarque: la méthode avance() permet de faire avancer d'un pas, avec une vitesse aléatoire, la
méthode position() renvoie la position du fantôme. Le programme complet est alors le suivant:
package ch7;
public class Prog1V6 implements Runnable {
static Fantome premier;
Fantome fantôme;
public Prog1V6(Fantome fantôme) {
this.fantôme = fantôme;
}
public void run() {
while (!fantôme.arrivé()) {
fantôme.avance();
if (fantôme.position() > premier.position()) {
premier.resetPremier();
fantôme.setPremier();
premier = fantôme;
}
}
}
public static void main(String arg[]) {
FrameFantomes fenêtre = new FrameFantomes("ch7.Prog1");
Fantome f1 = fenêtre.créerFantôme();
Fantome f2 = fenêtre.créerFantôme();
Fantome f3 = fenêtre.créerFantôme();
premier = f1;
f1.resetPremier();
f2.resetPremier();
f3.resetPremier();
premier.setPremier();
Thread thread1 = new Thread(new Prog1V6(f1));
Thread thread2 = new Thread(new Prog1V6(f2));
Thread thread3 = new Thread(new Prog1V6(f3));
thread1.start();
thread2.start();
thread3.start();
}
}
Ce programme semble marcher correctement. Pourtant, dans certains cas, il peut montrer des disfonctionnements. Par exemple, lorsque deux fantômes sont premiers ex æquo. Nous pouvons obtenir facilement ce cas particulier en utilisant la méthode avanceSimple() qui provoque une avancée fixe du
fantôme (au lieu d'une avancée aléatoire pour avance()).
Page 86 sur 92
Initiation à la Programmation Java (4GI)
L'exécution du programme peut se traduire par une course qui se termine par deux gagnants au
lieu d'un. Pour bien comprendre ce disfonctionnement, il faut savoir que chaque appel de l'une des
méthodes avanceSimple(), resetPremier() et setPremier() provoque un changement de thread. Supposons que le fantôme de tête est f3 et se trouve à la troisième longueur, et que les les deux autres
fantômes se trouvent également sur la même ligne que le premier. On considère alors que seuls les
fantômes f1 et f2 avançent à tour de rôle. Ils vont donc exécuter chacun la séquence suivante:
fi.avanceSimple();
premier.resetPremier();
fi.setPremier();
premier = fi;
Comme à chacune de ces instructions il peut y avoir un changement de thread, nous pourrions, par
exemple, avoir le séquencement suivant (nous indiquons l'opération effectuée par le premier et le
second fantôme, la position de chaque fantôme à l'issue de cette opération, éventuellement sur fond
gris pour le fantôme indiqué premier, la valeur de la variable premier):
On constate alors qu'à l'étape 4, c'est le fantôme f3 qui est déselectionné (pour la deuxième fois de
suite) à la place du fantôme f1. Aussi, à l'étape 8, nous nous trouvons en présence de deux fantômes
selectionnés.
En fait, le problème dans cette approche vient du fait que l'accès à la variable partagée premier n'est
pas du tout synchronisé entre les différents threads. Il faut donc en synchroniser l'accès. La séquence
d'instructions
premier.resetPremier();
fi.setPremier();
premier = fi;
constitue une section critique.
Pour synchroniser l'accès à une section critique, la machine Java utilise des verrous d'exclusion
mutuelle. Une telle exclusion mutuelle est obtenue par l'instruction synchronized:
synchronized ( Expression ) Block
Cette instruction associe à l'objet renvoyé par l'expression, un verrou qui permettra de contrôler l'exécution du block associé. Un tel verrou garantit au thread qui l'a obtenue, un accès exclusif aux instructions du block. En d'autres termes, à tout instant, il ne peut y avoir au plus qu'un seul thread exécutant les instructions d'un block verrouillé par un verrou donné. Lorsqu'un deuxième thread essaie
d'utiliser un verrou déjà utilisé, il sera suspendu jusqu'à la libération du verrou. Ainsi, pour supprimer
le disfonctionnement précédent, nous devons coder la méthode run() de la manière suivante:
Page 87 sur 92
Initiation à la Programmation Java (4GI)
public void run() {
while (!fantôme.arrivé()) {
fantôme.avanceSimple();
if (fantôme.position() > premier.position()) {
synchronized (premier) {
premier.resetPremier();
fantôme.setPremier();
premier = fantôme;
}
}
}
}
Nous utilisons le fantôme désigné par la variable premier comme verrou d'accès puisque, cette
variable étant une variable de classe, le fantôme qu'elle référence est accessible par chaque thread.
La deuxième manière permettant de réaliser des exclusions mutuelles consiste à utiliser des méthodes
synchronisées, c'est à dire des méthodes déclarées avec le modificateur synchronized. Ainsi, l'exécution de la méthode s'éffectue en exclusion mutuelle en utilisant un verrou. Ce verrou est l'instance
de la classe java.lang.Class associée à la classe dont fait partie la méthode, dans le cas d'une méthode
de classe, ou l'objet lui-même (this) pour une méthode d'instance. Ainsi, pour notre problème, nous
devons utiliser une méthode statique (puisque la synchronisation se fait sur plusieurs instances de la
classe Fantome) de la manière suivante:
public static synchronized void nouveauPremier(Fantome f) {
premier.resetPremier();
f.setPremier();
premier = f;
}
public void run() {
while (!fantôme.arrivé()) {
fantôme.avanceSimple();
if (fantôme.position() > premier.position()) nouveauPremier(fantôme);
}
}
6.5 Groupe de Threads
Nous voulons maintenant obtenir un comportement différent de notre programme. Nous voulons obtenir une photo de l'arrivée, c'est à dire, arrêter la course dès l'arrivée du premier.
Une première manière d'obtenir ce comportement est d'utiliser un container (par exemple un vecteur)
accessible par l'ensemble des fantômes, pour obtenir un accés à chaque fantôme compétiteur. Le premier arrivée n'a plus qu`à arrêter (grâce à la méthode stop()) les autres concurrents.
Une manière plus élégante consiste à associer les threads fantômes pour former un groupe de thread.
La classe java.lang.ThreadGroup permet d'effectuer les opérations précédentes (stop, suspend,
resume, destroy) sur l'ensemble des threads du groupe.
Page 88 sur 92
Initiation à la Programmation Java (4GI)
Un thread est placé dans un groupe de thread au moment de sa création, et ne peut plus être déplacé
dans un autre groupe. Dans notre exemple, nous utiliserons le groupe de nom "Peloton de fantômes",
accessible à travers la variable de classe peloton définie comme suit:
static ThreadGroup groupeFantômes = new ThreadGroup("Groupe des Fantômes");
La création des threads se fera ainsi (il suffit d'indiquer le groupe d'appartenance lors de l'appel au
constructeur de Thread):
Thread thread1 = new Thread(groupeFantômes, new Prog1V7(f1));
Thread thread2 = new Thread(groupeFantômes, new Prog1V7(f2));
Thread thread3 = new Thread(groupeFantômes, new Prog1V7(f3));
Lorsque le premier fantôme termine sa course, il doit stopper les autres concurrents. Nous modifierons
donc la méthode run() de la manière suivante:
public void run() {
while (!fantôme.arrivé()) {
fantôme.avance();
if (fantôme.position() > premier.position()) {
...
}
}
groupeFantômes.stop();
Cette nouvelle méthode présente toutefois un disfonctionnement. En effet, un fantôme étant également
dans le groupe de fantômes, lors de l'exécution de l'appel groupeFantômes.stop(), il se détruira, mais
pas forcément en dernier. Dans ce cas, il risque de se détruire en laissant d'autres threads continuer
de s'exécuter. Il faut donc que l'exécution de l'appel groupeFantômes.stop() soit fait par un thread
tueur n'appartenant pas au groupe de threads coureurs, comme cela est fait dans la version suivante:
package ch7;
public class Prog1V8 implements Runnable {
static ThreadGroup groupeFantômes =
new ThreadGroup("Groupe des Fantômes");
static Fantome premier;
Fantome fantôme;
static ThreadTueur tueur;
static class ThreadTueur extends Thread {
public void run() { groupeFantômes.stop(); }
}
public Prog1V8(Fantome fantôme) {
this.fantôme = fantôme;
}
public void run() {
while (!fantôme.arrivé()) {
fantôme.avance();
if (fantôme.position() > premier.position()) {
synchronized (premier) {
premier.resetPremier();
fantôme.setPremier();
premier = fantôme;
}
Page 89 sur 92
Initiation à la Programmation Java (4GI)
}
}
tueur.start();
}
public static void main(String arg[]) {
FrameFantomes fenêtre = new FrameFantomes("ch7.Prog1V8");
tueur = new ThreadTueur();
tueur.setPriority(Thread.NORM_PRIORITY+1);
Fantome f1 = fenêtre.créerFantôme();
Fantome f2 = fenêtre.créerFantôme();
Fantome f3 = fenêtre.créerFantôme();
premier = f1;
premier.setPremier();
Thread thread1 = new Thread(groupeFantômes, new Prog1V8(f1));
Thread thread2 = new Thread(groupeFantômes, new Prog1V8(f2));
Thread thread3 = new Thread(groupeFantômes, new Prog1V8(f3));
thread1.start();
thread2.start();
thread3.start();
}
}
Remarque: dans cette dernière version, nous avons imposé au thread tueur une priorité supérieure.
Ainsi, dès l'appel de la méthode start(), c'est ce thread qui prendra la main au détriment des autres
threads associès aux fantômes.
6.6 Les processus
La langage Java permet la manipulation de processus lourds (type processus unix). La création d'un
tel processus est réalisé par l'une des méthodes exec de la classe java.lang.Runtime.
La méthode exec dont la signature est
public Process exec(String command) throws IOException
exécute la commande passée en argument dans un processus indépendant. Elle retourne aussitôt un
objet de la classe java.lang.Process, objet qui permettra d'avoir un contrôle sur ce processus.
Par exemple, le programme ci-dessous permet l'exécution de la commande unix "ls":
package ch7;
import java.io.*;
public class TestExec {
public static void main(String arg[]) throws IOException{
System.out.println("lancement");
Process processus = java.lang.Runtime.getRuntime().exec(arg[0]);
System.out.println("lancement terminé");
System.out.println("attente terminée");
}
}
Page 90 sur 92
Initiation à la Programmation Java (4GI)
Le problème est que nous ne voyons aucun résultat de l'exécution de ce processus. Pour voir le résultat,
il faut "récupérer" le flot de sortie du processus. Ceci est possible par l'intermédiaire de la méthode
getInputStream de la classe java.lang.Process, méthode qui renvoie un flot d'entrée correspondant
au flot de sortie du processus.
Le programme suivant permet alors de visualiser le résultat renvoyé par un processus parallèle:
package ch7;
import java.io.*;
public class TestExec1 {
public static void main(String arg[]) throws IOException, InterruptedException{
System.out.println("lancement");
Process processus = java.lang.Runtime.getRuntime().exec("ls");
BufferedReader in = new BufferedReader(
new InputStreamReader(
processus.getInputStream()));
String inputLine;
while ((inputLine = in.readLine()) != null)
System.out.println("--> "+inputLine);
in.close();
System.out.println("programme terminé");
}
}
Ce que nous venons de faire avec le flot de sortie du processus peut également être fait avec le flot
d'entrée. Il faut utiliser pour cela la méthode getOutputStream qui renvoie un flot de sortie correspondant au flot d'entrée du processus.
Ainsi, le programme suivant permet de lancer par un processus parallèle un interprète du langage
scheme (commande snow), lui soumettre une expression à évaluer et enfin imprimer le résultat:
package ch7;
import java.io.*;
public class TestExec2 {
public static void main(String arg[]) throws IOException, InterruptedException{
Process processus = java.lang.Runtime.getRuntime().exec("snow");
/* swow est un interprète du langage scheme */
BufferedReader in = new BufferedReader(
new InputStreamReader(
processus.getInputStream()));
PrintWriter out = new PrintWriter(processus.getOutputStream());
System.out.println("On envoie : (reverse '(1 2 3 4 5))");
out.println("(reverse '(1 2 3 4 5))");
Page 91 sur 92
Initiation à la Programmation Java (4GI)
out.println("(quit)");
out.flush();
String résultat = in.readLine();
System.out.println("On reçoit : "+résultat);
System.out.println("Exécution terminée");
}
}
L'exécution de ce programme donnera:
$ java ch7.TestExec2
On envoie : (reverse '(1 2 3 4 5))
On reçoit : (5 4 3 2 1)
Exécution terminée
$
Page 92 sur 92
Téléchargement