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 « de visibilité package »
Implémentation de l'interface via une classe interne
Implémentation de l'interface via une classe anonyme
Travaux pratiques
Le sujet
La correction
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 de visibilité package (private package classes), de classes internes (inner classes) et de classes anonymes (anonymous classes) sont aussi étudiées.
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.
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 ); } } |
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.
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!" ); } } |
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 ); } } |
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.
Je vous propose une seconde solution qui consiste à utiliser une classe dite « de visibilité package ». Une telle classe se code dans un fichier de code Java au même niveau que sa classe publique.
Comme une classe « de visibilité package » se code au même niveau de la classe publique du fichier de code considéré, elle devra donc avoir un aure nom. Vous pouvez de plus avoir autant de classes « de visibilité package » que souhaité. Bien entendu, ce type de classes ne pourra pas avoir accès aux membres privés des autres classes du fichier. Noté enfin qu'une classe « de visibilité package » ne peut être utilisée que et uniquement que dans le package qui la définie. De l'extérieur du package, le compilateur refusera l'accès à cette classe.
public
, abstract
ou final
(qui en interdit la dérivation).
Voici un exemple d'utilisation d'une classe « de visibilité package » : il n'y a qu'un unique fichier de code, nommé Demo.java
,
contenant la classe de fenêtre (la classe publique) et celle pour notre gestionnaire d'événements.
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!" ); } } |
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 ); } } |
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.
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 ); } } |
ActionListener
.
Mais il est aussi possible définir une classe anonyme à partir d'une autre classe (abstraite ou non).
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
.
Faite en sorte d'ajouter des gestionnaires d'événements pour les deux autres boutons de l'interface graphique, en utilisant des classes anonymes.
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 ); } } |
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 :