[JAVA] Point 46: Préférez les fonctions sans effets secondaires dans les flux

46. Choisissez le traitement de flux sans effets secondaires

Changements de paradigme dus aux flux

Les flux ne sont pas que des API, ce sont des changements dans un paradigme enraciné dans la programmation fonctionnelle, et nous devons nous adapter à ce paradigme. La partie la plus importante du paradigme de flux est de structurer l'opération comme le résultat d'une série de transformations par des fonctions pures. Une fonction pure est une fonction dont le résultat dépend uniquement de son entrée, ne dépend pas d'états mutables et ne change pas les autres états. Pour atteindre ce paradigme, les opérations intermédiaires et de terminaison sur le flux doivent être exemptes d'effets secondaires.

Ci-dessous, nous examinerons un programme qui calcule la fréquence des mots contenus dans un fichier.

// Uses the streams API but not the paradigm--Don't do this!
Map<String, Long> freq = new HashMap<>();
try (Stream<String> words = new Scanner(file).tokens()) {
    words.forEach(word -> {
        freq.merge(word.toLowerCase(), 1L, Long::sum);
    });
}

Il utilise des flux, des expressions lambda et des références de méthode, et les résultats sont corrects, mais il ne tire pas parti de l'API de flux. Le problème est que nous changeons l'état externe (variable freq) dans forEach. En général, le code qui n'affiche pas le résultat dans le flux forEach est le code qui modifie l'état, il peut donc s'agir d'un code incorrect.

Voici ce que cela devrait être.

// Proper use of streams to initialize a frequency table
Map<String, Long> freq;
try (Stream<String> words = new Scanner(file).tokens()) {
    freq = words
        .collect(groupingBy(String::toLowerCase, counting()));
}

** forEach doit être utilisé pour afficher le résultat de l'opération d'un flux, pas l'opération elle-même. ** **

Utilisation du collecteur

Le code amélioré ci-dessus utilise un collecteur et est essentiel pour l'utilisation des flux. L'API Collectors a 39 méthodes et a un maximum de 5 arguments, ce qui semble effrayant. Cependant, vous pouvez utiliser cette API sans aller plus loin. Dans un premier temps, nous ignorerons l'interface du collecteur et effectuerons une réduction (combinant les éléments du flux en un seul objet).

Il existe des méthodes toList () '', toSet () '', `` toCollection () '' pour créer les éléments de la collection de flux. Ils renvoient respectivement une liste, un ensemble et une collection. Le top 10 du tableau des fréquences est extrait à l'aide de ceux-ci.

// Pipeline to get a top-ten list of words from a frequency table
List<String> topTen = freq.keySet().stream()
    .sorted(comparing(freq::get).reversed())
    .limit(10)
    .collect(toList());

Supposé dans le code ci-dessus, les membres des collecteurs doivent être importés statiquement pour la lisibilité du pipeline de flux.

toMap, méthode

À l'exception des trois ci-dessus, les 36 méthodes restantes sont principalement destinées à la cartographie des flux. Le plus simple est `` toMap (keyMapper, valueMapper) '', qui prend une fonction qui fait du flux une clé et une fonction qui fait du flux une valeur. Un exemple est le suivant.

// Using a toMap collector to make a map from string to enum
private static final Map<String, Operation> stringToEnum =
    Stream.of(values()).collect(
        toMap(Object::toString, e -> e));

Le code ci-dessus lève IllegalStateException s'il y a plusieurs clés identiques. Une façon d'éviter de tels conflits est d'avoir une fonction de fusion (BinaryOperator <V> '' où V est le type de valeur de mappage) dans l'argument. L'exemple suivant crée une carte de l'album le plus vendu pour chaque artiste à partir d'un flux d'objets Album.

// Collector to generate a map from key to chosen element for key
Map<Artist, Album> topHits = albums.collect(
   toMap(Album::artist, a->a, maxBy(comparing(Album::sales))));

Une autre utilisation de la méthode `` toMap '', qui prend trois arguments, est de rendre le dernier écrit positif lorsqu'un conflit de clé se produit. L'exemple de code à ce stade est le suivant.

// Collector to impose last-write-wins policy
toMap(keyMapper, valueMapper, (oldVal, newVal) -> newVal)

Il existe également une méthode `` toMap '' qui prend quatre arguments, et le quatrième argument spécifie la carte qui implémente la valeur de retour.

méthode groupingBy

En plus de la méthode toMap '', l'API Collectors dispose également d'une méthode groupingBy ''. groupingbyLa méthode crée une carte qui catégorise les éléments en fonction de la fonction de classification. La fonction classifieur est une fonction qui reçoit un élément et renvoie la catégorie (touche Map) de cet élément. C'est celui utilisé dans le programme d'anagrammes illustré au point 45.

words.collect(groupingBy(word -> alphabetize(word)))

Pour renvoyer un collecteur qui génère un Map dont la valeur est autre que List dans la méthode groupingBy, il est nécessaire de spécifier le collecteur en aval en plus de la fonction de classificateur. Dans l'exemple le plus simple, si vous passez toSet à ce paramètre, la valeur de Map sera Set au lieu de List. Un autre exemple simple de prise de deux arguments pour la méthode groupingBy consiste à transmettre counting () au collecteur en aval. counting () peut agréger le nombre d'éléments dans chaque catégorie. Le tableau des fréquences présenté au début de ce chapitre en est un exemple.

Map<String, Long> freq = words
        .collect(groupingBy(String::toLowerCase, counting()));

La méthode groupingBy, qui prend trois arguments, vous permet de spécifier le type de Map à générer. (Cependant, la fabrique de cartes vient dans le deuxième argument et le collecteur en aval vient dans le troisième.)

Autres méthodes

La méthode de comptage est spécialisée pour une utilisation en tant que collecteur en aval, et des fonctionnalités similaires peuvent être obtenues directement à partir de Stream, de sorte que des appels tels que `` collect (counting ()) '' ne doivent pas être effectués. Il existe 15 autres méthodes avec de telles caractéristiques dans les collecteurs, dont 9 sont des noms de méthodes commençant par la somme, la moyenne et la synthèse. En outre, il existe des méthodes de réduction, de filtrage, de mappage, de flatMapping et de collecteAndThen similaires à celles de Stream.

Il existe trois méthodes de collection que je n'ai pas encore mentionnées, mais elles n'ont pas grand-chose à voir avec les collectionneurs. Les deux premiers sont les méthodes minBy '' et maxBy ''. Ils prennent un comparateur comme argument et renvoient les éléments les plus petits et les plus grands des éléments du flux.

La dernière méthode Collectors est join '', qui ne manipule que le flux de l'instance CharSequence '' (telle que String). La jointure sans argument renvoie un collecteur qui joint uniquement les éléments. La jointure avec un argument prend le délimiteur comme argument et renvoie un collecteur qui prend en sandwich le délimiteur entre les éléments. La jonction avec 3 arguments prend le préfixe et le suffixe comme arguments en plus du délimiteur. Si le délimiteur est une virgule et que le préfixe est [et suffixe],

[came, saw, conquered].

devenir de cette façon.

Recommended Posts

Point 46: Préférez les fonctions sans effets secondaires dans les flux
Élément 80: Préférez les exécuteurs, les tâches et les flux aux threads
Fonctions Azure en Java
Point 45: Utilisez judicieusement les flux
[Ruby] Gestion des exceptions dans les fonctions
Créer des fonctions Azure en Java
Rubrique 65: Préférez les interfaces à la réflexion