Guide complet sur le langage Java
Guide complet sur le langage Java
l1.1. – Introduction
Java est un langage de programmation récent (les premières versions datent de 1995) développé par
Sun MicroSystems. Il est fortement inspiré des langages C et C++.
Comme C++, Java fait partie de la « grande famille » des langages orientés objets. Il répond donc
aux trois principes fondamentaux de l’approche orientée objet (POO) : l’encapsulation, le
polymorphisme et l’héritage Nous reviendrons bien évidemment en détails sur ces trois notions
dans la suite de ce document (Cf. chapitre 2 : « Les principes de la POO »).
1.2. – Historique
Le langage Java trouve ses origines dans les années 1990. A cette époque, quelques ingénieurs
(innovateurs) de SUN Microsystems commencent à parler d'un projet d'environnement indépendant
du hardware pouvant facilement permettre la programmation d'appareil aussi variés que les
téléviseurs, les magnétoscopes etc ... James Grosling (SUN Microsystems) développe un premier
langage, Oak, permettant de programmer dans cet environnement. En 1992, tout est prêt pour
envahir le marché avec cette nouvelle technologie mais la tentative se solde par un échec.
Bill Joy (co-fondateur de SUN Microsystems) sauve malgré tout le projet. Devant la montée en
puissance de l'Internet, il lui semble intéressant d’insister sur l’élaboration d’un langage
indépendant des plates-formes et des environnements (les principaux problèmes rencontrés sur
l’Internet étant liés à l'hétérogénéité des machines et des logiciels utilisés).
Dès lors tout s'accélère. Oak est renommé (en 1995) Java et soumis à la communauté Internet
grandissante. Une machine virtuelle, un compilateur ainsi que de nombreuses spécifications sont
données gratuitement et Java entame une conquête fulgurante. Aujourd'hui, après de nombreuses
améliorations (parfois modifications) Java n'est plus uniquement une solution liée à l’Internet. De
plus en plus de sociétés (ou de particuliers) utilisent ce langage pour l’ensemble de leurs
développements (applications classiques, interfaces homme-machine, applications réseaux …).
Binaire Windows
Fichier source
Binaire Unix
Binaire Mac
+
Machine virtuelle sous Unix
Bibliothèques
.jar ou .zip Binaire Unix
Collections
de classes
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 4/120
1.4.2.- Applets et applications
Il est possible de développer des applications isolées (standalone applications), fonctionnant avec
l'interpréteur comme un programme habituel dans un langage classique mais aussi des "applets".
Les applets sont des programmes qui peuvent être téléchargés sur l’Internet puis exécutés
automatiquement quand ils sont intégrés à dans des pages HTML. Dans ce dernier cas, l'ensemble
des fichiers .class est utilisé avec un fichier HTML qui fait appel à une des classes (Cf. chapitre 6).
Bien que les principes de programmation soient très proches, ce document traite uniquement des
applications car la philosophie est plus proche des langages classiques (C/C++, Pascal, ADA …)
Néanmoins, nous consacrerons le chapitre 7.1 est consacré aux spécificités des applets.
1.5.4. - En résumé
Contrairement au C/C++, le compilateur Java ne génère pas de fichier exécutable. Il crée
pour chacune des classes d'un fichier Java un fichier .class qui sera interprété par la
Machine Virtuelle Java.
Compilation : javac [<options>] <fichiers-source- « .java »> . Exemple : javac [Link]
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 5/120
Exécution : java [<options>] <fichiers-class sans extension> . Exemple : java Test
2.1.- L’encapsulation
2.1.1. - La notion d’objet
La complexité des problèmes qu'on est capable de résoudre est directement proportionnelle au type
et à la qualité de notre capacité d'abstraction. Tous les langages de programmation fournissent des
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 6/120
abstractions. Le langage assembleur, par exemple, est une abstraction de la machine sous-jacente.
Beaucoup de langages « impératifs » (Fortran, Pascal, C) sont des abstractions du langage
assembleur. Ces langages sont de nettes améliorations par rapport à l'assembleur, mais leur
abstraction première requiert une réflexion en termes de structure d’ordinateur et non en terme de
structure du problème à résoudre.
L'approche orientée objet va plus loin en fournissant au programmeur des outils représentant des
éléments dans l'espace problème. Ces éléments sont représenté dans l ‘espace solution par des «
objets ». L'idée est que le programme est autorisé à s'adapter à l'esprit du problème en ajoutant de
nouveaux types d'objet, de façon à ce que, quand on lit le code décrivant la solution, on lit aussi
quelque chose qui décrit le problème. Ainsi, la POO permet de décrire le problème avec les termes
mêmes du problème plutôt qu'avec les termes de la machine où la solution sera mise en œuvre.
Alan Kay résume ainsi les cinq caractéristiques principales de Smalltalk (premier langage de
programmation orienté objet et l'un des langages sur lequel est basé Java) :
• Toute chose est un objet. Il faut penser à un objet comme à une variable améliorée : il stocke
des données, mais on peut « effectuer des requêtes » sur cet objet, lui demander de faire des
opérations sur lui-même. En théorie, on peut prendre n'importe quel composant conceptuel du
problème qu'on essaye de résoudre (un chien, un immeuble, un service administratif, etc...) et le
représenter en tant qu'objet dans le programme ;
• Un programme est un ensemble d'objets qui communiquent entre eux en s'envoyant des
messages. Pour qu'un objet effectue une requête, on « envoie un message » à cet objet. Plus
concrètement, un message est un appel à une fonction appartenant à un objet particulier ;
• Chaque objet a son propre espace de mémoire composé d'autres objets. On peut ainsi
cacher la complexité d'un programme par la simplicité des objets mis en œuvre ;
• Chaque objet est d'un type précis. Dans le jargon de la POO, chaque objet est une instance
(ou une réalisation) d'une classe, où « classe » a une signification proche de « type » (Cf.
chapitre suivant) ;
• Tous les objets d'un type particulier peuvent recevoir le même message. C'est une
caractéristique lourde de signification, comme vous le verrez plus tard, parce qu'un objet de type
« cercle » est aussi un objet de type « forme géométrique », un cercle se doit d'accepter les
messages destinés aux formes géométriques. Cela veut dire qu'on peut écrire du code parlant
aux formes géométriques qui sera accepté par tout ce qui correspond à la description d'une
forme géométrique.
Interface Utilisation
type
PuissanceAmpoule amp = new Ampoule() ;
Ampoule A [Link]() ;
A Allumer
Eteindre
L'interface précise quelles sont les caractéristiques d’un objet (ici type et puissance) et quelles sont
les opérations que l’on peut effectuer sur un objet particulier (ici Allumer et Eteindre). L’interface
ne précise pas la manière dont elles sont effectuées (c’est-à-dire le code correspondant à
l’opération) : c’est le rôle de l’implémentation. Du point de vue de la programmation, une classe
dispose de fonctions associées à chaque requête possible (appelée méthodes), et lorsqu’on effectue
une requête particulière sur un objet, ces fonctions sont appelées. Ce mécanisme est souvent résumé
en disant qu'on « envoie un message » à un objet, l'objet se « débrouillant » pour l'interpréter
(exécution du code associé).
Dans l’exemple ci-dessus, le nom de la classe est Ampoule, le nom de l'objet Ampoule créé est
amp. D’après l’interface, on peut demander à un objet Ampoule de s'allumer et de s'éteindre. Un
objet Ampoule est créé en définissant une « référence » (amp) pour cet objet et en appelant new
pour créer un nouvel objet de cette classe. Pour envoyer un message à cet objet, il suffit de spécifier
le nom de l'objet suivi de la requête avec un point entre les deux. Du point de vue de l'utilisateur
d'une classe prédéfinie, c'est tout ce qu'il est nécessaire de savoir pour utiliser les objets de la classe
en question.
2.1.3. – L’implémentation cachée
Pour comprendre la notion d’implémentation cachée, il est nécessaire de différentier les
programmeurs qui créent les classes d’objets et ceux qui les utilisent dans leur applications (qui
peuvent d’ailleurs être eux-mêmes des créateurs de classes pour d’autres programmeurs utilisateurs
etc …). Le but des programmeurs « clients » est de créer une boite à outils de classes réutilisables
pour le développement rapide d’applications. Les « créateurs » de classes se concentrent sur la
construction de la classe pour ne donner aux programmeurs « clients » que le strict nécessaire.
L’intérêt principal d’une telle démarche est que le programmeur « créateur » peut changer des
portions de code (améliorations, ajouts, suppressions …) sans que ces changements aient un impact
sur l’utilisation des classes. Les portions cachées correspondent en général aux données de l’objet
qui pourraient facilement être corrompues par un programmeur « clients » négligent ou mal
informé.
La raison première du contrôle d'accès est donc d'empêcher les programmeurs « clients » de toucher
à certaines portions auxquelles ils ne devraient pas avoir accès - les parties qui sont nécessaires pour
les manipulations internes du type de données mais n'appartiennent pas à l'interface dont les
utilisateurs ont besoin pour résoudre leur problème. C'est en réalité un service rendu aux utilisateurs
car ils peuvent voir facilement ce qui est important pour leurs besoins et ce qu'ils peuvent ignorer.
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 8/120
La deuxième raison d'être du contrôle d'accès est de permettre au concepteur de la bibliothèque de
changer le fonctionnement interne de la classe sans se soucier des effets que cela peut avoir sur les
programmeurs « clients ». Par exemple, on peut implémenter une classe particulière d'une manière
simpliste afin d'accélérer le développement et se rendre compte plus tard qu'on a besoin de la
réécrire afin de gagner en performances. Si l'interface et l'implémentation sont clairement séparées
et protégées, cela peut être facilement réalisé.
La plupart des langages orienté objet (dont C++ et Java) utilisent trois mots clefs pour fixer des
limites au sein d'une classe : public, private et protected. Le mot clef public veut dire que les
définitions qui suivent sont disponibles pour tout le monde. Le mot clef private, au contraire, veut
dire que personne, le créateur de la classe et les fonctions internes de ce type mis à part, ne peut
accéder à ces définitions. Si quelqu'un tente d'accéder à un membre défini private, il récupère une
erreur lors de la compilation. Le mot clef protected se comporte comme private, en moins
restrictif. Une classe dérivée a accès aux membres protected, mais pas aux membres private. Nous
reviendrons sur la notion de protected dans le chapitre sur l'héritage (Cf. chapitre 2.2).
class Moteur
{
float Cylindree ;
float Puissance ;
void Allumer () ;
void Eteindre () ;
}
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 9/120
La composition s'accompagne d'une grande flexibilité : les objets membres de la nouvelle classe
sont généralement privés, ce qui les rend inaccessibles aux programmeurs « clients » de la classe.
Cela permet de modifier ces membres sans perturber le code des clients existants.
L'héritage ne dispose pas de cette flexibilité car le compilateur doit placer des restrictions lors de la
compilation sur les classes créées avec héritage (Cf. chapitre suivant). Parce que la notion d'héritage
est très importante au sein de la programmation orientée objet, elle est trop souvent recommandée et
le nouveau programmeur pourrait croire que l'héritage doit être utilisé partout. Cela mène à des
conceptions très compliquées et souvent cauchemardesques. La composition est la première
approche à examiner lorsqu'on crée une nouvelle classe, car elle est plus simple et plus flexible.
Avec de l'expérience, les endroits où utiliser l'héritage deviendront raisonnablement évidents.
2.2.- L’héritage
Une fois votre classe créée et testée, il serait dommage de devoir en créer une nouvelle pour
implémenter des fonctionnalités similaires. Il est plus intéressant de prendre la classe existante, de
la cloner et de faire les ajouts ou les modifications à ce clone. C'est ce que permet l 'héritage, avec la
restriction suivante : si la classe d’origine (appelée classe de base ou classe parent) est modifiée, le
clone (appelé classe dérivée, héritée, enfant ou sous-classe) répercutera ces changements.
Une classe fait plus que décrire des contraintes sur un ensemble d'objets ; elle a aussi des relations
avec d'autres classes. Deux classes peuvent avoir des caractéristiques et des comportements en
commun, mais l'une des deux peut avoir plus de caractéristiques que l'autre et peut aussi réagir à
plus de messages (ou y réagir de manière différente). L'héritage exprime cette similarité entre les
classes en introduisant le concept de classes de base et de classes dérivées. Une classe de base
contient toutes les caractéristiques et comportements partagés entre les classes dérivées. Un type de
base est créé pour représenter le cœur de certains objets du système. De ce type de base, on dérive
d'autres types pour exprimer les différentes manières existantes pour réaliser ce cœur.
Prenons l’exemple classique des « formes géométriques », utilisées entre autres dans les systèmes
d'aide à la conception ou dans les jeux vidéos (Cf. Modèle HBDS ci-dessous). La classe de base est
la « forme géométrique », et chaque forme a une taille, une couleur, une position, etc... Chaque
forme peut être dessinée, effacée, déplacée, peinte, etc... A partir de cette classe de base, des classes
spécifiques sont dérivées (héritées) : des ellipses, des rectangles, des cercles et autres, chacune avec
des caractéristiques et des comportements additionnels (certaines figures peuvent être inversées par
exemple). Certains comportements peuvent être différents, par exemple quand on veut calculer
l'aire de la forme. La hiérarchie des classe révèle à la fois les similarités et les différences entre les
formes.
Aire
Couleur
FormesGéom
EpTrait
Long Méthodes (dessiner, effacer … )
Larg
Rectangle
X
est composée de
Y
PtAxe
GdAxe Points
Ellipse
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 10/120
Quand on hérite d'une classe, on crée une nouvelle classe. Cette nouvelle classe non seulement
contient tous les membres d’une classe existante (bien que les membres privés soient cachés et
inaccessibles), mais surtout elle duplique l'interface de la classe de base. Autrement dit, tous les
messages acceptés par les objets de la classe de base seront acceptés par les objets de la classe
dérivée. Comme on connaît « le type » de la classe par les messages qu'on peut lui envoyer, cela
veut dire que la classe dérivée est du même « type » que la classe de base.
Dans l'exemple précédent, « un cercle est une forme ». Cette équivalence de type via l'héritage est
l'une des notions fondamentales dans la compréhension de la programmation orientée objet.
Comme la classe de base et la classe dérivée ont toutes les deux la même interface, certaines
implémentations accompagnent cette interface. C'est à dire qu'il doit y avoir du code à exécuter
quand un objet reçoit un message particulier. Si on ne fait qu'hériter une classe sans rien lui rajouter,
les méthodes de l'interface de la classe de base sont importées dans la classe dérivée. Cela veut dire
que les objets de la classe dérivée n'ont pas seulement le même « type », ils ont aussi le même
comportement, ce qui n'est pas particulièrement intéressant.
Il y a deux façons de différencier la nouvelle classe dérivée de la classe de base originale. La
première est relativement directe : il suffit d'ajouter de nouvelles fonctions à la classe dérivée. Ces
nouvelles fonctions ne font pas partie de la classe parent. Cela veut dire que la classe de base n'était
pas assez complète pour ce qu'on voulait en faire, on a donc ajouté de nouvelles fonctions. Cet
usage simple de l'héritage se révèle souvent être une solution idéale. Cependant, il faut vérifier s'il
ne serait pas souhaitable d'intégrer ces fonctions dans la classe de base qui pourrait aussi en avoir
l'usage. Ce processus de découverte et d'itération dans la conception est fréquent dans la
programmation orientée objet.
Bien que l'héritage puisse parfois impliquer l’ajout de nouvelles fonctions à l'interface, il existe une
autre manière de différencier la nouvelle classe : changer le comportement d'une des fonctions
existantes de la superclasse. C’est ce que l’on appelle redéfinir (ou surcharger) cette fonction. Pour
redéfinir une fonction, il suffit de créer une nouvelle définition pour la fonction dans la classe
dérivée. C'est comme dire : « j'utilise la même interface ici, mais je la traite d'une manière différente
dans cette nouvelle classe ».
Un débat est récurrent à propos de l'héritage : l'héritage ne devrait-il pas seulement redéfinir les
fonctions de la classe de base et ne pas ajouter de nouvelles fonctions membres qui ne font pas
partie de la superclasse ? Ceci implique que le type de la classe dérivée est exactement le même que
celui de la classe de base (il a exactement la même interface) et donc que l’on peut exactement
substituer un objet de la classe dérivée à un objet de la classe de base. On fait souvent référence à
cette substitution pure sous le nom de principe de substitution. Dans un sens, c'est la manière idéale
de traiter l'héritage puisque la relation entre la classe de base et la classe dérivée est une relation
estun, (« un cercle est une forme »). Un test pour l'héritage peut donc être de déterminer si la
relation « est-un » entre les deux classes considérées a un sens.
Mais parfois il est nécessaire d'ajouter de nouveaux éléments à l'interface d'une classe dérivée et
donc d’étendre l'interface en créant une nouvelle classe. La nouvelle classe peut toujours être
substitué à la classe de base, mais la substitution n'est plus parfaite parce que les nouvelles fonctions
ne sont pas accessibles à partir de la classe parent.
Prenons le cas d'un système de climatisation. Supposons que notre maison dispose des tuyaux et des
systèmes de contrôle pour le refroidissement, autrement dit elle dispose d'une interface qui nous
permet de contrôler le refroidissement. Imaginons que le système de climatisation tombe en panne
et qu'on le remplace par une pompe à chaleur qui peut à la fois chauffer et refroidir. La pompe à
chaleur « est-comme-un » système de climatisation mais il peut faire davantage de choses.
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 11/120
L'interface du nouvel objet a été étendue mais le système existant ne connaît rien qui ne soit dans
l'interface originale.
2.3.- Le polymorphisme
Il arrive que l’on veuille traiter un objet non en tant qu'objet de la classe spécifique, mais en tant
qu'objet de sa classe de base. Cela permet d'écrire du code indépendant des classes spécifiques.
Dans l'exemple de la forme géométrique, les fonctions manipulent des formes génériques sans se
soucier de savoir si ce sont des ellipses, des rectangles, des triangles ou même des formes non
encore définies. Toutes les formes peuvent être dessinées, effacées etc … Ces fonctions envoient
simplement un message à un objet forme, elles ne se soucient pas de la manière dont l'objet traite le
message. Un tel code n'est pas affecté par l'addition de nouvelles classes et ajouter de nouvelles
classes est la façon la plus commune d'étendre un programme orienté objet pour traiter de nouvelles
situations.
Par exemple, on peut dériver une nouvelle classe de forme appelée « pentagone » sans modifier les
fonctions qui traitent des formes génériques. Cette capacité à étendre facilement un programme en
dérivant de nouvelles sous-classes est important car il améliore considérablement la conception tout
en réduisant le coût de maintenance.
Un problème se pose cependant en voulant traiter les classes dérivées comme leur classe de base
générique (les ellipses comme des formes géométriques, les vélos comme des véhicules, les
cormorans comme des oiseaux etc...). Si une fonction demande à une forme générique de se
dessiner, ou à un véhicule générique de tourner, ou à un oiseau générique de se déplacer, le
compilateur ne peut savoir précisément lors de la phase de compilation quelle portion de code sera
exécutée. C'est d'ailleurs le point crucial : quand le message est envoyé, le programmeur ne veut pas
savoir quelle portion de code sera exécutée ; la fonction dessiner peut être appliquée aussi bien à
une ellipse qu'à un rectangle ou à un triangle et l'objet exécute le bon code suivant son type
spécifique. Si on n'a pas besoin de savoir quelle portion de code est exécutée, alors le code exécuté
(lorsque on ajoute un nouvelle sous-classe) peut être différent sans exiger de modification dans
l'appel de la fonction. Le compilateur ne peut donc précisément savoir quelle partie de code sera
exécutée, comment va-t-il réagir ? Par exemple, dans le diagramme suivant, l'objet Contrôleur
d'oiseaux travaille seulement avec des objets Oiseaux génériques, et ne sait pas de quelle classe ils
proviennent. C’est pratique du point de vue de Contrôleur d'oiseaux car il n'a pas besoin d'écrire
du code spécifique pour déterminer le type exact d'Oiseau avec lequel il travaille, ou le
comportement de cet Oiseau. Comment se fait-il alors que, lorsque bouger() est appelé tout en
ignorant le type spécifique de l'Oiseau, on obtienne le bon comportement (une Oie court, vole ou
nage, et un Pingouin court ou nage) ?
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 12/120
Déplacer Oiseaux
Contrôleur Bouger
D’oiseaux Oie
contrôle
Bouger
Pingouin
Pour créer une association tardive, Java utilise une portion spéciale de code en lieu et place de
l'appel absolu. Ce code calcule l'adresse du corps de la fonction, en utilisant des informations
stockées dans l'objet. Ainsi, chaque objet peut se comporter différemment suivant le contenu de
cette portion spéciale de code. Quand un objet reçoit un message, l'objet sait quoi faire de ce
message.
Dans certains langages (en particulier le C++), il faut préciser explicitement qu'on souhaite
bénéficier de la flexibilité de l'association tardive pour une fonction. Dans ces langages, les
fonctions membres ne sont pas liées dynamiquement par défaut. Cela pose des problèmes, donc en
Java l'association dynamique est le défaut et mot clef supplémentaire n'est pas requis pour
bénéficier du polymorphisme.
void faireQuelqueChose(Forme f)
{
[Link]();
// ...
[Link]();
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 13/120
}
Cette fonction s'adresse à n'importe quelle Forme, elle est donc indépendante du type spécifique de
l'objet qu'elle dessine et efface. Si nous utilisons ailleurs dans le programme cette fonction
faireQuelqueChose() :
«Transtypage
ascendant» Forme
Dans le code de faireQuelqueChose(), remarquez qu'il ne dit pas « Si tu es un Cercle, fais ceci, si
tu es un Carré, fais cela, etc... ». Ce genre de code qui vérifie tous les types possibles que peut
prendre une Forme est confus et il faut le changer à chaque extension de la classe Forme. Ici, il
suffit de dire : « Tu es une forme géométrique, je sais que tu peux te dessiner() et t'effacer(), alors
fais-le et occupe-toi des détails spécifiques ». Ce qui est impressionnant dans le code de
faireQuelqueChose(), c'est que tout fonctionne comme on le souhaite. Appeler dessiner() pour un
Cercle exécute une portion de code différente de celle exécutée lorsqu'on appelle dessiner() pour
un Carré ou une Ligne, mais lorsque le message dessiner() est envoyé à une Forme anonyme, on
obtient le comportement idoine basé sur le type réel de la Forme. C’est impressionnant dans la
mesure où le compilateur Java ne sait pas à quel type d'objet il a affaire lors de la compilation du
code de faireQuelqueChose(). On serait en droit de s'attendre à un appel aux versions dessiner() et
effacer() de la classe de base Forme, et non celles des classes spécifiques Cercle, Carré et Ligne.
Mais quand on envoie un message à un objet, il fera ce qu'il a à faire, même quand la généralisation
est impliquée. C'est ce qu'implique le polymorphisme. Le compilateur et le système d'exécution
s'occupent des détails, et c'est tout ce que vous avez besoin de savoir, en plus de savoir comment
modéliser avec.
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 14/120
3.- Le langage Java
// La classe qui contient une méthode "main" est une application executable
*/
[Link] (val) : affiche à l’écran la variable val quelque soit le type de val.
[Link] (val) : affiche à l’écran la variable val quelque soit le type de val et effectue
un retour chariot.
Exemples :
[Link] (val) ; affiche la valeur 4 à l’écran et effectue un retour chariot.
[Link] ("valeur = "+ val) ; affiche à l’écran : valeur = 4 (si val vaut 4).
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 15/120
Le « + » signifie que l’on concatène la chaîne de caractères "valeur = " et la chaîne représentant
la variable val.
Nous reviendrons en détails sur les notions de classe et de méthode (Cf. chapitre 3.7) ainsi que sur
l’utilisation des mots-clés static, public (Cf. chapitre 3.10). Un chapitre particulier est également
consacré à la méthode main (Cf. chapitre 3.7.5).
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 16/120
3.2.2.- Les entiers
On distingue quatre type d’entiers : byte, short, int et long.
float double
Taille (bits) 32 64
Etendue 1.4E-45 .. 3.4E+38 4.9E-324 .. 1.8E+308
Exemples :
int i ; // declaration
...
i = i + 1 ; // incrementation equivalente a : i++ ;
i = i - 1 ; // decrementation equivalente a : i-- ;
Les opérations logiques sur les bits ( >>, <<, &, | , …) sont très peu utilisés en Java. Ces
opérateurs sont issus du langage C. Ils permettent de réaliser des opérations directement sur les bits
des nombres entiers. Ce document ne traite pas de ces opérations. Il s’agit d’un sujet « pointu » très
bien expliqué dans la plupart des manuels de langage C.
Affectation X←3 x = 3 ;
Test if (condition)
{ instructions
;
}
Condition ? | else
{ instructions
;
} // if
cas2 } // switch
.......
......
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 18/120
Boucle "tant que" / condition
while (condition)
{ instructions ;
} // while
Boucle avec Bf , Pas
compteur for (i=Bi;i<=Bf;i=i+Pas)
{ instructions ;
} // for i
i : Bi i
Boucle infinie
for (; ;)
{ instructions ;
} // for
Boucle /condition
"jusqu'à ce do
{ instructions ;
que"
} while ! ( condition) ;
Sortie de boucle
! break ;
Au suivant continue ;
*!
Autres goto debranchement;
débranchements instructions
debranchement:
instructions
vrai
true
faux
false
non ! (condition)
Condition
et &&
ou ||
égal = ==
différent ≠ !=
long b ; int
a;
Toutefois, la conversion d’entiers en valeurs à virgule flottante fait exception à cette règle générale.
La conversion d’un « int » en un « float » ou un « double » peut entraîner une perte de précision.
Dans le deuxième cas, il faut expliciter la conversion en indiquant entre parenthèses le type dans
lequel on désire convertir. Exemple : la conversion d’un « long » en un « int ».
long b ; int
a;
Sapin s ;
Arbre a ;
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 20/120
3.4.3.- Des types simples vers les objets et vice
versa
En Java, il est impossible de convertir des variables de type simple en objet et vice versa.
Cependant, Java fournit un package ([Link]) contenant des classes spéciales correspondant à
chaque type simple (ex : Integer pour les entiers, Float pour les flottants …). Grâce aux méthodes
de classes définies dans ces classes, ont peut créer, pour chaque variable de type simple, un objet
correspondant de la classe. Exemple : création d’un objet de type Integer à partir de l’entier 35 .
On dispose maintenant d’un véritable objet aObj. Il s’avère souvent nécessaire de revenir aux
valeurs de type simple. Il existe des méthodes pour réaliser ces opérations.
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 21/120
int a[], b[][]; cette déclaration est équivalente à la précédente.
class Variables{
staticpublicvoidmain (String args[]) {
Définition et affectation d’un chaîne de
String ch="CoursdeJava"; caractères. La variablech devient de fait
une variable de 13 caractères.
àl’écra n
[Link]
("chaine="+ch); chaine= Cours dejava
}
}
3.6.- Classes et objets
Comme nous l’avons décrit au chapitre 2 de ce document, le langage Java est "orienté objet", il est
entièrement basé sur la notion d'encapsulation, c'est-à-dire qu’il permet de définir des classes qui
contiennent des membres. Les membres sont soit des champs soit des méthodes. L'instance de la
classe (appelé objet) est alors implicitement passée en paramètre à la méthode.
Les champs (parfois appelé attribut) sont des variables qui définissent des caractéristiques propres à
chaque objet (variables d’instance) ou à un groupe d’objets de la classe (variables de classe). Ces
variables ont une portée différente suivant l’endroit où elles sont déclarées.
Les méthodes sont des fonctions qui définissent les comportements des objets de la classe.
Contrairement au C/C++, Java est « purement » orienté objet, c’est-à-dire qu’il est impossible de
définir des fonctions autres que des méthodes. Toutes les classes Java héritent d’une classe de base
unique : la classe Object du package [Link].
Fichier "[Link]"
class Point2D {
Il est possible d’imbriquer des classes dans des classes (Cf. chapitre 5.6.6 - Les classes intérieures).
Fichier "[Link]"
class Point2D {
double x,y;
}
Fichier "[Link]"
class Cercle {
Point2Dcentre= new Point2D();
double rayon;
Color couleur;
} On crée un objet Point2D. Sans cette
création, le programme génère une erreu
r
(mémoire non allouée) lors de
Fichier "[Link]"
l’affectation d’un des champs d’un obje
t
de la classe.
class Program {
staticpublicvoidmain (args[]) {
CercleC1= new Cercle();
CercleC2= new Cercle();
[Link].x=3.45;
...
[Link].y=3.56;
...
[Link]=[Link];
[Link]=[Link];
...
}
}
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 23/120
3.6.4.- Les variables de classe
Ces variables sont globales pour une classe et pour toutes les instances de cette classe. Les variables
de classe sont utiles pour faire communiquer entre eux différents objets d’une même classe ou pour
définir des propriétés communes à un groupe d’objets de la même classe. Pour différencier les
variables d’objet des variables de classe on utilise le mot-clé static
Fichier "[Link]"
class Point2D {
double x,y;
}
Fichier "[Link]"
class Cercle {
Point2Dcentre= new Point2D();
double rayon; Le champ couleur est une variable
staticColor couleur; de classe
}
Fichier "[Link]"
class Program {
publicvoidmain (args[]) {
CercleC1= new Cercle();
CercleC2= new Cercle();
On applique une couleur à
[Link].x=3.45; tous les objets de la classe.
[Link].y=3.56;
[Link]=[Link];
...
}
}
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 24/120
Fichier "[Link]"
class TestReference {
publicstaticvoidmain (args[]) {
CercleC1,C2;
C1= new Cercle();
C2=C1;
[Link]=3.0;
Dans la première partie du programme, on déclare deux variables de type Cercle (C1 et C2), on crée
un objet Cercle affecté à C1 et on affecte à C2 la valeur de C1. Comme on peut le constater par le
résultat à l’écran, la variable d’instance x a été modifiée pour les deux objets. L’affectation de la
valeur de C1 à C2 n’a pas créé un nouvel objet mais simplement une référence à l’objet pointé par
C1 (Cf. figure ci-dessous). Pour créer deux objets distincts, il faut utiliser l’opérateur new pour
chacun des objets.
Objet Cercle
C1
centre.x : 0.0
C2 centre.y : 0.0
rayon = 3.0
couleur …
L’utilisation des références prend toute son importance lorsque les arguments sont transmis
aux méthodes (Cf. chapitre 3.7.4)
3.6.6.- Les classes intérieures
Depuis la version 1.1, Java a introduit la notion de classes intérieures (inner classes). Java autorise
en effet la définition de classes à l’intérieur d’une classe. L'exemple suivant montre comment créer
ce type de classe.
class TopLevel {
class Internal {
int attribut=0;
}
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 25/120
Si l'on ne mentionne pas d'objet pour appliquer l'opérateur new, c'est la classe courante qui est prise
en considération. On peut alors écrire :
class TopLevel {
class Internal
{ int attribut=0;
}
Internal internal()
{ return new
Internal();
} static public void main(String
argv[]){
…
}
}
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 26/120
Fichier "[Link]"
class Point2D {
double x,y;
}
Fichier "[Link]"
class Cercle {
Point2Dcentre= new Point2D();
double rayon;
Color couleur;
Fichier "[Link]"
class Program {
publicvoidmain (args[]) {
CercleC= new Cercle();
[Link].x=3.45;
[Link].y=3.56;
Appel de la méthode Move appliquée
[Link](4.0,3.0); à l’objet C de la classe Cercle
...
}
}
Vous aurez sans doute remarqué l'utilisation de mot clé void. Celui-ci sert à définir un type de
retour nul (sans valeur) pour la méthode. Si une méthode doit retourner une valeur (non nulle), il
faut spécifier son type à cet endroit précis de la définition de la méthode.
3.7.2.- Les méthodes de classe
Comme les variables de classe, les méthodes de classe (à déclarer en static) s’appliquent à la
toute classe et non à ses instances. Ces méthodes servent surtout à grouper au même endroit, dans
une classe, des méthodes à caractère général. Par exemple, la classe Math définie dans [Link]
contient de nombreuses méthodes de classe. Il n’existe pas d’instance de la classe Math, mais on
peut utiliser directement ses méthodes. Par exemple la méthode max(…) :
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 27/120
Fichier "[Link]"
class Cercle {
Point2Dcentre= new Point2D();
double rayon;
Color couleur;
void Move(double x) {
centre.x=centre.x+x;
}
void Move() {
centre.x=centre.x+1.0;
centre.y=centre.y+1.0;
}
}
Fichier "[Link]"
class Program {
publicvoidmain (args[]) {
CercleC= new Cercle();
[Link].x=3.45;
[Link].y=3.56;
[Link](4.0,3.0);
... Appel des trois méthodes Move
[Link](2.0); appliquée à l’objet C de la classe Cercle
...
for(int i=1;i<=10;i++
)
[Link]();
}
}
3.7.4.- Le passage des paramètres
Lorsque vous appelez une méthode dont les arguments comportent un ou plusieurs objets, les
variables qui sont passées au corps de la méthode sont passées par référence. Ceci signifie qu’il n’y
a pas de copie locale (à la méthode) de l’objet passé en paramètre. Par conséquent, toute
manipulation de l’objet à l’intérieur de la méthode affecte l’objet original. Cette règle de passage
des paramètres s’applique à tous les objets ainsi qu’aux tableaux. Seul les variables de type simple
(entiers, réels, booléens et caractères) sont passées par valeur (on recopie localement la variable et
on travaille sur cette copie).
Notons au passage que la méthode main admet en paramètre un tableau de chaînes de caractères
("String argv[]"). Celui-ci contient les éventuelles options spécifiées sur la ligne de commande
lors du lancement de la machine virtuelle. Pour connaître la taille du tableau, il suffit de récupérer la
valeur renvoyée par [Link]. A titre d'information, le nom du paramètre peut être n'importe
quel nom, mais on utilise souvent argv, repris de la terminologie du langage C/C++.
Fichier "[Link]"
class Start {
static int a = 3;
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 29/120
Fichier "[Link]"
class Point2D {
double x,y; Le constructeur de la classe
Point2Dinitialise les valeurs
Point2D() { x=0.0,y=0.0; }
de x et de y.
}
Fichier "[Link]"
class Cercle {
Point2Dcentre;
double rayon;
Color couleur;
class Program {
[Link].x=3.45;
[Link].y=3.56; L’utilisateur de la classe
Cercle choisit le rayon et la
[Link](4.0,3.0); couleur deC2 (ici 3 et bleue)
...
[Link](3.4,3.6);
...
}
}
Sur cet exemple, on peut remarquer qu’un constructeur peut créer des objets qu'il utilise. On
s'aperçoit également que les constructeurs servent principalement à définir l'état initial des objets
instanciés. Il est bien sûr possible d’initialiser les variables lors de leur déclaration, mais le
constructeur permet de créer des objets « sur mesure ».
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 30/120
• Si vous en définissez au moins un, le constructeur par défaut (qui n'admet pas de paramètres)
n'est plus fourni. Si vous en avez utilité il vous faudra alors le définir explicitement.
3.8.2.- Le destructeur
Nous venons donc de voir que des constructeurs pouvaient être fournis pour permettre la création
d'objets. Parallèlement, un destructeur (et un seul) peut être défini, pour être utilisé lors de la
destruction de l'objet. Celui-ci doit forcément se nommer finalize, il ne prend aucun paramètre et ne
renvoie aucun type (void). Voici un petit exemple :
class Point2D {
...
void finalize() {
[Link]("Objet point (2D) détruit");
} .
..
3.9.- L’héritage
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 31/120
Classe A
La classe A est la super-classe de B
La classe B est une sous-classe de A
La classe B est la super-classe de C, D et E
Classe B
Les
classes C, D et E sont des sous-classes de B
Pour les variables d’instances, à la création d’un nouvel objet, un emplacement est attribué à chaque
variable définie dans la classe courante et dans toutes ses super-classes. Ainsi, la combinaison de
toutes les classes constitue un modèle de l’objet courant. De plus, chaque objet apporte
l’information appropriée à sa situation. Les méthodes opèrent de manière similaire. Les objets ont
accès à tous les noms de méthodes de leur classe et de leurs super-classes. Par contre, les définitions
de méthodes sont choisies dynamiquement (à l’exécution) au moment de l’appel. Si l’appel porte
sur un objet particulier, Java commence par chercher la définition de la méthode dans la classe de
l’objet. S’il ne la trouve pas, il poursuit sa recherche dans la super-classe et ainsi de suite (Cf. figure
1 page suivante).
Si deux méthodes ont la même signature (nom, nombre et type d’arguments) dans une sous-classe et
dans une super-classe, Java exécute la première trouvée en remontant la hiérarchie. On dit alors que
la méthode a été redéfinie (Cf. figure 2, page suivante).
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 32/120
Définition de la méthode
Classe
Classe Classe
Objet Objet
figure 1
Classe
La définition initiale est
remplacée par celle de la
méthode polymorphe
Définition de la
méthode initiale
Classe
Objet Objet
figure 2
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 33/120
3.9.3.- L’utilisation de l’héritage
Afin d’assimiler ce nouveau concept, considérons une extension du programme manipulant la classe
Cercle. Il s’agit d’introduire de nouvelles classes permettant de manipuler non seulement des
cercles mais aussi des carrés, des triangles et toute autre figure géométrique. L’idée la plus «
basique » consiste à définir une classe Cercle, une classe Carre, une classe Rectangle …
indépendantes les unes des autres. Ci-dessous un exemple de la définition d’une classe Carre et de
notre classe Cercle.
Si l'on regarde le code de la classe Cercle et celui de la classe Carre, on se rend compte
immédiatement de la similitude de la plupart des champs et des comportements. L’héritage nous
permet de regrouper les champs et des comportements communs dans une super-classe (appelons-là
Shape) dont les classes Cercle et Carre vont dériver. On dit encore que les classes Cercle et Carre
héritent des champs et des méthodes de la classe Shape.
En Java, pour signifier qu’une classe hérite d’une autre on utilise le mot-clé extends. Par exemple,
si la classe Cercle hérite de la classe Shape on écrira :
Un point important et souvent source d'erreur : on n'hérite en aucun cas des constructeurs. Si
vous ne spécifiez pas explicitement un constructeur particulier, vous ne pourrez pas l'utiliser.
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 34/120
Après utilisation de l’héritage, notre exemple devient :
class Shape{
Point2Dcentre; Champs communs aux
Color couleur; classes dérivées
Shape() {
centre= new Point2D();
couleur= [Link];
}
Shape(Color c) {
centre= new Point2D();
couleur=c;
}
void Move(double x, double y) {
centre.x=centre.x+x;
centre.y=centre.y+y;
} Méthodes communes
void Move(double x) { aux classes dérivées
centre.x=centre.x+x;
}
void Move() {
centre.x=centre.x+1.0;
centre.y=centre.y+1.0;
}
}
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 35/120
• un appel au constructeur de la classe mère peut uniquement se faire en première instruction
d'une définition de constructeur. Une conséquence évidente est qu'on ne peut utiliser qu'un
seul appel au constructeur de la classe mère.
3.9.4.- Un mot sur les conversions de type
On distingue le « Upcasting » du « Downcasting ».
Supposons une classe abstraite A et deux classes dérivées de cette classe abstraite B et C. Les
classes B et C peuvent être traitées comme des classes A, on peut donc écrire :
B b ;
b = new B (…) ; On convertit un objet de la classe dérivée en un objet de A a ; la
classe de base, c’est le « Upcasting » et dans ce cas il a = b ; n’y a pas de syntaxe
particulière.
Dans le sens contraire, un objet de la classe A peut être converti en un objet de la classe B
uniquement s’il a été défini au départ comme un objet de B et non de C. Dans ce cas («
Downcasting »), on utilise une syntaxe particulière (la même qu’en C/C++) :
Pour savoir à quelle classe appartient un objet, on utilise l’opérateur « instanceof » qui renvoie la
valeur true ou false. Exemple :
if (a
instanceof B)
{
… ; }
• le modificateur static (dont nous avons déjà parlé) utilisé pour les variables et les méthodes de
classe ;
• le modificateur final permettant de « figer » l’implémentation d’une classe, d’une méthode ou
d’une variable ;
• le modificateur abstract pour la création de classes ou de méthodes abstraites (que nous
verrons plus tard).
Il existe d’autres modificateurs que nous ne détaillerons pas : synchronized et volatile (utilisés
pour les threads) et native (utilisé pour la création de méthodes natives).
import [Link].*;
Le modificateur public
On utilise le mot clé public pour signifier qu’une classe, une méthode ou une variable sont
accessibles n’importe où dans « l’univers des classes Java » (même dans d’autres packages). Le
mot-clé public est le mode par défaut.
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 37/120
class Carre { public class Carre {
double cote ; Est équivalent à public double cote ;
Le modificateur private
Diamétralement opposé au modificateur public, il s’agit de la forme de protection la plus restrictive
qui limite la visibilité des méthodes et des variables d’instance à la classe dans laquelle elles sont
définies. Notons que les sous-classes ne peuvent hériter ni de variables privées, ni de méthodes
privées.
void Move { …}
…
}
Une règle pratique d’utilisation de la protection private veut que toute donnée ou comportement
interne à une classe, que les autres classes ou sous-classes n’ont pas besoin de modifier, doit être
private. Il faut bien garder à l’esprit que le rôle de l’encapsulation est justement de cacher à la vue
du monde extérieur certaines données propres aux objets afin de sécuriser leurs manipulations.
Une autre règle veut que toutes les variables d’instance d’une classe soient privées et que pour
obtenir ou pour changer ces variables on crée des méthodes spéciales non privées.
Le modificateur protected
Il s’agit d’une forme de protection entre classe et sous-classes. Le mot-clé protected signifie que
des méthodes et des variables d’une classe donnée demeurent accessibles à toute classe du même
package (différence avec C++), mais ne sont accessibles, en dehors du package, qu ‘aux sousclasses
de la classe à laquelle elles appartiennent.
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 38/120
3.10.2.- Le modificateur « static »
Ce modificateur est utilisé pour définir des variables ou des méthodes de classe (Cf. chapitre 3.7.2).
Le mot-clé static indique simplement que l’élément est stocké dans la classe. Il est possible de «
mixer » des modificateurs de contrôle d’accès avec static.
On utilise très rarement final pour les classes. Le seul intérêt est l’efficacité. En effet, dans le cas de
classes finales, on a la certitude que les instances n’appartiennent qu’à cette classe et on peut ainsi
optimiser le code relatif à ces instances. Il en est de même pour l’utilisation de final pour les
méthodes. Par contre, final est très utilisé pour les déclarations de constantes.
A noter qu’une méthode déclarée private est par définition finale, puisqu’on ne peut pas la dériver.
Le fait de déclarer une méthode private final est donc redondant mais autorisé par le compilateur.
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 39/120
3.11.- Les packages
Les packages permettent d’organiser des groupes de classes. Un package contient un nombre
quelconque de classes reliées entre elles par leur objectif, leur portée ou leur héritage.
Les packages deviennent intéressants à utiliser dès que l’application contient beaucoup de classes.
la commande import
Pour utiliser un package il faut faire appel à la commande import.
Il est possible d’importer une classe individuelle, par exemple : import [Link] ; ou
tout un package de classe, par exemple : import [Link].* ;
Noter que le caractère « * » est différent de celui utilisé à la suite d’un prompt pour spécifier le
contenu d’un répertoire. Par exemple, si l’on demande d’afficher le contenu du répertoire
classes/java/awt/*, cette liste inclut les fichiers .class et tous les sous répertoires tels que peer et
image. La commande import [Link].* ; importe toutes les classes publiques de ce package
mais pas les sous packages peer et image. L’import ne marche que « sur un niveau ».
Conflits de nom
Lorsqu’il y a un conflit de noms entre deux classes de packages différents, il suffit de préfixer le
nom de la classe par le nom du package pour lever l’ambiguïté. Exemple : deux packages ayant
chacun une classe « Name ».
import MonProjet.* ;
import SonProjet.* ;
Cette ligne signifie que toutes les classes appartenant au package MonProjet se trouvent dans un
répertoire MonProjet. Il est donc important de préciser dans la variable d’environnement
CLASSPATH l’ensemble des chemins des différents packages à importer.
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 40/120
3.12.- Classes abstraites et interfaces
3.12.1.- Les classes abstraites
Les classes abstraites sont des classes dont le seul but est de fournir des informations communes
aux sous-classes. Les classes abstraites n’ont pas d’instance, mais peuvent contenir tout ce qu’une
classe normale peut contenir (variables et méthodes de classe ou d’instance …). En plus elles
peuvent contenir des méthodes abstraites. Ces méthodes abstraites sont-elles aussi un moyen de
mettre à part, dans des super-classes, des comportements communs dont l’utilisation concrète est
définie dans les sous-classes. Les méthodes abstraites n’ont pas d’implémentation, elles sont
définies par leur signature (nom de la méthode, noms et types des paramètres éventuels). On
déclare les classes et les méthodes abstraites au moyen du modificateur abstract.
publicabstractclass MyAbstractClass {
int a ;
int a ;
Implémentationde la méthode
public int subClass (double x) {
abstraite dans une classe dérivée
} non abstraite
}
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 41/120
public interface I1
{ void m();
}
void g() {
// Le code de g
}
}
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 42/120
3.13.1.- Les exceptions prédéfinies
En Java, les exceptions sont de véritables objets. Ce sont des instances de classes qui héritent de la
classe Throwable. Lorsqu’une exception est levée, une instance de la classe Throwable est créée.
Ci-dessous un aperçu de la hiérarchie des classes pour les exceptions.
Throwable
Error Exception
RuntimeException IOException
AWTException
EOFException
ClassNotFoundException
ClassNotFoundException
Les instances de la classe Error sont des erreurs internes à la machine virtuelle Java. Elles sont
rares et fatales.
Les sous-classes de la classe Exception sont réparties en deux catégories :
• les exceptions d’exécution (runtime) sont souvent l’effet du manque de robustesse du code. Par
exemple l’exception NullPointerException est levée lorsque l’on manipule un objet non
instancié (oubli de l’instruction new) ;
• les autres exceptions correspondent à des événements anormaux échappant au contrôle du
programme. Par exemple, l’exception EOFException est levée si on essaie de lire au-delà d’un
fichier.
Traiter les exceptions levées : les mots clés try, catch et finally
try{
section de code dans laquelle on s'attend à ce
qu'une exception soit levée.
}
catch(IOExceptione){
Section de code à exécute
r
pour une exception donnée.
}
catch(NullPointerException
pe){
Il est possible de capturer plusieurs
} exceptions en multipliant les clauses
catch.
finally{
Ce bloc (facultatif) permet d'introduire un code qui sera
} exécuté quoiqu’il arrive (qu’une exception soit levée ou non).
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 43/120
Intercepter une exception : le mot clé throws
Si une méthode est susceptible de lever une exception et si elle ne peut pas la traiter, elle se doit de
prévenir le système qu'elle relaye cette tâche. Pour ce faire, on utilise le mot clé throws dans la
définition de la méthode. Ce mot clé permet d'avertir le système qu'une certaine catégorie
d'exception ne sera pas traitée en local (dans l'exemple suivant, l'ensemble des exceptions liées aux
entrées/sorties).
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 44/120
Nous aurons l’occasion de découvrir d’autres exemples d’utilisation des exceptions dans le chapitre
suivant consacré aux fichiers.
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 45/120
• une classe CoordBin contenant la description d’un enregistrement de type coordonnées (x, y, z
sont codés en binaire (double)…) et la définition de méthodes permettant de lire et d’écrire un
tel enregistrement dans un fichier binaire ;
• une classe ficBinaire montrant l’utilisation des méthodes de lecture et d’écriture. Il s’agit d’un
programme qui écrit des enregistrements de type CoordBin dans un fichier, qui les relit dans le
même fichier avant d’afficher les valeurs à l’écran.
Fichier [Link]
[Link].*;
publicclass CoordText { xt, yt et ztsont des coordonnées codés en chaînes de caractères.
pres_z est un caractère ("0" ou "1") indiquant la présence
uo
String xt,yt,zt,pres_z; non de la troisième dimension.
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 46/120
Fichier [Link]
import On importe deux packages.
[Link].*;
import [Link].*;
String("[Link]"); CoordText
try {
// Ouverture d'un fichier texte de nom "nomfic" en mode lecture
RandomAccessFile ficText = new RandomAccessFile(nomfic,"r");
// Ouverture d'un fichier texte de nom "nomfic2" en mode ecriture
RandomAccessFile ficText2 = new RandomAccessFile(nomfic2,"rw");
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 47/120
for (int i=1;i<=NbLigne;i++)
{ On lit NbLigneobjets de type CoordText
dans le fichier [Link], puis on écrit les
[Link](ficText); objets lus dans un autre fichier texte
//Affichageal'ecranpourverification
[Link]
([Link]+"");
[Link]
([Link]+"");
[Link]
([Link]+"");
[Link]
(coord.pres_z);
Ecriture des mêmes objets dans un format
[Link](ficText2); différent défini d ans la classe
CoordText.
}
Fichier [Link]
import [Link].*;
public CoordBin() {
x=0.0;
y=0.0; Le constructeur initialise les champs de la classe z=0.0; pres_z=false;
}
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 48/120
public void readCoordBin (RandomAccessFile ficBin) { try
{
x=[Link]();
y=[Link](); Lit dans un fichier binaire un objet de type
z=[Link](); CoordBin ayant été écrit par la méthode
pres_z=[Link](); writeCoordBin
}
catch (IOException e) {
[Link]("Erreur a la lecture d'un objet de type Coord");
}
}
}
Fichier [Link]
import [Link].*; import
[Link].*; public class
ficBinaire {
// Exemple d'ecriture/lecture d'objets binaires dans un fichier.
public static void main (String args[]) {
String nomfic = new String("[Link]");
Ouverture du fichier [Link] en
CoordBin coord = new CoordBin ();
mode lecture/écriture ("rw"). Si le
try { fichier n'existe pas, il est crée.
int NbEnr =
(int)[Link]()/[Link]; for (int
i=1;i<=NbEnr;i++) { [Link](ficBin);
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 49/120
[Link]("Erreur a l'ouverture du fichier
");
[Link](nomfic);
}
}
}
3.15.- La sérialisation
3.15.1.- Introduction
Nous avons vu dans le chapitre précédent (les bases des flux en Java) comment écrire ou lire des
caractères (du texte) à partir d’un fichier stocké sur un disque. Nous allons voir dans ce chapitre un
principe de flux plus avancé : comment écrire et lire des objets dans un fichier, c’est la
sérialisation. Les fichiers que vous désérialisez seront lus et chargés dans votre programme en
conservant l’état dans lequel ils étaient quand ils ont été sérialisés. Par exemple, imaginez que vous
créez un objet de type Integer (pas int), que vous l’initialisez avec la valeur 0, que vous le modifiez
ensuite pour avoir la valeur 1, et que, pour finir, vous le sérialisez. Quand vous désérialiserez cet
objet, il aura la valeur 1. Nous allons voir comment créer des objets sérialisables, comment les
sérialiser et les désérialiser, et comment empêcher que certaines variables de ces objets ne soient
sérialisées.
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 50/120
class AutreClasse
{
MaClasse mc = new MaClasse() ; // instance de la classe MaClasse
public AutreClasse() { // constructeur de la classe autreClasse
try {
// créer un nouveau fichier
FileOutputStream fos = new FileOutputStream("[Link]");
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 51/120
readChar() o readDouble() o readFloat() o readInt() o
readLine() o readLong() o readShort() o
readUnsignedByte() (retourne int, un vestige du C/C++…)
o readUnsignedShort() (de même)
Vous pouvez sérialiser plusieurs objets dans un même fichier en ayant recours plusieurs fois à la
méthode writeObject() ou en utilisant les autres méthodes de ObjectOutputStream. Dans ce cas,
vous devez appeler plusieurs fois les méthodes de ObjectInputStream. La règle est la suivante : le
premier objet sérialisé est le premier fichier désérialisé, le second écrit est le second lu, etc….
//…
//…
}
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 52/120
4.- Java et les Interfaces
Homme - Machine (IHM)
Dans la création d’IHM, on distingue deux grandes parties : la partie statique qui constitue l’aspect
graphique de l’interface (menu, boutons, boîtes de dialogues …) et la partie dynamique qui définit
et gère les actions à effectuer en fonction d’événements générés par l’utilisateur (ex : un clic souris
sur un bouton provoque l’ouverture d’une boîte de dialogue).
Java met à la disposition du programmeur le package Swing (extension de l’ancien package AWT
Abstract Windowing Toolkit ) qui offre tous les composants nécessaires à la création des IHM. Les
principaux composants (menu, bouton …) sont regroupés dans des classes de ce package (classe
JMenu, classe JButton …). La classe Event est utilisée pour la gestion des événements.
Les deux chapitres suivants n’ont pas pour but de dresser une liste exhaustive des composants de
Swing et de leur utilisation, mais d’expliquer les mécanismes généraux permettant la création
d’IHM. Pour connaître l’ensemble des composants (classes, méthodes, attributs) il faut consulter
l’API Java (fichier HTML) livrée avec tout compilateur Java. Notons que les composants Swing ne
sont accessibles que depuis la version 1.2 (Java 2). La plupart des composants de Swing existe dans
le package AWT mais ce dernier présente une contrainte : certaines méthodes d’affichage des
composants font directement appel au système d’exploitation, ce qui ne permet pas d’assurer 100%
de portabilité au niveau de l’aspect graphique (un bouton n’aura pas tout à fait le même aspect sous
Unix et sous Windows).
C’est pour pallier ce petit défaut que le package Swing a été créé. Pour assurer la portabilité 100%,
un bouton (ou tout autre composant graphique) est dessiné non plus par les fonctions du système
d’exploitation, mais par Java lui-même (ce qui a un prix en terme de temps d'exécution).
Les mécanismes de gestion d'évènements et de positionnement des composants de Swing sont le
mêmes que ceux de l’AWT.
Le package Swing est plus riche que l’AWT (plus de composants, plus de méthodes …) ce qui
incite désormais le programmeur à utiliser ce nouveau package. L’utilisation de l’un ou de l’autre
des packages dépend de ce que l’on veut privilégier (la vitesse d’exécution ou la richesse et la
portabilité de l’interface). Le nom des classes est quasiment identique. Le nom de la plupart des
classes Swing est précédé d’un « J ». Exemple : la classe Button d’AWT est nommée JButton
dans Swing.
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 54/120
public static void main (String args []) {
new ButtonTest ("Mon Bouton") ;
} Le programme principal se contente de créer un objet ButtonTest
}
…
setLayout (new FlowLayout()) ;
[Link] (new Button ("un")) ;
[Link] (new Button ("deux")) ;
[Link] (new Button ("trois")) ;
[Link] (new Button ("quatre")) ;
[Link] (new Button ("cinq")) ;
setSize (400,100);
setVisible(true); …
• Le placement GridLayout : les composants sont rangés en ligne et en colonnes et ils ont la
même largeur et la même hauteur. Ils sont placés de gauche à droite puis de haut en bas.
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 55/120
…
setLayout (new GridLayout(2,3)) ;
[Link] (new Button ("un")) ;
[Link] (new Button ("deux"))
[Link] (new Button ("trois")) ;
[Link] (new Button ("quatre")) ;
[Link] (new Button ("cinq")) ;
setSize (400,100);
setVisible(true);
…
• Le placement BorderLayout : permet de placer les composants sur les bords et au centre. On
indique la politique de positionnement, puis chaque fois qu’on ajoute un composant on indique
où le placer.
…
setLayout (new BorderLayout()) ;
[Link] ("North",new Button ("un")) ;
[Link] ("East",new Button ("deux"))
[Link] ("Center",new Button ("trois")) ;
[Link] ("West",new Button ("quatre")) ;
[Link] ("South",new Button ("cinq")) ;
setSize (400,100);
setVisible(true);
…
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 56/120
absolues. Lorsqu’on « retaille » la fenêtre les dimensions des composants ne sont pas
recalculées.
4.1.3.- Conclusion
Créer la partie statique d’une interface utilisateur est relativement simple en Java. On dispose d’un
nombre important de composants que l’on peut disposer à sa guise grâce aux différentes politiques
de placement prises en compte par le langage. Chaque composant possède des attributs et des
méthodes permettant de modifier leur aspect et leurs comportements.
Bien que les mécanismes mis en œuvre soient simples, le programmeur doit garder à l’esprit que la
réalisation d’une interface utilisateur « professionnelle » (plusieurs niveaux de conteneurs, des
menus, des boutons, des boîtes dialogues …) est une tâche difficile et longue.
La partie statique de votre interface étant terminée, il reste à l’animer …
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 57/120
import [Link].*;
publicclass MyListener extends EventFrame implementsActionListener{
EventFramef;
MyListener(EventFrameframe){
f=frame;
}
publicvoidactionPerformed (ActionEvent event) {
if( [Link]()== [Link]){
Il s’agit bien des boutons de
[Link]("OK"); l’objet f, copie locale de
} l’objet frame passé en
if( [Link]()== [Link]){ paramètre du constructeur.
[Link]("Cancel");
}
}
}
4.2.3.- Enregistrement des écouteurs
Le code ci-dessus correspond à la définition d’un écouteur d’événements (objet de classe
MyListener) susceptible de répondre à un click sur le bouton mais on a pas « dit au bouton » qu'il
fallait avertir l'écouteur en cas de clic souris. Chaque écouteur doit être enregistré auprès des objets
qu'il est censé écouter (un écouteur peut écouter plusieurs sources d'évènements - une source
d'évènement peut alerter plusieurs écouteurs). Prenons l’exemple complet en y ajoutant une ligne
qui permet d’enregistrer l’écouteur d’événement aux boutons en question et l’ensemble du code de
la classe représentant la frame.
On obtient :
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 59/120
4.3.- Les menus et les boites de
dialogue
Pour les menus dynamiques (pop-up), le processus de création est le même, il faut simplement
ajouter les éléments de menu à un menu dynamique (JPopupMenu) et non à un barre de menu. Il
faut également inscrire le listener (qui hérite de MouseAdapter) pour les événements de «
relâché de souris » (mouseReleased) et afficher le menu dynamique à la position de la souris.
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 60/120
class EventHandler implements ActionListener
{ public void actionPerformed (ActionEvente) {
new TestModalDialog ("Test", } [Link]);
}
[Link]
publicclass appliMenu {
publicstaticvoidmain(String[] args){
Frame1frame= new Frame1();
[Link](); Création d’un objet « fenêtre cadre »
}
}
Rafraîchissement de l’écran
[Link]
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 61/120
[Link]
[Link].*;
[Link].*;
public class MenuBarre {
[Link](jMenu1);
[Link](jMenu2); Construction d’un objet écouteur d’événements
[Link](jMenuItem1);
[Link](jMenuItem2);
[Link](jMenuItem5);
[Link](jMenuItem3);
[Link](jMenuItem4);
[Link](jMenuBar1);Ajout de l’écouteur au sous-menu « Nouveau »
[Link]
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 62/120
import [Link].*;
import [Link].*;
[Link]
import [Link].*;
import [Link].*;
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 63/120
[Link](200,100);
Construction et affichage d’une boite de dialogue
[Link](true);
}
}
[Link]
[Link].*;
[Link].*;
[Link].*;
publicclass popupAdapterextendsMouseAdapter{
private JPopupMenup;
popupAdapter(JPopupMenupopup)
{
p=popup;
}
publicvoidmouseReleased (MouseEvente) {
if ([Link]
()) {
[Link]((Component)[Link](),[Link](),[Link]());
}
} Affichage du menu pop-up quand on relâche le
} bouton droit de la souris.
Ouverture de la boite de dialogue après sélection de l’item « Nouveau » dans le menu « Fichier »
:
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 64/120
4.3.4.- Une boite de dialogue particulière :
JOptionPane
Cette classe est utilisée pour les messages à l’écran, les avertissements … Son utilisation est très
simple :
Le premier paramètre est la frame propriétaire (à activer quand la boite se referme – null signifie
qu’aucune frame n’est active). Les deuxième et troisième sont le titre de la fenêtre et le message, le
dernier paramètre est le type du message (parmi ERROR_MESSAGE, INFORMATION_MESSAGE,
WARNING_MESSAGE, QUESTION_MESSAGE, PLAIN_MESSAGE).
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 65/120
4.4.- Les environnements de
développement intégrés
Ce sont des logiciels d’aide à la programmation d’applications importantes et plus particulièrement
d’aide à la création d’IHM. Ces logiciels utilisent tous les concepts que nous venons de voir dans
les chapitres précédents. Il s’agit de soulager la tâche du programmeur en générant le squelette du
code correspondant à une IHM. Grâce à ce genre d’outils, il est possible de réaliser la maquette
d’une IHM en quelques clics de souris. On ajoute ensuite de manière très simple les comportements
en fonction des actions que l’on veut effectuer et l’outil génère le code correspondant. Il reste au
programmeur à remplir le corps des méthodes réalisant les actions. Le programmeur n’a pas besoin
de connaître le fonctionnement exact du système de gestion des événements pour réaliser une
interface utilisateur puisque toute cette partie est générée automatiquement.
Le produit de Borland, Jbuilder, est également gratuit (sauf la version professionnelle) et gourmand
en mémoire (moins que Forte 1.0). Il s’inspire des générateurs permettant de programmer des IHM
en C++ (C++Builder, VisualC++) et peut paraître plus familier à certains. La dernière version est la
version 7.
Il est possible de le télécharger à l’adresse [Link] Autres
sites très intéressants concernant JBuilder (version 4 et 5). [Link] et
[Link]
Le produit de Sun, Forte 1.0 est gratuit (logiciel et documentations) et très simple à utiliser. Il est
malheureusement très gourmand en mémoire (compter minimum 128 MO, 256MO pour être à
l’aise). Il est possible de le télécharger à l’adresse [Link]
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 66/120
5.- Java : programmation
avancée
Sur certains systèmes, la Machine Virtuelle Java ne partage pas d'elle-même le temps du processeur
entre les threads susceptibles d'être exécutés. Si vous voulez que vos programmes Java se
comportent de façon similaire sur tous les systèmes, vous devez céder le contrôle régulièrement
dans vos threads avec les méthodes telles que sleep() ou yield(), pour permettre aux autres
threads en attente, de s'exécuter. Sinon, tant que l'exécution d'un thread n'est pas interrompue, les
autres threads resteront en attente !
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 67/120
méthode run()). La seconde méthode est très utile : elle permet d'ajouter les fonctionnalités des
threads à une classe existante, dans une classe dérivée.
Un certain nombre de méthodes sont nécessaires pour contrôler l'exécution d'un thread, en voici les
principales :
start() : Cette méthode permet de démarrer effectivement le thread sur lequel elle est invoquée,
ce qui va provoquer l'appel de la méthode run () du thread. Cet appel est obligatoire pour démarrer
l'exécution d'un thread. En effet, la création d'un objet de classe Thread ou d'une classe dérivée de
Thread (par exemple, grâce à l'appel new Thread ()) ne fait que créer un objet sans appeler la
méthode run ().
stop(): Cette méthode permet d'arrêter un thread en cours d'exécution. Elle est utile
principalement pour stopper des threads dont la méthode run() ne se termine jamais. Néanmoins,
cette méthode est « deprecated » (non recommandée) dans la dernière version du JDK. Motif
invoqué par les concepteurs : « Deadlock-prone » (sujet au blocage du système). Les méthodes «
deprecated » sont détaillées en annexe (9.2).
sleep() : Ces méthodes static permettent d'arrêter le thread courant pendant un certain laps de
temps, pour permettre ainsi à d'autres threads en attente, de s'exécuter. Par exemple, une fois mis à
jour une horloge par un thread, celui-ci peut arrêter son exécution pendant une minute, en attendant
la prochaine mise à l'heure.
yield() : Cette méthode static permet au thread courant de céder le contrôle pour permettre à
d'autres threads en attente, de s'exécuter. Le thread courant devient ainsi lui-même en attente, et
regagne la file d'attente. De manière générale, vos threads devraient s'arranger à effectuer des séries
d'instructions pas trop longues ou à entrecouper leur exécution grâce à des appels aux méthodes
sleep() ou yield().Il faut éviter de programmer des séries d'instructions interminables sans appel
à sleep() ou yield(), en pensant qu'il n'y aura pas d'autres threads dans votre programme. La
Machine Virtuelle Java peut avoir elle aussi des threads système en attente et votre programme
s'enrichira peut-être un jour de threads supplémentaires...
setPriority() : Permet de donner une priorité à un thread. Le thread de plus grande priorité sera
toujours exécuté avant tous ceux en attente.
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 68/120
5.1.4.- Les états d’un thread
Pendant son existence, un thread passe par plusieurs états qu'il est utile de connaître pour bien
comprendre les threads et leurs utilisations. La figure suivante représente l'ensemble des états
possibles d'un thread et les transitions existantes pour passer d'un état à un autre. Ce modèle peut
sembler complexe mais il faut garder à l’esprit que pour la plupart des utilisations, seules les
méthodes start(), stop()(« deprecated »), yield() et sleep () sont nécessaires. A noter
que les méthodes resume() et suspend() sont également « deprecated ».
Quand un nouveau thread est créé, il est dans l'état nouveau thread et ne devient exécutable
qu'après avoir appelé la méthode start() sur ce nouveau thread.
Parmi tous les threads dans l'état exécutable, le système donne le contrôle au thread de plus grande
priorité, ou à priorité égale, celui en tête de file d'attente, parmi les threads dans l'état exécutable. Le
thread qui a le contrôle à un moment donné est le thread courant. Le thread courant en cours
d'exécution cède le contrôle à un autre thread exécutable dans l'une des circonstances suivantes :
• A la fin de la méthode run(), le thread passe dans l'état mort ;
• A l'appel de yield(), le thread passe dans l'état exécutable et rejoint la fin de la file d'attente ;
• Sur les systèmes permettant de partager le temps d'exécution entre différents threads, le thread
passe dans l'état exécutable après qu'un certain laps de temps se soit écoulé ;
• En attentant que des opérations d'entrée/sortie se terminent, le thread passe dans l'état bloqué ;
• A l'appel de sleep(), le thread passe dans l'état bloqué pendant le temps spécifié en argument,
puis repasse à l'état exécutable, une fois ce délai écoulé ;
• A l'appel d'une méthode synchronized sur un objet Objet1, si Objet1 est déjà verrouillé par
un autre thread, alors le thread passe dans l'état bloqué tant que Objet1 n'est pas déverrouillé
(voir le chapitre suivant sur la synchronisation des threads) ;
• A l'invocation de wait() sur un objet, le thread passe dans l'état en attente pendant le délai
spécifié en argument ou tant qu'un appel à notify() ou notifyAll() n'est pas survenu (cf. la
synchronisation des trheads). Ces méthodes sont déclarées dans la classe Object ;
• A l'appel de stop(), le thread passe dans l'état mort ;
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 69/120
• A l'appel de suspend(), le thread passe dans l'état bloqué et ne redevient exécutable qu'une fois
que la méthode resume() a été appelée.
Les deux derniers méthodes ne sont pas static et peuvent être invoquées aussi sur les threads
exécutables qui ne sont en cours d'exécution.
5.1.5.- La synchronisation des threads
Tous les threads d'une même Machine Virtuelle partage le même espace mémoire et peuvent donc
avoir accès à n'importe quels méthode ou champ d'objets existants. Ceci est très pratique, mais dans
certains cas, vous pouvez avoir besoin d'éviter que deux threads n'aient accès n'importe quand à
certaines données. Si par exemple, un thread threadCalcul a pour charge de modifier une champ
var1 qu'un autre thread threadAffichage a besoin de lire pour l'afficher, il semble logique que
tant que threadCalcul n'a pas terminé la mise à jour ou le calcul de var1, threadAffichage soit
interdit d'y avoir accès. Vous aurez donc besoin de synchroniser vos threads.
La synchronisation des threads se fait grâce au mot clé synchronized, employé principalement
comme « modifieur » d'une méthode. Soient une ou plusieurs méthodes methodeI() déclarées
synchronized, dans une classe Classe1 et un objet objet1 de classe Classe1. Comme tout objet
Java comporte un verrou (lock en anglais) permettant d'empêcher que deux threads différents n'aient
un accès simultané à un même objet, quand l'une des méthodes methodeI() synchronized est
invoquée sur objet1, deux cas se présentent :
• L’objet objet1 n'est pas verrouillé. Dans ce cas, le système pose un verrou sur cet objet puis
la méthode methodeI() est exécutée normalement. Quand methodeI() est terminée, le
système retire le verrou sur Objet1.
La méthode methodeI () peut être récursive ou appeler d'autres méthodes synchronized de
Classe1. A chaque appel d'une méthode synchronized de Classe1, le système rajoute un
verrou sur Objet1, retiré en quittant la méthode. Quand un thread a obtenu accès à un objet
verrouillé, le système l'autorise à avoir accès à cet objet tant que l'objet a encore des verrous ;
• L’objet objet1 est déjà verrouillé . Si le thread courant n'est pas celui qui a verrouillé
objet1, le système met le thread courant dans l'état bloqué, tant que objet1 est verrouillé. Une
fois que objet1 est déverrouillé, le système remet ce thread dans l'état exécutable, pour qu'il
puisse essayer de verrouiller objet1 et exécuter methodeI ().
Si une méthode synchronized d'une classe Classe1 est également static, à l'appel de cette
méthode, le même mécanisme s'exécute mais cette fois-ci en utilisant le verrou associé à la classe
Classe1.
Si Classe1 a d'autres méthodes qui ne sont pas synchronized, celles-ci peuvent toujours être
appelées n'importe quand, que objet1 soit verrouillé ou non.
Comme il est expliqué dans le chapitre précédent, synchronized permet d'éviter que plusieurs
threads aient accès en même temps à même objet, mais ne garantit pas l'ordre dans lequel les
threads ces méthodes seront exécutées. Pour cela, il existe plusieurs méthodes de la classe Object
qui permettent de mettre en attente volontairement un thread sur un objet (méthode wait()) et de
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 70/120
prévenir des threads en attente sur un objet que celui-ci est à jour (méthodes notify() ou
notifyAll()). Ces méthodes ne peuvent être invoquées que sur un objet verrouillé par le thread
courant, c'est-à-dire que le thread courant est en train d'exécuter une méthode ou un bloc
synchronized, qui a verrouillé cet objet. Si ce n'est pas le cas, une exception
IllegalMonitorStateException est déclenchée.
Quand wait() est invoquée sur un objet objet1 (objet1 peut être this), le thread courant perd le
contrôle, est mis en attente et l'ensemble des verrous d'objet1 est retiré. Comme chaque objet Java
mémorise l'ensemble des threads mis en attente sur lui-même, le thread courant est ajouté à la liste
des threads en attente de objet1. objet1 étant déverrouillé, un des threads bloqués parmi ceux qui
désiraient verrouiller objet1 peut passer dans l'état exécutable et exécuter une méthode ou un bloc
synchronized sur objet1.
Un thread thread1 mis en attente est retiré de la liste d'attente de objet1, quand une des trois
raisons suivantes survient :
• thread1 a été mis en attente en donnant en argument à wait() un délai qui a fini de s'écouler ;
• Le thread courant a invoqué notify() sur objet1, et thread1 a été choisi parmi tous les
threads en attente ; • Le thread courant a invoqué notifyAll () sur objet1.
thread1 est mis alors dans l'état exécutable, et essaye de verrouiller objet1 pour continuer son
exécution. Quand il devient le thread courant, l'ensemble des verrous qui avait été enlevé d'objet1
à l'appel de wait(), est remis sur objet1, pour que thread1 et objet1 se retrouvent dans le même
état qu'avant l'invocation de wait().
5.1.6.- Conclusion
La programmation de la synchronisation des threads est une tâche ardue, sur laquelle vous passerez
sûrement du temps ... Si vous désirez l'utiliser, il faut bien s'imaginer par quels états vont passer les
threads, quelle implication aura l'utilisation des méthodes wait() et notify() sur l'ordre
d'exécution des instructions du programme, tout en gardant bien à l'esprit que ces méthodes ne
peuvent être invoquées que sur des objets verrouillés.
Le piège le plus classique est de se retrouver avec un « deadlock » parce que les threads sont tous
en attente après avoir appelé chacun d'eux la méthode wait().
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 72/120
• créées de toute pièce grâce aux méthodes createImage() des classes Component et Toolkit.
Ces images sont souvent utilisées comme buffer ou comme bitmap affichés à l'écran qu'une fois
leur dessin terminé.
Les méthodes drawImage() de la classe Graphics permettent d'afficher une image à un point
donné en la redimensionnant éventuellement (exemple d’applet – Cf. chapitre A.3.2).
• Soit à l'appel de l'une des méthodes drawImage() de la classe Graphics avec en paramètre une
image img. Si img n'est encore pas chargée, la méthode drawImage() débute le chargement de
l'image de manière asynchrone et rend la main. Le dernier paramètre de drawImage() doit être
une instance d'une classe implémentant l'interface ImageObserver comme par exemple la classe
Component. Cette interface ne déclare qu'une seule méthode imageUpdate() et
l'implémentation de cette méthode dans la classe Component redessine le composant pour mettre
à jour le dessin de l'image au fur et à mesure de son chargement. Donc, si vous donnez en
dernier paramètre le composant dans lequel l'image est affichée, l'image sera affichée
automatiquement aussitôt qu'elle est disponible (exemple Cf. chapitre A.3.2) ;
ImageObserver observer) ou
prepareImage (Image img, int width, int height, ImageObserver observer) des
classes Component et Toolkit. Ces méthodes permettent de débuter le chargement de l'image
img par un autre thread de manière asynchrone et rend la main. Les paramètres width et height
permettent de redimensionner l'image dès son chargement. Le dernier paramètre observer doit
être une instance d'une classe implémentant l'interface ImageObserver et permet de surveiller
l'état du chargement de l'image. La classe Component implémentant cette interface, vous pouvez
utiliser un composant comme paramètre ;
De plus, les méthodes checkImage () des classes Component et Toolkit permettent de vérifier
l'état du chargement d'une image. Ces méthodes prennent en dernier paramètre une instance d'une
classe implémentant l'interface ImageObserver, dont la méthode imageUpdate () est appelée pour
lui communiquer l'état de l'image.
5.3.3. – La création d’images
Une image peut être créée grâce à la méthode createImage (int width, int height) de la
classe Component. Cette méthode crée une image vierge dans laquelle vous pouvez dessiner grâce
aux méthodes de dessin de la classe Graphics. Il existe une deuxième version de la méthode
createImage() disponible dans les classes Component et Toolkit : createImage
(ImageProducer producer). Le paramètre producer doit être d'une classe qui implémente l'interface
ImageProducer. Le package [Link] fournit deux classes qui implémentent cette
interface :
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 73/120
• La classe MemoryImageSource permet de créer une image initialisée avec un tableau décrivant
la couleur de chacun des points d'une image ;
• La classe FilterdImageSource permet de créer une image qui est le résultat de l'application
d'un filtre sur une image existante.
Pour plus d'information sur le fonctionnement du filtrage d'images, voir le tutoriel Java livré avec le
JDK.
L'utilisation des threads et des images permet de réaliser rapidement des animations en Java.
Comme le montrent l’exemple du chapitre A.3.2, le principe de programmation d'une animation est
presque toujours le même : vous créez un thread dont la méthode run() utilise une boucle qui à
chaque tour affiche une nouvelle image puis arrête le thread courant pendant un laps de temps avec
la méthode sleep() de la classe threads.
Bien que comme au cinéma, une animation sera en apparence bien fluide à 25 images par seconde
(équivalent à un laps de temps entre chaque image de 40 millisecondes), évitez un laps de temps de
rafraîchissement aussi court, car les machines ont souvent du mal à suivre.
Si vous utilisez la méthode repaint() (comme dans la plupart des cas) pour mettre à jour l'image
d'une animation, le « redessin » du fond du composant effectué par la méthode update() de la
classe Component est inutile si vous devez transférer une image à l'écran occupant toute la surface
du composant. N'oubliez pas alors de surcharger la méthode update() pour qu'elle appelle
directement la méthode paint() sans redessiner le fond de l'image. Ceci évitera un clignotement
désagréable.
Le double buffering
L’exemple qui suit montre comment programmer le « double buffering » pour gérer l'animation
d'une image générée par un programme. Le double buffering a pour but d’éviter l'effet de
clignotement d'une animation (exemple : un texte défilant horizontalement). Le principe est simple :
au lieu de dessiner directement à l'écran un dessin qui évolue à chaque laps de temps, on utilise une
image dans laquelle on dessine puis qu’on transfère à l'écran.
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 74/120
public class ScrollText extends Applet implements Runnable
{ private String texte; private Thread threadAnimation;
private Dimension tailleApplet; private int
positionTexte, largeurTexte; private FontMetrics
metrics;
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 75/120
dans la classe Graphics. Le constructeur de la classe Graphics étant déclaré protected, il est
impossible de construire directement des objets de type Graphics. On obtient un tel objet (appelé
« contexte graphique ») grâce à la méthode getGraphics() de la classe Component ou, dans le cas
d’une applet (Cf. chapitre 6), comme paramètre de la méthode paint() (obligatoirement présente
dans l’applet – le contexte graphique est initialisé dans le fichier HTML et passé en paramètre de la
méthode paint()). Pour une application, il faut récupérer le contexte graphique d’un composant
après l’avoir affiché en appelant la méthode getGraphics sur le composant. La méthode
d’affichage du composant initialise le contexte graphique.
Pour une applet :
[Link] (20,20,60,60) ;
…
}
Si la couleur de l’objet n’est pas standard, il faut créer un objet Color en utilisant directement les
valeurs RVB de la couleur dans le constructeur de l’objet Color.
Segments et suites de segments drawLine(int, int, int, int) permet de tracer un segment. Les 4
paramètres sont les coordonnées des extrémités. Pour une suite de segments, on pourra utiliser
drawPolyline(int[], int[], int) les tableaux d'entiers passés en paramètre contiennent les
coordonnées des points à relier; le 3ème paramètre est le nombre de points; si le premier et le
dernier point sont confondus, on retrouve drawPolygon. Rectangles drawRect(int, int, int, int) et
fillRect(int, int, int, int) permettent de dessiner et de remplir des rectangles aux côtés verticaux et
horizontaux. Les deux premiers paramètres représentent les coordonnées du coin supérieur gauche,
les deux autres représentent la largeur et la hauteur.
On peut obtenir des rectangles à coins arrondis avec les méthodes drawRoundRect(int, int, int, int,
int, int) et fillRoundRect(int, int, int, int, int, int). Dans ce cas les deux derniers paramètres
définissent le paramétrage des arrondis.
On peut également dessiner des rectangles avec effet 3D (on n'utilise pas la même couleur pour les
4 côtés) en utilisant draw3DRect(int, int, int, int, boolean) et fill3DRect(int, int, int, int, boolean); le
5ème paramètre de type boolean permet de choisir entre deux effets : en relief ou en creux.
Polygones drawPolygon(int[], int[], int) et fillPolygon(int[], int[], int) permettent de dessiner et de
remplir des polygones. Les deux tableaux d'entiers contiennent les coordonnées des sommets, le
3ème paramètre contient le nombre de sommets.
On peut aussi utiliser la classe Polygon et les méthodes drawPolygon(Polygon) et
fillPolygon(Polygon)
Ellipses et cercles drawOval(int, int, int, int) et fillOval(int, int, int, int) permettent de dessiner et de
remplir des ellipses inscrites dans le rectangle dont les caractéristiques sont passées en paramètre.
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 77/120
Pour obtenir un cercle, il suffit de prendre la largeur égale à la hauteur, donc les deux derniers
paramètres égaux. Arcs
drawArc(int, int, int, int, int, int) et fillArc(int, int, int, int, int, int) permettent de dessiner des arcs et
de remplir des secteurs circulaires. Les 4 premiers paramètres définissent l'ellipse utilisée, les deux
derniers les angles de début et de fin de l'arc.
}
}
Les méthodes scale, rotate, translate et shear de la classe Graphics2D choisissent une
transformation de coordonnées pour le contexte graphique en fonction de l’une ou l’autre des quatre
transformations fondamentales. Il est également possible de composer des transformations. D’un
point de vue mathématique, l’ensemble de ces transformations peut être exprimé sous forme
matricielle. Ce type de transformation est appelé : transformation affine. Dans la bibliothèque
Java 2D, la classe AffineTransform décrit ce type de transformation. Il est possible de construire
directement un objet de cette classe si les paramètres sont connus. Exemple :
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 78/120
Il existe des méthodes de construction, getRotateInstance, getScaleInstance,
getTranslateInstance et getShearInstance, qui construisent les matrices représentant ces types
de transformations. Par exemple, l’appel suivant :
t = [Link](2.0F,0.5F);
2 0 0
0 0.5 0
0 0 1
[Link](angle);
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 79/120
6.2.- Création d’une applet
Pour créer une applet il faut créer une sous classe de la classe Applet. La classe Applet fait partie
du package [Link] qui fournit l’essentiel des comportements dont une applet a besoin
pour fonctionner au sein d’un navigateur. De plus, les applets font largement appel au package
AWT de Java.
Une applet peut comporter un grand nombre de classes auxiliaires mais c’est toujours la classe
principale qui déclenche son exécution effective. La première classe de l’applet est toujours
conforme à la signature suivante :
…
}
Une application Java élémentaire comporte une classe dotée d’une fonction main, point d’entrée du
programme. Les applets suivent un processus similaire mais plus complexe. Elles n’ont pas de
fonction main. Elles ont différentes activités liées aux événements majeurs de leur cycle de vie,
comme l’initialisation, la peinture et les événements souris. A chacune de ces activités correspond
une méthode de sorte que lorsqu’un événement se produit le navigateur appelle la méthode
appropriée. Les cinq méthodes les plus importantes pour l’exécution d’une applet concernent
l’initialisation (méthode init()), le démarrage (méthode start()), l’arrêt (méthode
stop()), la destruction (méthode destroy()) et la peinture (méthode paint() – seule
méthode indispensable).
La méthode init()
L’initialisation se produit lorsque l’applet est chargée (ou rechargée) et s’apparente à la méthode
main() des applications. L’initialisation d’une applet inclut généralement la lecture des
paramètres de l’applet, la création d’objets auxiliaires dont elle peut avoir besoin, la mise à l’état
initial, le chargement d’images, de polices …
La méthode start()
A la suite de l’initialisation, l’applet doit être démarrée. Le démarrage se distingue de l’initialisation
car il peut se produire à des moments très différents dans la vie d’une applet alors que l’initialisation
ne se produit qu’une seule fois (par exemple après l’arrêt de l’applet).
La méthode stop()
L’arrêt et le démarrage sont complémentaires. L’arrêt de produit par exemple lorsque l’utilisateur
quitte la page Web contenant l’applet ou lorsque l’utilisateur arrête lui-même l’applet en invoquant
la méthode stop().
La méthode destroy()
La destruction permet à l’applet de faire une remise en ordre (libération de la mémoire, fermeture
des connexions réseau …) juste avant la sortie du navigateur.
La méthode paint(Graphics g)
La peinture recouvre tout ce qu’une applet dessine à l’écran, qu’il s’agisse de texte, d’un arrière
plan coloré ou d’une image. La peinture peut se produire des milliers de fois au cours de la vie
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 80/120
d’une applet (déplacements et recouvrements des fenêtres …). La méthode paint prend un
argument, une instance de la classe Graphics. Cet objet (qui représente le contexte graphique) est
créé par le navigateur.
Un exemple d’applet simple :
import [Link]; import [Link];
import [Link]; public class HelloApplet
extends [Link] {
<HTML>
<HEAD>
<TITLE>Exempled’appeld’uneapplet</TITLE >
</HEAD>
CODE indique le nom du fichier à exécute
r
<BODY>
<APPLETCODE =“[Link] “ WIDTH=200HEIGHT=50>
</APPLET>
</BODY>
</HTML> WIDTHet HEIGHTdélimitent la taille de la fenêtre
dans laquelle sera dessinée l’a
pplet
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 81/120
6.4.- Passer des paramètres à une
applet
Dans le cas d’une application, les paramètres sont transmis à la méthode main sous la forme d’une
ligne de commande. Les arguments sont ensuite analysés dans le corps de la classe. Les applets en
revanche n’ont pas de ligne de commande mais elles peuvent recevoir différentes indications à
partir du fichier HTML par le biais de la balise <PARAM NAME= … VALUE=>. Ajoutons une
ligne au fichier HTML défini ci-dessus permettant de personnaliser le « Hello » :
<HTML>
<HEAD>
<TITLE> Exemple d’appel d’une applet </TITLE>
</HEAD>
<BODY>
<APPLET CODE =“[Link]“ WIDTH=200 HEIGHT=50>
<PARAM NAME=name VALUE=“TOTO“>
</APPLET>
</BODY>
</HTML>
Pour récupérer la valeur name dans le code de l’applet, il existe une méthode spécifique
getParameter. Le code de l’applet devient alors :
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 82/120
import [Link];
import [Link];
import [Link];
7.- Java3D
Remarque : ce chapitre est fortement inspiré du cours d’Alain Berro, professeur à l’Université de
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 83/120
Toulouse (« Découvrez Java 3D » – [Link] Certaines notions très complexes
(texture, éclairage, animation …) ne sont pas traitées en détails dans ce chapitre. Vous trouverez
une documentation très complète traitant du sujet sur le site [Link]
Sun offre une documentation en ligne très riche et très bien réalisée :
[Link]
[Link] [Link]
media/3D/[Link]
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 84/120
Le graphe de scène fournit un moyen simple et flexible de représenter une scène en 3D. Il regroupe
les objets à visualiser, les informations de visualisation et les éventuels outils d'interaction entre
l'utilisateur et les objets.
Un univers virtuel (VirtualUniverse) est défini comme un espace tridimensionnel dans lequel est
inséré une ou plusieurs scènes (Locale).
Construire une scène consiste à associer une branche de volume et une branche de visualisation.
La branche de volume forme la partie gauche du graphe de scène (ci-dessous) et elle va contenir
les différents objets physiques (Géométrie, Son, Texte) à visualiser.
La branche de visualisation forme la partie droite de la scène (ci-dessous) et contient les outils
nécessaires à visualiser l'espace de volume (position du point de vue, projection, style
d'affichage ...). Afin d'avoir une visualisation plus réaliste, Java 3D permet également de tenir
compte des caractéristiques physiques de la tête de l'utilisateur ( PhysicalBody) (position des yeux,
écartement, ...) et de l'environnement sensoriel (PhysicalEnvironment).
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 85/120
[Link]. -Description des principales classes d'objets formant le graphe de scène
La super structure
• VirtualUniverse contient l'ensemble des scènes de l'univers ;
• Locale permet de définir les caractéristiques d'une scène (origine et espace de coordonnées,
branche(s) de volume et branche(s) de visualisation).
Les éléments de visualisation
• ViewPlatform définit la position et l'orientation du point de vue de l'utilisateur ;
• View regroupe toutes les caractéristiques du processus de rendu. Il contient les paramètres et les
références sur les objets définissant la manière de visualiser la scène. Il possède aussi une
référence sur les différents objets « Canvas3D »;
• Canvas3D est le cadre d'affichage dans lequel la scène doit être rendue ;
• Screen3D contient les informations sur les propriétés physiques du périphérique d'affichage ;
• PhysicalBody contient les informations sur les caractéristiques physiques de l'utilisateur ;
• PhysicalEnvironment définit l'environnement sensoriel, dans lequel on place les objets
nécessaires à une interaction entre l'utilisateur et la scène (périphérique de saisie, périphérique
d'interaction sonore, ...).
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 86/120
Les éléments d'interaction
InputDevice est une interface permettant de créer un pilote de périphérique.
• BranchGroup est un groupe racine d'un sous graphe de la scène. Il peut être attaché à un autre
sous graphe ou à un objet Locale. Il peut être compilé en appelant la méthode compile(). Un
BranchGroup attaché à un autre sous graphe peut être détaché pendant l'exécution à condition
que l'aptitude associée (à l'action 'détacher') soit mise à jour.
Méthode Rôle Capacité du nœud
compile la branche dont le
Compile
BranchGroup est la racine
détache le BranchGroup de son
Detach ALLOW_DETACH
parent
• TransformGroup est un groupe de transformation spatiale. C'est un objet group enrichi d'un
objet Transform3D qui va permettre de positionner, orienter ou mettre à l'échelle tous les
enfants du groupe.
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 87/120
Les composants de nœud
Les composants de nœud sont liés aux nœuds par une relation de référence.
La classe NodeComponent est la super classe commune à tous les objets permettant de définir les
caractéristiques d'une scène (forme, couleur, texture, matière, ...).
L'optimisation en Java 3D
L'API Java 3D inclus un certain nombre de techniques permettant d'optimiser le temps de rendu
d'une scène.
Les bits d'aptitude (Capability bits)
Tous les objets (SceneGraphObjet) du graphe de scène (Node ou NodeComponent) sont
susceptibles de subir un certain nombre d'actions (modification ou interrogation). Afin d'optimiser
le graphe de scène, Java 3D associe un bit d'aptitude (capability) aux différentes actions possibles
sur un objet. Si le bit d'apitude n'est pas positionné alors l'action n'est pas possible. L'utilisation des
bits d'aptitude est nécessaire si l'objet appartient à une branche compilée ou si l'objet est vivant.
Par exemple, si vous souhaitez ajouter un nœud à un groupe, ce dernier devra avoir la capacité
ALLOW_CHILDREN_EXTEND.
Méthode Rôle
SetCapability attribue une aptitude à un objet
GetCapability retourne l'état de l'aptitude
ClearCapability désactive l'aptitude d'un objet
La compilation des branches de l'arbre de scène (Compile)
La compilation d'un sous graphe de scène permet d'optimiser la structure de la scène de manière à
avoir un rendu plus rapide. Elle ne peut être effectuée que sur les sous graphes dont la racine est un
BranchGroup ou un SharedGroup. Après la compilation, seuls les objets ayant définis leurs
aptitudes pourront être modifiés ou consultés.
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 88/120
En 3D, un point P(X,Y,Z) a comme coordonnées homogènes (X,Y,Z,1). Après le multiplication par
la matrice de transformation on obtient le résultat (XP, YP, ZP, H). Les coordonnées transformées
de P sont alors (XP/H, YP/H, ZP/P).
Dans Java 3D, la matrice des transformations géométriques est enregistrée dans un objet
Transform3D. Il n'est pas utile de connaître les formules de transformation, il suffit d'utiliser les
méthodes de mise à jour de la classe Transform3D.
Méthode Rôle
setTranslation modifie la transformation en translation
rotX, rotY, rotZ modifie la transformation en rotation d'axe X, Y ou Z
setScale modifie la transformation en changement d'échelle
Remarques :
• Les méthodes setTranslation, rotX, rotY et rotZ ne sont pas cumulatives. Chaque appel à
l'une de ces méthodes suppriment la transformation précédemment contenue dans le
Transform3D. Alors que la méthode setScale combine la mise à l'échelle à la transformation
déjà présente dans le Transform3D.
• Les transformations s'effectuent toujours par rapport au centre du repère.
La classe Transform3D possède également tous les outils mathématiques permettant de
manipuler les matrices de transformation.
Méthode Rôle
mul permet de multiplier deux Transform3D
add, sub, transpose addition, soustraction, transposition
setIdentity la matrice de transformation devient la matrice identité
Remarques :
• Multiplier deux matrices de transformation est identique à l'enchaînement de deux
TransformGroup.
Code source
import [Link]; import
[Link]; import
[Link]; import
[Link]; import [Link].j3d.*;
import [Link].Vector3d; import
[Link].Point3d; import
[Link].*; import
[Link];
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 91/120
//----- Rend la fenêtre visible
[Link](true);
}
//----- Création du volume private
BranchGroup createBrancheVolume()
{
//----- Création du noeud racine
BranchGroup racine = new BranchGroup();
Racine = new BranchGroup();
//----- Création du Volume
[Link](new ColorCube());
//----- Optimisation du graphe de scène
[Link]();
return racine;
}
Une manière d'écrire un programme Java 3D de façon plus simplifiée est d'utiliser la classe
SimpleUniverse. Cette classe permet au programmeur d'éviter la programmation de la branche de
visualisation. Le constructeur d'un objet SimpleUniverse crée un graphe de scène contenant les
objets VirtualUniverse, Locale et toute la branche de visualisation. La recette devient donc
beaucoup plus simple :
• Créez une classe qui hérite de JFrame. Cette classe va simplement servir de fenêtre de
visualisation ;
• Créez un objet Canvas3D que vous insérez dans votre fenêtre ;
• Créez un objet SimpleUniverse avec une référence sur l'objet Canvas3D ;
• Créez un branchement (BranchGroup) entre l'objet SimpleUniverse et la partie volume ;
• Réalisez la partie volume et attachée là ;
• Compilez l'arborescence de la partie volume.
Code source
import [Link];
import [Link];
import [Link];
import [Link]; import
[Link].j3d.*; import
[Link].Vector3d; import
[Link].Point3d; import
[Link].*;
import [Link];
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 92/120
//----- Données relatives à la fenêtre
private int largeur; // Taille private
int hauteur; private int posx; //
Position private int posy;
//----- Objets composants la structure principale
private SimpleUniverse universe;
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 93/120
private BranchGroup racineVolume; // Noeud de branchement du volume
private TransformGroup tgVolume; // Noeud de transform attaché au volume
// Constructeur public SceneSU (int l, int h,
int px, int py)
{
//----- Instanciation de la fenêtre graphique
[Link]("Visualisation 3D"); [Link] = l;
[Link] = h; [Link](largeur,hauteur);
[Link] = px; [Link] = py;
[Link](posx,posy);
[Link](JFrame.EXIT_ON_CLOSE);
//----- Contenu de la fenêtre
Container conteneur = getContentPane();
[Link](new BorderLayout());
//----- Création du Canvas
GraphicsConfiguration config =[Link]();
Canvas3D c = new Canvas3D(config);
[Link]("Center",c);
//----- Création de l'univers virtuel
[Link] = new SimpleUniverse(c);
//----- Position de l'observateur Transform3D t3d_oeil = new
Transform3D(); t3d_œ[Link](new Vector3d(0.0,0.0,10.0));
[Link]().getViewPlatformTransform().
setTransform(t3d_oeil) ; //Création noeud racine et matrice de
transformation du branche volume [Link] = new TransformGroup();
[Link] = new BranchGroup();
[Link]([Link]);
[Link](createBrancheVolume());
//----- Ajout de la branche de volume
[Link](racineVolume);
//----- Rend la fenêtre visible
[Link](true);
}
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 94/120
}
}
Remarque :
Tant que le Shape3D n'est pas vivant ou compilé, les composants de l'objet peuvent être modifiés ou
consultés, sinon il faudra positionner les aptitudes appropriées.
Méthode héritée de la
Rôle Capacité du nœud
classe Primitive
setAppearance mise à jour de l'apparence ENABLE_APPEARANCE_MODIFY
getAppearance retourne l'apparence ENABLE_APPEARANCE_MODIFY
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 95/120
Remarque :
Pour utiliser ces primitives, l'utilisateur doit obligatoirement définir une apparence par défaut
(setAppareance(new Appearance()) ;) sinon la primitive n’apparaitra pas dans la scène. La
dernière primitive (ColorCube) est un cube avec six faces de couleurs différentes et de côté de taille
1.0. Cette primitive hérite directement de la classe Shape3D.
Remplissage de l'objet
Après la déclaration du format du GeometryArray dans le constructeur, l'utilisateur doit remplir les
tableaux de données correspondants. Pour cela les méthodes suivantes ont été définies :
Méthode Rôle Capacité
setColor permet de remplir le tableau de ALLOW_COLOR_WRITE
setColors couleurs.
permet de remplir le tableau des
setCoordinate
setCoordinates coordonnées des points formant la ALLOW_COORDINATE_WRITE
géométrie.
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 96/120
setNormal permet de remplir le tableau des ALLOW_NORMAL_WRITE
setNormals normales.
setTextureCoordinate permet de remplir le tableau des ALLOW_TEXCOORD_WRITE
setTextureCoordinates coordonnées de texture.
Remarques :
• Les méthodes présentées ci-dessus ont un grand nombre de spécifications différentes.
Consultez la spécification de GeometryArray afin de choisir la méthode la plus appropriée à
votre besoin.
• Comme on peut le voir dans ce tableau, il a été défini un ensemble d'aptitudes dans la classe
GéometryArray qui autorise ou non la manipulation des différentes données.
Les quatre classes ci-dessus ne permettent pas de réutiliser les sommets définis dans le tableau alors
que certaines configurations géométriques invitent à la réutilisation des sommets. Par exemple, la
classe Cube utilise 24 sommets pour décrire la forme, or un cube n'a que 8 sommets. Une meilleure
représentation de cette classe permettrait un gain de place. L'API Java 3D a défini deux familles de
classes permettant de réutiliser les sommets :
• La classe GeometryStripArray : classe abstraite à partir de laquelle des primitives de bande
sont dérivées (LineStripArray, TriangleStripArray et TriangleFanArray) ;
• La classe IndexedGeometryArray. Les objets IndexedGeometryArray fournissent un niveau
supplémentaire d'adressage pour éviter la redondance des informations sur les sommets. Les
positions, les couleurs, les normales et les coordonnées de clipping de texture pour les sommets
sont stockées dans des tableaux appelés les "data array". Des tableaux supplémentaires
nommés "index array" sont ajoutés afin d'indexer les données contenues dans les data array.
Les index array peuvent avoir plusieurs références au même sommet dans les data array et
l'ordre des références indique l'ordre dans lequel les sommets seront traités pendant le rendu.
Remarques :
• L'indexation implique un nombre de calculs supplémentaires et peut entraîner une baisse de
performance du rendu.
• Cette classe se dérive en IndexedLineGeometry, IndexedTriangleArray, ...
Exemple : la classe CubeIndex
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 97/120
La classe CubeIndex est un cube représenté sous forme d’un IndexedQuadArray.
Le rendu de CubeIndex est plus réaliste en situation d’éclairage que celui de Cube car les normales
sont des normales aux sommets alors que les normales dans Cube sont des normales de surfaces.
Le regroupement de l'ensemble des attributs d'apparence dans un objet Appearance est nommé
appearance bundle et permet une optimisation du rendu. Chaque objet Appearance possède une
méthode lui permettant d'accéder (get + nom de l'attribut) et de mettre à jour ( set + nom de
l'attribut) chaque attribut. Chacune de ces actions est associée à une capacité. Le tableau ci-dessous
présente les méthodes et les capacités de trois attributs :
La classe PointAttributes
Les objets PointAttributes spécifient le rendu des points.
Propriété Valeur par défaut
taille du point en pixel 1
antialiasing disable
Constructeurs :
• PointAttributes() crée un objet avec les propriétés par défaut ;
• PointAttributes (int pointSize, boolean pointAntialiasing).
L'utilisation de la technique de l'antialiasing demande plus de calculs mais les résultats sont plus
satisfaisants.
Méthode Rôle Capacité
setPointSize mise à jour de la taille des points ALLOW_SIZE_WRITE
setPointAntialiasingEnable active ou désactive l'antialiasing ALLOW_ANTIALIASING_WRITE
La classe LineAttributes
Les objets LineAttributes spécifient le rendu des lignes.
Propriété Valeur par défaut
largeur de la ligne en pixel 1
antialiasing disable
dessin (pattern) PATTERN_SOLID
Le tracé d'une ligne peut être solide (PATTERN_SOLID), en pointillé (PATTERN_DOT), en tiret
(PATTERN_DASH) ou les deux (PATTERN_DASH_DOT).
Constructeurs :
• LineAttributes() crée un objet avec les propriétés par défaut ;
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 99/120
• LineAttributes(float lineWidth, int linePattern, boolean
lineAntialiasing).
L'utilisation de la technique de l'antialiasing demande plus de calculs mais les résultats sont plus
satisfaisants.
Méthode Rôle Capacité
setLineWidth
mise à jour de la largeur d'une ALLOW_WIDTH_WRITE
ligne
setLineAntialiasingEnab
active ou désactive l'antialiasing ALLOW_ANTIALIASING_WRITE
le
setLinePattern mise à jour du dessin des lignes ALLOW_PATTERN_WRITE
La classe PolygonAttributes
Les objets PolygonAttributes spécifient le rendu des polygones.
Propriété Valeur par défaut
mode de tramage POLYGON_FILL
élimination des facettes CULL_BACK
Il y a trois modes de tramage du polygone.
• POLYGON_POINT, le polygone est représenté par ses sommets ; • POLYGON_LINE, le polygone
est représenté en fil de fer ;
• POLYGON_FILL, le polygone est plein.
Il y a également trois modes d'élimination des facettes.
• CULL_BACK, élimination des faces arrière ; • CULL_FRONT, élimination des faces avant ;
• CULL_NONE, aucune élimination. Constructeur :
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 100/120
Si la couleur de la forme 3D a été définie au niveau de la géométrie, il y a conflit . Dans ce cas, la
priorité est donnée à la couleur définie dans la géométrie.
Constructeurs :
• ColoringAttributes() crée un objet avec les propriétés par défaut ;
• ColoringAttributes(Color3f color, int shadeModel) ;
• ColoringAttributes(float red, float green, float blue, int shadeModel).
setShadeModel
mise à jour du mode ALLOW_SHADE_MODEL_WRITE
d'interpolation
La classe TransparencyAttributes
Les objets TransparencyAttributes spécifient la transparence (blending) d'une primitive.
Propriété Valeur par défaut
opacité (alpha blending) 0.0
mode de transparence NONE
La valeur de l'opacité d'un objet est comprise entre 0.0 (objet totalement opaque) et 1.0 (objet
totalement transparent).
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 101/120
Constructeurs :
• TransparencyAttributes() crée un objet avec les propriétés par défaut ;
• TransparencyAttributes(int tMode, float tVal).
setTransparencyMode
mise à jour du mode de ALLOW_MODE_WRITE
transparence
Les classes Texture et TextureAttributes
Le tutoriel Java 3D consacre un chapitre entier à la création et l'application de texture.
La classe Texture permet de définir les caractéristiques d'une texture.
setTextureBlendColor
mise à jour de la couleur de ALLOW_BLEND_COLOR_WRITE
mélange
setTextureTransform applique une transformation ALLOW_TRANSFORM_WRITE
setPerspectiveCorrectionMode
mise à jour du rendu en
perspective
Remarques :
• Nous pouvons également combiner couleur & branche de volume ou image & branche de
volume ;
• Mais si nous définissons une couleur et une image d'arrière-plan alors l'image a priorité sur
la couleur.
L'utilisation d'une image comme arrière-plan n'est pas recommandée si le point de vue de
l'observateur bouge. En effet, l'image étant plaquée sur le fond de la scène, l'arrière-plan ne se
modifie pas lors des mouvements du point de vue, les impressions de profondeur et d'horizon sont
alors totalement faussées. Lors de réalisation d'un arrière-plan, il faut également définir ses limites
d'activation à l'aide d'un objet BoundingSphere.
Constructeurs :
• Background() ;
• Background(Color3f couleur) définit la couleur d'arrière-plan ;
• Background(float r, float g, float b) ;
• Backgroung(Branchgroup branch) attache une géométrie comme arrière-plan ;
• Background(ImageComponent2D image) définit l'image d'arrière-plan.
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 103/120
Méthode Rôle Capacité
setColor mise à jour de la couleur ALLOW_COLOR_WRITE
setGeometry
mise à jour de la branche de ALLOW_GEOMETRY_WRITE
volume
setImage mise à jour de l'image ALLOW_IMAGE_WRITE
setApplicationBounds
mise à jour des limites ALLOW_APPLICATION_BOUNDS_W
d'application RITE
Pour ajouter un arrière-plan à la scène, il faut :
• Créer un objet Background ;
• Mettre à jour ses caractéristiques de couleur, géométrie ou image ;
• Définir ses limites (Bounds) ; • L'attacher au graphe de scène.
La lumière ambiante est une lumière d'intensité constante dans tout l'espace et dans toutes les
directions. Elle suffit à éclairer un objet. Mais si ce dernier est d'une couleur uni, l'observateur ne
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 104/120
pourra pas distinguer le moindre effet de relief car celui-ci est essentiellement perçu par la
différence de luminosité entre des points proches sur la surface de l'objet.
Exemple : Lors d'une journée avec couverture nuage uniforme, nous ne distinguons aucune ombre
et ne sommes pas capable de donner la position du soleil. Nous sommes dans une scène dont la
lumière ambiante est majoritaire. Nous avons également constaté, dans les corrigés de la partie
précédente, qu'en présence de lumière ambiante un objet plein apparaît comme une figure 2D.
[Link]. - La lumière diffuse
La lumière diffuse est une lumière réflechie qui dépend essentiellement de la position de la source
lumineuse et du matériau de l'objet. Pour un point précis de sa surface l'objet va émettre la même
quantité lumineuse dans toutes les directions. Donc peu importe la position de l'observateur, il
percevra la même luminosité venant de ce point. Mais comme l'objet ne présente pas les points de
sa surface sous le même angle à une source lumineuse, la quantité de lumière réfléchie diffère en
fonction du point considéré. Ainsi, l'observateur va percevoir le relief de l'objet.
Exemple : Le tableau noir utilisé dans les écoles est un objet parfaitement diffus. Il permet à
n'importe quel élève de la classe de percevoir de la même façon les caractères écrits au tableau tout
en évitant les effets de contre jour dus à la lumière spéculaire.
La lumière spéculaire est une lumière réfléchie qui traduit la capacité de reflexion du matériau. Un
objet parfaitement spéculaire va réfléchir la lumière issue de la source lumineuse avec un angle
opposé (par rapport à la normale à la surface de l'objet) à l'angle de réception. Exemple : Un miroir
est un objet parfaitement spéculaire.
SetColor
mise à jour de la ALLOW_COLOR_WRITE
couleur de la lumière
SetEnable mise à jour de l'état ALLOW_STATE_WRITE
SetInfluencingBoundingLeaf
mise à jour de la zone ALLOW_INFLUENCING_BOUNDS_WRITE
d'influence
[Link]. - Source de lumière ambiante
Cette source (AmbientLight) permet d'éclairer la scène de façon uniforme. Ainsi l'observateur
pourra percevoir la présence des géométries.
Propriété Valeur par défaut
couleur (1,1,1) blanche
état on (allumé)
Constructeurs :
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 105/120
• AmbientLight() ;
• AmbientLight(boolean lightOn, Color3f color) ;
• AmbientLight(Color3f color).
Constructeurs :
• PointLight()
• PointLight(boolean lightOn, Color3f color, Point3f position, Point3f
attenuation)
Source de lumière (SpotLight) dont les rayons sont contenus dans un cône de lumière.
7.5.- Interaction
7.5.1.- Manipulation à l’aide de la souris
Java 3D propose un paquetage définissant le comportement de la souris à partir de la classe abstraite
(MouseBehavior). Les trois classes filles de MouseBehavior permettent de réaliser les opérations
de translation (MouseTranslate), de rotation (MouseRotate) et de zoom (MouseZoom) sur une
branche de la scène.
Mouvement Action
vers la gauche Flèche gauche
vers la droite Flèche droite
vers le haut pg up
vers le bas pg down
vers l'avant Flèche haut
vers l'arrière flèche bas
Remarques :
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 108/120
• Si le Canvas3D (zone d'affichage de votre scène) est inséré dans un contenant ( JFrame ou
JPanel) qui implémente la classe KeyListener alors les évènements issus du clavier seront
interceptés par le Canvas3D. Donc votre objet KeyListener sera inactif.
• Avant de tester vos comportements issus du clavier, pensez à donner le focus à votre
Canvas3D en cliquant dessus.
Lorsque l'utilisateur clique dans la fenêtre de visualisation de la scène ( Canvas3D), un rayon est
projeté dans la scène3D parallèlement aux axes de projection. Ensuite, le processus recherche l'objet
intercepté par le rayon le plus proche de l'utilisateur. A partir de cet objet, le processus remonte
dans le graphe de scène jusqu'à trouver un TransformGroup possédant la capacité
ENABLE_PICK_REPORTING.
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 109/120
• ALLOW_TRANSFORM_READ : autorise la lecture de sa matrice de transformation ; •
ALLOW_TRANSFORM_WRITE : autorise la mise à jour de sa matrice de transformation. Vous
pouvez ajouter plusieurs comportements de picking sur la même branche du graphe de scène.
Mouvement Action
rotation bouton gauche de la souris
translation bouton droit de la souris
zoom bouton du milieu ou ALT + bouton gauche
Constructeur :
PickTranslateBehavior(BranchGroup root, Canvas3D canvas, Bounds bounds)
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 110/120
Les types de données composés sont uniquement créés dans les définitions de classes. Les motsclefs
struct, union et typedef ont été enlevés au profit des classes.
La conversion des types est plus contrôlée en Java. Elle est automatique seulement s’il n’y a pas de
perte d’information. Toutes les autres conversions sont explicites. La conversion des types de base
en objet (ou vice versa) est impossible. Il existe des méthodes et des classes spéciales pour convertir
les valeurs entre objets et types de base.
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 111/120
A.2.- Problèmes avec les méthodes
de la classe Thread
A.2.1. - Why is [Link] deprecated ?
Because it is inherently unsafe. Stopping a thread causes it to unlock all the monitors that it has
locked. (The monitors are unlocked as the ThreadDeath exception propagates up the stack.) If any
of the objects previously protected by these monitors were in an inconsistent state, other threads
may now view these objects in an inconsistent state. Such objects are said to be damaged. When
threads operate on damaged objects, arbitrary behavior can result. This behavior may be subtle and
difficult to detect, or it may be pronounced. Unlike other unchecked exceptions, ThreadDeath kills
threads silently; thus, the user has no warning that his program may be corrupted. The corruption
can manifest itself at any time after the actual damage occurs, even hours or days in the future.
You can avoid the use of [Link] by replacing the applet's stop and run methods with:
private volatile Thread blinker;
if (threadSuspended)
[Link]();
else [Link](); // DEADLOCK-
PRONE!
threadSuspended = !threadSuspended;
}
You can avoid the use of [Link] and [Link] by replacing the event handler
above with:
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 113/120
public synchronized void mousePressed(MouseEvent e) {
[Link]();
threadSuspended =! threadSuspended;
if (!threadSuspended)
notify();
}
and adding the following code to the "run loop" :
synchronized(this) { while
(threadSuspended)
wait();
}
The wait method throws the InterruptedException, so it must be inside a try ... catch
clause. It's fine to put it in the same clause as the sleep. The check should follow (rather than
precede) the sleep so the window is immediately repainted when the the thread is "resumed." The
resulting run method follows:
Note that the notify in the mousePressed method and the wait in the run method are inside
synchronized blocks. This is required by the language, and ensures that wait and notify are
properly serialized. In practical terms, this eliminates race conditions that could cause the
"suspended" thread to miss a notify and remain suspended indefinitely.
While the cost of synchronization in Java is decreasing as the platform matures, it will never be
free. A simple trick can be used to remove the synchronization that we've added to each iteration of
the "run loop." The synchronized block that was added is replaced by a slightly more complex piece
of code that enters a synchronized block only if the thread has actually been suspended :
if (threadSuspended)
{ synchronized(this)
{ while (threadSuspended)
wait();
}
}
The resulting run method is :
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 114/120
public void run()
{ while (true) {
try {
[Link]().sleep(interval);
if (threadSuspended)
{ synchronized(this)
{ while (threadSuspended)
wait();
}
}
} catch (InterruptedException e) {}
repaint();
}
}
In the absence of explicit synchronization, threadSuspended must be made volatile to ensure
prompt communication of the suspend-request.
seconde : 0:00:00:0
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 115/120
import [Link].*; import
[Link];
Ce programme est une d'applet qui implémente l'interface Runnable. Comme décrit au chapitre sur
les applets, la méthode paint () de la classe Applet est appelée pour mettre à jour le dessin
apparaissant dans la fenêtre d'une applet. Dans cet exemple, elle est surchargée pour dessiner le
chronomètre.
Quand l'applet est créée, une instance de la classe Chrono est allouée et la méthode start () créant
le thread chronometre est appelée. Si on exécute cette applet, on se rend compte que le chronomètre
à tendance à retarder. En effet, à chaque tour de boucle while (), le thread est arrêté pendant un
dixième de seconde grâce à l'appel [Link] (100) après le redessin de l'applet avec la
méthode repaint () dans run (). Le fait de redessiner le chronomètre prend un faible délai qui
s'additionne au 1/10 de seconde d'arrêt du thread chronomètre. Une programmation plus précise
devrait notamment tenir compte de ce délai pour le soustraire de la valeur de 100 millisecondes
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 116/120
passée à la méthode sleep (). La classe System déclare une méthode currentTimeMillis (),
donnant le temps courant, qui peut aider à résoudre ce problème.
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 117/120
[Link] (this) * 2,
[Link] (this) * 2, this);
}
}
} // fin applet
Deux exemples de chargement d’image (avec et sans utilisation de ImageObserver)
L'applet suivante utilise la méthode drawImage() pour charger et afficher une image :
import [Link];
import [Link].*;
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 118/120
public void paint (Graphics gc)
{
// Si le chargement de l'image est terminé, affichage de l'image
// sinon affichage d'une chaîne de caractères d'attente if
(chargementTermine) [Link] (image, 0, 0, this);
else [Link] ("Chargement en cours...", 10, size ().height - 10);
}
A la différence de l'applet ImageSimple, l'image de cette applet n'est affichée qu'une fois que
l'image est entièrement chargée.
Cette applet utilise les 8 images de taille égale (ci-dessus). Elles sont extraites de l'image
téléchargée grâce à l'utilisation de la classe de filtre CropImageFilter, puis un thread provoque
leur affichage l'une après l'autre à intervalle régulier pour donner l'effet animé.
import
[Link];
import [Link].*; import
[Link].*;
Ir Taylor NTAMBUE 0819900542 Java Pour les Etudiants de L2 LMD ISP Tshikapa page 120/120