Participer au site avec un Tip
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 :

Expressions Lambdas et références sur méthodes

Implémentations d'interfaces Aspects avancés sur la définition d'interfaces



Accès rapide :
La vidéo
Utilisation d'expressions lambda
Interface fonctionnelle
Qu'est-ce qu'une expression lambda en Java ?
Syntaxe de définition d'expressions lambda
Travaux pratiques
Le sujet
La correction
Les lambdas et la capture de l'environnement de définition
Les références sur méthodes
Un premier exemple appliqué à la gestion des événements
Un second exemple appliqué à la manipulation de collections
Travaux pratiques
Le sujet
La correction

La vidéo

Cette nouvelle vidéo vous présente deux notions introduites à partir du Java SE 8.0 : les expressions lambdas et les références sur méthodes. Des exemples appliqués à la gestion des événements et à la gestion des collections vous sont proposés.


Expressions Lambdas et références sur méthodes

Utilisation d'expressions lambda

Malgré ce que laisse penser le terme, une expression lambda n'est pas une expression (en tout cas pas en Java). En Java, on utilise une expression lambda pour nous faciliter la vie en cas de besoin d'implémentation d'une interface possédant une seule et unique méthode abstraite. Une expression lambda est donc en lien avec le concept d'interface, que nous avons déjà bien appréhendé dans le chapitre précédent.

Le fonctionnement des expressions lambda est spécifié dans la JSR 335 (Project Lambda). Cette JSR fait partie de la spécification Java SE 8.0.

Interface fonctionnelle

Nous appellerons une interface possédant une seule méthode abstraite une « interface fonctionnelle ». Le Java SE 8.0 intègre une annotation permettant de marquer de telles interfaces : cette annotation est de type java.lang.FunctionalInterface. De nombreuses interfaces déjà existantes dans les versions précédentes de Java ont été marquées via cette annotation : c'est le cas, par exemple, de l'interface java.lang.Runnable souvent utilisée par vos Threads.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
package java.lang;

@FunctionalInterface
public interface Runnable {

    void run();

}
Définition de l'interface java.lang.Runnable

D'autres interfaces fonctionnelles ont été rajoutées depuis la version 8.0 du Java SE : par exemple, l'interface fonctionnelle java.util.function.Predicate<T>. Dans ce cas précis, une seule méthode abstraite existe sur l'interface : la méthode boolean test(T t). Par contre, cette interface expose un certain nombre d'autres méthodes non abstraites (des « default methods » et des méthodes statiques). Nous reviendrons sur cette interface plus tard dans ce document.

Qu'est-ce qu'une expression lambda en Java ?

En Java, une expression lambda est une forme de syntaxe permettant de simplifier l'implémentation d'une interface fonctionnelle (une interface à une seule et unique méthode). On peut simplifier la chose en affirmant que cette syntaxe est du « sucre syntaxique » pour la production d'une classe anonyme : on entend par là, qu'elle simplifie la mise en oeuvre d'une classe anonyme.

Comme vous allez le constater, cette nouvelle syntaxe est beaucoup plus compacte. Pour produire la classe anonyme, le compilateur va devoir faire de l'inférence de type (de la déduction de type) à partir des éléments présents dans votre code.

Avant de vous présenter la syntaxe de définition d'une expression lambda, reprenons l'exemple d'un gestionnaire d'événements pour le clic sur un bouton. Comme nous l'avons vu dans le chapitre précédent, plusieurs possibilités d'implémentations sont possibles. Je vous rappelle ici celle à base d'une classe anonyme.

 1 
 2 
 3 
 4 
 5 
ActionListener listener = new ActionListener() {
    public void actionPerformed( ActionEvent event ) {
        System.out.println( "Button clicked" );
    }
};
Exemple d'implémentation de l'interface ActionListener via une classe anonyme
je repars, bien entendu, de cet exemple car l'interface ActionListener n'a qu'une seule et unique méthode : actionPerformed. C'est donc bien une FunctionalInterface.

