[Java11] Résumé du flux -Avantages du flux-

Qu'est-ce que Stream

Cartes pour collections-Classes qui prennent en charge les opérations fonctionnelles sur un flux d'éléments, telles que la réduction des transformations. Source: Document officiel

Tout d'abord, voyons réellement

Partiellement modifié depuis Référence officielle

stream.java


final int sum = widgets.stream()
                       .filter(w -> w.getColor() == RED)
                       .mapToInt(w -> w.getWeight())
                       .sum();

Dans l'exemple ci-dessus, «widgets» est extrait de «widgets» avec «color» RED »et la somme des valeurs de« weight »est calculée. Si vous l'écrivez dans l'instruction for, ce sera comme suit.

pour déclaration.java


int sum = 0;
for (Widget w: widgets) {
    if (w.getColor() == RED) {
        sum += w.getWeight();
    }
}

Que puis-je faire?

Comme je l'ai écrit au début, Stream est "* une classe qui prend en charge les opérations fonctionnelles sur un flux d'éléments, comme la conversion de réduction de carte pour les collections. *"

--Map pour la conversion de collecte-réduire

En gros, c'est ** ce que je faisais avec l'instruction for **. Vous pouvez calculer la valeur totale ci-dessus, obtenir la valeur maximale, créer une nouvelle liste, etc.

--Supporte les opérations fonctionnelles

De nombreuses méthodes de classe Stream prennent une interface fonctionnelle comme argument. Par exemple, transmettez ** comment filtrer ** à la fonction de filtre et ** comment convertir en type entier ** à la fonction mapToInt. Stream a de nombreuses méthodes qui renvoient un nouveau Stream, et vous pouvez lui transmettre le traitement pour un traitement flexible.

Ce qui est bon?

Je pense qu'il y a deux avantages majeurs:

  1. Meilleure lisibilité
  2. Amélioration des performances grâce au traitement parallèle

Meilleure lisibilité

Ceci est dû au fait que la méthode d'écriture utilisant Strean est ** "déclarative" ** par rapport au code normal. Portez une attention particulière à la partie de l'exemple ci-dessus où la somme («somme») est calculée.

--Pour Stream Appelez simplement sum (). Je dis simplement ** "trouver la valeur totale" ** pour l'ensemble de données.

--Pour déclaration La valeur initiale de «sum» est définie sur 0 et les valeurs de chaque élément sont ajoutées. La valeur finale est la valeur totale. ** "Comment trouver la valeur totale" ** est implémenté.

L'instruction for est appelée ** "instructive" ** par rapport au Stream. L'écriture "déclarative" vous permet de masquer les petites implémentations et de réduire la quantité de code. C'est juste une déclaration de ce qu'il faut faire, il est donc facile de comprendre ce que vous voulez faire à partir du code. En revanche, plus le processus est compliqué, plus il est difficile à comprendre et plus il provoque des bugs.

Par expérience, que Stream soit utilisé ou non, moins le code est impératif dans une méthode, plus il est facile d'écrire du code sécurisé.

Amélioration des performances par traitement parallèle

Il existe de nombreux cas où cet avantage ne peut être obtenu. (Expérience) En effet, il est ** plus rapide de s'exécuter en série **, sauf si vous avez affaire à une très grande quantité de données.

Heureusement, tout ce que vous avez à faire pour transformer l'exécution de Strean en traitement parallèle est d'appeler parallel (). Vous pouvez maximiser les avantages du traitement parallèle en considérant ** la quantité de données ** et ** le problème de ne pas maintenir l'ordre ** au lieu du traitement parallèle dans les nuages sombres.

Les articles suivants seront utiles. Référence: Connaissance de base de l'interface fonctionnelle et de l'API Stream qui montre son vrai caractère dans l'expression lambda (3/3)

Comment écris tu

Il y a ** 3 étapes ** pour écrire un processus à l'aide de Stream.

  1. Créez un flux
  2. Fonctionnement intermédiaire
  3. Opération de terminaison

Un exemple est le suivant.

stream.java


