Article n° 45

En ligne: 26 avril 2014
Modifié le: 26 avril 2014
Popularité: 2
Nb visites: 1749

mots clés

Java

Java 8

Des classes anonymes aux méthodes anonymes

Par: Chris

On reproche souvent à Java d’être verbeux et d’être moins concis que d’autres langages. Les expressions lambda de Java 8 permettent, entre autre, de simplifier et de raccourcir son code.

Des classes anonymes aux méthodes anonymes

Avant de voir ce qu’est une méthode anonyme en Java 8, faisons un petit rappel sur les méthodes anonymes.

Exemple d’utilisation d’une classe anonyme

Je vais d’abord écrire un petit programme sans utiliser les possibilités de Java 8.

Le but du programme suivant va être d’afficher la liste des fichiers d’un répertoire mais en sélectionnant uniquement ceux qui ont l’extension « .png ».

On va commencer par une première version qui va afficher l’ensemble des fichiers du répertoire « images » de mon ordinateur. Je créé une méthode qu’il me suffira d’appeler à partir de la méthode main() du programme.

Voici ce que pourrait être cette première version :

public static void main(String[] args)
 {
   dirListV1();
 }
 
 public static void dirListV1()
 {
   File dossierImages = new File( "/home/christophe/Images/papiers_peints" );
   String[] listeFichiers = dossierImages.list();
   for ( String nomFichier: listeFichiers )
   {
     System.out.println( nomFichier );
   }
 }

Ce programme m’affiche la liste suivante :

Comme je veux maintenant n’afficher que les fichiers ayant l’extension .png, il faut que je définisse un objet d’une classe qui implémente FilenameFilter et que je le passe en paramètre à la méthode List().

Une nouvelle version du programme pourrait être la suivante :

Définition de la classe de filtre en tant que classe interne.

static class FiltrePng implements FilenameFilter
 {
   @Override
   public boolean accept(File dir, String name)
   {
     return name.endsWith( ".png" );
   }
 }

Je définie une nouvelle version de ma méthode chargée d’afficher la liste de mes fichier mais maintenant avec mon filtre.

public static void dirListV2()
 {
   File dossierImages = new File( "/home/christophe/Images/papiers_peints" );
   FilenameFilter monFiltre = new FiltrePng() ;
   String[] listeFichiers = dossierImages.list( monFiltre );
   for ( String nomFichier: listeFichiers )
   {
     System.out.println( nomFichier );
   }
 }

Ce qui me donne après exécution du programme :

Bien sur, on voit que l’on peut supprimer la ligne d’initialisation de la variable référence monFiltre puisque cet objet n’est utilisé qu’une seule fois. Ce la donne :

public static void dirListV2()
 {
   File dossierImages = new File( "/home/christophe/Images/papiers_peints" );
   String[] listeFichiers = dossierImages.list( new FiltrePng() );
   for ( String nomFichier: listeFichiers )
   {
     System.out.println( nomFichier );
   }
 }

Puis, vous vous dite maintenant, pourquoi ne pas définir directement la classe filtre au moment du passage de paramètres à la méthode List().

public static void dirListV3()
 {
   File dossierImages = new File( "/home/christophe/Images/papiers_peints" );
   String[] listeFichiers = dossierImages.list( new FilenameFilter()
   {
     @Override
     public boolean accept(File dir, String name)
     {
       return name.endsWith( ".png" );
     }
   } );
   for ( String nomFichier: listeFichiers )
   {
     System.out.println( nomFichier );
   }
 }

C’est en principe cette version de programme que vous avez sans doute l’habitude d’écrire en utilisant ce que l’on appelle une classe interne anonyme. Si vous développez beaucoup en Swing il y a des chances que vous utilisiez également ce type de syntaxe pour gérer vos événements avec l’interface. On peut cependant reprocher à ce type d’écriture une certaine lourdeur et manque de clarté pour ceux qui n’en ont pas l’habitude. Nous allons donc voir maintenant comment écrire cela en Java 8.

Les méthodes anonymes