Ce qu'il faut comprendre, c'est que votre IDE peut vous aider à produire une partie du code d'une classe anonyme. Pour valider ce point avec Eclipse, commencez par écrire le code suivant : ActionListener listener = new  (n'oubliez pas l'espace final), puis enclenchez la séquence de touches CTRL + SPACE : votre IDE doit normalement produire la suite du code et notamment le squelette de la méthode actionPerformed. Mais comment fait-il ? Et bien, il fait lui aussi de l'inférence de type. La variable listener étant typée via ActionListener, il en déduit qu'il faut générer un squelette pour la méthode actionPerformed.

Ceux qui ont proposé la syntaxe des expressions lambda en sont arrivés à la conclusion suivante : si votre IDE peut produire une partie du code, pourquoi le compilateur ne pourrait pas en faire autant et vous décharger ainsi d'une partie du code ?

Syntaxe de définition d'expressions lambda

On reconnaît une expression lambda grâce à l'utilisation de l'opérateur ->. Malgré cela, plusieurs variations dans la syntaxe sont possibles. Les quatre exemples suivant doivent normalement parfaitement compiler (à condition d'utiliser un Java SE 8.0 ou supérieur).

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
// La forme la plus compacte (uniquement si vous n'avez qu'un seul paramètre).
// Le type du paramètre sera déduit de la signature de la méthode à redéfinir.
// Une seule instruction doit être placée à la suite de l'opérateur -&gt;
ActionListener listener1 = e -> System.out.println( "Button clicked" );

// Ici, le paramètre est placé entre parenthèses.
// Vous pouvez définir plusieurs paramètres séparés par des virgules.
ActionListener listener2 = (e) -> System.out.println( "Button clicked" );

// Si vous préférez vous pouvez typer le paramètre.
ActionListener listener3 = (ActionEvent e) -> System.out.println( "Button clicked" );

// Enfin, si vous avez plusieurs instructions à placer dans la lambda,
// mettez les entre accolades.
ActionListener listener4 = (ActionEvent e) -> { System.out.println( "Button clicked" ); };
Les différentes syntaxes de définition d'une expression lambda

Voici quelques explications complémentaires sur ces quatre syntaxes de définition d'expressions lambda.

L'exemple suivant reprend le code vu dans le chapitre précédent qui créé une interface graphique et lui ajoute des gestionnaires d'événements. Les classes anonymes sont remplacées par des expressions lambda.

 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 
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( e -> btnActivateMe.setText( "First button clicked!" + e.getSource() ) );
        
        btnPushMe.addActionListener( (e) -> System.out.println( "btnPushMe clicked" + e.getSource() ) );

        btnActivateMe.addActionListener( (e) -> {
            // On change le titre de la fenêtre !
            setTitle( "Button clicked" + e.getSource() );
        } );

        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, sous forme d'expressions lambda, sur notre interface graphique

Travaux pratiques

Le sujet

Nous souhaitons trier une collection de chaînes de caractères en étant « case insensitive » (non sensible à la casse (minuscules/majuscules)). L'interface java.util.List expose une méthode sort acceptant un comparateur en paramètres. Ce comparateur doit implémenter l'interface java.util.Comparator<T> (un type générique, nous reviendrons sur ce sujet ultérieurement). Voici un exemple de code réalisant ce tri en utilisant une implémentation de l'interface Comparator produite par une classe anonyme.

 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 
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class Sample {
    public static void main( String [] args ) {
        
        List<String> collection = new ArrayList<>();
        collection.add( "Java" );
        collection.add( "c" );
        collection.add( "Python" );
        collection.add( "C++" );
        collection.add( "ada" );
        collection.add( "lisp" );

        collection.sort( new Comparator<String>() {
            @Override public int compare( String l1, String l2 ) {
                return l1.compareToIgnoreCase( l2 );
            }
        } );
        
        for ( String language : collection ) {
            System.out.println( language );
        }
    }
}
Trier une collection de chaînes de caractères, de manière « case insensitive »

Le but de l'exercice est de réécrire ce code en remplaçant la classe anonyme par une expression lambda. A vous de faire, mais attention, on regarde la correction après avoir fait l'exercice ;-).

