Rechercher
 

Améliorations / Corrections

Vous avez des améliorations (ou des corrections) à proposer pour ce document : je vous remerçie par avance de m'en faire part, cela m'aide à améliorer le site.

Emplacement :

Description des améliorations :

Implémentations d'interfaces

Méthodes abstraites, classes abstraites et interfaces Expressions Lambdas et références sur méthodes



Accès rapide :
La vidéo
Un cas concret ou il est nécessaire d'implémenter une interface
Implémentation de l'interface via une classe normale
Implémentation de l'interface via une classe privée
Implémentation de l'interface via une classe interne
Implémentation de l'interface via une classe anonyme
Travaux pratiques
Le sujet
La correction

La vidéo

Cette vidéo vous présente plusieurs techniques pour implémenter une interface. Les exemples de code proposés sont appliqués à la gestion des événements en Java (qui s'appuie donc sur des interfaces). Les notions de classes privées, de classes internes (inner classes) et de classes anonymes (anonymous classes) sont aussi étudiées.


Implémentations d'interfaces

Un cas concret ou il est nécessaire d'implémenter une interface

Nous allons voir, au travers de ce chapitre qu'il existe plusieurs manières d'implémenter une interface. Afin de pouvoir comparer ces différentes techniques, nous allons considérer un exemple de code utilisant une interface : nous allons parler d'interfaces graphiques.

il ne faut pas confondre le concept, en programmation orientée objet, d'interface avec la notion d'interface graphique. Ce sont bien deux sujets différents que nous allons étudier conjointement dans ce chapitre.

Pour créer une interface graphique en Java, vous avez le choix d'utiliser plusieurs librairies : AWT, Swing, JavaFX, ... Dans notre cas, le sujet de l'interface graphique est secondaire, ce qui nous intéresse, c'est de continuer à étendre nos connaissances sur la notion d'interface (POO). Je choisis donc d'utiliser la librairie Swing pour bâtir notre IHM (Interface Homme/Machine ; interface graphique), car le code à produire restera minimaliste.

Voici donc un programme permettant d'afficher une fenêtre graphique proposant trois boutons affichés les uns à côté des autres sur une même ligne.

 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 
package fr.koor.poo;

import java.awt.FlowLayout;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.plaf.nimbus.NimbusLookAndFeel;

public class Demo extends JFrame {

    private static final long serialVersionUID = -4939544011287453046L;
    
    private JButton btnClickMe = new JButton( "Click me!" );
    private JButton btnPushMe = new JButton( "Push me!" );
    private JButton btnActivateMe = new JButton( "Activate me!" );
    
    
    public Demo() {
        super( "Implémentation d'interface" );
        this.setDefaultCloseOperation( DISPOSE_ON_CLOSE );
        
        JPanel contentPane = (JPanel) this.getContentPane();
        // Un FlowLayout permet de positionner les boutons les uns à la suite des autres.
        contentPane.setLayout( new FlowLayout() );
        contentPane.add( btnClickMe );
        contentPane.add( btnPushMe );
        contentPane.add( btnActivateMe );
        
        this.setSize( 400, 200 );
        this.setLocationRelativeTo( null );
    }
    
    
    public static void main( String[] args ) throws Exception {        
        // Try to set Nimbus look and feel
        UIManager.setLookAndFeel( new NimbusLookAndFeel() );

        // Start the demo
        Demo demo = new Demo();
        demo.setVisible( true );
    }
    
}
Exemple simple de construction d'une fenêtre Swing contenant trois boutons
pour de plus amples informations sur la librairie Swing, je vous propose de suivre notre tutoriel dédié à Swing.

Maintenant, le sujet qui nous intéresse est de savoir comment réagir à un clic sur un des boutons de notre fenêtre : c'est à ce niveau qu'il va nous falloir implémenter une interface. Effectivement, Swing propose le concept de « listener » (d'écouteur) pour coder un gestionnaire d'événement. Comment cela fonctionne ? Swing (plus précisément, l'AWT, la brique de base de Swing) propose l'interface java.awt.event.ActionListener. Celle-ci ne définit qu'une unique méthode : actionPerformed. C'est cette méthode qui doit fournir le code à exécuter en cas d'appui sur le bouton associé.

Votre responsabilité est donc d'implémenter cette interface et donc la méthode actionPerformed. En fait, il existe plusieurs manières de réaliser cette implémentation : chacune d'entre elles ayant des avantages et des inconvénients. Ensuite, il faut enregistrer votre listener sur le bouton que vous souhaitez écouter, via un appel à la méthode addActionListener. Je vous propose de tester ces différentes approches d'implémentations de votre interface d'écoute.

Implémentation de l'interface via une classe normale

Donc la première solution est d'utiliser une classe Java classique qui implémente l'interface ActionListener. Nous connaissons déjà les éléments syntaxiques du langage Java pour réaliser cette implémentation. Mais nous pouvons rencontrer une difficulté : comment accéder à des éléments graphiques de la fenêtre au niveau de votre gestionnaire d'événement. Par exemple, comment le clic sur l'un des boutons peut permettre de changer le texte d'un autre bouton ? Pour y arriver vous allez devoir taper de la ligne de code ! Voici un exemple de code qui permet d'obtenir ce résultat. On commence par la classe associée au gestionnaire d'événement.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
package fr.koor.poo;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;

public class MyListener implements ActionListener {

    @Override public void actionPerformed( ActionEvent event ) {
        // L'objet d'événement sait qui est le générateur de cet événement.
        JButton clickedButton = (JButton) event.getSource();
        
        // Un composant graphique connaît son conteneur de plus haut niveau (la fenêtre).
        Demo window = (Demo) clickedButton.getTopLevelAncestor();
        
        // On permet de récupérer (un getter) un élément graphique à partir de la fenêtre.
        JButton lastButton = window.getBtnActivateMe();
        
        // On peut changer le texte du bouton
        lastButton.setText( "First button clicked!" );
    }
    
}
Implémentation de l'interface via une classe normale

Et voici maintenant la classe associée à la fenêtre graphique avec en plus l'enregistrement de l'écouteur (ligne 30) et l'ajout d'un getter permettant de retrouver le dernier bouton de l'interface graphique (lignes 36 à 38).

 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 
 48 
 49 
 50 
package fr.koor.poo;

import java.awt.FlowLayout;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.plaf.nimbus.NimbusLookAndFeel;

public class Demo extends JFrame {

    private static final long serialVersionUID = -4939544011287453046L;
    
    private JButton btnClickMe = new JButton( "Click me!" );
    private JButton btnPushMe = new JButton( "Push me!" );
    private JButton btnActivateMe = new JButton( "Activate me!" );
    
    
    public Demo() {
        super( "Implémentation d'interface" );
        this.setDefaultCloseOperation( DISPOSE_ON_CLOSE );
        
        JPanel contentPane = (JPanel) this.getContentPane();
        contentPane.setLayout( new FlowLayout() );
        contentPane.add( btnClickMe );
        contentPane.add( btnPushMe );
        contentPane.add( btnActivateMe );
        
        btnClickMe.addActionListener( new MyListener() );
        
        this.setSize( 400, 200 );
        this.setLocationRelativeTo( null );
    }
    
    public JButton getBtnActivateMe() {
        return btnActivateMe;
    }
    
    
    public static void main( String[] args ) throws Exception {        
        // Try to set Nimbus look and feel
        UIManager.setLookAndFeel( new NimbusLookAndFeel() );

        // Start the demo
        Demo demo = new Demo();
        demo.setVisible( true );
    }
    
}
Utilisation du gestionnaire d'événement

Si l'on doit trouver un avantage à cette technique, ce serait qu'on peut facilement réutiliser le gestionnaire d'événement étant donné que c'est une classe autonome.

Implémentation de l'interface via une classe privée

La seconde solution, que je vous propose, est d'utiliser une classe dite « privée ». Une classe privée se code dans un fichier de code Java à côté de sa classe publique. Pour rappel, un fichier de code Java ne peut contenir qu'une unique classe publique : elle doit impérativement avoir le même nom que le fichier (sans l'extension, bien entendu). Par contre nous n'avons jamais dis qu'il n'était pas possible de coder plusieurs classes dans un même fichier de code Java. C'est possible et il existe plusieurs possibilités : la première consiste à utiliser une classe privée.

Une classe privée se code au même niveau de la classe publique du fichier de code Java considéré. Du coup, une classe privée ne pourra pas avoir le même nom que le fichier de code Java. Vous pouvez de plus avoir autant de classes privées que souhaité. Bien entendu, une classe privée ne pourra pas avoir accès aux membres privés des autres classes du fichier. Noté enfin qu'une classe privée ne peut être utilisée que et uniquement que dans le fichier de code Java que la définie. De l'extérieur, le compilateur refusera l'accès à cette classe privée.

une classe privée ne doit pas, syntaxiquement parlant, être préfixée du mot clé private, contrairement à la classe publique du fichier qui, elle, doit bien avoir son qualificateur de visibilité. Plus généralement, devant une classe on ne peut utiliser que les mots clés suivants : public, abstract ou final (qui en interdit la dérivation).

Voici un exemple d'utilisation d'une classe privée : il n'y a qu'un unique fichier de code, contenant les deux classes, appelé Demo.java.

 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 
 48 
 49 
 50 
 51 
 52 
 53 
 54 
 55 
 56 
 57 
 58 
 59 
 60 
 61 
 62 
 63 
 64 
 65 
 66 
 67 
 68 
 69 
 70 
 71 
package fr.koor.poo;

import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.plaf.nimbus.NimbusLookAndFeel;

public class Demo extends JFrame {

    private static final long serialVersionUID = -4939544011287453046L;
    
    private JButton btnClickMe = new JButton( "Click me!" );
    private JButton btnPushMe = new JButton( "Push me!" );
    private JButton btnActivateMe = new JButton( "Activate me!" );
    
    
    public Demo() {
        super( "Implémentation d'interface" );
        this.setDefaultCloseOperation( DISPOSE_ON_CLOSE );
        
        JPanel contentPane = (JPanel) this.getContentPane();
        contentPane.setLayout( new FlowLayout() );
        contentPane.add( btnClickMe );
        contentPane.add( btnPushMe );
        contentPane.add( btnActivateMe );
        
        btnClickMe.addActionListener( new MyListener() );
        
        this.setSize( 400, 200 );
        this.setLocationRelativeTo( null );
    }
    
    public JButton getBtnActivateMe() {
        return btnActivateMe;
    }
    
    
    public static void main( String[] args ) throws Exception {        
        // Try to set Nimbus look and feel
        UIManager.setLookAndFeel( new NimbusLookAndFeel() );

        // Start the demo
        Demo demo = new Demo();
        demo.setVisible( true );
    }
    
}


class MyListener implements ActionListener {

    @Override public void actionPerformed( ActionEvent event ) {
        // L'objet d'événement sait qui est le générateur de cet événement.
        JButton clickedButton = (JButton) event.getSource();
        
        // Un composant graphique connaît son conteneur de plus haut niveau (la fenêtre).
        Demo window = (Demo) clickedButton.getTopLevelAncestor();
        
        // On permet de récupérer (un getter) un élément graphique à partir de la fenêtre.
        JButton lastButton = window.getBtnActivateMe();
        
        // On peut changer son text
        lastButton.setText( "First button clicked!" );
    }
    
}
Implémentation de l'interface via une classe privée (fichier Demo.java)

Cette technique limite l'utilisation de la classe MyListener au fichier Demo.java (une certaine forme d'encapsulation). Par contre, on a les mêmes problèmes que dans l'exemple précédent : votre listener accède plus ou moins difficilement au contenu de la fenêtre.

Implémentation de l'interface via une classe interne

Une classe interne est une classe définie à l'intérieur d'une autre classe. Contrairement à une classe privée, une classe interne peut (ou pas) être accessible de l'extérieur du fichier qui la contient : tout dépend de la visibilité octroyée à la classe interne.

Il y a une autre différence entre une classe privée et une classe interne et non des moindres : une classe interne a accès à ses membres (attributs et méthodes), mais elle a aussi accès aux membres de la classe qui la porte. Il n'est donc plus nécessaire de passer par un certain nombre d'appels de méthodes pour accéder aux éléments graphiques de la fenêtre. Voici un exemple d'utilisation qui permet de réduire le nombre de lignes de code de 71 à 57 pour exactement le même résultat.

 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 
 48 
 49 
 50 
 51 
 52 
 53 
 54 
 55 
 56 
 57 
package fr.koor.poo;

import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.plaf.nimbus.NimbusLookAndFeel;


public class Demo extends JFrame {

    private static final long serialVersionUID = -4939544011287453046L;
    
    private JButton btnClickMe = new JButton( "Click me!" );
    private JButton btnPushMe = new JButton( "Push me!" );
    private JButton btnActivateMe = new JButton( "Activate me!" );
    
    
    public Demo() {
        super( "Implémentation d'interface" );
        this.setDefaultCloseOperation( DISPOSE_ON_CLOSE );
        
        JPanel contentPane = (JPanel) this.getContentPane();
        contentPane.setLayout( new FlowLayout() );
        contentPane.add( btnClickMe );
        contentPane.add( btnPushMe );
        contentPane.add( btnActivateMe );
        
        btnClickMe.addActionListener( new MyListener() );
        
        this.setSize( 400, 200 );
        this.setLocationRelativeTo( null );
    }
    
    // Définition de notre classe interne
    private class MyListener implements ActionListener {

        @Override public void actionPerformed( ActionEvent event ) {
            btnActivateMe.setText( "First button clicked!" );
        }
        
    }    
    
    public static void main( String[] args ) throws Exception {        
        // Try to set Nimbus look and feel
        UIManager.setLookAndFeel( new NimbusLookAndFeel() );

        // Start the demo
        Demo demo = new Demo();
        demo.setVisible( true );
    }
    
}
Implémentation de l'interface via une classe interne

Quelques explications s'imposent ! Tout d'abord, notre interface ActionListener est implémenté par la classe MyListener (à partir de la ligne 40). Comme nous sommes à l'intérieur de la définition de la classe Demo, nous avons à faire à une classe interne. Sa visibilité est privée afin de garantir que cette classe ne soit pas vu de l'extérieur du fichier Demo.java.

Ensuite, comme MyListener est une classe interne, elle a accès aux membres de la classe Demo (quels que soient les visibilités de ces membres). En fait, une classe interne possède deux paramètres implicites : this qui référence l'écouteur et Demo.this qui référence la classe de fenêtre graphique. C'est via ce second paramètre implicite qu'on peut, notamment, accéder au bouton. Que vous écriviez btnActivateMe.setText( "First button clicked!" ) ou Demo.this.btnActivateMe.setText( "First button clicked!" ), vous obtiendrez le même résultat.

Implémentation de l'interface via une classe anonyme

Une classe anonyme, comme son nom l'indique, est une classe non nommée qui ne sert qu'à produire qu'une seule et unique instance. De plus, une classe anonyme est implicitement une classe interne : elle a donc, elle aussi, accès aux membres de la classe « portante » (la classe Demo).

Dans l'exemple qui suit, la classe anonyme est directement définie lors de l'enregistrement de l'écouteur. Notez-y la présence du new qui permet de produire l'unique instance de notre classe anonyme, cette classe étant une implémentation de l'interface ActionListener. Comme tout est fait un peu en même temps, le code en est encore plus compacte.

 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 
 48 
 49 
 50 
 51 
 52 
 53 
 54 
package fr.koor.poo;

import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.plaf.nimbus.NimbusLookAndFeel;


public class Demo extends JFrame {

    private static final long serialVersionUID = -4939544011287453046L;
    
    private JButton btnClickMe = new JButton( "Click me!" );
    private JButton btnPushMe = new JButton( "Push me!" );
    private JButton btnActivateMe = new JButton( "Activate me!" );
    
    
    public Demo() {
        super( "Implémentation d'interface" );
        this.setDefaultCloseOperation( DISPOSE_ON_CLOSE );
        
        JPanel contentPane = (JPanel) this.getContentPane();
        contentPane.setLayout( new FlowLayout() );
        contentPane.add( btnClickMe );
        contentPane.add( btnPushMe );
        contentPane.add( btnActivateMe );
        
        // Enregistrement d'un objet produit grâce à une classe anonyme basée sur
        // l'interface ActionListener. Cet objet peut directement accèder au bouton.   
        btnClickMe.addActionListener( new ActionListener() {
            @Override public void actionPerformed( ActionEvent event ) {
                btnActivateMe.setText( "First button clicked!" );
            }
        } );
        
        this.setSize( 400, 200 );
        this.setLocationRelativeTo( null );
    }
    
    public static void main( String[] args ) throws Exception {        
        // Try to set Nimbus look and feel
        UIManager.setLookAndFeel( new NimbusLookAndFeel() );

        // Start the demo
        Demo demo = new Demo();
        demo.setVisible( true );
    }
    
}
Implémentation de l'interface via une classe anonyme
nous avons produit une classe anonyme à partir de l'interface ActionListener. Mais il est aussi possible définir une classe anonyme à partir d'une autre classe (abstraite ou non).
si vous utilisez l'IDE Eclipse, commencer par taper btnPushMe.addActionListener( new , puis enclenchez la séquence de touches CRTL + SPACE afin de compléter la définition de la classe anonyme. Il ne vous restera plus qu'à renseigner les instructions à exécuter pour votre méthode actionPerformed.

Travaux pratiques

Le sujet

Faite en sorte d'ajouter des gestionnaires d'événements pour les deux autres boutons de l'interface graphique, en utilisant des classes anonymes.

La correction

 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 
 48 
 49 
 50 
 51 
 52 
 53 
 54 
 55 
 56 
 57 
 58 
 59 
 60 
 61 
 62 
 63 
 64 
 65 
package fr.koor.poo;

import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.plaf.nimbus.NimbusLookAndFeel;


public class Demo extends JFrame {

    private static final long serialVersionUID = -4939544011287453046L;
    
    private JButton btnClickMe = new JButton( "Click me!" );
    private JButton btnPushMe = new JButton( "Push me!" );
    private JButton btnActivateMe = new JButton( "Activate me!" );
    
    
    public Demo() {
        super( "Implémentation d'interface" );
        this.setDefaultCloseOperation( DISPOSE_ON_CLOSE );
        
        JPanel contentPane = (JPanel) this.getContentPane();
        contentPane.setLayout( new FlowLayout() );
        contentPane.add( btnClickMe );
        contentPane.add( btnPushMe );
        contentPane.add( btnActivateMe );
        
        btnClickMe.addActionListener( new ActionListener() {
            @Override public void actionPerformed( ActionEvent event ) {
                btnActivateMe.setText( "First button clicked!" );
            }
        } );
        
        btnPushMe.addActionListener( new ActionListener() {
            @Override public void actionPerformed( ActionEvent e ) {
                System.out.println( "btnPushMe clicked" );
            }
        } );

        btnActivateMe.addActionListener( new ActionListener() {
            @Override public void actionPerformed( ActionEvent e ) {
                // On change le titre de la fenêtre !
                Demo.this.setTitle( "Button clicked" );
            }
        } );

        this.setSize( 400, 200 );
        this.setLocationRelativeTo( null );
    }
    
    public static void main( String[] args ) throws Exception {        
        // Try to set Nimbus look and feel
        UIManager.setLookAndFeel( new NimbusLookAndFeel() );

        // Start the demo
        Demo demo = new Demo();
        demo.setVisible( true );
    }
    
}
Ajout de gestionnaires d'événements sur notre interface graphique


Méthodes abstraites, classes abstraites et interfaces Expressions Lambdas et références sur méthodes