FAQ Langage JavaConsultez toutes les FAQ

Nombre d'auteurs : 41, nombre de questions : 296, dernière mise à jour : 8 mars 2017  Ajouter une question

 

Cette FAQ a été réalisée à partir des questions fréquemment posées sur le forum Java de http://java.developpez.com ainsi que l'expérience personnelle des auteurs.

Nous tenons à souligner que cette FAQ ne garantit en aucun cas que les informations qu'elle propose sont correctes. Les auteurs font leur maximum, mais l'erreur est humaine. Cette FAQ ne prétend pas non plus être complète. Si vous trouvez une erreur, ou que vous souhaitez nous aider en devenant rédacteur, lisez ceci.

Sur ce, nous vous souhaitons une bonne lecture.


SommaireBases du langageÉvènements (6)
précédent sommaire suivant
 

Les listeners (écouteurs) et events (évènements) sont une mise en œuvre du patron de conception observateur : ils permettent d’envoyer des notifications, par exemple la modification d'une valeur, depuis un module ou une sous-partie de l'application vers un autre module de l'application. Le module receveur peut alors réagir en conséquence en fonction des informations qui lui ont été transmises. Le listener ou écouteur représente la partie qui reçoit la notification ; on parle aussi d'observer ou observateur. L’event ou évènement est l'information qui est transmise par la notification.

Le package java.util dispose de plusieurs classes et interfaces qui permettent de définir ces entités.

Une première implémentation concrète a été fournie dans le JDK 1.0, mais elle s’avère peu utilisée :

  • java.util.Observable - depuis le JDK 1.0, l’implémentation concrète d'un objet qui peut être observé ;
  • java.util.Observer - depuis le JDK1.0, l’implémentation concrète d'un objet qui peut en observer un autre.


Une seconde implémentation plus facile à étendre et à dériver a été déployée dans le JDK 1.1  :

  • java.util.EventObject - à partir du JDK 1.1, cette classe est la classe mère de presque tous les objets event ;
  • java.util.EventListener - à partir du JDK 1.1, cette interface est l'interface mère de presque tous les objets listener.


Cette seconde API, à laquelle nous allons nous intéresser, est très présente dans tout le JDK. Elle est principalement utilisée dans les interfaces graphiques AWT, Swing et JavaFX, mais se trouve être également présente dans des portions du JDK ayant peu ou pas de rapport avec les interfaces graphiques : ImageIO, les beans ou encore JDBC. L'API XML DOM utilise un concept de gestion d’évènements identique, mais qui n’hérite pas directement des interfaces et classes définies dans java.util.

Mis à jour le 13 octobre 2015 bouye

Pour définir votre propre event, c'est assez simple : vous devez tout simplement créer une classe qui hérite de java.util.EventObject et dans laquelle vous allez stocker toute information que vous jugez utile de faire remonter dans votre notification. L'objet évènement contient également une référence à la source de l’évènement, car un même observateur peut s'enregistrer auprès de plusieurs sources et donc il a besoin de connaitre laquelle lui fait parvenir une notification.

Par convention, le nom de l’évènement est constitué du nom de la valeur, variable ou entité observée suivi du mot Event :

  • MouseEvent - colporte les détails des évènements relatifs à la souris ;
  • KeyEvent - colporte les détails des évènements relatifs aux touches du clavier ;
  • PropertyChangeEvent - colporte les détails des évènements relatifs aux modifications de valeur des propriétés ;
  • etc.


Par exemple, pour un capteur de température :

Code Java : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public final class TemperatureEvent extends EventObject {      
     private final double oldTemperature; 
     private final double newTemperature; 
  
    /** 
     * Crée une nouvelle instance. 
     * @param source La source de l’évènement. 
     * @param oldTemperature L'ancienne température. 
     * @param newTemperature La nouvelle température. 
     */ 
    public void TemperatureEvent(Object source, double oldTemperature, double newTemperature) { 
        super(source); 
        this.oldTemperature = oldTemperature; 
        this.newTemperature = newTemperature; 
    } 
  
    /** 
     * Retourne la température avant modification. 
     * @return Une température en degrés Kelvin. 
     */ 
    public double getOldTemperature() { 
        return oldTemperature; 
    } 
  
    /** 
     * Retourne la température après modification. 
     * @return Une température en degrés Kelvin. 
     */ 
    public double getNewTemperature() { 
        return newTemperature; 
    } 
}