La correction

Voici donc le même programme réécrit pour utiliser une expression lambda en lieu et place de la classe anonyme.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
import java.util.ArrayList;
import java.util.List;

public class Sample {
    public static void main( String [] args ) {
        
        List<String> collection = new ArrayList<>();
        collection.add( "Java" );
        collection.add( "c" );
        collection.add( "Python" );
        collection.add( "C++" );
        collection.add( "ada" );
        collection.add( "lisp" );

        collection.sort( (l1, l2) -> l1.compareToIgnoreCase( l2 ) );
        
        for ( String language : collection ) {
            System.out.println( language );
        }
    }
}
Trier une collection de chaînes de caractères, de manière « case insensitive »
remarquez bien que le code est plus compact, y compris dans la liste des imports. Comme on ne mentionne plus explicitement l'interface Comparator, son importation n'est plus requise.
par contre, la compréhension de ce code nécessite que l'on connaisse l'interface implicitement implémentée. Heureusement, si vous placez la souris sur l'opérateur -> votre IDE (pour moi Eclipse) doit vous afficher les détails de l'interface implémentée, comme le montre la capture d'écran ci-dessous.
Assitance proposée par Eclipse pour le [dé]codage de vos expressions lambdas

Les lambdas et la capture de l'environnement de définition

Maintenant, passons aux choses sérieuses : les expressions lambda capturent leur environnement de définition. Mais qu'est-ce que cela veut bien dire ? Pour comprendre la chose regardons attentivement l'exemple ci-dessous : il définit deux expressions lambda (toutes les deux produites par la méthode createLambda) qui seront invoquées après coup.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
@FunctionalInterface
interface Demo {
    public int doSomething( int param );
}

public class Sample {

    public static Demo createLambda( int factoryParam ) {
        int localVariable = 10;
        return (lambdaParam) -> localVariable + factoryParam + lambdaParam;
    }
    
    public static void main( String [] args ) throws Exception {
        
        Demo l1 = createLambda( 100 );
        Demo l2 = createLambda( 200 );

        Thread.sleep( 1000 );

        System.out.println( l1.doSomething( 1000 ) );
        System.out.println( l2.doSomething( 2000 ) );
        
    }
}
Exemple de captures effectuées par des expressions lambda

Ce qui peut sembler fou dans cet exemple, c'est que la lambda, définie en ligne 10, utilise les valeurs d'une variable locale et un paramètre après être sorti de l'appel à la méthode createLambda, qui définit ces deux éléments. Or, la durée de vie d'un paramètre ou d'une variable locale, ne serait-elle pas le temps d'exécution de la méthode ? Comment la lambda peut-elle compiler ?

Et bien, c'est là qu'intervient le concept de capture. Lors de la création de la lambda, elle réalise une capture de ce à quoi elle a accès lors de sa définition. Par la suite, elle peut s'en resservir. Du coup, les résultats affichés par ce programme sont bien 1110 puis 2210.

En fait, cette possibilité était déjà bien présente dans le Java SE 7.0. A l'époque, au lieu d'utiliser une lambda, on définissait une classe anonyme. Cette classe anonyme pouvait déjà utiliser les valeurs de paramètres et de variables locales à condition de les avoir marqués comme étant finaux, avec le mot final. Voici le même programme que précédemment, mais réalisé avec une classe anonyme et de la finalisation.

 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 
@FunctionalInterface
interface Demo {
    public int doSomething( int param );
}

public class Sample {

    public static Demo createLambda( final int factoryParam ) {
        final int localVariable = 10;
        return new Demo() {            
            @Override 
            public int doSomething( int param ) {
                return localVariable + factoryParam + param;
            }
        };
    }
    
