Java et la programmation fonctionnelle

Maintenant que je commence à prendre un peu plus en main la programmation fonctionnelle, comme vu dans mes précédents articles sur Scala ou mon projet Scalable Explorer, je dois reconnaitre qu'elle apporte une nouvelle façon de penser et surtout des facilités d'écriture (grâce à "l'intelligence" des compileurs) non négligeables. Le passage de Java à Scala est donc relativement aisé et permet une ouverture d'esprit pour la compréhension et la prise en main des nouveaux concepts.

Armés de ces nouveaux outils, faisons un petit retour en arrière... Qu'en est-il de la programmation fonctionnelle en Java? Il est impossible que ces concepts ne puissent pas lui être appliqués, même si on connait la rigueur de ce langage, qui en est devenue une caractéristique majeure. Je vais donc ici exposer comment certains principes fonctionnels peuvent être reproduits dans le monde Java.

Les frameworks

Il existe différents frameworks apportant les outils ou la syntaxe nécessaires à la mise en oeuvre des concepts fonctionnels : fun4j ou Functional Java sont les plus répandus, mais ils ont du mal à s'imposer : peu documentés et parfois mal maintenus, ils ne paraissent pas être la meilleure alternative.

Les "Functors"

La fondation Apache a mis en place les "Apache Commons Collection Utilities", une collection de librairies et d'objets, dans le but d'apporter une "functional touch" au code Java. Ces "functors" ou "functional objects" sont en fait un lot d'interfaces pour créer de nouveaux comportements, essentiellement pour le traitement des collections. Trois concepts majeurs donc : Transformer, Closure et Predicate.

Les Closures

Les closures sont des fonctions qui vont être exécutées sur tous les objets d'une liste. On peut ainsi créer rapidement des traitements de groupe (utiliser ou modifier les objets de la liste, sans modifier le contenu de la liste elle-même). Par exemple :

public static void main(String[] args) {
   List<String> list = Arrays.asList("Apache!","Java!","Functors!");
   CollectionUtils.forAllDo(list, new Closure() {  
     public void execute(Object o) {
       System.out.println(o.toString().replace("!","..."));  
     }  
   });
}
/* Sortie :
Apache...
Java...
Functors...
*/

Les Transformers

Le but ici est de créer une liste d'objets à partir d'une autre, totalement différente (pratique dans le cas de conversions de bean par exemple) :

public static void main(String[] args) {  
  Collection<String> strings = Arrays.asList("Optimus Prime", "Bumblebee", "Megatron", "The fallen");  
  Collection<Autobots> bots = CollectionUtils.collect(strings, new Transformer() {  
      public Object transform(Object o) {  
          return new AutoBot(o.toString());  
      }  
  });  
  CollectionUtils.forAllDo(bots, PrintIt.getInstance() );  
}
/* Sortie :
Optimus Prime à vos ordres!
Bumblebee à vos ordres!
Megatron s'est réveillé...
The fallen prendra sa revanche!!
*/

Les Predicate

Ils sont utilisés principalement pour filtrer des listes : leur rôle est de simplement tester un objet et renvoyer true ou false selon que l'objet passe ou non le filtre :

public static void main(String[] args){
  List<Integer> peopleAges = Arrays.asList(5,18,9,24,35,44,11);  
  Collection adults = CollectionUtils.predicatedCollection(peopleAges,  
      new Predicate() {  
          public boolean evaluate(Object o) {
              Integer num = (Integer) o;  
              return num >= 18;
          }  
      }); 
  System.out.println("Adultes de la liste : ");
  CollectionUtils.forAllDo(numbersOnlyList, PrintIt.getInstance() ); 
}
/* Sortie :
Adultes de la liste : 
18,24,35,44
*/

La librairie Apache fournit également un certain nombre de prédicats prédéfinis pour ce genre de test simple (nullité, comparaison...). Il est aussi possible de créer des prédicats plus complexes grâce à des combinaisons, opérées par les interfaces du type Predicates.or ou Predicates.and.

Pour terminer, je vous conseille fortement de lire le livre Common Java Cookbook traitant de ces librairies communes qui facilitent la manipulation des tableaux, collections, strings, dates et autres objets récurrents en Java.

On voit donc que la programmation fonctionnelle pure est loin de vraiment voir le jour en Java : il faut reconnaitre que sa grammaire même (et donc son compilateur) ne s'y prête pas vraiment, puisqu'au fond ce qui caractérise la programmation fonctionnelle est, au delà des concepts qui peuvent être reproduits en Java comme nous venons de le voir, c'est sa syntaxe : le compilateur doit autoriser certaines souplesses et doit être capable de deviner (inférence) tout ce qui ne relève pas de l'algorithme (typage des variables, cast...)

Sources


Fichier(s) joint(s) :

1 commentaires:

Paul-Emmanuel Faidherbe a dit…

Petite problématique du jour : une liste contient uniquement un élément null. Comment faire en sorte rapidement de savoir que la liste est "vide" au sens métier?
Il faut pour cela vider la liste de tous ses éléments null et vérifier si sa taille est supérieure à 0. Pour faire cela en 2 lignes :

list.removeAll(Collections.singletonList(null));

if (list.size() > 0) {...}



HTH!