Mis à jour le 13 octobre 2015 bouye

Pour définir votre propre listener, c'est assez simple : vous devez tout simplement créer une interface qui hérite de java.util.EventListener et dans laquelle sont définies une ou plusieurs méthodes qui permettent de renvoyer des notifications.

Par convention, le nom de l'observateur est constitué du nom de la valeur, variable ou entité observée suivi du mot Listener :

  • MouseListener - observateur des évènements relatifs à la souris ;
  • KeyListener - observateur des évènements relatifs aux touches du clavier ;
  • PropertyChangeListener - observateur des évènements relatifs aux modifications de valeur des propriétés ;
  • etc.


Par convention, les méthodes définies dans le listener prennent en paramètre un objet customisé qui hérite dejava.util.EventObject et qui contient des informations supplémentaires en rapport avec la notification. L'objet émetteur de la notification n’étant généralement pas intéressé par un retour de votre observateur, ces méthodes ont généralement le type de retour void. Le nom des méthodes reprend en général le nom de la valeur ou variable ou entité observée suivi d'un mot ou d'un adjectif indiquant le type de notification :

  • mousePressed() - l'utilisateur appuie sur un bouton de la souris ;
  • keyReleased() - l'utilisateur a relâché son doigt d'une des touches du clavier ;
  • propertyChange() - la valeur d'une propriété a été modifiée ;
  • etc.


Par exemple, pour un capteur de température :

Code Java : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
public interface TemperatureListener extends EventListener { 
    /** 
     * Invoquée quand la température monte. 
     * @param event Les détails de l’évènement. 
     */ 
    public void temperatureRaised(TemperatureEvent event); 
  
    /** 
     * Invoquée quand la température baisse. 
     * @param event Les détails de l’évènement. 
     */ 
    public void temperatureDropped(TemperatureEvent event); 
}

Lorsque votre listener contient plusieurs méthodes, on fournit généralement une classe abstraite nommée Adapter qui contient des implémentations vides de ces méthodes. Cela permet par la suite de créer des nouvelles instances du listener en héritant de l'adapter et donc sans devoir implémenter les méthodes qui ne nous intéressent pas.

Code Java : Sélectionner tout
1
2
3
4
5
6
7
8
9
public class TemperatureAdapter implements TemperatureListener { 
    @Override 
    public void temperatureRaised(TemperatureEvent event) { 
    } 
  
    @Override 
    public void temperatureDropped(TemperatureEvent event) { 
    } 
}

Mis à jour le 13 octobre 2015 bouye

Maintenant que vous avez votre interface listener et votre objet event, il vous faut un objet observable, c'est-à-dire qui sera la source des notifications que vont recevoir vos observateurs. Nous allons commencer par ajouter des méthodes permettant d'enregistrer et de désenregistrer des écouteurs sur votre objet.

Par convention, ces méthodes sont nommées add (pour « ajout ») ou remove (pour « retrait ») suivi du nom de la valeur, variable ou entité observée suivi du mot Listener et elles prennent en argument un listener du type approprié : addXXXListener(XXXListener) et removeXXXListener(XXXListener).

Par exemple, pour un capteur de température :

  • addTemperatureListener(TemperatureListener listener) - pour l'ajout ;
  • removeTemperatureListener(TemperatureListener listener) - pour le retrait.


Les choses sont un peu différentes en JavaFX où on utilise des méthodes simplement nommées addListener() et removeListener() en distinguant le type d’écouteur utilisé grâce au type du paramètre de chaque méthode.

Nous allons devoir maintenant implémenter le stockage des écouteurs dans l'objet. Si votre objet est un bean, vous pouvez utiliser la classe java.beans.PropertyChangeSupport. Si votre objet est un composant Swing, vous pouvez utiliser la classe javax.swing.EventListenerList. Et si votre objet est un contrôle JavaFX, vous utiliserez les classes propriétés présentes dans cette API. Mais nous allons partir du principe que vous n'utilisez aucune de ces API.

On utilisera donc un tableau ou une liste pour stocker tous les écouteurs enregistrés sur notre objet observable. Bien qu'il soit possible de stocker un conteneur par type d’écouteur, en général on utilise un seul conteneur pour tous les types de listeners qui peuvent être enregistrés sur l'objet. De plus, pour conserver de bonnes performances et pour éviter de lancer des instance of à tout va lors du parcours de la liste, on stockera aussi le type de l'écouteur enregistré. Notre liste aura donc toujours un nombre pair d’éléments et sera constituée comme suit : [type écouteur A, écouteur A, type écouteur B, écouteur B, type écouteur C, écouteur C, [...] , type écouteur N, écouteur N].

