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.

 
INTERFACE
 

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());

 
GESTION DES EXCEPTIONS
 

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.

 
MANIPULATION DES THREADS
 

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.

 
CREATION D'UNE APPLET
 

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).