    public static void main( String [] args ) throws Exception {
        
        Demo l1 = createLambda( 100 );
        Demo l2 = createLambda( 200 );

        Thread.sleep( 1000 );

        System.out.println( l1.doSomething( 1000 ) );
        System.out.println( l2.doSomething( 2000 ) );
        
    }
}
On simule les lambdas en Java SE 7.0

Du coup, on peut en déduire une affirmation : une lambda ne peut en aucun cas modifier les éléments (paramètres ou variables locales) qu'elle a capturé, car ils ont automatiquement finalisés. Si vous cherchez à modifier un des éléments capturés, une erreur de compilation sera systématiquement produite, comme en atteste l'exemple suivant.

 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 
@FunctionalInterface
interface Demo {
    public int doSomething( int param );
}

public class Sample {

    public static Demo createLambda( int factoryParam ) {
        int localVariable = 10;
        return (lambdaParam) -> {
            factoryParam++;
            return localVariable + factoryParam + lambdaParam;
        };
    }
    
    public static void main( String [] args ) throws Exception {
        
        Demo l1 = createLambda( 100 );
        Demo l2 = createLambda( 200 );

        Thread.sleep( 1000 );

        System.out.println( l1.doSomething( 1000 ) );
        System.out.println( l2.doSomething( 2000 ) );
        
    }
}
Exemple d'une tentative de modification d'un élément de la capture
la tentative de modification d'un paramètre capturé est présente en ligne 11.

Et voici les résultats produits par le compilateur Java :

$> javac Sample.java 
Sample.java:11: error: local variables referenced from a lambda expression must be final or effectively final
            factoryParam++;
            ^
Sample.java:12: error: local variables referenced from a lambda expression must be final or effectively final
            return localVariable + factoryParam + lambdaParam;
                                   ^
2 errors
$>

Les références sur méthodes

Pour clore ce chapitre, je tenais à vous présenter le concept de référence sur méthode. Ce concept a été ajouté à Java SE, à partir de sa version 8.0. Il est en lien direct avec les lambas et les classes anonymes dans le sens ou une référence sur méthode, en Java, correspond à une implémentation automatique d'une classe anonyme et à un renvoi sur une méthode de votre choix.

Un premier exemple appliqué à la gestion des événements

Considérons la méthode suivante : on part d'un principe qu'elle sera associée à un gestionnaire d'événements sur un bouton Swing.

 1 
 2 
 3 
private void btnPushMeListener( ActionEvent event ) {
    System.out.println( "btnPushMe clicked" + e.getSource() );
}
Une méthode à invoquer en cas de clic sur un bouton

Vous pouvez alors enregistrer un listener sur le bouton qui va automatiquement renvoyer vers la méthode btnPushMeListener. On connaît déjà au moins deux manières de réaliser cet enregistrement d'écouteur. Soit via une classe anonyme :

 1 
 2 
 3 
 4 
 5 
 6 
btnPushMe.addActionListener( new ActionListener() {
    @Override
    public void actionPerformed( ActionEvent event ) {
        btnPushMeListener( event );     // On renvoit sur la méthode dédiée.
    }
} );
Enregistrement d'un écouteur via une classe anonyme

Soit via une expression lambda :

 1 
btnPushMe.addActionListener( (event) -> btnPushMeListener( event ) );
Enregistrement d'un écouteur via une expression lambda

Et bien, nous avons maintenant une nouvelle possibilité équivalente : l'utilisation d'une référence sur méthode. Une référence sur méthode s'introduit par l'opérateur ::, mais il faut bien comprendre qu'on produit une instance (un objet) de type ActionListener (en tout cas, dans notre exemple).

 1 
btnPushMe.addActionListener( this::btnPushMeListener );
Enregistrement d'un écouteur via une référence sur méthode
il est important de comprendre que la méthode référencée doit impérativement être compatible (sur sa signature, on parle aussi de prototype) avec la méthode portée par l'interface implémentée (ici ActionListener) car on doit relayer l'appel sur cette méthode. Si la signature de votre méthode n'est pas compatible avec ce qui est attendu, une erreur de compilation sera produite.