L’idée est que dans l’exemple précédent, la méthode List() attend une classe filtre qui ne définie que la méthode accept( File dir, String name ). Et c’est cette méthode, généralement très courte, qui est utilisée pour définir le filtre. Il pourrait être donc intéressant de définir directement cette méthode sans avoir à faire référence à la classe. C’est ce que propose Java 8 avec les expressions lambda.

Si vous utilisez Netbeans 7.4, vous pouvez voir que ce dernier vous propose de transformer la ligne ou se trouve votre classe anonyme en expression lambda. Il suffit de cliquez sur le petit repère (ampoule jaune) en début de ligne pour que Netbeans transforme votre code.

Vous devriez obtenir le code suivant :

{
 File dossierImages = new File( "/home/christophe/Images/papiers_peints" );
 String[] listeFichiers = dossierImages.list(
                         (File dir, String name) -> name.endsWith( ".png" ));
 for ( String nomFichier: listeFichiers )
 {
   System.out.println( nomFichier );
 }
}

Netbeans a transformé tous le code définissant la classe et la méthode en la simple ligne suivante :

(File dir, String name) -> name.endsWith( ".png" )

On peut encore simplifier un peu son code en ne spécifiant plus les types des paramètres dir et name que Java 8 peut déduire de lui même.

(dir, name) -> name.endsWith( ".png" )

Lorsque l’on va dans l’aide de Java 8 ou dans le source sur la classe FilenameFilter nous voyons que cette interface est annotée :

@FunctionalInterface
public interface FilenameFilter

Il s’agit d’une nouvelle annotation Java 8 pour spécifier que l’on a affaire à une interface fonctionnelle et qu’elle ne doit définir qu’une seule méthode. La plupart des classes et interfaces qui ne définissent qu’une seule méthode comme les Listener, l’interface Comparable, ont été modifiées pour être utilisées comme des interfaces fonctionnelles.

La classe Stream

Pour terminer cette toute petite introduction, je vais utiliser une nouvelle classe de Java 8, le Stream. Cette classe est très puissante et permet par exemple de gérer le traitement parallèle. Je vais me contenter ici de m’en servir dans l’exemple précédent pour supprimer la boucle for.

Depuis Java 5, on n’a plus besoin de gérer un compteur avec les boucles for lorsque l’on utilise des objets qui implémentent l’interface Iterable. Avec Java 8, l’utilisation de Stream avec une expression lambda va me permettre de supprimer carrément la boucle.

Stream.of( listeFichiers ).forEach( s -> { System.out.println( s ); });

La méthode forEach va exécuter pour chaque élément de listeFichiers le code entre par l’intermédiaire du paramètre s.

Le code de l’ensemble de la méthode tient maintenant sur 3 lignes tout en étant plus lisible que lorsque l’on utilise une classe anonyme.

{
 File dossierImages = new File( "/home/christophe/Images/papiers_peints" );
 String[] listeFichiers = dossierImages.list( ( dir, name ) -> name.endsWith( ".png" ) );
 Stream.of( listeFichiers ).forEach( s -> { System.out.println( s ); });    
}

Nous venons de voir deux cas d’utilisation des expressions lambda. Ce que l’on peut constater sur la syntaxe, c’est que :

- on n’est pas obliger de spécifier le type des paramètres lorsqu’il n’y a pas d’ambiguïté,
- on peut supprimer les parenthèses lorsqu’il n’y a qu’un seul paramètre,
- on peut supprimer les lorsque le code se résume à une simple expression. C’est le cas dans le premier exemple qui ne fait que renvoyer la valeur de l’expression mais pas dans le second.

Conclusion

Cet article avait principalement pour but de présenter la nouvelle syntaxe qu’apporte les expressions lambda en douceur même si l’on a fait qu’effleurer les différentes possibilités. Pour ma part, pour cet exemple, je n’ai fait que me souvenir de choses que je réalisaient dans un langages qui s’appelait Clipper et que j’utilisai il y a plus de 20 ans sous MS-DOS et ce qui s’appelait les blocs de codes.

Ressources utilisées pour cet article

- OS : Linux Ubuntu 12.4 LTS
- JDK : Java SE 8 d’Oracle
- Netbeans 7.4

SPIP |