# Java : du style impératif au fonctionnel

Java, dans ses versions antérieures à la JDK 8, supportait principalement la programmation orientée objet (POO) combinée avec la programmation impérative. La programmation impérative est un paradigme où l'on spécifie non seulement ce que l'on souhaite accomplir, mais également la manière de l'accomplir.

À partir de la version 8, Java a introduit la programmation fonctionnelle. Avec cette approche, nous indiquons ce que nous voulons accomplir sans détailler la manière de le faire. Les bibliothèques prennent en charge les détails de l'implémentation. Cette méthode permet de rédiger du code plus concis, clair et facile à maintenir, en comparaison avec l'approche impérative, souvent plus verbeuse et complexe.

Dans cet article, nous allons examiner plusieurs techniques de transition vers la programmation fonctionnelle qui peuvent améliorer la lisibilité de nos programmes au quotidien.

### **Boucle simple :**`for()` **\=&gt;**`range()`**ou**`rangeClosed()`

Considérons la traditionnelle boucle `for` suivante

```java
for(int i = 0; i < 5; i++) {
  System.out.println(i);
}
```

Il s'agit d'une boucle classique qui effectue une opération avec la valeur, ici juste l'afficher. Cette boucle peut être transformée en une version fonctionnelle en utilisant la méthode [`range()`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/IntStream.html#range-int-int-) de [`IntStream`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/IntStream.html), comme illustré ci-dessous.

```java
import java.util.stream.IntStream;

...
IntStream.range(0, 5)
  .forEach(i -> System.out.println(i));
```

On peut simplifier encore davantage en utilisant la [référence de méthode](https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html) `println`.

```java
import java.util.stream.IntStream;

...
IntStream.range(0, 5)
  .forEach(System.out::println);
```

On observe que la version fonctionnelle est plus concise et plus facile à lire, et que l'opération est exprimée de manière plus claire comparée au style impératif.

Et si nous souhaitons inclure la borne supérieure de la boucle, comme suit :

```java
for(int i = 0; i <= 5; i++) {
  System.out.println(i);
}
```

