 |
6. CONCEPTS AVANCES |
Ce chapitre propose un rapide aperçu de notions plus avancées du langage. Il
présente le concept d'interface très utilisé en Java, le mécanisme des
exceptions similaire à celui du C++, la manipulation des threads qui est fondamentale
dans la conception d'une interface graphique, et la notion d'applet qui permet d'embarquer un
programme Java dans une page Web.
Java ne permet pas l'héritage multiple. Il existe plusieurs raisons à ce choix des
concepteurs du langage. Tout d'abord, un héritage multiple dans une modélisation est
très souvent une erreur de conception et peut être remplacé par une
agrégation ou une composition. Ensuite, l'héritage multiple peut conduire à
des ambiguïtés. La principale raison étant qu'une classe qui dérive d'une
autre hérite à la fois de son interface (i.e. le prototype des méthodes
publiques de la classe), mais également de son implémentation (i.e. les
propriétés privées et le corps des méthodes). C'est justement
l'héritage de l'implémentation qui est ambigu. Par exemple, une classe C
hérite des classes A et B, toutes les deux disposant d'une
méthode m. Quelle implémentation de la méthode m est
appelée pour un objet de la classe C ?
Pour éviter tout problème lié à l'héritage multiple, mais en
fournissant tout de même un mécanisme similaire, Java propose la notion
d'interface correspondant à notre définition du paragraphe
précédent. Une interface est définie de la même manière qu'une
classe en Java. La différence est seulement qu'une interface ne possède pas
d'attributs et toutes ses méthodes sont abstraites (i.e. sans corps). Cela revient donc
à peu de chose près à une classe abstraite pure en C++. Une classe ne peut
hériter que d'une seule classe, mais elle peut dériver de plusieurs interfaces.
L'héritage d'une interface signifie qu'on ajoute à une classe la possibilité
de répondre à l'appel de méthodes supplémentaires. Mais il faut bien
entendu implémenter ces méthodes, l'héritage d'interface ne fournissant aucune
implémentation. Considérons maintenant l'exemple suivant.
class Forme {
protected int x1;
protected int y1;
public Forme(int a,int b) { x1=a; y1=b; }
}
interface Affichable {
public void afficher(java.awt.Graphics g);
}
class Ligne extends Forme implements Affichable {
protected int x2;
protected int y2;
public Ligne(int a,int b,int c,int d)
{ super(a,b); x2=c; y2=d; }
public void afficher(java.awt.Graphics g)
{ g.drawLine(x1,y1,x2,y2); }
}
class Rectangle extends Forme implements Affichable {
protected int l;
protected int h;
public Rectangle(int a,int b,int c,int d)
{ super(a,b); l=c; h=d; }
public void afficher(java.awt.Graphics g)
{ g.drawRect(x1,y1,l,h); }
}
Les classes Ligne et Rectangle héritent de la classe
Forme et de l'interface Affichable. Elles profitent donc des
fonctionnalités de Forme et implémentent l'interface
Affichable, c'est-à-dire qu'elles proposent la méthode
afficher. Notons que l'héritage d'une classe est spécifié par le
mot-clé extends (la classe héritée est étendue), alors que
l'héritage d'une ou plusieurs interfaces est spécifié par le mot-clé
implements (l'interface héritée est implémentée). Le terme
implémenter est préféré au terme hériter lorsqu'on
parle d'une interface. De retour à notre exemple, il est ainsi possible de faire un
tableau d'éléments "affichables" et de les traiter en masse.
Affichable t[] = { new Ligne(10,10,100,100),
new
Rectangle(40,40,20,20) };
int i = 0;
while (i<t.length) t[i++].afficher(getGraphics());
La gestion des exceptions est très proche de celle du C++: lorsqu'une méthode est
susceptible de lever une exception, elle est surveillée par un bloc try et
l'instruction catch prépare la réception de l'erreur éventuelle.
Les bibliothèques standards Java lèvent des exceptions appartenant toutes
à la classe Exception (ou à l'une de ses sous-classes). L'exemple
suivant prévoit la récupération de l'exception pouvant être levée
par la création d'un objet de la classe FileInputStream.
java.io.FileInputStream f;
try { f=new java.io.FileInputStream("entree.txt"); }
catch (Exception e) { System.out.println(e.getMessage()); }
Un objet de la classe Exception possède la méthode
getMessage qui retourne une chaîne de caractères décrivant
l'erreur. Ainsi, pour proposer ses propres exceptions, il suffit de dériver une classe de la
super-classe Exception et de redéfinir la méthode getMessage.
class ExceptionDebordement extends Exception {
public String getMessage()
{ return "Debordement de tableau !"; }
}
Pour jeter une exception lorsqu'on détecte un problème, on utilise le
mot-clé throw.
public int getElement(int i) throws ExceptionDebordement {
if (i>=tab.length) throw new ExceptionDebordement();
return tab[i];
}
En Java, il est obligatoire d'indiquer qu'une méthode peut lever une exception, à
l'aide du mot-clé throws. Il est également obligatoire lorsqu'on appelle
une méthode susceptible de jeter une exception de récupérer cette exception
par un bloc try, la compilation est impossible sinon. On peut décider ensuite
de traiter l'exception ou de la transmettre à la méthode appelante. Il est
également possible de jeter d'autres objets que ceux de la classe Exception,
mais dans ce cas il faut que l'objet appartienne à une classe dérivant de
Throwable.
Un thread est un processus léger dans un programme. Contrairement aux processus
qui ne partagent par défaut aucune donnée (par exemple la fonction fork
en C dédouble un programme, i.e. son code et ses données, en deux processus
indépendants), les threads d'un même programme partagent leurs données
(par exemple, un même objet peut être manipulé dans des threads
différents). Les threads en Java appartiennent à la classe
Thread. Voici un exemple de thread qui prend en charge la mise à jour de
l'affichage d'une horloge toutes les minutes, pendant que l'application effectue des
opérations sur le thread principal.
class Horloge implements Runnable {
protected Thread thread;
protected boolean fin;
public void setThread(Thread t) { thread=t; }
public Thread getThread() { return thread; }
public Horloge() { ... }
public void afficher() { ... }
public void stop() { fin=true; }
public void run() {
fin=false;
while (!fin) {
afficher();
try { thread.sleep(1000); }
catch(Exception e) {}
}
}
}
...
Horloge h = new Horloge();
h.setThread(new Thread(h));
h.getThread().start();
...
h.stop();
Lorsqu'un thread est créé, il faut lui attacher l'objet qui va
s'exécuter dessus. Ce dernier doit implémenter l'interface Runnable pour
disposer de la méthode run. Le thread démarre lorsque sa
méthode start est appelée, celle-ci appelle alors la méthode
run de l'objet attaché. Dans cette méthode, le développeur place
le code qu'il souhaite exécuter sur le thread. Pour notre exemple, la classe
Horloge implémente l'interface Runnable et la méthode
run qui effectue en boucle un affichage de l'horloge suivi d'une pause d'une seconde.
La méthode sleep d'un thread le stoppe pendant un certain nombre de
millisecondes. Si la méthode run se termine, le thread s'arrête
naturellement. Pour forcer l'arrêt d'un thread, il est possible d'appeler sa
méthode destroy, mais il s'agit d'un arrêt brutal qui peut
entraîner des complications. Il est plutôt conseillé de gérer
l'arrêt du thread à l'aide d'une variable comme le montre l'exemple avec
l'attribut fin et la méthode stop.
Un objet peut être exécuté en même temps sur des threads
différents, i.e. en même temps plusieurs de ses méthodes peuvent être
appelées. Cela peut conduire à de sérieux problèmes. Imaginons que deux
threads appellent en même temps les méthodes reserveTicket et
numeroTicket d'un même objet dans l'exemple suivant.
public int reserveTicket(String nom) {
if (n>=N) return -1;
ticket[n]=nom;
return n++;
}
public int numeroTicket(String nom) {
int i = 0;
while (i<n && !ticket[i].equals(nom)) ++i;
if (i==n) return -1;
return i;
}
Pendant que l'un modifie des attributs de l'objet, le second les consulte. Ce dernier peut
alors se retrouver avec des informations invalides (i.e. des données anciennes
mélangées à des données nouvelles). Pour empêcher l'accès
multiple aux attributs d'un objet, il est possible d'utiliser le mot-clé
synchronized. Devant une méthode, ce mot signifie que deux threads ne
peuvent pas utiliser en même temps la méthode. Si plusieurs méthodes
possèdent ce mot-clé, cela signifie que seulement l'une des méthodes peut
être appelée à la fois (et par un seul thread en même temps). De
telles méthodes sont dites synchronisées. Dans l'exemple suivant,
reserveTicket et numeroTicket ne peuvent pas être appelées
en même temps.
public synchronized int reserveTicket(String nom) { ... }
public synchronized int numeroTicket(String nom) { ... }
Pour une synchronisation encore plus évoluée, il existe les méthodes
wait() et notify(). La méthode wait() suspend le
thread courant, celui-ci rend alors les méthodes synchronisées accessibles
à d'autres threads. Le thread suspendu attend ensuite qu'un autre le
réveille. Cela ne peut être fait qu'en appelant la méthode notify
de l'objet qui a suspendu le thread. Prenons l'exemple suivant.
public synchronized void prendreJeton() {
while (n==0) {
try { wait(); }
catch (Exception e) {}
}
--n;
}
public synchronized void rendreJeton() {
++n;
notify();
}
Un thread suspendu avec la méthode wait n'est réellement
réactivé avec notify qu'une fois que le thread qui a
appelé notify a libéré la méthode synchronisée dans
laquelle il se trouve.
Pour exécuter un programme Java, il est nécessaire d'appeler une JVM, ce qui peut
être fait avec la commande java. Mais il est possible de créer un type
d'application embarquable dans du code HTML, la JVM est alors appelée implicitement par le
navigateur. Ce genre d'application est appelée applet, ou appliquette,
car elle est normalement plus légère qu'une application fonctionnant seule (dite
autonome ou standalone). Il est néanmoins possible de faire à peu
près les mêmes choses que dans une application Java classique, les restrictions
majeures concernent la sécurité de l'ordinateur. En effet, une applet dans une page
HTML est démarrée automatiquement lorsqu'un utilisateur visualise la page, il est
évident qu'une applet ne peut alors pas effectuer n'importe quelle opération, comme
consulter ou modifier des fichiers un peu partout sur le disque-dur. Voici comment embarquer une
applet dans une page Web.
<APPLET CODE="MonApplet" CODEBASE=".">
<PARAM NAME="couleur" VALUE="red">
<PARAM NAME="taille" VALUE="25">
</APPLET>
Il suffit d'utiliser la balise <APPLET> dans laquelle les balises
<PARAM> correspondent à des paramètres que l'on peut transmettre
à l'applet. L'attribut CODE indique le nom de la classe de l'applet et
CODEBASE l'endroit où trouver le fichier de cette classe. Les attributs
WIDTH et HEIGHT peuvent aussi être utilisés pour
spécifier les dimensions de l'applet. Dans l'exemple, les paramètres
couleur et taille avec
respectivement les valeurs red et 25 sont transmis à l'applet.
Le code suivant montre comment récupérer ces paramètres et la manière
dont s'organise une applet.
import java.applet.*;
import java.awt.*;
public class MonApplet extends Applet {
protected String couleur;
protected int taille;
public void init() {
couleur=getParameter("couleur");
taille=Integer.parseInt(getParameter("taille"));
}
public void start() { ... }
public void stop() { ... }
public void paint(Graphics g) { ... }
}
Dans l'exemple HTML précédent, le navigateur crée automatiquement un objet
de la classe MonApplet, qui doit appartenir à la classe Applet du
package java.applet. Il n'est pas recommandé d'intervenir dans le constructeur
de l'objet. A la place, la méthode init est proposée pour initialiser
l'applet. Cette méthode est appelée par le navigateur quand il le juge
nécessaire. Ensuite, une fois l'applet initialisée, la méthode
start est exécutée, également par le navigateur, elle correspond
à la méthode main d'une application classique. C'est dans cette
méthode que le développeur démarre l'application. Enfin, une fois qu'elle ne
sert plus, l'applet est stoppée, la méthode stop est alors
appelée par le navigateur, avant la destruction de l'objet. Dans cette méthode
peuvent être placées des instructions pour libérer des ressources comme des
threads. La classe Applet hérite de java.awt.Component,
elle possède donc la méthode paint qui est gérée de la
même manière que n'importe quel autre composant graphique.
| |
| |
| Copyright (c) 1999-2016 - Bruno Bachelet - bruno@nawouak.net -
http://www.nawouak.net |
| La permission est accordée de copier, distribuer et/ou modifier ce document
sous les termes de la licence GNU Free Documentation License, Version 1.1 ou toute
version ultérieure publiée par la fondation Free Software Foundation. Voir
cette licence pour plus de détails (http://www.gnu.org). |
|
|