Un second exemple appliqué à la manipulation de collections

Je peux vous proposer un autre exemple, imaginons que l'on souhaite filtrer dans une collection de chaînes de caractères, toutes celles qui commencent par une lettre J (en majuscule). Java permet de traiter en lot les éléments d'une collection via la notion de streams. Pour acquérir un stream à partir d'une collection, il faut utiliser la méthode stream(). Une fois un stream obtenu, on peut filtrer ses éléments. Pour ce faire, il faut invoquer la méthode filter sur le stream : cette méthode accepte en paramètre une instance typée via l'interface java.util.function.Predicate<T>. Cette interface définit notamment la méthode abstraite test :

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
@FunctionalInterface
public interface Predicate<T> {

    public boolean test(T t);
    
    // Suite de l'interface (des méthodes statiques et des "default" méthodes).

}
Extrait de l'interface java.util.function.Predicate

Il est donc possible d'utiliser les références sur méthodes pour renvoyer les tests sur une méthode statique de votre classe, comme le montre l'exemple ci-dessous.

 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 
import java.util.ArrayList;
import java.util.List;

public class Sample {
    
    private static boolean filter( String language ) {
        return language.charAt( 0 ) == 'J';
    }
    
    public static void main( String [] args ) {
        
        List<String> collection = new ArrayList<>();
        collection.add( "Java" );
        collection.add( "c" );
        collection.add( "Jython" );
        collection.add( "C++" );
        collection.add( "ada" );
        collection.add( "lisp" );

        collection.stream()
                  .filter( Sample::filter )         // Référence sur une méthode statique
                  .forEach( System.out::println );  // Référence sur une méthode d'instance    
        
    }
}
Exemple de références sur méthodes appliquées à la manipulation de collections
une autre référence sur méthode est utilisée dans l'appel à la méthode forEach pour passer toutes les chaînes de caractères sélectionnées à la méthode System.out.println.

L'exécution de ce programme affiche plus que deux lignes : un pour le langage Java et l'autre pour Jython. Pour information, Jython est un environnement permettant d'utiliser les APIs Java au travers du langage Python. Au final, un programme Jython s'exécute dans une JVM.

Travaux pratiques

Le sujet

Reprendre le programme suivant et le recoder pour qu'il utilise des références sur méthode en lieu et place des trois expressions lambdas.

 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 
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( e -> btnActivateMe.setText( "First button clicked!" + e.getSource() ) );
        
        btnPushMe.addActionListener( (e) -> System.out.println( "btnPushMe clicked" + e.getSource() ) );

        btnActivateMe.addActionListener( (e) -> {
            // On change le titre de la fenêtre !
            setTitle( "Button clicked" + e.getSource() );
        } );

        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 );
    }
    
}
Listener implémentés via des expressions lambdas

La correction

Voici le même programme réécrit pour utiliser des références sur méthodes en lieu et place des expressions lambdas.

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

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

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( this::btnClickMeListener );
        btnPushMe.addActionListener( this::btnPushMeListener );
        btnActivateMe.addActionListener( this::btnActivateMeListener );

        this.setSize( 400, 200 );
        this.setLocationRelativeTo( null );
    }
    
    private void btnClickMeListener( ActionEvent e ) {
        btnActivateMe.setText( "First button clicked!" + e.getSource() );
    }

    private void btnPushMeListener( ActionEvent e ) {
        System.out.println( "btnPushMe clicked" + e.getSource() );
    }

    private void btnActivateMeListener( ActionEvent e ) {
        // On change le titre de la fenêtre !
        setTitle( "Button clicked" + e.getSource() );
    }

    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 );
    }
    
}
Listener implémentés via des références sur méthodes
personnellement, et dans le cadre de la mise en oeuvre de gestionnaires d'événements sur des boutons, je trouve que cette variante est la plus propre en termes de lisibilité et de maintenabilité. Mais je vous laisse méditer le sujet et vous faire votre propre opinion.


Implémentations d'interfaces Aspects avancés sur la définition d'interfaces