Dans notre capteur de températures, nous avons donc désormais :

Code Java : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class TemperatureSensor { 
    // Conteneur pour tous les observateurs sur cet objet. 
    private final List listenerList = new LinkedList(); 
  
    /** 
     * Enregistre un écouteur de température. 
     * @param listener L’écouteur. 
     */ 
    public void addTemperatureListener(TemperatureListener listener) { 
        if (listener == null) { 
            return; 
        } 
        // On ajoute le listener et son type. 
        // Cela permet de stocker plusieurs types de listeners dans le même conteneur. 
        listenerList.add(TemperatureListener.class); 
        listenerList.add(listener); 
    } 
  
    /** 
     * Désenregistre un écouteur de température. 
     * @param listener L’écouteur. 
     */ 
    public void removeTemperatureListener(TemperatureListener listener) { 
        if (listener == null) { 
            return; 
        } 
        removeListener(TemperatureListener.class, listener); 
    } 
  
    /** 
     * Méthode générique pour désenregistrer un écouteur. 
     * @param listenerClass Le type de l’écouteur. 
     * @param listener L’écouteur. 
     */ 
    private void removeListener(Class listenerClass, Object listener) { 
        int listSize = listenerList.size();         
        for (index = 0 ; index < listSize ; index += 2) { 
            // Comme un listener peut implémenter plusieurs interfaces et s'enregistrer plusieurs fois, 
            // on vérifie qu'on enlève le premier rencontré qui porte le bon type. 
            if (listenerList,get(index) == listenerClass && listenerList.get(index + 1) == listener) { 
                listenerList.remove(index + 1); 
                listenerList.remove(index); 
                break; 
            } 
        } 
    } 
}

Nous pouvons désormais enregistrer des listeners sur notre senseur de température, par exemple en faisant :

Code Java : Sélectionner tout
1
2
3
4
5
6
7
TemperatureSensor senseur = [...] 
senseur.addTemperatureListener(new TemperatureAdapter() { 
    @Override 
    public void temperatureRaised(TemperatureEvent event) { 
        System.out.printf("La température est montée de %.2fK à %.2fK.%n", event.getOldTemperature(), event.getNewTemperature()); 
    } 
});

Mis à jour le 14 octobre 2015 bouye

Nous pouvons désormais enregistrer des écouteurs dans notre objet observable, mais il nous manque encore le code qui permet d'envoyer des notifications aux observateurs. Ces notifications sont généralement lancées dans les méthodes setter ou encore aux endroits où le code est amené à modifier les valeurs des membres ou propriétés de l'objet.

Le principe est de créer des méthodes qui vont parcourir la liste des observateurs à rebours. En effet, on part du principe que cette liste se comporte comme une pile LIFO (Last In First Out - dernier entré, premier sorti) et donc que les écouteurs les plus récemment ajoutés dans la liste seront ceux qui recevront la notification en premier.

Par convention, ces méthodes sont en accès private ou protected et sont nommées fire (pour « tirer » ou « lancer ») suivi du nom de la valeur, variable ou entité observable suivi du mot Event ou du type d’évènement lancé.

Par exemple, ici pour notre senseur de température : fireTemperatureEvent() ou fireTemperatureRaised() suivant ce que l'on préfère.

Code Java : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
protected void fireTemperatureRaised(double oldTemperature, double newTemperature) { 
    TemperatureEvent event = null; 
    int listSize = listenerList.size(); 
    // On parcourt la liste à rebours. 
    for (int index = listSize - 2 ; index >= 0 ; index -= 2) { 
        // Seules les instances de TemperatureListener nous intéressent. 
        if (listenerList.get(index) == TemperatureListener.class) { 
            // On crée l'objet event de manière "lazy" (paresseuse), uniquement quand on en a vraiment besoin. 
            if (event == null) { 
                event = new TemperatureEvent(this, oldTemperature, newTemperature); 
            } 
            TemperatureListener  listener = (TemperatureListener)listenerList.get(index + 1); 
            // On invoque la notification appropriée sur le listener. 
            listener.temperatureRaised(event); 
        } 
    } 
}