final int sum = widgets.stream() //Créer un flux
                       .filter(w -> w.getColor() == RED) //Fonctionnement intermédiaire
                       .mapToInt(w -> w.getWeight()) //Fonctionnement intermédiaire
                       .sum(); //Opération de terminaison

Créer un flux

Je pense qu'il est souvent créé à partir d'une collection telle que List ou Set. C'est exactement ce qu'est «widgets.stream ()». En outre, il existe des méthodes telles que la création d'un Stream à partir de plusieurs valeurs et l'utilisation de Stream.Builder.

Fonctionnement intermédiaire

filter`` mapToInt est l'opération intermédiaire. Il convertit la valeur de chaque élément et extrait l'élément en fonction de la condition. Renvoie un Stream tel que Stream <T> ou ʻIntStream`. Une chaîne de méthodes est possible car elle renvoie un Stream. Le traitement de l'opération intermédiaire n'est pas le moment où "fileter", etc. est appelé. Il est exécuté pour la première fois lorsque l'opération de terminaison est exécutée. Il s'agit d'une opération intermédiaire

Un exemple de connaissance réelle du timing d'exécution d'une opération intermédiaire
public class Main {
    public static void main(String[] args) throws Exception {
        List<Widget> widgets = List.of(new Widget(RED, 10), new Widget(BLUE, 20));
        
        Stream<Widget> stream1 = widgets.stream();
        Stream<Widget> stream2 = stream1.filter(w -> w.getColor() == RED);
        System.out.println("complete filtering");
        IntStream stream3 = stream2.mapToInt(w -> w.getWeight());
        System.out.println("complete mappint to integer");
        final int sum = stream3.sum();
    }
}

class Widget {
    private Color color;
    private int weight;
    
    public Widget(Color color, int weight) {
        this.color = color;
        this.weight = weight;
    }
    public Color getColor() {
        System.out.println(color);
        return color;
    }
    public int getWeight() {
        System.out.println(weight);
        return weight;
    }
}
complete filtering
complete mappint to integer
java.awt.Color[r=255,g=0,b=0]
10
java.awt.Color[r=0,g=0,b=255]

On constate que chaque processus n'est pas exécuté immédiatement après l'opération intermédiaire.

Opération de terminaison

«sum» est l'opération de terminaison. D'autres incluent collect`` findFirst. Renvoie la valeur totale ou une nouvelle collection. Contrairement à l'opération intermédiaire, la valeur renvoyée varie. Il y a beaucoup de choses que vous pouvez faire. "Calculer la valeur totale", "Créer une nouvelle collection", "Traiter pour chaque élément (pour chaque)" et ainsi de suite.

Je pense qu'il vaut mieux montrer un exemple de ce qui peut être fait pour les opérations intermédiaires et les opérations de terminaison, je voudrais donc résumer en détail à partir de la prochaine fois.

Interface fonctionnelle

Je l'ai ignoré jusqu'à présent, mais je voudrais mentionner l'existence de w-> w.getColor () == RED`` w-> w.getWeight (). Cette grammaire est appelée ** expression lambda **, mais vous ne devez pas nécessairement écrire une expression lambda. ** Les expressions Lambda ne sont qu'un moyen d'implémenter une interface fonctionnelle. ** **

Cette compréhension est inévitable pour comprendre Stream, car la plupart des méthodes de Stream prennent une interface fonctionnelle comme argument.

Pour être honnête, il est normal d'écrire dans une atmosphère au début, donc même si vous ne pouvez pas comprendre le pire à partir de maintenant, il peut être bon d'écrire un Stream.

Qu'est-ce qu'une interface fonctionnelle?

Un type d'interface. Cette interface n'a ** qu'une seule méthode ** Ce qui est fourni en standard en Java , Tu peux aussi créer le tien.

Pour rendre chaque type facile à comprendre, le premier exemple est divisé autant que possible comme suit.

Stream<Widget> stream1 = widgets.stream();
        
Predicate<Widget> predicate = w -> w.getColor() == RED;
Stream<Widget> stream2 = stream1.filter(predicate);
        
ToIntFunction<Widget> toIntFunction = w -> w.getWeight();
IntStream stream3 = stream2.mapToInt(toIntFunction);
        
final int sum = stream3.sum();

Le Predicate`` ToIntFunction, auquel l'expression lambda est affectée, est l'interface fonctionnelle. La définition de «ToIntFunction» est la suivante.

java:java.util.function.ToIntFunction.java


@FunctionalInterface
public interface ToIntFunction<T> {
    int applyAsInt​(T value);
}

@ FunctionalInterface est ajouté à l'interface fonctionnelle, mais il n'y a pas de problème fonctionnel même s'il n'est pas ajouté.

Comment mettre en œuvre

Vous devez créer une instance qui implémente une interface fonctionnelle à transmettre à une méthode Stream, mais il existe trois façons principales de l'implémenter.

--Classe anonyme --Style Lambda --Référence de la méthode

est.

Comparons chaque méthode d'implémentation. En principe, nous supposons que vous avez une classe Widget comme celle-ci.

Widget.java


class Widget {
    private Color color;
    private int weight;
    
    public Widget(Color color, int weight) {
        this.color = color;
        this.weight = weight;
    }
    
    public Color getColor() {
        return color;
    }
    
    public boolean isColorRed() {
        return color == RED;
    }
    
    public int getWeight() {
        return weight;
    }
}

Classe anonyme

Ce n'est pas très facile à lire.

Classe anonyme.java


final int sum = widgets.stream()
                .filter(new Predicate<Widget>() {
                    public boolean test(Widget w) {
                        return w.isColorRed();
                    }
                }) 
                .mapToInt(new ToIntFunction<Widget>() {
                    public int applyAsInt(Widget w) {
                        return w.getWeight();
                    }
                }) 
                .sum();
Vous pouvez toujours faire ceci ...

Bien sûr, cela fonctionne toujours.

static class WidgetTestColorIsRed implements Predicate<Widget> {
    public boolean test(Widget w) {
        return w.isColorRed();
    }
}
static class WidgetToWeightFunction implements ToIntFunction<Widget> {
    public int applyAsInt(Widget w) {
        return w.getWeight();
    }
}
final int sum = widgets.stream()
                .filter(new WidgetTestColorIsRed())
                .mapToInt(new WidgetToWeightFunction())
                .sum();

Eh bien, je ne pense pas qu'il y ait beaucoup de cas où vous pouvez simplement écrire.

Style Lambda

Il est extrêmement facile à lire.

Style Lambda.java


final int sum = widgets.stream()
                .filter(w -> w.isColorRed()) 
                .mapToInt(w -> w.getWeight())
                .sum(); 

Il existe plusieurs façons d'écrire un style lambda.

//Aucun argument
() -> "constant";

//1 argument
n -> n + 1; //Les parenthèses peuvent être omises
(n) -> 2 * n;

//2 arguments ou plus
(a, b) -> Math.sqrt(a * a + b * b);
(x, y, z) -> x * y * z;

//Plusieurs lignes
(a, b, c) -> {
     double s = (a + b + c) / 2;
     return Math.sqrt(s * (s - a) * (s - b) * (s - c));
}

Référence de la méthode

Je ne l'ai pas encore utilisé, mais si vous pouvez l'écrire avec une référence de méthode, il sera plus facile de voir le code si vous l'utilisez positivement **. C'est facile à lire car vous n'avez pas besoin de mettre une variable comme w et vous savez quel est le type d'élément Stream à ce stade.

Écrivez comme class :: method.

Référence de la méthode.java


final int sum = widgets.stream()
                .filter(Widget::isColorRed)
                .mapToInt(Widget::getWeight)
                .sum();

Les classes anonymes, les expressions lambda et les références de méthode sont tous des moyens d'instancier une interface fonctionnelle. Vous pouvez créer une instance de la même manière même si vous disposez d'une interface fonctionnelle que vous avez vous-même définie. Dans une interface fonctionnelle, nous savons que nous n'avons besoin d'implémenter qu'une seule méthode, donc même l'écriture sans aucune information de type, comme les expressions lambda, peut être déduite.

en conclusion

La prochaine fois, j'aimerais vous présenter l'utilisation spécifique de Stream.

référence

Recommended Posts