Dans ce cas, nous utiliserons [`rangeClosed()`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/IntStream.html#rangeClosed-int-int-) de [`IntStream`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/IntStream.html) :

```java
import java.util.stream.IntStream;

...
IntStream.rangeClosed(0, 5)
  .forEach(System.out::println);
```

Les méthodes [`range()`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/IntStream.html#range-int-int-) et [`rangeClosed()`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/IntStream.html#rangeClosed-int-int-) de [`IntStream`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/IntStream.html) renvoient un [`Stream`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html) de `int` sur lequel il est possible d'utiliser l'itérateur interne pour réaliser diverses actions. Dans les exemples précédents, nous avons utilisé [`forEach`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#forEach-java.util.function.Consumer-), mais d'autres opérations sont également possibles, comme nous le verrons plus loin dans cet article.

Contrairement à l'itérateur externe fourni par la boucle `for`, l'utilisation de l'itérateur interne avec [`IntStream`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/IntStream.html) est beaucoup plus concise, sans encombre, et évite la gestion manuelle de l'index. Cela rend le code plus lisible, facile à modifier et généralement plus agréable à manipuler.

Vous pouvez donc rechercher dans votre code des occasions de remplacer les boucles `for` traditionnelles par `range` et `rangeClosed` de `IntStream`. Assurez-vous que votre code fonctionne toujours correctement après ces modifications, par exemple en exécutant les tests automatisés existants.

### **Boucle avec pas :**`for(...i = i + ...)` =&gt; `iterate()` et `takeWhile()`

Supposons une boucle avec pas qui permet de sauter certaines valeurs du range :

```java
for(int i = 0; i < 15; i = i + 3) {
  System.out.println(i);
}
```

On peut convertir cette boucle en utilisant `range()` de `IntStream` et appliquer la méthode [`filter()`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#filter-java.util.function.Predicate-) sur le flux renvoyé par `range`. Cependant, explorons des approches plus efficaces.

Cette boucle initialise l'index à 0, puis l'incrémente de 3 à chaque itération, tout en vérifiant que la valeur reste inférieure à 15. Avant de poursuivre, imaginons une correspondance conceptuelle de cette boucle comme suit :

```java
//imaginary code
for(int i = 0; i < 15; i = i + 3) //imperative
for(seed, i -> i < 15, i -> i + 3) //functional
```

Dans cette correspondance, nous avons le **seed** qui représente la valeur initiale, suivi d'un [**lambda**](https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/lambda-quickstart/index.html) pour la condition, et enfin un autre [**lambda**](https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/lambda-quickstart/index.html) pour calculer la valeur suivante.

La bonne nouvelle est que `IntStream` dispose de la méthode [`iterate()`](https://docs.oracle.com/en%2Fjava%2Fjavase%2F11%2Fdocs%2Fapi%2F%2F/java.base/java/util/stream/IntStream.html#iterate(int,java.util.function.IntPredicate,java.util.function.IntUnaryOperator)) qui nous permet d'exprimer exactement cette logique :

`iterate(int seed, IntPredicate hasNext, IntUnaryOperator next)`

* `seed` : c'est la valeur initiale
    
* `hasNext` : c'est un `IntPredicate`, une interface fonctionnelle qui prend une valeur entière et retourne un booléen
    
* `next` : c'est un `IntUnaryOperator`, une interface fonctionnelle qui prend une valeur entière et retourne une valeur du même type
    

Ainsi, nous pouvons convertir notre boucle comme suit :

```java
import java.util.stream.IntStream;

...
IntStream.iterate(0, i -> i < 15, i -> i + 3)
  .forEach(System.out::println);
```

Et que faire lorsqu'il s'agit d'une boucle infinie avec une condition à l'intérieur ?

Considérons l'exemple suivant :

```java
for(int i = 0;; i = i + 3) {
  if(i > 20) {
    break;
  }

  System.out.println(i);
}
```

La méthode [`iterate()`](https://docs.oracle.com/en%2Fjava%2Fjavase%2F11%2Fdocs%2Fapi%2F%2F/java.base/java/util/stream/IntStream.html#iterate(int,java.util.function.IntPredicate,java.util.function.IntUnaryOperator)) possède une version surchargée qui permet de se passer du second argument, à savoir l'[`IntPredicate`](https://docs.oracle.com/javase/8/docs/api/java/util/function/IntPredicate.html).

[`iterate(int seed, IntUnaryOperator next)`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/IntStream.html#iterate-int-java.util.function.IntUnaryOperator-)

Cependant, cette version renvoie un `Stream` infini. Comment alors arrêter ce flux ? Heureusement, Java a prévu cette situation. Nous allons utiliser la méthode `takeWhile()`, qui prend un argument de type `IntPredicate` pour arrêter le flux lorsque la condition devient fausse.

La boucle réécrite est alors la suivante :

```java
IntStream.iterate(0, i -> i + 3)
  .takeWhile(i -> i <= 20)
  .forEach(System.out::println);
```

Génial tout ça non 😊

### `foreach(...) { if... }`**\=&gt;**`stream()`**et**`filter()`

Nous pouvons facilement convertir une boucle `forEach` en style fonctionnel. Considérons l'exemple suivant :

```java
List<String> names = List.of("Jack", "Paula", "Kate", "Peter");
  
for(String name: names) {
  System.out.println(name);
}
```

Le refactoring de ce code consiste à utiliser directement la méthode [`forEach()`](https://docs.oracle.com/javase/8/docs/api/java/util/ArrayList.html#forEach-java.util.function.Consumer-) sur la collection, comme suit :

```java
List<String> names = List.of("Jack", "Paula", "Kate", "Peter");
  
names.forEach(name -> System.out.println(name));
```

Une alternative consiste à utiliser la méthode `stream()` :

```java
List<String> names = List.of("Jack", "Paula", "Kate", "Peter");
  
names.stream()
  .forEach(name -> System.out.println(name));
```

La méthode `forEach` est disponible pour les types [`Collection<E>`](https://docs.oracle.com/javase/8/docs/api/java/util/Collection.html) et [`Stream<T>`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html).

Voyons maintenant comment traiter l'exemple suivant :

```java
List<String> names = List.of("Jack", "Paula", "Kate", "Peter");
  
for(String name: names) {
  if(name.length() == 4) {
    System.out.println(name);
  }
}
```

L'instruction `if` peut être directement remplacée par un appel à la méthode [`filter()`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#filter-java.util.function.Predicate-) de `Stream`. Voyons cela en pratique :

```java
List<String> names = List.of("Jack", "Paula", "Kate", "Peter");
  
names.stream()
  .filter(name -> name.length() == 4)
  .forEach(name -> System.out.println(name));
```

La valeur sera transmise à l'étape suivante du traitement si le [`Predicate`](https://docs.oracle.com/javase/8/docs/api/java/util/function/Predicate.html) fourni à la méthode `filter` renvoie `true`.

### Itération avec transformation : `foreach(...) { ...transformation... }` =&gt; `stream()` et `map()`

Considérons l'exemple d'une boucle `forEach` contenant une transformation en majuscules, comme suit :

```java
List<String> names = List.of("Jack", "Paula", "Kate", "Peter");
  
for(String name: names) {
  System.out.println(name.toUpperCase());
}
```

La boucle `for` peut être remplacée par [`stream()`](https://docs.oracle.com/javase/8/docs/api/java/util/Collection.html#stream--), puis la transformation en majuscules peut être effectuée à l'aide de [`map()`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#map-java.util.function.Function-), qui accepte une [`fonction lambda`](https://docs.oracle.com/javase/8/docs/api/java/util/function/Function.html). Le code devient alors :

```java
List<String> names = List.of("Jack", "Paula", "Kate", "Peter");
  
names.stream()
  .map(name -> name.toUpperCase())
  .forEach(nameInUpperCase -> System.out.println(nameInUpperCase));
```

Ce code peut être optimisé davantage en remplaçant les expressions lambda par des références de méthode, comme suit :

```java
List<String> names = List.of("Jack", "Paula", "Kate", "Peter");
  
names.stream()
  .map(String::toUpperCase)
  .forEach(System.out::println);
```

Supposons maintenant que nous ayons besoin d'appliquer un filtre avant la transformation :

```java
List<String> names = List.of("Jack", "Paula", "Kate", "Peter");
  
for(String name: names) {
  if(name.length() == 4) {
    System.out.println(name.toUpperCase());
  }
}
```

Si nous avons besoin d’appliquer un filtre avant la transformation, nous devons d'abord utiliser la méthode [`filter()`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#filter-java.util.function.Predicate-) pour sélectionner les éléments qui répondent à une certaine condition, puis appliquer la transformation avec [`map()`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#map-java.util.function.Function-). Voici comment nous pouvons procéder :

```java
List<String> names = List.of("Jack", "Paula", "Kate", "Peter");
  
names.stream()
  .filter(name -> name.length() == 4)
  .map(String::toUpperCase)
  .forEach(System.out::println);
```

### Manipuler une Source de Données sous forme Stream

Pour illustrer notre démonstration, imaginons que nous souhaitons compter le nombre de lignes contenant un mot spécifique dans un fichier. Le code traditionnel pour accomplir cette tâche pourrait ressembler à ce qui suit :

```java
//Sample.java
import java.nio.file.*;

public class Sample {
  public static void main(String[] args) {
    try {
      final var filePath = "./Sample.java";
      final var wordOfInterest = "public";

      try (var reader = Files.newBufferedReader(Path.of(filePath))) {
        String line = "";
        long count = 0;

        while((line = reader.readLine()) != null) {
          if(line.contains(wordOfInterest)) {
            count++;
          }
        }

        System.out.println(String.format("Found %d lines with the word %s", count, wordOfInterest));
      }
    } catch(Exception ex) {
      System.out.println("ERROR: " + ex.getMessage());
    }
  }
} 
```

En résumé, nous utilisons la méthode [`newBufferedReader()`](https://docs.oracle.com/javase/8/docs/api/java/nio/file/Files.html#newBufferedReader-java.nio.file.Path-) pour obtenir un [`BufferedReader`](https://docs.oracle.com/javase/8/docs/api/java/io/BufferedReader.html), que nous parcourons à l'aide d'une boucle `while` avec la méthode [`readLine()`](https://docs.oracle.com/javase/8/docs/api/java/io/BufferedReader.html#readLine--). À chaque itération, nous vérifions si la ligne contient le mot recherché, puis nous incrémentons le compteur.

Cependant, si nous pouvons obtenir le contenu du fichier sous forme de flux ([`Stream`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html)), nous pouvons simplifier et moderniser notre code en remplaçant la boucle `while` et la condition `if` par les méthodes [`stream()`](https://docs.oracle.com/javase/8/docs/api/java/util/Collection.html#stream--) et [`filter()`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#filter-java.util.function.Predicate-).

Heureusement, Java propose une solution pour cela. La classe [`Files`](https://docs.oracle.com/javase/8/docs/api/java/nio/file/Files.html) du package `java.nio.file` dispose de la méthode [`lines()`](https://docs.oracle.com/javase/8/docs/api/java/nio/file/Files.html#lines-java.nio.file.Path-), qui nous fournit le contenu du fichier sous forme de flux de lignes. Nous pouvons donc réécrire notre code comme suit :

```java
//Sample.java
import java.nio.file.*;

public class Sample {
  public static void main(String[] args) {
    try {
      final var filePath = "./Sample.java";
      final var wordOfInterest = "public";

      try(var stream = Files.lines(Path.of(filePath))) {
        long count = stream.filter(line -> line.contains(wordOfInterest))
          .count();

        System.out.println(String.format("Found %d lines with the word %s", count, wordOfInterest));
      }
    } catch(Exception ex) {
      System.out.println("ERROR: " + ex.getMessage());
    }
  }
}
```

Nous espérons que les techniques présentées dans cet article vous aideront à optimiser et améliorer la qualité de votre code.