Ce qui permet de mettre dans le code chargé de relever la température sur le capteur physique :

Code Java : Sélectionner tout
1
2
3
4
5
6
7
8
// Sauvegarde de l'ancienne température. 
double oldTemperature = temperature; 
// Relever la température sur le capteur physique. 
temperature = [...] 
// Lancer une notification d’élévation de température si besoin. 
if (temperature > oldTemperature) { 
    fireTemperatureRaised(oldTemperature, temperature); 
}

Avertissement : la notification aux observateurs se déroule de manière linéaire et séquentielle (les uns après les autres) dans le thread en train d’exécuter le code de l'objet observable. Il y a donc quelques points importants qu'il faut bien prendre en compte :
  • si un listener exécute un code de longue durée en réponse à cette notification, il mettra en attente non seulement les notifications destinées aux autres observateurs, mais aussi le code dans l'objet observable. Les tâches de longue durée devront donc être exécutées, côté listener, dans des threads séparés ;
  • toute erreur ou exception, non traitée par le code du listener remontera jusqu’à l'objet observable et bloquera les notifications aux autres observateurs, mais aussi le code dans l'objet observable ;
  • toute réaction destinée à apparaitre dans une UI (AWT, Swing, JavaFX, etc.) devra être effectuée dans le thread courant de cette UI (Event Dispatch Thread, JavaFX Application Thread, etc.) et non pas dans celui de l'objet observable.

Mis à jour le 14 octobre 2015 bouye

Parfois, on veut qu'un observateur puisse « consommer » un évènement et empêcher sa propagation aux observateurs suivants. Rien n'est défini dans l'API évènementielle de base pour prendre en charge une telle fonctionnalité. Cependant les évènements AWT, Swing et JavaFX disposent d'un mécanisme de sémaphore qu'il est aisément possible de reproduire :

Évènement
On introduit dans l’évènement un sémaphore qui permet d'indiquer si on désire que la notification puisse continuer à se propager.

Code Java : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public final class TotoEvent extends EventObject { 
  
    [...] 
  
    private boolean consumed = false; 
  
    /** 
     * Consomme l’évènement. 
     */ 
    public void consume() { 
        consumed = true; 
    } 
  
    /** 
     * Indique si l’évènement a été consommé. 
     * @return {@code True} si le test est vérifié, {@code false} sinon. 
     */ 
    public boolean isConsumed() { 
        return consumed; 
    } 
}

Observateur
L'observateur qui désire consommer l’évènement et stopper la propagation n'a désormais plus qu'à invoquer la méthode consume() de l’évènement.

Code Java : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
public class AnObserver implements TotoListener { 
  
    [...] 
  
    @Override 
    public void totoEventReceived(TotoEvent event) { 
        // Gérer l’évènement. 
        [...] 
        // Consommer l’évènement pour empêcher la propagation. 
        event.consume(); 
    } 
}

Observable
L'objet observable doit s'assurer de bien stopper la propagation lorsque l’évènement a été consommé. Quand c'est le cas, la méthode isConsumed() de l’évènement retourne alors la valeur true.

Code Java : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
protected void fireTotoEvent() {  
    TotoEvent event = null;  
    int listSize = listenerList.size();  
    // On parcourt la liste à rebours.  
    for (int index = listSize - 2 ; index >= 0 ; index -= 2) {  
        // Seules les instances de TotoListener nous intéressent.  
        if (listenerList.get(index) == TotoListener.class) {  
            // On crée l'objet event de manière "lazy" (paresseuse), uniquement quand on en a vraiment besoin.  
            if (event == null) {  
                event = new TotoEvent(this);  
            }  
            TotoListener listener = (TotoListener)listenerList.get(index + 1);  
            // On invoque la notification appropriée sur le listener.  
            listener.totoEventReceived(event);  
            // si l'event a été consommé, on arrête la propagation. 
            if (event.isConsumed()) { 
                break;  
            } 
        }  
    }  
}

Mis à jour le 14 octobre 2015 bouye

Proposer une nouvelle réponse sur la FAQ

Ce n'est pas l'endroit pour poser des questions, allez plutôt sur le forum de la rubrique pour ça


Réponse à la question

Liens sous la question
précédent sommaire suivant
 

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2017 Developpez Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

 
Responsables bénévoles de la rubrique Java : Mickael Baron - Robin56 -