DÈvelopper des applications avec le langage Java
15 juin 1999
Table des matiËres
- PrÈsentation du langage
1.1 Introduction
Le langage java a fait une entrÈe spectaculaire dans le monde du dÈvelop-pement logiciel en 1995. Il combinait des options dÈj‡ popularisÈes par certains de ces concurrents (l’orientation objet) , et des caractÈristiques particuliËrement adaptÈes aux autres tendances dominantes du moment (Internet).
1.2 Philosophie
1.2.1 Un langage orientÈ objet
L’orientation objet s’est progressivement imposÈe comme technologie de dÈ-veloppement, laissant espÈrer dans la conception des applications:
- plus de rÈutilisation (du code, mais aussi de l’architecture)
- une plus grande rÈactivitÈ au changement
- un moindre co˚t de maintenance
Les langages orientÈs objets les mieux connus Ètaient C++ et SmallTalk. java occupe une position intermÈdiaire, plus proche de C++ en ce qui concerne le style de programmation. Nous verrons les principes de la programmation objet un peu plus loin, mais la notion essentielle est celle de classe, qui dÈnit le comportement des variables du langage, appelÈs objets, et un programme java est un ensemble de dÈnition de classes.
1.2.2 Un langage interprÈtÈ
java est un langage compilÈ, la compilation produisant un pseudocode des-tinÈ ‡ Ítre interprÈtÈ par une machine virtuelle, habituellement dÈsignÈe sous le nom de JVM. Les spÈcications prÈcises tant du peudocode que du modËle d’exÈcution de la JVM font partie intÈgrante des spÈcications du langage.
1
1.2.3 Un langage multi-plateformes
Les spÈcications ci-dessus Ètant indÈpendantes de tout environnement phy-sique, le premier bÈnÈce est la portabilitÈ: en principe, une application Ècrite et compilÈe dans un environnement peut Ítre exÈcutÈe dans n’importe quel autre. Bien s˚r quelques prÈcautions sont ‡ prendre:
- Un constructeur peut respecter les spÈcications de la machine virtuelle tout en introduisant des extensions. Si une application tire partie de ces extensions, elle ne pourra fonctionner que dans les environnement les im-plÈmentant.
- Un programme rÈaliste doit procÈder avec le monde extÈrieur (le systËme chier, l’interface utilisateur, les bases de donnÈes). Les concepteurs du langage ont fait de leur mieux pour rendre portable cet interfaÁage, mais il n’est pas toujours possible de cacher le problËme au dÈveloppeur qui a besoin des fonctionnalitÈs spÈciques d’un environnement.
- Un seul langage n’Ètant pas toujours susant pour toutes les applications, certains programmes peuvent devoir utiliser l ‘ouverture de java pour appeler des fonctions extÈrieures ( native). Ceci bien s˚r rend le dÈveloppeur responsable de la portabilitÈ correspondante.
MalgrÈ ces rÈserves, la portabilitÈ est nettement supÈrieure ‡ celle du C, or celle-ci, bien que plus imparfaite, Ètait dÈj‡ assez bonne pour Ítre un des facteurs de son succËs. La portabilitÈ de java est donc trËs satisfaisante.
1.2.4 Un environnement d’exÈcution complet
La machine virtuelle est prolongÈe par une bibliothËque de classes trËs com-plËte qui non seulement accÈlËre le dÈveloppement des applications mais joue un rÙle important dans leur portabilitÈ. Ces classes sont regroupÈes en packages.
1.3 java et Internet
En plus des bÈnÈces dÈcrits dans les paragraphes prÈcÈdents, la structure du pseudocode est telle qu’il est possible pour la machine virtuelle de requÈrir des ÈlÈments compilÈs (les .class), en fonction des besoins, auprËs de n’importe quelle source. Ceci est Èvidemment trËs adaptÈ au Web, et a donnÈ naissance ‡ la notion d’applet, mini-applications pouvant fonctionner dans un browser sans que l’utilisateur ait eu ‡ se prÈocupper de leur installation, et qui ont ÈtÈ l’un des principaux facteurs de l’explosion mÈdiatique de java, mÍme si l’intÈrÍt du langage est trËs loin de s’y limiter.
1.4 Le marchÈ
La popularitÈ de java a atteint des sommets littÈralement inouÔs pour un langage de programmation: le grand public peut voir son nom dans tous les
2
media! De faÁon plus concrËte pour son succËs industriel, beaucoup d’entreprises s’y sont intÈressÈes:
- Du point de vue du style de dÈveloppement, il remplit un crÈneau non encore occupÈ dans la gamme des langages orientÈs objet (il reste proche de C++ , dont le succÈs, mesurÈ en nombre de developpeurs, est supÈrieur
‡ celui de concurrents comme SmallTalk ), et nous verrons qu’il protÈge le dÈveloppeur des dicultÈs de plus bas niveau rencontrÈes dans C++.
- Ses possibilitÈs d’adaptation ‡ des environnements rÈpartis, basÈs sur des composants, sont immÈdiates.
- La notion de langage interprÈtÈ suscite toujours des inquiÈtudes en ce qui concerne les performances. S’il est vrai qu’une exÈcution interprÈtÈe ne peut pas rivaliser avec l’exÈcution de code machine, les machines vir-tuelles sont libres, une fois le pseudocode chargÈ et vÈriÈ, de le traduire en language machine pour l’exÈcution ultÈrieure. Ce mÈcanisme, connu sous le nom de compilation Just In Time a beaucoup fait pour dÈdramatiser le problËme.
- L’orientation objet
2.1 Les principes gÈnÈraux
Les 4 principaux mots magiques de la programmation objet sont: l’encapsu-lation l’abstraction, l’hÈritage, et le polymorphisme.
L’encapsulation: au lieu de s’intÈresser principalement aux procÈdures, le dÈveloppement objet cherche ‡ regrouper un ensemble de donnÈes et de fonctions qui peuvent leur Ítre appliquÈes. L’ensemble de ces donnÈes (attributs) et de ces fonctions (mÈthodes) constitue un objet, et, si le langage est typÈ, est dÈcrit dans un type qu’on appelle une classe.
La combinaison de l’encapsulation et de l’abstraction conduit ‡ ne prÈsenter aux utilisateurs d’une telle classe que les mÈthodes d’intÈrÍt public, les protÈ-geant des dÈtails de la rÈalisation, et de leur Èvolution.
L’hÈritage dÈsigne la possibilitÈ de construire de nouvelles classes en s’ap-puyant sur des classes existantes: la nouvelle classe (dÈrivÈe), hÈrite des attributs et des mÈthodes de l’ancienne (base).
Le polymorphisme dÈsigne la possibilitÈ d’Ècrire une seule fois du code qui pourra s’appliquer ‡ des objets diÈrents, en exÈcutant des mÈthodes diÈrentes, mais avec la mÍme signication. Il existe plusieurs formes de polymorphisme, certaines sans rapport avec l’orientation objet, mais la plus importante est rÈa-lisÈe ‡ travers l’hÈritage, en autorisant la classe dÈrivÈe ‡ redÈnir certaines mÈthodes de la classe de base. La diÈrence entre l’hÈritage seul et l’hÈritage combinÈ avec le polymorphisme est ‡ peu prËs la mÍme qu’entre une mobylette et une moto de course, ne pas oublier qu’une moto de course peut Ítre trËs dan-gereuse si elle est mal utilisÈe! D’une part l’hÈritage peut Ítre utilisÈ ‡ tort, quand par exemple un objet devrait simplement possÈder une rÈfÈrence d’un
3
autre type dont il a besoin, d’autre part tant qu’un langage ne comporte pas des assertions sÈmantiques (elles ont envisagÈes pour java), la phrase ci-dessus avec la mÍme signication reste incontrÙlable.
2.2 Elements de langage, classes et objets.
La notion de base est celle de la classe, qui est en quelque sorte un gabarit de crÈation d’objets. Ce gabarit est utilisÈ pour crÈer des instances d’objets. Un objet ayant en gÈnÈral besoin d’Ítre initialisÈ, il est possible de dÈnir des pseudo-mÈthodes d’initialisation, appelÈes usuellement constructeurs, qui sont automatiquement exÈcutÈes au moment de l’instantiation. Des mÈthodes seront ensuite invoquÈes sur les objets instantiÈs.
Outre les attributs et propriÈtÈs (nous parlerons de membres) dÈcrits ci-dessus, qui sont associÈs ‡ des instances spÈciques d’objet , une classe peut aussi possÈder des membres qui ne sont pas liÈs ‡ un objet particulier, appelÈs attributs ou mÈthodes de classe. Dans certains cas il est possible et intÈres-sant de dÈnir des classes qui ne sont mÍme pas destinÈes ‡ Ítre intanciÈes, et contiennent un ensemble de mÈthodes qui ressemblent ‡ des fonctions tradi-tionnelles dÈguisÈes, mais nÈanmoins rÈgroupÈes logiquement. Ces classes sont souvent dÈsignÈes sous le nom de classes utilitÈs.
Les langages objet possÈdent gÈnÈralement une notion de protection, c-a-d que seuls certains membres d’une classe, dits publics, sont destinÈes ‡ Ítre exposÈes au code extÈrieur ‡ la classe, les autres, dits privÈs, Ètant rÈservÈ ‡ son propre usage, ‡ des ns d’implÈmentation. On trouve parfois une graduation plus ne que les seuls niveaux publics et privÈs (c’est le cas de java et de C++), et la notion de protection s’applique souvent aussi aux classes elles-mÍmes. Il est en gÈnÈral dÈconseillÈ d’exposer directement des attributs comme publics, souvent le monde extÈrieur n’a pas besoin d’Ítre conscient de leur existence, et mÍme s’il doit l’Ítre, il est prÈfÈrable de dÈnir une ou deux mÈthodes, appelÈes accesseurs, pour lire et/ou modier leur valeur.
Si le langage est hybride, une notion plus traditionelle de fonction peut coexister avec les ÈlÈments purement objets.
2.3 La position de java
Comment se positionne java dans la gamme des langages orientÈs objet? Si l’on considÈre les deux langages les mieux connus:
- C++ est un langage hybride (il y a de nombreux types primitifs, on peut Ècrire du code C, c’est ‡ dire une sorte d’assembleur structurÈ!), et forte-ment typÈ.
- SmallTalk est une langage pur, non typÈ.
java occupe une position intermÈdiaire:
- Il est hybride, mais beaucoup moins que C++: il y a toujours des types pri-mitifs (numÈriques+char+void), mais le dÈveloppeur ne peut rien dÈnir
4
qui soit en dehors d’une classe, toute classe dÈnie par l’utilisateur dÈrive d’une classe prÈdÈnie ( Objet).
- Il est aussi fortement typÈ que C++
Par ailleurs, java s’est appuyÈ sur une syntaxe trËs proche de celle du C++, aprËs avoir ÈliminÈ les complexitÈs les plus redoutables, au prix bien s˚r de se priver de certaines possibilitÈs.
2.4 Le Garbage Collector
Instantier un objet signie le crÈer, ce qui pose le problËme du cycle de vie et de sa destruction. Les langages objets Èpargnent en gÈnÈral la gestion de cette destruction au dÈveloppeur. java est dans ce cas, contraitement ‡ C++ qui, pour satisfaire les besoins en performance d’une partie de son marchÈ, laisse au dÈveloppeur le soin de spÈcier allocation et dÈallocation. Le Garbage Collection a longtemps eu trËs mauvaise rÈputation, ‡ cause de certaines im-plÈmentations de Lisp o˘ l’exÈcution de cette phase provoquait des pauses trËs perceptibles dans l’exÈcution des applications. La banalisation du multithread, en permettant d’exÈcuter ce nettoyage en parallËle, a encore une fois dÈdrama-tisÈ le problËme. Il reste que combiner Garbage Collection et Temps RÈel est, sinon impossible, tout au moins dÈlicat.
- Les programmes java
3.1 Les environnements de dÈveloppement
3.1.1 Le jdk
Le produit de base est le Java Development Toolkit de Sun. Il contient une implÈmentation de rÈfÈrence de la machine virtuelle, les librairies fonda-mentales nÈcessaires ‡ son fonctionnement, un compilateur, et quelques outils supplÈmentaires. Il n’y a pas d’outil de dÈveloppement visuel, bien que le Bean Development Toolkit contienne, lui, un tel outil, en la personne de la beanbox. Il n’y a malheureusement pas non plus, pour l’instant, de debugger digne de ce nom.
3.1.2 Les environnements intÈgrÈs
Beaucoup de constructeurs proposent des environnements intÈgrÈs avec de-bugger, dÈveloppement visuel, etc… Les plus connus sont:
- Visual J++ de Microsoft
- JBuilder d’Imprise (ex Borland)
- VisualAge for Java d’IBM
- Visual Cafe de Symantec
- Kawa de Tek-Tools
5
3.2 L’architecture des programmes java
3.2.1 Structure logique, classes et packages
Si java comporte des types primitifs, il reste un langage objet plutÙt pur, car le dÈveloppeur ne peut dÈnir que des classes. Le composant de base est donc la classe. Les classes peuvent, nous l’avons dit Ítre regroupÈes en packages. les packages jouent deux rÙles:
- Ils reprÈsentent un espace de nommage, analogue au namespace de C++. Ceci Èvite entre autres les collisions de noms entre fournisseurs de compo-sants.
- Ils fournissent un niveau particulier de protection: il y a en java, comme en C++, 3 niveaux explicites de protection:
- private: uniquement pour un membre, ou une classe intÈrieure, seule la classe englobante peut y faire rÈfÈrence
- protected: similaire, mais l’accËs est autorisÈe Ègalement aux classes dÈrivÈes de la classe englobante
- public: membres ou classes, accËs autorisÈ pour tous
Mais contrairement ‡ C++, en l’absence de qualicateur explicite, le dÈfaut n’est pas un de ces 3 niveaux, mais un 4Ëme, le niveau package, c-‡-d accËs autorisÈ ‡ toutes les classes du mÍme package.
Une classe est spÈciÈe comme appartenant ‡ un package en prÈxant son code par la syntaxe:
package test;
ou
package com.omg.Corba;
Comme on le voit dans ce dernier exemple, les packages peuvent Ítre imbriquÈs. Une classe sans spÈcication de package est considÈrÈe appartenir ‡ un pa-ckage global au nom vide. Les packages (non vides) font partie intÈgrante du nom de la classe, ainsi dans
package test;
public class Benchmark {…}
le nom complet de la classe est test.Benchmark. Une rÈfÈrence depuis le code d’une classe ‡ une classe appartenant ‡ un autre package doit normalement spÈcier ce nom complet. On peut s’en dispenser avec la directive d’ import: ainsi, dans
Package test;
import framework.*;
6
public class Benchmark {
…
Application app=new Application();
…
}
Application peut faire rÈfÈrence ‡ framework.Application (il est dÈconseillÈ que le package test contienne aussi une classe appelÈe Application!). On aurait pu obtenir le mÍme rÈsultat, de faÁon plus Èconomique, et plus s˚re, avec:
Package test;
import framework.Application;
public class Benchmark {
…
Application app=new Application();
…
}
3.2.2 Structure physique, les chiers
L’unitÈ de pseudo-code sera donc le code compilÈ dÈnissant totalement une classe. Le nom du chier est le nom exact de la classe suivi du suxe .class. L’architecture des sources est presque aussi simple: un source java doit contenir au plus la dÈnition d’une classe publique, et le nom du chier doit alors Ítre le nom exact de cette classe suivi du suxe .java. Une classe peut avoir besoin de classes auxiliaires privÈes, celles-ci peuvent Ítre dÈnies dans le mÍme chier source, mÍme si elles donneront lieu ‡ une unitÈ de code compilÈ indÈpendante. D’autre part, depuis jdk1.1, on peut dÈnir des classes intÈrieures ‡ une autre classe. Pour le code compilÈ d’une classe appartenant ‡ un package, les segments du nom du package sont traduits non pas directement dans le nom de chier mais par des sous-rÈpertoires successifs. Ainsi le code compilÈ de la classe test.Benchmark devra se trouver dans un chier dont la spÈcication sera de la forme:
xxx \test\Benchmark.class
Bien qu’il soit souhaitable de respecter une cohÈrence identique pour les sources, aucune contrainte n’est imposÈe sur celles-ci, ni par la spÈcication, ni par les outils.
3.2.3 La compilation
Dans le cas du jdk, la compilation est exÈcutÈe en lanÁant depuis la ligne de commande l’utilitaire javac. La syntaxe est:
javac [options] fichier .java
7
Les options les plus utilisÈes sont:
- -classpath suivi d’une liste de rÈpertoires o˘ rechercher les classes rÈfÈ-rencÈes par le source compilÈ, sÈparÈs par des ; sous Windows ou par des : sous Unix.
- -d suivi de la spÈcication du rÈpertoire o˘ Ècrire le code compilÈ
Si la classe appartient ‡ un package, l’argument de -d sera la racine relative du rÈpertoire eectif, c-a-d que:
javac -d C:\dev\classes benchmark.java
produira (en supposant que nous soyons dans le rÈpertoire o˘ se trouve le source de la classe test.Benchmark) le chier:
C:\dev\classes\test\Benchmark.class
classpath est interprÈtÈ de la mÍme faÁon, et si Benchmark rÈfÈrence la classe framework.Application dont le compilÈ se trouve dans:
C:\dev\classes\framework\Application.class
l’argument de -classpath devra contenir, entre autres, C:\dev\classes
3.2.4 L’exÈcution et les machines virtuelles
Le code compilÈ est exÈcutÈ par la machine virtuelle. Quand une nouvelle classe est requise (par exemple, parce qu’elle est rÈfÈrencÈe par une classe dÈj‡ chargÈe. Nous verrons plus loin comment la premiËre classe est chargÈe), la ma-chine virtuelle charge le code en mÈmoire puis exÈcute des opÈrations regroupÈes en 3 phases:
- VÈrication:
- ContrÙler que le bloc chargÈ commence bien par le mot magique qui identie les classes java compilÈes
- ContrÙler que le code compilÈ ne comporte que des bytecode lÈgaux
- ContrÙler que tout branchement dÈbouche bien sur un dÈbut d’ins-truction
- bien d’autres choses…
- PrÈparation: principalement crÈation des variables de classes et leur aec-tation avec les valeurs par dÈfaut ou des valeurs initiales spÈciÈes tant qu’elles sont dÈterminÈes ‡ la compilation
- RÈsolution: localiser les classes rÈfÈrencÈes symboliquement et les charger. Cette phase peut-Ítre retardÈe jusqu’au moment o˘ une classe donnÈe est absolument nÈcessaire.
Quand la machine virtuelle est lancÈe explicitement, par exemple depuis la ligne de commande, elle accepte une option -classpath identique ‡ celle du compi-lateur.
8
3.2.5 Applications, applets, servlets
Une application autonome doit possÈder un point d’entrÈe standard, sous la forme d’une classe publique, possÈdant une mÈthode publique de classe dont le nom est main. Plus prÈcisÈment l’en-tÍte de cette mÈthode (sa signature) doit Ítre:
public static void main(String[] arg)
static signie que c’est une mÈthode de classe et pas une mÈthode d’instance, void signale qu’elle ne renvoie aucun rÈsultat, et (String[] arg) qu’elle reÁoit comme un argument un tableau de chaÓnes de caractËres. Si une application est lancÈe par:
java FileCopy input output
FileCopy.main recevra comme argument un tableau de 2 chaÓnes de caractËres:
{input,output}.
Une telle application est exÈcutÈe en lanÁant l’exÈcutable de la machine virtuelle avec le nom de la classe en argument. Certains environnements d’exÈ-cution fournissent une application cadre ( framework), qui gËre des composants, qui n’ont donc pas besoin d’un main() propre, mais dont la responsibilitÈ est d’exporter d’autres mÈthodes. Les deux cas les plus frÈquemment rencontrÈs sont:
- L’environnement d’exÈcution d’ applets au sein d’un navigateur web. Les applets sont des classes java qui enrichissent le contenu de pages web, et qui doivent possÈder certaines mÈthodes d’initialisation, de lancement, de suspension et de terminaison. Ce sont normalement des composants graphiques qui doivent donc rÈagir ‡ des ÈvËnements, et se reprÈsenter dans la page. L’ exÈcution d’un applet est dÈclenchÈe par le chargement dans le navigateur d’une page html contenant un rÈfÈrence ‡ cet applet.
- l’environnement d’exÈcution de servlets au sein d’un serveur web. Les serv-lets sont des classes java qui permettent de gÈnÈrer, en rÈponse ‡ des re-quÍtes en provenance d’un navigateur lointain, des pages web dynamiques, et qui doivent possÈder des mÈthodes de traitement de requÍtes HTTP. L’exÈcution d’un servlet est dÈclenchÈe par la rÈception par le serveur d’une requÍte dont l’url rÈfÈrence ce servlet.
3.3 Les packages de base
Un certain nombre de packages font partie de la spÈcication du langage. La liste suivante n’est pas exhaustive:
- Les packages noyau: bien qu’ils soient sÈparÈs pour des raisons logiques, chacun de ces packages fait rÈfÈrence aux autres, et ne pourrait fonctionner sans eux, mÍme si la spÈcication java l’autorisait:
- java.lang C’est le seul package qui n’ait jamais besoin d’Ítre im-portÈ. Il contient entre autres la class Object, les classes String et
9
StringBuffer dÈj‡ rencontrÈes, la classe Exception. Il contient aussi la classe utilitaire System, celle-ci est une classe utilitÈ, ne contenant que des membres de classe, et en particulier s’appuie sur le package java.io, par exemple pour fournir l’Èquivalent de stdout et stderr.
-
- java.util Un package d’intÈrÍt trËs gÈnÈral, mais contenant des classes moins systËme: la trËs utilisÈe classe Vector se trouve ici. Ce ne sont pas des classes utilitÈs, util signiant ici utilitaire, ce qui possÈde (malheureusement) un sens diÈrent sous une apparence proche!
- java.io Le package de base d’accËs aux entrËes-sorties (chiers, ter-minaux).
- java.awt Le package initial pour la programmation des GUI. Un des points les plus remaniÈs de java depuis sa sortie (‡ juste titre!), il est maintenant conseillÈ d’utiliser javax.swing.
- java.applet Le package de dÈnition des applets.
- java.net Le package pour la programmation de rÈseau.
- java.beans le package des composants java.
- java.security Le package de la sÈcuritÈ: signature, encryption, politique de sÈcuritÈ. En fait bien que ce package puisse Ítre ignorÈ dans un premier temps par le dÈveloppeur applicatif, c’est absolument un package noyau, et les 3 premiers packages y font rÈfÈrence.
En principe, les packages dont la racine est java sont ceux qui font partie des spÈcications du langage, et ceux dont la racne est javax sont des extensions. NÈanmoins javax.swing fait partie intÈgrante de jdk1.2, et son inclusion dans javax est basÈe sur ce que l’on appelle en gÈnÈral pudiquement des raisons socio-historiques.
- ElÈments de base du langage
4.1 ElÈments lexicaux
Le jeu de caractËres utilisÈ pour l’Ècriture des programmes est l’Unicode, qui Ètend les codes ASCII pour y inclure tous les caractËres nationaux de tous les pays. Les caractËres accentuÈs sont typiquement acceptÈs comme lettres dans la dÈnition des identicateurs. Ceci signie qu’un dÈveloppeur FranÁais qui le dÈsire peut dÈclarer des variables comme rÈsultat. En thÈorie ceci est aussi vrai pour les noms de classe, mais ces noms ayant des consÈquences sur les noms de chier, les environnements et les outils peuvent mal rÈagir ‡ de tels noms, et la prudence est encore conseillÈe.
4.2 Types, valeurs, et variables
java est un langage fortement typÈ, ce qui veut dire que toute variable doit Ítre dÈclarÈe et typÈe avant qu’on puisse y faire rÈfÈrence, et que toute expression
10
possÈde un type dÈterminable ‡ la compilation.
Les types rentrent dans trois catÈgories:
- Les types primitifs:
- Les types intÈgraux signÈs:
- byte sur 8 bits (attention, en C/C++, byte est souvent unsigned char)
- short sur 16 bits
- int sur 32 bits
- long sur 64 bits
- Les types intÈgraux signÈs:
Le modicateur unsigned n’existe pas en java. Les valeurs de ces types, et leurs constantes littÈrales, sont celles du C++ sur les ma-chines o˘ les tailles coÔncident. Le fait que la taille de ces types soit justement , en java , indÈpendante de l’environnement, s’il peut Ítre un blocage rhÈdibitoire pour les dÈveloppeurs qui doivent coller ‡ la machine, est un immense soulagement pour tous les autres!
- char un entier non signÈ sur 16 bits, reprÈsentant un code Unicode Les valeurs et constantes littÈrales sont, cette fois, un sur-ensemble du C++, l’Unicode ajoutant les sÈquences:
‘\uxxxx ‘
o˘ xxxx reprÈsente 4 chirs hexadÈcimaux.
-
- Les types ottants IEEE signÈs
- oat sur 32 bits
- double sur 64 bits
- boolean reprÈsentant une valeur de vÈritÈ, et dont les valeurs pos-sibles sont true ou false
- void qui est un type sans valeur!
- Les types ottants IEEE signÈs
- Les types qui rÈfÈrencent une classe. Si l’on a une dÈclaration:
Car c0;
en supposant que Car soit une classe dÈnie dans un package courant ou importÈ, sa signication est que l’identicateur o1 est une variable qui dÈnote un objet de type Car. L’instruction:
c0=c1;
ne signie pas que c0 est modiÈ en quoi que ce soit, ni qu’une copie de c1 est crÈÈe, mais que c0 dÈnote maintenant le mÍme objet que c1. Si c0 est modiable, ce sera par:
- c0.attribute =value ; en admettant que Car possÈde au moins un attribut de classe modiable
11
- c0.modifyingMethod (); en admettant que Car possËde au moins une mÈthode modiante
Si c0 est passÈ en argument ‡ une mÈthode:
public void use(Car car) {…}
par l’instruction
xxx.use(c0);
ce n’est pas une copie qui est transmise, mais bien une rÈfÈrence partagÈe sur l’objet initial, et si la mÈthode appelÈe modie cet objet ci-dessus, ces modications seront visibles ‡ travers c0. Par contre, si la mÈthode exÈcute
car=anotherCar;
c0 n’est pas touchÈ. Noter que, les types primitifs ne possÈdant ni mÈ-thodes ni attributs, ils ne peuvent Ítre modiÈs en Ètant passÈs comme argument. On dit quelquefois que les valeurs primitves sont passÈes par valeur, alors que les objets sont passÈs par rÈfÈrence. En fait, en java, tous les arguments sont toujours passÈs par valeur, mais les valeurs qui dÈnotent des objets sont elles-mÍme des rÈfÈrences!
Nous verrons d’autre part, avec l’hÈritage, que dans le cas de la dÈclaration Car c0;, ‡ l’exÈcution, c0 peut rÈfÈrencer un objet d’un type plus riche que Car.
3. Les types obtenus ‡ partir d’un autre type par l’adjonction de [], c’est-
‡-dire les types tableaux.
En rÈalitÈ un type tableau est une classe, que son type de base soit primi-tif ou non, gÈnÈrÈ automatiquement par le systËme compilateur/machine virtuelle. Le type ne spÈcie pas la dimension d’un tableau, mais un ta-bleau existant ne peut modier cette dimension: il possËde un attribut length qui se comporte exactement comme une variable membre d’ins-tance dÈclarÈe final. Les ÈlÈments individuels du tableau sont accÈdÈs par la syntaxe:
array[i]
la machine virtuelle vÈriant que la valeur de i est compatible avec la dimension du tableau, et ils peuvent Ítre modiÈs:
array[i]=new_element;
Les tableaux multi-dimensionnels sont rÈalisÈs comme des tableaux de tableaux.
12
Le type chaÓne de caractËres n’apparaÓt pas explicitement: en eet ce n’est pas un type primitif, mais une classe prÈdÈnie, String, ‡ laquelle le compilateur rÈserve un traitement spÈcial: en eet les constantes littÈrales de ce type ont la mÍme forme qu’en C/C++ ( Hello World! , c:\\winnt), l’opÈrateur + est surchargÈ de son sens usuel numÈrique pour exprimer la concatÈnation, tous les autres types sont susceptibles d’une conversion en String qui peut Ítre dÈclenchÈe implicitement, par exemple quand une expression de ce type est un opÈrande de la concatÈnation. Un objet de type String ne peut jamais Ítre modiÈe: en eet les seuls membres d’instance non privÈs sont des mÈthodes non modiantes. Les mÈthodes dont le nom semble modiant gÈnËre en fait de nouvelles chaÓnes Dans le code suivant:
String unixPath=expression ;
String winPath=unixPath.replace(‘\\’,’/’);
unixPath est inchangÈ. Pour avoir l’Èquivalent d’un tableau de caractËres modi-ables, on doit utiliser la classe StringBuffer , qui est l’Èquivalent d’un char[] dont la dimension serait modiable. On peut convertir aisÈment un StringBuffer en String et vice-versa.
4.3 Les dÈclarations
4.3.1 Les variables
Les dÈclarations de variable se trouvent aussi bien dans le corps qu’une mÈthode que directement ‡ l’intÈrieur d’une classe.Une dÈclaration est toujours de la forme:
type identificateur [ = valeur_initiale] ;
ce qui est beaucoup plus simple que la syntaxe correspondante en C/C++. L’initia-lisation est optionelle, son absence impose certaines contraintes. Une dÈclaration peut concerner:
- Une variable membre
- d’instance
- de classe, la dÈclaration ci-dessus est alors prÈcÈdÈe du mot-clef static
- Une variable locale
Les consÈquences de l’absence d’initialisateur dans la dÈclaration varient suivant les cas. Dans les cas 1, une valeur par dÈfaut est aectÈe. Cette valeur est
- Pour les types numÈriques, le 0 du type concernÈ (par exemple, ‘\u0000’ pour char)
- Pour un boolean, false
- Pour un type rÈfÈrÈnce sur un object, y compris un type tableau, null, qui signie que la variable ne rÈfËrence aucun objet
13
Dans le cas 2, aucune valeur par dÈfaut n’est aectÈe, mais le compilateur exige de pouvoir se convaincre que la variable recevra une valeur avant toute utilisa-tion.
Une variable peut aussi Ítre qualiÈe de final, ce qui signie qu’elle ne doit prendre qu’une seule valeur au cours de son cycle de vie. L‡ plus encore, en l’absence d’initialisation, les exigences du systËme sont variables:
- Dans le cas 2, le mÈcanisme est le mÍme que pour les variables non final, mais bien s˚r en outre le compilateur refuse toute autre aectation
- Dans le cas 1(b), une initialisation est exigÈe (mais le message d’erreur de javac est un peu confus)
- Dans le cas 1(a), les valeurs par dÈfaut ne sont pas utilisÈes, tout construc-teur doit comporter une aectation ‡ ce membre, bien s˚r avant tout usage, et non suivie d’une autre.
Du point de vue du style, c’est-‡-dire de la lisibilitÈ et de la maintenance du code, il est toujours avantageux si cela est possible de ne dÈclarer une variable locale que quand elle peut recevoir une initialisation signicative. Dans certains cas, nÈanmoins, l’initialisation doit Ítre rÈalisÈe dans des branches condition-nelles, et le compilateur garantit que le dÈveloppeur ne pourra pas oublier un cas. Le traitement des variables final membres d’instance est particuliËrement intÈressant, car il permet de dÈclarer des attributs non modiables dont la valeur dÈpend nÈanmoins des arguments du constructeur.
4.3.2 Les mÈthodes
La dÈclaration d’une mÈthode prend la forme:
return-type methodName (argument-list ) optional-throws-clause { methodBody
}
o˘:
return-type est ou bien le type de l’expression renvoyÈe par l’exÈcution de la mÈthode, ou bien void.
argument-list est une liste de dÈclarations sans qualicateurs ni initialisateurs sÈparÈe par des virgules, qui dÈcrit les arguments attendus par la mÈthode. Cette liste peut Ítre vide.
optional-throws-clause a, si elle est prÈsente, la forme throws exception-list , nous reviendrons plus tard sur les exceptions et cette clause.
methodBody est une liste d’instructions (voir la dÈnition d’une instruction plus loin) qui ne peut Ítre vide que si return-type est identique ‡ void, sinon tout chemin d’exÈcution doit se terminer par une instruction return renvoyant une expression dont le type est compatible avec return-type .
14
4.4 Les expressions
4.4.1 Structure des expressions
La syntaxe des expressions java, et, jusqu’‡ un certain point, leur signica-tion, est trËs voisine du C++. Une expression est composÈe ‡ partir des ÈlÈments suivants:
- Les constantes littÈrales. Il en existe de type
- int: 0, -100, 1234567890
- long: 0L, -100L, 1234567891234567890L
- char: ‘a’, ‘\n’, ‘\\’, ‘\, ‘È’, ‘\u03e9’
- float: 0f, 3.14f, -6.022e+15f
- double: 0., 3.14, 1e137
- boolean: false et true
- String, bien que ce ne soit pas un type primitif: , Hello, One\nTwo. En outre le compilateur transformera la concatÈnation de deux constantes littÈrales de type String:
String msg=Hello +World!;
sera compilÈ exactement comme
String msg=Hello World!;
- Les variables. Ce sont des identicateurs reconnus comme dÈnis et valides dans le bloc d’instructions courant en tant que (certains cas font rÈfÈrence
- des notions dÈcrites plus loin):
- Une variable locale, introduite par une dÈnition normale ou
- Une variable introduite par l’initialisation d’une instruction for en cours d’exÈcution.
- Un argument de la mÈthode ou du constructeur en cours d’exÈcution.
- L’identicateur liÈ ‡ l’exception en cours de traitement si un bloc de traitement d’exception est en cours d’exÈcution.
- Une variable membre d’instance de this, si la mÈthode en cours d’exÈcution est une mÈthode d’instance ou un constructeur.
- Une variable membre de classe de la classe dont une mÈthode (d’ins-tance ou de classe) ou un constructeur est en cours d’exÈcution.
- des notions dÈcrites plus loin):
Les cas (a) ‡ (d) sont en fait des variantes de variables locales, c’est-‡-dire des variables dont la durÈe de vie n’est pa s liÈe ‡ celle d’une instance ou d’une classe, mais ‡ la durÈe d’exÈcution d’un bloc de code. En java, une variable locale ne peut jamais en cacher une autre, c’est-‡-dire redÈnir une autre variable locale de mÍme nom qui serait visible au point de la nouvelle dÈnition.
15
- Les opÈrateurs :
- unaires: ++, –, +, -, , !
Comme en C/C++, ++ et — existent en prÈxÈ et en postxÈ
-
- binaires, groupÈs par prÈcÈdence:
- multiplicatifs: *, /, %
- additifs: +, –
- de shift: <<,>>,<<<, >>>
- de comparaison: <, <=, >, >=
- d’ÈgalitÈ: ==, !=
- de manipulation binaire: &, |,
- logiques: &&, ||,
- ternaire: cond ? expr0 : expr1
- binaires, groupÈs par prÈcÈdence:
- Les opÈrations liÈes au transtypage
- Les conversions: (type )expr
- La comparaison de type: expr instanceof referenceType
- Les aectations: =, et op =, op Ètant un des opÈrateurs binaires dÈnis en (b)i, (b)ii, (b)iii, ou (b)vi.
- L’opÈrateur d’accËs ‡ un ÈlÈment d’un tableau, concrÈtisÈ par []: expr [index ]
- L’opÈrateur d’accËs ‡ un attribut, concrÈtisÈ par .:
- expr .attrName
- referenceType .attrName
- Les invocations de mÈthodes:
- expr .methName (arguments )
- referenceType .methName (arguments )
- Les instantiations d’objet: new class (arguments )
En dehors de leurs utilisations syntaxiques explicites (conversions, dÈclarations et invocations de mÈthode, instructions de boucle, traitements d’exceptions) les parenthËses sont utilisÈes pour grouper des sous-expressions, soit par lisibilitÈ, soit pour imposer une signication diÈrente de celle impliquÈe par les prÈcÈ-dences par dÈfaut. Par exemple:
a*x+y
a la mÍme signication que
(a*x)+y
16
et non pas la mÍme que
a+(x*y)
Contrairement ‡ C/C++, , n’est jamais un opÈrateur, il intervient uniquement comme sÈparateur, dans les cas suivants (certains font rÈfÈrence ‡ des notions dÈnies dans des sections ultÈrieures):
- entre plusieurs variables dÈclarÈes simultanÈment
- entre les paramËtres formels d’une dÈclaration de mÈthode ou de construc-teur
- entre les argments passÈs lors d’une invocation de mÈthode ou d’une ins-tantiation d’objet
- entre plusieurs interfaces hÈritÈs par un mÍme interface ou une mÍme classe
- entre plusieurs exceptions dÈclarÈes levÈes dans une dÈclaration de mÈ-thode
- entre plusieurs instructions-expressions dans le code d’initialisation ou d’itÈration d’une instruction for.
4.4.2 Remarques sur les expressions
- L’aectation exige que le membre gauche soit ce qu’on appelle an anblais une lvalue, c’est-‡-dire une expression dÈnotant non pas simplement une valeur, mais une location o˘ une valeur peut Ítre conservÈe. Les lvalue en java sont:
- les variables, locales, ou membres de classe ou d’instances.
- Une expression d’accËs ‡ un attribut de classe.
- Une expression d’accËs ‡ un attribut d’instance dont le membre gauche est une lvalue.
- Une expression d’accËs ‡ un ÈlÈment de tableau dont le membre gauche est une lvalue.
En outre, pour Ítre aectable:
- une lvalue ne doit pas Ítre une variable final ayant dÈj‡ ÈtÈ initiali-sÈe.
- le type du membre droit doit Ítre compatible avec celui du membre gauche (c’est-‡-dire Ítre susceptible d’une conversion implicite dans celui-ci, voir plus loin des remarques additionnelles sur ce que signie la compatibilitÈ pour les types primitifs)
- le membre gauche doit aussi satisfaire ‡ toutes les conditions d’ac-cessibilitÈ, mais ceci n’est pas une condition liÈe ‡ l’opÈrateur d’af-fectation, mais intrinsÈque ‡ toute expression.
17
- Les opÈrateurs ++ et — sont ‡ considÈrer comme des opÈrateurs d’aec-tation.
- Nous avons dÈcrit plus haut quelles Ètaient les conversions explicites et im-plicites valides entre types rÈfÈrence. Bien que les types primitifs se situent en dehors de l’orientation objet, d’une certaine faÁon le type int est une extension du type short. Un type numÈrique sera gÈnÈralement conver-tible implicitement en un type plus Ètendu, une conversion explicite Ètant nÈcessaire dans le sens inverse. Il n’y a aucune conversion entre les types numÈriques et le type boolean (pas plus que null ne peut Ítre converti en false). Attention: contrairement ‡ ce qu’on pourrait penser, il existe des cas o˘ une conversion implicite peut amener une perte silencieuse de prÈcision:
int i=1234567890;
float f=i;
Un float n’a pas susamment de prÈcision pour conserver tous les chires signicatifs de i. Le mÍme problËme se pose en aectant un long ‡ un double.
- Une consÈquence de l’application stricte des rÈgles qui prÈcËdent est que la derniËre instruction du bloc suivant gÈnËre une erreur de compilation:
short s=2;
int i=3;
short t=i*s;
En eet, la division est comprise comme i*(int)s, puisqu’il s’agit d’une conversion implicite acceptable, et l’instruction cherche alors ‡ aecter un int ‡ un short , ce qui exige une conversion explicite. Des formes acceptÈes, ayant un sens diÈrent, mais, dans ce cas, le mÍme rÈsultat, sont:
short t=(short)(i*s);
ou
short t=(short)i*s;
Le compilateur admet une exception ‡ cetter rÈgle pour les aectations de constantes calculables ‡ la compilation, ce qui est bien agrÈable car il n’y a pas de constantes lexicales entiËres en dessous de l’ int. Par exemple:
short s=1;
est lÈgal, sans nÈcessiter (short)1 . Cette exception ne concerne que les aectations: si une mÈthode attend un argument de type short, il faudra lui passer (short)1.
18
- Comme nous l’avons dÈj‡ vu, les conversions implicites des type primi-tifs vers le type String sont une exception ‡ ces rÈgles. Elles sont en particulier dÈclenchÈes par l’opÈrateur binaire + considÈrÈ comme opÈra-teur de concatÈnation, ce qui est dÈj‡ une interprÈtation particuliËre du compilateur.
- Si les opÈrateurs de divison entiËre peuvent lever une ArithmeticException en cas de division par 0, ce n’est pas le cas des opÈrations ottantes, qui utilisent les notions IEEE d’innitÈ et de NAN (NotANumber ), et ne pro-voquent jamais d’exception.
4.5 Les instructions
Une fois encore, la syntaxe s’appuie sur le C/C++. Elles peuvent Ítre re-groupÈes en deux catÈgories, les instructions composÈes, qui peuvent conte-nir d’autres instructions , et les instructions simples, qui ne peuvent pas. Par ailleurs, toute instruction peut optionellement Ítre prÈcÈdÈe d’une Ètiquette, sous la forme:
identificateur :
instruction
le sÈparateur de ligne n’Ètant pas obligatoire.
4.5.1 Instruction simples
Les instructions simples doivent toutes Ítre terminÈes par un ;, que nous ne rÈpÈterons pas dans leur description. Ce sont:
- L’instruction vide: sans commentaires.
- L’instruction-expression: l’expression est ÈvaluÈe pour son eet de bord, ce sera une aectation (y compris l’Èvaluation des opÈrateurs ++ et –), une invocation de mÈthode, ou une instantiation d’objet. Le compilateur rejetera toute autre expression.
- L’instruction de retour de mÈthode. Deux formes:
- return si la mÈthode n’a pas de type de retour
- return expr si la mÈthode a un type de retour (autre que void) dÈclarÈ.
- L’instruction break: sortie d’une instruction composÈe. Le mot-clef break peut optionnellement Ítre suivi d’un identicateur qui doit Ítre celui d’une Ètiquette d’une instruction composÈe englobant l’instruction courante. Si cette Ètiquette est spÈciÈe, elle identie l’instruction cible. Sinon, l’ins-truction cible est l’instruction composÈe de type boucle ( for, while, ou do) ou switch (voir plus loin leurs descriptions) la plus intÈrieure englo-bant l’instruction courante. Si aucune instruction cible n’est identiÈe le compilateur rejetera le break . Sinon l’eet est de transfÈrer le contrÙle
19
d’exÈcution ‡ l’instruction suivant immÈdiatement l’instruction cible (ce peut Ítre une instruction return implicite ‡ la n d’une mÈthode sans type de retour).
- L’instruction continue: enchaÓnement de boucle. Le mot-clef continue peut optionnellement Ítre suivi d’un identicateur qui doit Ítre celui d’une Ètiquette d’une instruction composÈe englobant l’instruction courante. Si cette Ètiquette est spÈciÈe et si l’instruction identiÈe est une instruc-tion de boucle, elle identie l’instruction cible. Sinon, l’instruction ci-ble est l’instruction composÈe de type boucle la plus intÈrieure englobant l’instruction courante. Si aucune instruction cible n’est identiÈe le com-pilateur rejetera le continue. Sinon l’eet est de transfÈrer le contrÙle d’exÈcution au point de l’instruction de boucle qui enchaÓne sur l’itÈration suivante.
- L’instruction throw: sa syntaxe est:
throw exceptionExpr
exceptionExpr devant s’Èvaluer comme une instance dÈrivÈe de Throwable (normalement Exception), et son eet est de lever l’exception en question, et de transfÈrer abruptement le contrÙle d’exÈcution au premier point de code appelant ayant dÈclarÈ traiter une exception de ce type (voir plus loin l’instruction try ).
4.5.2 Instructions composÈes
- Le bloc: la syntaxe du bloc est une liste d’instructions encadrÈes par { et }. Cette liste peut Ítre vide, {} est donc une instruction composÈe lÈgale de type bloc.
- Le bloc synchronisÈ: la syntaxe est celle du bloc prÈcÈdÈe de synchronized(expr ). expr doit s’Èvaluer comme une rÈfÈrence non null ‡ un objet. Si expr pos-
sËde un type primitif, le compilateur rejetera l’expression. Sinon, et si expr s’Èvalue comme null , une exception sera levÈe ‡ l’exÈcution
- L’instruction conditionnelle: sa syntaxe est:
if (booleanExpr ) yesStatement [ else noStatement]
yesStatement et noStatement Ètant des instructions quelconques, et la partie else Ètant optionnelle.
- L’instruction de branchement: sa syntaxe consiste en l’encadrement par switch(expr) { et } d’une suite d ‘Ètiquettes de choix et d’instruc-tions, une Ètiquette de choix Ètant soit:
default:
soit:
case constantExpr :
20
Cette forme est soumise ‡ certaines contraintes:
- expr doit Ítre d’un type intÈgral autre que long.
- les constantExpr doivent Ítre dÈterminÈes au moment de la compi-lation et aectables au type de expr .
- deux Ètiquettes de choix dans la mÍme instruction de branchement ne peuvent pas Ítre identiques
A l’exÈcution de cette instruction, exp r est ÈvaluÈe et une des Ètiquettes est choisie en fonction de sa valeur . Si cette valeur ne corrrespond ‡ aucun des case …: et si l’Ètiquette default n’est pas prÈsente, le contrÙle est transfÈrÈ ‡ l’instruction suivant l’instruction de branchement. Sinon le contrÙle est transfÈrÈ ‡ l’instruction prÈcÈdÈe par l’Ètiquette. Cette instruction est certes utile, mais avant mÍme de ne pas Ítre orientÈe objet, elle est aussi peu compatible avec la programmation structurÈe. C’est en fait un hÈritage quasi-direct de l’assembleur!
5. L’instruction de traitement d’exception. Sa syntaxe est:
try tryBlock
catch(ExceptionClass id ) handlerBlock
…
[ finally finalBlock]
block reprÈsentant une instruction de type bloc, les … signiant que la clause catch peut Ítre rÈpÈtÈe, et les [] signiant que la clause finally est optionelle (en rÈalitÈ un tel bloc peut aussi comporter une clause finally sans aucune clause catch ). Les ExceptionClass doivent Ítre des types rÈfÈrences dÈrivÈs de Throwable. A l’exÈcution:
- tryBlock est exÈcutÈ.
- Si une exception est levÈe et non traitÈe ‡ l’intÈrieur de cette exÈcu-tion, et si son type est compatible avec l’ ExceptionClass d’une ou plusieurs clauses catch :
- le contrÙle est transfÈrÈ au handlerBlock de la premiËre telle clause, id rÈfÈrenÁant l’objet exception qui a ÈtÈ levÈ.
- si l’exÈcution de ce bloc lËve une nouvelle exception, elle est pro-pagÈe vers les niveaux supÈrieurs
- Si aucune clause ne satisfait la condition prÈcÈdente, l’exception ori-ginelle est propagÈe vers les niveaux supÈrieurs.
- Si une clause finally est prÈsente, dans tous les cas :
- finalBlock est exÈcutÈ aprËs terminaison du mainBlock et/ou du handlerBlock
- si cette exÈcution lËve une exception, elle est propagÈe vers les niveaux supÈrieurs, remplaÁant Èventuellement l’expression men-tionnÈe en (b)ii ou (c).
21
6. L’instruction for: la syntaxe en est:
for ([init] ;[booleanExpr] ;[next] ) statement
statement Ètant une instruction, les [] indiquant un caractËre option-nel, init Ètant soit une dÈclaration de variable locale avec initialisation, soit une liste d’instructions-expressions sÈparÈes par des virgules, next Ètant une liste d’instructions-expressions sÈparÈes par des virgules. L’exÈ-cution de l’instruction for se dÈcompose ainsi:
-
- Initialisation:
- Si init est une dÈclaration de variable, elle est exÈcutÈe, la por-tÈe de la variable Ètant le reste des composants du for.
- Sinon, la ou les instructions composant init sont exÈcutÈes dans l’ordre.
- ItÈration:
- Si booleanExpr est prÈsent et si son Èvaluation renvoie false, l’instruction for est terminÈe
- Sinon, statement est exÈcutÈ, la ou les instructions composant next sont exÈcutÈes dans l’ordre, et une autre itÈration est en-tamÈe.
- Initialisation:
- L’instruction while: la syntaxe en est:
while (booleanExpr ) statement
L’exÈcution ne comporte qu’une phase, l’itÈration:
-
- Si l’Èvaluation de booleanExpr renvoie false, l’instruction while est terminÈe
- Sinon, statement est exÈcutÈ, et une autre itÈration est entamÈe.
- L’instruction do: la syntaxe en est:
do statement while (booleanExpr ) ;
ce qui en fait la seule instruction composÈe ‡ se terminer par un ;.
L’exÈcution ne comporte qu’une phase, l’itÈration:
- statement est exÈcutÈ
- Si l’Èvaluation de booleanExpr renvoie false, l’instruction do est terminÈe, sinon une autre itÈration est entamÈe
22
- Les classes de java
5.1 DÈnition de base
Au minimum, la dÈnition d’une classe prend la forme:
class ClassName {
// dÈclaration des membres
}
les membres Ètant des variables ou des mÈthodes, d’instance ou de classe, et des constructeurs. La dÈclaration d’un membre de classe est identiÈe en Ètant prÈcÈdÈe du qualicateur static (Depuis jdk1.1 , un membre peut Ègalement Ítre une classe intÈrieure). Nous savons dÈj‡ que la classe peut Ítre optionellement dÈclarÈe public . La dÈclaration de chaque membre peut aussi Ítre prÈcÈdÈe d’un qualicateur de protection, public, protected, ou private .
5.2 Attributs
La dÈclaration d’une variable membre est identique ‡ la dÈclaration d’une variable locale. En dehors du qualicateur final, applicable Ègalement aux va-riables locales, du qualicateur static, applicable Ègalement ‡ tous les membres
‡ l’exclusion des constructeurs, et des qualicateurs de protection, applicables Ègalement ‡ tous les membres, deux qualicateurs sont rÈservÈs aux attributs:
transient, liÈ ‡ la sÈrialisation
volatile, liÈ ‡ la concurrencr
5.3 MÈthodes
Les mÈthodes sont, en java, la seule forme des fonctions. On a dÈj‡ vu qu’on ne peut rien dÈnir en dehors d’une classe, mais on ne peut pas non plus dÈnir de fonctions locales au corps d’une mÈthode. Si l’on a besoin de fonctions n’Ètant pas associÈes ‡ un objet possÈdant un Ètat (c’est-‡-dire un ensemble d’attributs) propre, ces fonctions seront dÈnies comme mÈthodes de classe d’une classe correspondant ‡ leur fonctionnalitÈ.
Plusieurs mÈthodes d’une mÍme classe peuvent avoir le mÍme nom si le nombre ou les types de leurs arguments sont diÈrents. La seule diÈrence des types de retour n’est pas susante! Dans le code d’une mÈthode d’instance, la pseudo-variable this est automatiquement dÈnie par le compilateur, et dÈnote l’objet sur lequel la mÈthode a ÈtÈ invoquÈe.
5.4 Constructeurs
Un constructeur est une pseudo-mÈthode n’ayant pas de spÈcication de type de retour, et dont le nom est celui de la classe. Bien que le constructeur soit
23
plus proche d’une mÈthode de classe que d’une mÈthode d’instance, la pseudo-variable this est dÈnie et dÈnote l’objet qui est en cours d’instantiation. Une classe peut possÈder plusieurs constructeurs, toujours diÈrenciÈs par leurs listes d’arguments. Si aucun constructeur n’est dÈclarÈ, un constructeur implicite, public, sans arguments, et avec un corps vide, est gÈnÈrÈ.Un constructeur peut en appeler un autre par une utilisation patriculiËre de this:
public class Container {
public Container(int size) {…}
public Container() {
this(10);
}
}
this(10) est ici interprÈtÈ comme appelant le constructeur appropriÈ ‡ la liste d’arguments qui lui est passÈ. L’exemple ci-dessus permet de rÈaliser une notion de valeur par dÈfaut de l’argument size. Attention:
- Un appel ‡ this avec des arguments de mÍme type que le constructeur courant provoquera une rÈcursion innie. heureusement les compilateurs le dÈtectent.
- Un constructeur qui en appelle un autre peut exÈcuter du code supplÈ-mentaire, mais l’appel ‡ this doit Ítre la premiËre instruction.
L’appel du constructeur est dÈclenchÈ par la syntaxe:
var=new ClassName(args);
la liste d’arguments pouvant bien s˚r Ítre vide. Cette syntaxe est la seule pou-vant dÈclencher la crÈation d’un objet. Dans le cas des tableaux, que nou savons Ítre des classes dÈguisÈes, le constructeur est accessible par une syntaxe parti-culiËre:
String[] names=new String[n];
qui alloue un tableau de n chaÓnes, dont tous les ÈlÈments (traitÈs comme des attributs de la classe String[]) sont initialisÈs ‡ null. Dans le cas des tableaux multi-dimensionnels, cette syntaxe peut s’Ètendre sous deux formes:
String[][] names=new String[p][q];
qui alloue un tableau de p tableaux de q chaÓnes, chacun des p*q ÈlÈments initialisÈs ‡ null, ou
String[][] names=new String[p][];
qui alloue un tableau de p tableaux de chaÓnes, chacun de ces p Èlements de type String[] Ètant lui-mÍme initialisÈ ‡ null.
24
5.5 Destruction
Comme on l’a dit, la destruction d’un objet n’est jamais demandÈe explici-tement par le programme. Un objet vit tant qu’il est rÈfÈrencÈ, les rÈfÈrences pouvant provenir directement:
- d’une variable locale
- d’une variable membre de classe d’une classe chargÈe
- d’une variable membre d’instance d’un objet vivant
- d’un ÈlÈment d’un tableau, ce qui est une variante particuliËre du cas prÈcÈdent, et trËs rÈpandue: tous les containers existant couramment utilisent en dÈnitive un tableau.
DËs qu’un objet n’est plus rÈfÈrencÈ, la machine virtuelle est libre de libÈrer son espace mÈmoire, gr‚ce au Garbage Collector mentionnÈ plus haut. NÈanmoins, l’initialisation de l’objet peut avoir allouÈ des ressources prÈcieuses (comme des connexions rÈseau) dont la machine virtuelle n’est pas consciente. Une mÈthode particuliÈre:
void finalize() {…}
est dÈnissable comme l’endroit adÈquat pour libÈrer les ressources autres que la mÈmoire allouÈes par l’objet. Son rÙle est similaire ‡ celui du destructeur en C++. NÈanmoins, si la machine virtuelle est tenue de dÈtruire les objets non rÈfÈrencÈs avant de se plaindre qu’il n’y a plus de mÈmoire pour en crÈer de nouveaux, et, si possible avant que l’utilisateur ne se plaigne que l’application rame (!), elle est, dans ces limites, totalement libre du moment de ces destructions, et de leur ordonnancement. Ceci, joint au fait que le dÈveloppeur ne peut pas choisir entre allocation dynamique des objets et allocation automatique sur la pile, fait que le rÙle du naliseur est beaucoup moins crucial et prÈpondÈrant qu’en C++ .
5.6 HÈritage
Une classe peut hÈriter d’une autre. La syntaxe pour ceci est:
class DerivedClass extends BaseClass {…}
Une classe ne peut Ètendre qu’une seule classe de base, ce qui Èlimine l’hÈritage multiple que l’on trouve dans d’autres langages objet. Nous verrons plus loin comment une certaine forme d’hÈritage multiple est nÈanmoins possible. Une classe dÈrivÈe hÈrite de tous les membres non privÈs de sa classe de base, mais elle peut redÈnir les mÈthodes dont elle hÈrite, ce qui permet le polymorphisme. En outre dans une mÈthode d’une classe dÈrivÈe, une nouvelle pseudo-variable, super, est dÈnie. Elle permet:
- de forcer l’appel ‡ une mÈthode dÈnie dans la classe de base, quand celle-ci a ÈtÈ redÈnie dans la classe courante. Ceci est en particulier utile quand une redÈnition veut s’appuyer sur la dÈnition de base.
25
- dans un constructeur, d’appeler un constructeur de la classe de base par une technique analogue ‡ celle vue pour this:
super(args);
En fait tout constructeur d’une classe dÈrivÈe ne contenant pas d’ap-pel explicite ‡ this(…) ou ‡ super(…) est supposÈ commencer par super();, ce qui dÈclenchera une erreur du compÓlateur si la classe de base ne possËde pas de constructeur sans argument, explicite ou implicite.
En fait, toutes les classes autres qu’ Object qui ne comportent pas de clause extends explicite sont automatiquement considÈrÈes comme ayant une clause extends Object . Toutes les classes autres qu’ Object sont donc des classes dÈrivÈes. Une classe peut par contre rÈsister ‡ sa propre dÈrivation:
DÈclarer la classe elle-mÍme comme final interdit toute dÈrivation
DÈclarer une mÈthode non privÈe comme final interdit la redÈnition de cette mÈthode particuliËre
Enn l’hÈritage permet des conversions, implicites ou explicites, entre les types rÈfÈrences. De faÁon gÈnÈrale si Base est une classe, et Derived une classe dÈrivÈe de Base:
- Une expression de type Derived peut Ítre silencieusement convertie dans le type Base, ce qui permet ‡ une variable dÈclarÈe Base de rÈfÈrence un objet de typ rÈel Derived , et ‡ une mÈthode acceptant un argument dÈclarÈ Base de recevoir un argument de type rÈel Derived , ce qui est la base du polymorphisme.
- Une expression de type Base peut Ítre explicitement convertie dans le type Derived par la syntaxe:
(Derived)expr
qui sera acceptÈe par le compilateur, mais qui pourra donner lieu ‡ une erreur ‡ l’exÈcution, si la promesse correspondante n’est pas tenue. Ceci s’applique aussi au cas o˘ Base est un nterface, et Derived est une classe ou un interface qui n’est pas explicitement dÈrivÈ de Base.
- Une expression de type Derived[] peut Ítre silencieusement convertie dans le type Base[], mais c’est une conversion dangereuse, qui peut amener indirectement une erreur ultÈrieure ‡ l’exÈcution: en eet le code peut ensuite aecter ‡ un ÈlÈment du tableau converti une rÈfÈrence ‡ un objet qui n’est pas une instance de Derived . Ceci est dÈmontrÈ dans
26
l’exemple (par ailleurs inintÈressant!) suivant:
public class Bug {
private static void spoil(Object[] arg) {
if (arg.length>0)
arg[0]=new Object();
}
public static void main(String[] arg) {
spoil(arg);
}
}
Ce code compile sans erreur ni avertissement, et l’exÈcuter par java Bug ne provoque aucun incident, mais exÈcuter:
java Bug quoi
provoque le message d’erreur suivant:
java.lang.ArrayStoreException:
at Bug.spoil(Bug.java: 4)
at Bug.main(Bug.java: 7)
En C++, ceci correspondrait au fait que si Derived dÈrive de Base, Derived * est acceptable comme Base * , mais que Derived ** n’est pas acceptable comme Base **.
5.7 AccËs
Il faut bien distinguer la visibilitÈ de l’accessibilitÈ, mÍme si les diagnostics du compilateur ne le font pas toujours. Si un objet obj d’une classe Clz possÈde un membre elt, hÈritÈ ou nom, ce membre est visible:
- comme obj.elt dans tous les cas
- comme Clz.elt si c’est un membre de classe, et c’est alors la notation conseillÈe.
Par contre, comme nous l’avons dÈj‡ dÈcrit, le niveau de protection d’ elt, et la relation entre la classe qui contient le code rÈfÈrenÁant et Clz peuvent en interdire l’usage. Dans le code d’une mÈthode de Clz, les notations this.elt ou Clz.elt peuvent Ítre remplacÈes par la forme abrÈgÈe elt, dans la mesure o˘ il n’y a pas de conit avec le nom d’un argument ou d’une variable locale. Pour Èviter les ambig¸itÈs sans alourdir le code, il est conseillÈ de donner aux attributs, surtout ceux d’instance, un nom reconaissable, comme, dans ce cas, elt_ ou m_elt.
27
5.8 Abstraction
Bien que les niveaux de protection procurent une certaine sÈparation de l’interface et de l’implÈmentation, elle n’est pas totale, en particulier le source d’une classe mÈlange les deux. Il existe un Èquivalent pur interface d’une classe, introduit par le mot-clef interface, justement. La syntaxe de la dÈnition d’un interface est voisine de celle d’une classe, avec quelques importantes diÈrences:
- Un interface peut Ítre public ou package, mais aucun qualicateur de protection ne peut Ítre appliquÈ ‡ ses membres qui sont tous implicitement publics
- Seules les mÈthodes peuvent Ítre des membres d’instances, tout membre attribut est implicitement dÈclarÈ final
- Les mÈthodes n’ont pas de dÈnition, la partie {…} est remplacÈe par ;. Elles sont abstraites.
- Une classe qui veut hÈriter d’un interface dÈclare implements interface (en eet elle n’hÈrite que des dÈclarations, pas des dÈnitions qu’elle devra fournir), et cette clause peut Ítre multiple, c’est-‡-dire qu’une classe peut implÈmenter plusieurs interfaces!
- Un interface ne peut bien s˚r pas hÈriter d’une classe, pas mÍme d’ Object, mais il peut hÈriter d’un ou de plusieurs interfaces, en utilisant cette fois le mot-clef extends .
Cette notion d ‘abstrait est Ègalement Ètendue aux classes: une classe peut Ítre dÈclarÈe abstract , ce qui indique qu’elle n’implÈmente pas toutes les mÈthodes qu’elle dÈclare (ou dont elle hÈrite). Les mÈthodes dÈclarÈes sans Ítre implÈmen-tÈes doivent Ítre elles-mÍmes prÈcÈdÈes du mot-clef abstract. Un cas typique de classe abstraite est une classe qui implÈmente partiellement un interface. Une classe abstraite est donc intermÈdiaire entre un interface et une classe norma-le, mais une autre classe ne peut (‡ nouveau) dÈriver que d’une seule classe abstraite, ce qui limite leur emploi dans une certaine mesure.
On peut dÈclarer une variable avec pour type une rÈfÈrence ‡ un interface ou ‡ une classe abstraite, mais on est alors certain qu’‡ l’exÈcution, si cette variable contient autre chose que la rÈfÈrence null, elle dÈnote un objet ayant un autre type que le type dÈclarÈ!
- La gestion des exceptions
6.1 MÈcanismes gÈnÈraux
De nombreuses erreurs peuvent survenir pendant l’exÈcution d’un programme, qui rendent impossible de pousrsuivre l’exÈcution comme si rien n’Ètait arrivÈ. java dÈnit pour cela la notion d’exception. Une exception provoque la termi-naison abrupte du bloc de code en cours d’exÈcution, et le transfert du contrÙle de l’exÈcution en un point spÈciÈ par le programmeur comme traitant cette
28
exception. Les exceptions sont en fait des classe java, dÈclenchÈes soit impli-citement par une erreur du programme, soit explicitement par l’emploi de la syntaxe:
throw new ExceptionClass (…);
dans du code ayant dÈterminÈ qu’il ne peut pas s’exÈcuter correctement dans le cas prÈsent. Nous avons vu que le programmeur peut spÈcier le traitement d’une exception par la syntaxe:
try tryBlock
catch(ExceptionClass id ) handlerBlock
…
Le contrÙle de l’exÈcution sera tranfÈrÈ au dÈbut du bloc handlerBlock si une exception dÈrivÈe de la classe ExceptionClass est levÈe pendant l’exÈcution de tryBlock .
6.2 ParticularitÈs ‡ la compilation
Les exceptions ne peuvent pas Ítre n’importe quelles classes, elles doivent Ítre dÈrivÈes de la classe java.lang.Throwable . La hiÈrarchie dÈrivÈe de cette classe est divisÈe en 3 parties:
- La classe java.lang.Error et ses dÈrivÈes
- La classe java.lang.RunTimeException et ses dÈrivÈes
- Les autres classes dÈrivÈes de java.lang.Exception
Une mÈthode peut dÈclarer les mÈthodes que son exÈcution peut lever sans qu’elle les gËre elle-mÍme par la clause:
throws ExceptionClass1, ExceptionClass2
Une mÈthode doit dÈclarer de telles exceptions si elles sont du type 3. Cette obligation n’existe pas pour les autres types:
- Les dÈrivÈes de Error sont des erreurs dicilement rÈcupÈrables et qui peuvent se produire n’importe o˘
- Les dÈrivÈes de RunTimeException sont des erreurs qui peuvent Ítre le-vÈes par de nombreuses constructions du langage, mais dont le dÈvelop-peur peut se garantir par des techniques que le compilateur n’est pas en mesure de reconnaÓtre: par ex. ArithmeticException (division par 0), NullPointerException, ClassCastException (toutes ces exceptions sont dans le package java.lang).
Exiger que tout programme dÈclare ou traite ces erreurs nuirait en dÈnitive ‡ la lisibilitÈ et ‡ la maintenabilitÈ du code.
29
- Les Composants java
7.1 Introduction
Au dÈbut, les seuls composants java Ètaient les classes dÈrivÈes de java.awt.Component , c’est-‡-dire des composants graphiques interactifs, destinÈs ‡ Ítre programmÈs explicitement par le dÈveloppeur. Le notion maintenant recouvre des composants
qui peuvent ne pas avoir de facette interactive, mais surtout qui peuvent Ítre manipulÈs par des outils de conception, voire interagir avec d’autres composants, qui ne les connaissaient pas au moment de la compilation. Ces mÈcanismes ont besoin de moyens de communication standardisÈs mais aussi extensibles.
7.2 SÈrialisation
La capacitÈ de sauvegarder l’Ètat d’un objet et de le restaurer ultÈrieurement est intÈressante en soi. Elle l’est particuliËrement dans le cas de composants que des outils visuels permettent de paramÈtrer sur-mesure, car ce paramÈtrage est perdu si on ne peut le sauvegarder. java prÈvoit un mÈcanisme pour sau-vegarder un objet sur un ux de sortie (comme un chier). Ce mÈcanisme est ‡ plusieurs vitesses, c’est-‡-dire qu(il est plus ou moins automatique ou sous contrÙle du programme suivant le dÈsit du dÈveloppeur. Une classe qui implÈ-mente l’interface Serializable prote au maximum de l’automatisation. Une certaine mesure de contrÙle peut Ítre amenÈe par un nouveau qualicateur pour les attributs, transient. Un attribut dÈclarÈ transient ne sera ni Ècrit, ni relu au moment de la sÈrialisation. Il y a plusieurs possibles pour dÈclarer un attribut transient:
- C’est un attribut temporaire, calculÈ ‡ partir des autres pour faciliter ou accÈlÈrer certaines opÈrations (par exemple un cache).
- C’est un attribut qui n’a de sens qu’‡ l’exÈcution (par exemple une connec-tion base de donnÈes).
- C’est une rÈfÈrence sur un objet qui n’est pas sÈrialisable. Bien s˚r, si connaÓtre son Ètat est indispensable pour reconstituer l’Ètat de l’objet courant, celui-ci n’est pas non lus sÈrialisable!
- C’est une attribut sensible, au sens de la sÈcuritÈ. AprËs tout, Ècrire tous ses attributs sur un ux de sortie sur simple demande est un peu ‡ contre courant de la sÈcuritÈ (de l’encapsulation aussi, si l’on y rÈÈchit!).
Mais si la classe veut Ítre sÈrialisable, mais en contrÙlant totalement ce qui est Ècrit et relu, et dans quel format, elle doit implÈmenter l’interface Externalizable, et fournir les mÈthodes writeObject et readObject .
7.3 Gestion des ÈvÈnements
Les ÈvÈnements sont un des principaux moyens de communication des com-posants, tels qu’ils soient, entre eux. Les composants ayant de plus en plus d’im-portance dans java, le modËle ÈvÈnementiel est d’autant plus important. TrËs re-
30
maniÈ Ègalement depuis jdk1.0, initialement dÈni dans java.awt , les interfaces de base sont maintenant dans java.util (pourquoi pas?), avec les implÈmen-tations prÈdÈnies interessantes partagÈes entre java.beans et javax.swing. Le modÈle relËve du principe abonnement/notication. Il y a 3 type d’acteurs, ayant des relations logiques trËs circulaires:
- Les ÈvÈnements eux-mÍme. Ce sont des classes dÈrivÈes de java.util.EventObject , c’est une classe concrÈte, mais sa seule propriÈtÈ est sa source. Des objets
de cette classe peuvent Ítre instantiÈs et transmis, mais ils ne comportent aucun Ètat, ne transportent aucune autre information que celle d’avoir ÈtÈ suscitÈs, et par qui.
- Les listeners. Il existe un interface java.util.EventListener , mais il est vide. Pour chaque classe spÈcique d’ÈvÈnement, on dÈnit un inter-face qui Ètend EventListener, et qui contient une mÈthode pour traiter l’ÈvÈnement en question. Par exemple, un ÈvÈnement trÈs populaire est java.awt.event.ActionEvent . Cet ÈvÈnement correspond ‡ l’action de l’utilisateur sur un bouton, ou un item de menu, et contient (entre autres) une variable d’Ètat reprÈsentant la commande sous forme d’une chaÓne de caractËres. Il lui correspond, dans le mÍme package, un listener:
public interface ActionListener extends EventListener { void ActionPerformed(ActionEvent e);
}
Une implÈmentation concrËte de ce listener pourra agir en fonction de la propriÈtÈ actionCommand de l’ActionEvent .
- Le source d’un ÈvÈnement. Il n’y a pas de classe ni d’interface de base,
‡ part Object! Une source est un gÈnËre des ÈvÈnements, elle doit donc connaÓtre certains types d’ÈvËnements , donc les types de listener corres-pondants et doit dÈnir des mÈthodes :
addXEventListener(xEventListener listener);
et
removeXEventListener(xEventListener listener);
rien n’interdit ‡ un objet de, ni ne le force ‡, Ítre simultanÈment source et listener. Des sources typiques sont:
- des composants graphiques classiques, dans java;awt ou javax.swing. Par exemple les boutons sont des sources de ActionEvent.
- des javabeans
31
7.4 Les Java Beans
La notion de JavaBean est assez informelle: il n’y a pas d’interface de ce nom
‡ implÈmenter obligatoirement. Presque n’importe classe publique java peut Ítre un bean, ‡ condition de respecter un petit nombre de contraintes, au moins si elle veut Ítre un bean intÈressant:
Un bean qui se respecte doit Ítre sÈrialisable, c’est ‡ dire qu’il doit implÈ-menter soit Serializable, soit Externalizable (tous les deux, dans le package java.io). Un bean est censÈ Ítre instantiÈ par des mÈthodes de classe particulËres de java.beans.Beans , qui chercheront ‡ rÈtablir, par dÈsÈrialisation, un Ètat antÈrieur conservÈ par sÈrialisation.
Un bean doit possÈder un constructeur public appelable sans argument. L’instantiation ci-dessus n’a pas d’argument particulier ‡ passer ‡ un constructeur.
Un bean doit possÈder un certain nombre de mÈthodes publiques recon-naissables par les outils gr‚ce ‡ un mÈcanisme appelÈ introspection. Une mÈtaclasse d’information peut Ítre utilisÈe pour publier ces mÈthodes, mais par dÈfaut des rËgles de nommage simple sont appliquÈes, ce qui suit se rÈfËrera ‡ ce cas simpliÈ. Les mÈthodes reconnues par les outils sont de plusieurs types:
Les propriÈtÈs: un bean exporte une propriÈtÈ name de type type s’il possËde au moins une des deux mÈthodes publiques suivantes:
type getName();
void setName(type value);
S’il possËde ces deux mÈthodes, la propriÈtÈ est accessible en lec-ture/Ècriture, si seule la mÈthode get existe, la propriÈtÈ est acces-sible en lecture seule, si seule la mÈhode set existe, la propriÈtÈ est accessible en Ècriture seule. Si type est boolean, la mÈthode get peut Ítre remplacÈe par isName()
- Les ÈvÈnements (voir plus haut la description des sources): Un bean annonce qu’il est une source d’ÈvÈnements EventName s’il possËde deux mÈthodes publiques:
- addEventNameListener(EventNameListener listener) , accom-pagnÈe ou nom d’une clause throws TooManyListeners
- removeEventNameListener(EventNameListener listener);
- Les deux peuvent se combiner: deux ÈvËnements liÈs aux propriÈ-
tÈs sont prÈdÈnis: PropertyChange et VetoableChange. Le premier sert ‡ dÈnir la notion de propriÈtÈ liÈe, c’est-‡-dire de propriÈtÈ dont il est possible de demander ‡ Ítre notiÈ des changements. Le second sert ‡ dÈnir la notion de propriÈtÈ contrainte, c’est-‡-dire de propriÈtÈ aux changements de laquelle il est possible de s’opposer.
32
Un bean qui possËde une propriÈtÈ accessible en Ècriture dont il veut faire une propriÈtÈ liÈe doit se signaler comme source d’ÈvÈnements PropertyChange (il existe des versions des fonctions add/remove avec un argument supplÈmentaire pour spÈcier le nom de la pro-priÈtÈ), et bien s˚r, gÈnÈrer les ÈlÈments dans la mÈthode set . Une classe auxiliaire, PropertyChangeSupport existe pour faciliter cette implÈmentation (toutes les classes et interfaces mentionnÈs ici sont dans le package java.beans). Un mÈcanisme semblable est dÈni pour les propriÈtÈs contraintes.
-
-
- Les mÈthodes publiques dont le nom n’est pas conforme ‡ un des schÈmas que nous venons de voir sont simplement reconnues comme mÈthodes accessibles.
-
- En fait, les spÈcications dec beans liÈes au jdk1.2 ont enrichi ce modËle et en particulier, spÈcient un interface, BeanChild, qu’un bean est encouragÈ
- implÈmenter pour proter de toutes les nouveautÈs apportÈes par 1.2. NÈanmoins, pour des raisons de compatibilitÈ, il n’est pour l’instant pas envisagÈ de rendre l’implÈmentation de cet interface obligatoire.
Bien que cette liste soit longue, ce qu’un bean est obligÈ d’implÈmenter est limitÈ, car il peut exporter n’importe quel nombre de propriÈtÈs, y compris 0, et de mÍme pour les ÈvÈnements. En fait dans un cas limite d’utilisation des servlets de WebSphere (extension IBM pour serveur HTTP), avec une dÈnition prÈ-jdk1.2 des Java Server Pages , il est possible d’utiliser comme bean… un String! Mais si le bean a ÈtÈ conÁu pour interopÈrer avec d’autres, un outil de manipulation visuelle, comme la BeanBox de Sun, peut prÈsenter ses propriÈtÈs ‡ Èditer, puis les sauver par sÈrialisation, peut assembler plusieurs beans, et connecter des ÈvÈnements gÈnÈrÈs par l’un ‡ des mÈthodes publiques de l’autre, tout cela avec un pilotage purement visuel du dÈveloppeur.
- Programmation java avancÈe
8.1 Introduction
L’un des attraits que possÈdait java dËs sa sortie, mÍme en version jdk1.0beta, c’est qu’il Ètait accompagnÈ de librairies qui rendaient extrÍmement facile de dÈvelopper des applications qui aurient mÈritÈ le qualicatif d’avancÈ avec tout autre environnement (par exemple une maquette de serveur HTTP ). En par-ticulier, le support de la programmation rÈseau, et de la concurrence, Ètaient remarquable d’entrÈe par leur facilitÈ d’utilisation.
8.2 Les threads et le multithreading
La concurrence est reprÈsentÈe en java par deux ÈlÈments:
- Une classe prÈdÈnie, Thread, qui modÈlise ce qu’on appelle aussi un pro-cessus lÈger, accompagnÈe d’une classe auxiliaire Runnable.
33
- Un mot-clef particulier, synchronized, qui prend en charge tous les be-soins de synchronisation
8.2.1 Qu’est ce qu’un thread
On appelle thread en gÈnÈral (pas spÈcialement en java) un processus exÈ-cutable interne ‡ un processus systËme. Un nombre potentiellement illimitÈ de threads peuvent coexister au sein d’un processus systËmes. Ils ont de gros avan-tages en ce qui concerne les performances par rapport la multiplication des processus lourds que sont les processus systËmes:
- Ils partagent ‡ priori leurs ressources, en particulier l’accËs ‡ la mÈmoire (ce qui prÈsente d’autres inconvÈnients)
- Ils peuvent s’allouer des ressources privÈes si cela est nÈcessaire
- La transition entre deux threads d’un mÍme processus est peu co˚teuse
La contrepartie de 1 est que le systËme ne fournit aucune protection directe contre des utilisations asociales de ses ressources. Une programmation multi-thread demande donc en gÈnÈral une gestion attentive de la synchronisation des accËs ‡ ces ressources partagÈes.
8.2.2 Les Thread java
CrÈer une instance de la classe Thread est la seule faÁon de lancer l’exÈcution d’une fonction dans un nouveauprocessus lÈger. Il y a deux mÈthodes, lÈgËrement diÈrentes:
1. Ecrire une classe qui dÈrive de Thread, et redÈnir la mÈthode run():
public class Task extends Thread {
public void run() {
// code ‡ exÈcuter
}
}
puis lancer l’exÈcution par
new Task().start();
- Ecrire une classe qui implÈmente l’interface Runnable, et dÈnir la mÈ-thode run():
public class task implements Runnable {
public void run() {
// code ‡ exÈcuter
}
}
34
puis lancer l’exÈcution par:
new Thread(new Task()).start();
Bien s˚r, une rÈfÈrence sur le Thread peut Ítre conservÈe, mais ce n’est pas nÈcessaire, un Thread actif est rÈfÈrencÈ par le systËme, et ne sera pas Garbage collectÈ tant que sa mÈthode run() s’exÈcute.
8.2.3 Les dicultÈs liÈes ‡ la concurrence
La dicultÈ de la conception d’une application multithread rÈside dans le fait de devoir prendre en compte les problËmes de synchronisation des threads. D’une faÁon gÈnÈrale, ces problËmes rentrent dans deux catÈgories:
- L’exclusion mutuelle, qui correspond ‡ la notion de sections critiques, c’est-
‡-dire de traitements dont on veut garantir qu’un seul Thread peut les exÈ-cuter ‡ la fois. Il s’agit par exemple de modications de donnÈes partagÈes entre les threads
- La notion d’ÈvÈnement/notication, qui correspond qu’un peut se mettre en attente d’un signal de la part d’un autre. Il s’agit par exemple du cas d’un Thread consommant des donnÈes produites par un autre Thread.
En java, ce seront des objets qui joueront aussi bien le rÙle de verrou d’exclusion mutuelle (moniteur) que d’ÈvÈnement. En fait un objet ne peut jouer le rÙle d’ÈvÈnement que s’il joue dÈj‡ celui de moniteur.
8.2.4 L’exclusion mutuelle
Pour mettre en Èvidence ce problËme, considÈrons simplement la trËs utilisÈe classe java.util.Vector . Supposons qu’une variable de type Vector , disons elements, soit visible par deux sections de code exÈcutÈs par deux threads diÈrents:
1. code exÈcutÈ par le 1er Thread:
Object elt=null;
if (elements.size()>0)
elt=elements.elementAt(0);
2. code exÈcutÈ par le 2Ëme Thread:
if (elements.size()>0)
elements.removeAt(0);
En l’absence de toute synchronization, l’ordonnancement des instructions entre les deux threads est libre. Dans le cas o˘ les deux threads arrivent au dÈbut des sections de code alors que elements contient exactement un ÈlÈment, les deux appels ‡ size() peuvent renvoyer 1, puis l’appel ‡ removeElementAt peut
35
s’exÈcuter en premier, suivi par l’appel ‡ elementAt qui dÈclenchera maintenant une exception, malgrÈ la prÈcaution prise de tester size() auparavant. Le mot-clef synchronized sert ‡ rÈsoudre ce problËme. On rÈÈcrit les deux sections:
1. code exÈcutÈ par le 1er Thread:
Object elt=null;
synchronized(elements) {
if (elements.size()>0)
elt=elements.elementAt(0);}
2. code exÈcutÈ par le 2Ëme Thread:
synchronized(elements) {
if (elements.size()>0)
elements.removeAt(0);}
L’eet de synchronized est d’acquÈrir le verrou liÈ ‡ l’objet elements pour la durÈe du bloc entre accolades. Maintenant les deux threads ne pourront pas pÈnÈtrer en mÍme temps dans les sections synchronisÈs, le deuxiËme arrivÈ devra attendre que le premier en soit sorti.
Ceci est l’usage le plus gÈnÈral de synchronized, mais l’usage le plus frÈquent est en fait un cas particulier, o˘ synchronized est utilisÈ comme qualicateur d’une mÈthode. Si la mÈthode est une mÈthode d’instance, ceci est Èquivalent
‡ encadrer le code par la mÈthode par synchronised(this){…} . Deux mÈ-thodes d’instance synchronisÈes ne peuvent pas s’exÈcuter en parallËle sur un mÍme objet, c’est-‡-dire que l’exÈcution de l’une prÈcËdera totalement l’exÈcu-tion de l’autre. Si synchronized est appliquÈ ‡ une mÈthode de classe, l’eet est de synchroniser sur l’unique objet reprÈsentant la classe. Deux mÈthodes de (la mÍme) classe synchronisÈes ne peuvent pas s’exÈcuter en parallËle.
Un Thread peut acquÈrir plusieus fois le mÍme verrou. Une mÈthode synchronized peut donc en appeler une autre, sans provoquer de deadlock, le verrou ne sera rel‚chÈ qu’aprËs la terminaison du bloc synchronized le plus externe. Bien s˚r,
les blocs synchronized qui sont terminÈs par une exception sont gÈrÈs propre-ment.
Pour des raisons analogues ‡ celles rencontrÈes dans l’exemple prÈcÈdent, la plupart des mÈthodes d’instance de Vector sont dÈj‡ synchronisÈes. NÈanmoins, la synchronisation n’est pas gratuite en termes de performance. C’est pourquoi les nouvelles classes de Collection du jdk1.2 ne sont plus synchronisÈes par dÈfaut.
D’autre part l’accËs a des attributs individuels d’un objet peut Ítre garanti des problËmes de synchronisation ‡ moindre co˚t, gr‚ce aux deux points sui-vante:
L’accËs ‡ une variable individuelle est atomique, c’est-‡-dire que, par exemple, un Thread qui lit une variable de type short dans une mÈthode
36
non synchronisÈe ne risque pas de recevoir un monstre dont 8 bits contien-draient ce qu’il y avait mis ‡ son dernier accËs, et les 8 autres ce qu’un autre Thread vient d’y mettre. Attention, ceci n’est pas garanti pour les variables sur 64 bits (long et double)!
- Si un attribut est dÈclarÈ volatile, chaque lecture est garantie renvoyer son Ètat le plus rÈcent, c’est-‡-dire que toute modication eectuÈe par un Thread est immÈdiatement visible par les autres. Sans ce qualicateur, la machine virtuelle n’est absolulment pas tenue de garantir ce comporte-ment.
8.2.5 Attente et notication d’ÈvÈnement.
Si un Thread a acquis le verrou associÈ ‡ un objet, la mÈthode wait peut Ítre invoquÈe sur cet objet dans le contexte de ce Thread. Celui-ci est alors mis en attente (aprËs avoir temporairement libÈrÈ le verrou), jusqu’‡ ce qu’un Thread actif, et ayant ‡ son tour acquis le verrou, invoque sur cet objet la mÈthode notify , notifyAll, ou aprËs un time-out si l’invocation de wait en a spÈciÈ un. La mÈthode notify sÈlectionne au hasard un Thread parmi ceux en attente sur l’objet pour le rÈactiver, alors que dans les autres cas tous les threads concernÈs le sont. MÍme si le Thread est choisi, il doit pour devenir eectivement actif obtenir de niveau le verrou, et d’autres threads peuvent Ítre en compÈtition (en particulier le Thread ayant invoquÈ notify qui avait acquis le verrou ‡ ce moment, s’il ne l’a pas rel‚chÈ depuis).
- Voici un exemple simple, mettant en jeu un producteur et un consom-mateur. La classe SharedData dÈnit une donnÈe partagÈe, ici un simple entier. Le consommateur peut lire la donnÈe par la mÈthode getValue, le producteur peut lk’Ècrire par la mÈthode putValue. La synchronisation est double: le consommateur ne peut rÈcupÈrer la donnÈe (une fois) que si le producteur vient de la mettre ‡ disposition, et le producteur ne peut
37
dÈposer une donnÈe que si la prÈcÈdente a ÈtÈ consommÈe.
public class SharedData {
private int contents_;
private boolean available_=false;
public synchronized int getValue() {
while (!available_) {
try {
wait();
} catch(InterruptedException e) {
}
}
available_=false;
notifyAll();
return contents_;
}
public synchronized void setValue(int value) { while (available_) {
try {
wait();
} catch(InterruptedException e) {
}
}
available_=true;
contents_=value;
notifyAll();
}
}
- Voici maintenant un exemple plus complexe, permettant ‡ un Thread d’ac-quÈrir une ressource en exclusivitÈ pour la modier, mais permettant un accËs partagÈ en lecture aux autres threads. La classe Lock ci-dessous
38
implÈmente un verrou de ce type.
public class Lock {
private int shared_ = 0;
private boolean exclusive_ = false;
public synchronized void acquireShared() {
if (exclusive_) {
while (exclusive_)
try {
wait();
} catch(InterruptedException e) {
}
}
shared_++;
}
public synchronized void release() {
if (shared_>0) {
shared_–;
if (shared_==0)
notify(); // Notify to one waiting exclusive Thread
}
else if (exclusive_) {
exclusive_ = false;
notifyAll(); // Notify to all waiting shared threads
}
}
public synchronized void acquireExclusive() { if (exclusive_ || shared_>0) {
while (exclusive_ || shared_>0)
try {
wait();
} catch(InterruptedException e) {
}
}
exclusive_ = true;
}
}
39
et voici la donnÈe partagÈe:
public class SharedData {
private int contents_;
private Lock theLock_=new Lock();
public int getValue() {
int value=0;
try {
theLock.acquireShared();
value=contents_;
} finally { theLock.release();
}
return value;
}
public void setValue(int value) {
try {
theLock.acquireExclusive();
contents_.setValue(value);
} finally {
theLock.release();
}
}
}
Cette fois, la donnÈe n’est pas consommÈe par la lecture.
8.3 La programmation rÈseau
8.3.1 Introduction
Les classes du package java.net contiennent principalement:
- Des classes URL permettant d’accÈder au contenu d’une ressource WEB
- Des classes correspondant ‡ la notion de socket pour la communication programme ‡ programme.
Les classes socket permettent une communication programme ‡ programme aussi bien en mode datagramme qu’en mode connectÈ. Nous reprendrons dans ces deux cas un mÍme exemple de couple client serveur, le serveur se bornant ici ‡ faire l’Ècho de chaÓnes de caractËres envoyÈes par le ou les clients.
8.3.2 Socket en mode connectÈ
En mode connectÈ, on commence d’abord par crÈer un canal de communica-tion entre les deux applications. Le mÈcanisme est dissymÈtrique, le client utilise
40
un objet Socket pour Ètablir un connexion avec un serveur qui utilise de son cÙtÈ un ServerSocket pour recevoir ces demandes de connexion. Un fois la connexion Ètablie, on dispose d’un InputStream et d’un OutputStream , respectivement pour lire ou Ècrire dans le socket. Dans notre exemple, de faÁon ‡ pouvoir servir plusieurs clients simultanÈment, le serveur crÈe un Thread pour chaque nouveau client. Le serveur crÈe un objet de type ServerSocket, le constructeur a comme argument la porte sur laquelle le socket est ‡ l’Ècoute. La mÈthode accept est bloquante jusqu’‡ la connexion d’un nouveau client, elle retourne un nouveau socket ouvert avec le client, ce socket est transmis au constructeur du Thread de service qui crÈe un BufferedReader et un PrintWriter, respectivement pour lire et Ècrire dans le socket.
import java.io.*;
import java.net.*;
41
class ServiceThread extends Thread { private final Socket socket_; private final BufferedReader in_; private final PrintWriter out_; ServiceThread(Socket socket) throws IOException {
socket_ = socket;
in_ = new BufferedReader(
new InputStreamReader(
socket_.getInputStream()));
out_ = new PrintWriter(
new BufferedWriter(
new OutputStreamWriter(
socket_.getOutputStream())), true);
}
public void run() {
try {
while(true) {
String str = in_.readLine();
if (str == null)
break;
out_.println(str.toUpperCase());
}
}
catch(IOException e) {
}
finally {
System.out.println(« Connection closed »);
try {
socket_.close();
}catch(IOException e) {
}
}
}
}
public class ThreadedEchoServer {
42
public static void main(String[] args) throws IOException { int port=0;
try {
port = Integer.parseInt(args[0]);
} catch (Exception e) { System.out.println(« Usage: EchoServer port »); System.exit(1);
}
try {
ServerSocket listen = new ServerSocket(port);
System.out.println(« Listening on socket » + listen); try {
while(true) {
Socket socket = listen.accept();
System.out.println(« New connection » + socket); try {
new ServiceThread(socket).start();
} catch(IOException e) { socket.close();
}
}
} finally { listen.close();
}
} catch(IOException e) {
System.out.println(e);
}
}
}
Du cotÈ client, le constructeur du socket comporte comme argument l’adresse Internet de la machine serveur ainsi que la porte sur laquelle Ècoute le serveur. L’adresse Internet est obtenue ‡ partir du nom de la machine serveur par la mÈthode getByName qui fait appel au service de nom. De la mÍme faÁon que le serveur, le client crÈe des stream pour lire et Ècrire dans le socket.
import java.io.*;
import java.net.*;
43
public class EchoClient {
public static void main(String[] args) {
int port=0;
String host=null;
try {
port = Integer.parseInt(args[1]);
host = args[0];
}
catch (Exception e) {
System.out.println(« Usage: EchoClient host port »);
System.exit(1);
}
try {
InetAddress address = InetAddress.getByName(host); Socket socket = new Socket(address, port); try {
System.out.println(« Socket » + socket);
BufferedReader in = new BufferedReader(
new InputStreamReader(
socket.getInputStream()));
PrintWriter out = new PrintWriter(
new BufferedWriter(
new OutputStreamWriter(
socket.getOutputStream())), true);
BufferedReader sysin = new BufferedReader(
new InputStreamReader(System.in));
while(true) {
String str = sysin.readLine();
if(str == null)
break;
out.println(str);
str = in.readLine();
System.out.println(str);
}
}
finally {
socket.close();
}
}
catch(IOException e) {
System.out.println(e);
}
}
}
44
8.3.3 Socket en mode datagramme
Un socket de type DatagramSocket permet l’envoi ou la rÈception de paquets de donnÈes. CotÈ Èmission, on construit un paquet de donnÈes DatagramPacket
‡ partir du contenu du message, de l’adresse de destination (un objet de type InetAdress ) et de la porte de destination. On peut ensuite transmettre le mes-sage par la mÈthode send sur un DatagramSocket. CotÈ rÈception, on construit Ègalement un DatagramPacket que l’on fournit comme argument ‡ la mÈthode receive sur un DatagramSocket.
Le serveur d’Ècho en mode datagramme:
import java.io.*;
import java.net.*;
public class DatagramEchoServer {
public static void main(String[] args) {
int port=0;
try {
port = Integer.parseInt(args[0]);
}
catch (Exception e) {
System.out.println(« Usage: EchoServer port »);
System.exit(1);
}
try {
DatagramSocket socket = new DatagramSocket(port);
byte[] buffer = new byte[1000];
DatagramPacket inpacket= new DatagramPacket(
buffer, buffer.length);
while(true) {
socket.receive(inpacket);
System.out.println(« Message from » + inpacket.getAddress().getHost
inpacket.getAddress() + « ) port » + inpacket.getPort());
String str = new String(buffer,0,inpacket.getLength()); str = str.toUpperCase();
DatagramPacket outpacket = new DatagramPacket(
str.getBytes(), str.length(),
45
inpacket.getAddress(), inpacket.getPort());
socket.send(outpacket);
inpacket.setLength(buffer.length);
}
}
catch(IOException e) {
System.out.println(e);
}
}
}
Le client en mode datagramme:
import java.io.*;
import java.net.*;
46
public class DatagramEchoClient {
public static void main(String[] args) {
int port=0;
String host=null;
try {
port = Integer.parseInt(args[1]);
host = args[0];
}
catch (Exception e) {
System.out.println(« Usage: EchoClient host port »);
System.exit(1);
}
try {
InetAddress address = InetAddress.getByName(host); DatagramSocket socket = new DatagramSocket(); byte[] buffer = new byte[1000];
DatagramPacket inpacket = new DatagramPacket( buffer, buffer.length);
BufferedReader sysin = new BufferedReader(
new InputStreamReader(System.in));
while(true) {
String str = sysin.readLine();
if(str == null)
break;
DatagramPacket outpacket = new DatagramPacket( str.getBytes(), str.length(),
address, port);
socket.send(outpacket);
socket.receive(inpacket);
str = new String(buffer,0,inpacket.getlength());
System.out.println(str);
inpacket.setlength(buffer.length);
}
socket.close();
}
catch(IOException e) {
System.out.println(e);
}
}
}
47
- La sÈcuritÈ
9.1 Les mÈcanismes de la sÈcuritÈ
Plusieurs mÈcanismes interviennent dans le maintien de la sÈcuritÈ:
- Au niveau le plus bas, le compilateur et le langage garantissent que le code exÈcutÈ ne peut pas faire exploser la machine virtuelle, mais peut tout au plus lever des exceptions propres.
- Nous avons vu tout ‡ l’heure les mÈcanismes de la vÈrication de pseudo-code.Le vÈricateur protËge la machine virtuelle contre du pseudo-code invalide, peut-Ítre malicieux, qui pourrait dÈclencher l’exÈcution de zÙnes de code rÈservÈes.
- Les chargeurs de classe sont un autre mÈcanisme sÈcurisant: Un chargeur de classe est un objet java responsable de trouver une classe Ètant donnÈ une rÈfÈrence symbolique ‡ rÈsoudre, et peut-Ítre de la charger. Une table est maintenue par la machine virtuelle, dont les clefs sont des couples d’un nom complet de classe et d’une rÈfÈrence sur un chargeur de classe, et dont les data sont des classes java chargÈes (les classes java reprÈsentÈes en mÈmoires par des objets java de classe java.lang.Class). Toute classe contient une rÈfÈrence au chargeur de classe qui l’a chargÈ, et donc qui l’a instantiÈ. Quand une rÈfÈrence symbolique sur une classe est rencontrÈe dans du code appartenant ‡ une classe dÈj‡ prÈsente, la procÈdure suivante est observÈe pour la rÈsoudre:
- On cherche dans la table ci-dessus s’il existe une entrÈe dont la clef est la combinaison du nom constituant la rÈfÈrence symbolique et du chargeur de la classe courante. Si c’est le cas, la classe correspondante est utilisÈe sans appeler aucune mÈthode du chargeur de classe . Ceci a deux consÈquences:
- Le chargeur de classe ne peut pas pervertir le systËme en rÈ-solvant diÈremment une mÍme rÈfÈrence symbolique en deux occasions.
- Si une classe ayant le mÍme nom est dÈj‡ prÈsente mais associÈe
- On cherche dans la table ci-dessus s’il existe une entrÈe dont la clef est la combinaison du nom constituant la rÈfÈrence symbolique et du chargeur de la classe courante. Si c’est le cas, la classe correspondante est utilisÈe sans appeler aucune mÈthode du chargeur de classe . Ceci a deux consÈquences:
‡ un autre chargeur, elle ne sera pas forcÈment utilisÈe dans ce contexte. Ceci Èvite qu’une classe chargÈe par exemple par un ap-plet malveillant et possÈdant le mÍme nom qu’une classe systËme non encore chargÈe soit trouvÈe et appelÈe par du code sain.
-
- Si la classe n’a pas ÈtÈ trouvÈe en (a), alors la mÈthode loadClass est invoquÈe sur le chargeur de la classe courante. Quand cette mÈ-thode retourne avec succËs, le rÈsultat est utilisÈ pour crÈer une nou-velle entrÈe dans la table, avec le nom de la classe, et ce chargeur. Ceci n’implique pas nÈanmoins que ce chargeur soit celui associÈ;‡ la classe, car rien un chargeur peut demander ‡ un autre chargeur de coopÈrer avec lui pour trouver une classe, si la politique de sÈcuritÈ ne s’y oppose pas.
48
Au lancement de la machine virtuelle, il existe un seul chargeur de classes, appelÈ le chargeur primordial (qui n’est pas rÈellement un objet java mais plutÙt partie intÈgrante de la machine virtuelle), qui est utilisÈ pour char-ger les classes locales, et en particulier les classes des packages de base. Plus tard d’autres chargeurs de classe pourront Ítre instantiÈs applicati-vement, en particulier pour aller chercher des classes sur le rÈseau. Un chargeur diÈrent est crÈÈ pour chaque serveur source de classes. La me-thode loadClass de ces chargeurs commence normalement par demander la coopÈration du chargeur de classe primordial. C’est ce qui fait que des applets en provenance d’ URLs diÈrents ne partageront pas des classes applets mÍme si elles ont le mÍme nom, mais que tous utiliserons la mÍme classe String .
- La politique de sÈcuritÈ. C’est un objet global qui rÈalise un mapping entre un CodeSource et un ensemble de permissions. Un CodeSource re-prÈsente l’origine d’une classe, en un sens similaire, mais plus riche que celui utilisÈ par les chargeurs de classes, puisqu’il combine URL originateur et (Èventuel) certicat de sÈcuritÈ. Les permissions couvrent tous les do-maines sensibles de l’activitÈ d’une application, comme l’accËs aux chiers, l’accËs au rÈseau, l’ouverture de fenÍtres, etc… La politique de sÈcuritÈ est un nouveau mÈcanisme du jdk1.2, qui permet un contrÙle plus n de la sÈcuritÈ, sans avoir besoin de le programmer, car une politique peut Ítre spÈciÈe par un chier texte, lui-mÍme aisÈment congurable par un outil spÈcialisÈ, policytool .
- Le gestionnaire de sÈcuritÈ. C’est encore un objet global dont les mÈthodes vÈrient que l’application n’outrepasse pas les permissions attribuÈes par la politique de sÈcuritÈ en fonction des CodeSource. Quand on lance une machine virtuelle depuis la ligne de commande, il n’y a pas de gestion-naire de sÈcuritÈ global tant que le code applicatif n’en installe pas un, ce qui fait que la politique de sÈcuritÈ n’est pas appliquÈe! Bien s˚r, un browser en installe un. Jusqu’au jdk1.1, c’Ètait une classe abstraite qu’il Ètait nÈcessaire d’implÈmenter, mais avec l’introduction de la politique de sÈcuritÈ, la classe de base est parfaitement utilisable telle quelle.
- Nous avons mentionnÈ les certicats de sÈcuritÈ en 5. Ils annoncent les mÈcanismes extÈrieurs de la sÈcuritÈ, avec les archives et les cabinets, pour empaqueter ensemble classes et certicats, la cryptographie, pour garantir la valeur du rÈsultat, les couches sÈcurisÈes de transport, etc…
9.2 L’Èvolution du modËle
- Dans les premiËres versions, le modËle de sÈcuritÈ Ètait gÈrÈ en tout-ou-rien, par exemple, dans le jdk1.0:
- Le code local, c’est-‡-dire chargÈ depuis le disque par le chargeur de classes primordial, est considÈrÈ s˚r, et n’est soumis ‡ aucune restriction
49
-
-
- Le code lointain, c’est-‡-dire tout le reste, par exemple des applets chargÈs sur le web, Ètait suspect, et n’Ètait autorisÈ ‡ accÈder ‡ aucune resource extÈrieure: systËme de chiers, exÈcution d’autres applica-tions, etc… C’est ce qu’on appelle le sandbox.
-
- Avec le jdk1.1, le modËle est toujours en tout ou rien, mais la frontiËre est plus souple: les certicats sont introduits, si un applet est signÈ et accompagnÈ d’un certicat, celui-ci est montrÈ ‡ l’utilisateur, qui peut dÈcider de s’y er ou non. Suivant sa dÈcision l’ applet tournera en dehors ou en dedans du sandbox.
- Avec le jdk1.2, le modËle est maintenant totalement dÈclinable. Les classes systËmes ont toujours toutes les permissions, mais en dehors du cas o˘ aucun gestionnaire de sÈcuritÈ n’est installÈ, le code de toute autre origine peut recevoir des droits opÈration par opÈration. La toute puissance des classes systËmes ne doit pas Ítre interprÈtÈe h‚tivement: le code qui s’exÈ-cute ‡ un moment donnÈ n’a une permission que s i tous les intervenants dans la pile des appels possÈdent cette permission . Cette remarque doit ‡ son tour Ítre nuancÈe, dans deux sens opposÈs:
- Si du code est exÈcutÈ pour le compte d’un autre Thread, cette exi-gence sera Ètendue ‡ la pile des appels du Thread client
- Du code autorisÈ peut explicitement demander ‡ rÈlaxer cette exi-gence vis-‡-vis de ses appelants.