[JAVA] Explorons un peu plus l'API Stream, dont je comprends que c'est une réécriture.

Lors de l'écriture de l'API Stream de Java, qu'est-ce que cela signifie? Voici un petit résumé de ce que je me demandais. Ce n'est pas une collection de conseils sur l'API Stream, je suis donc désolé si vous avez lu l'article avec de telles attentes.

Résumé de cet article

Vous pouvez vous faire une petite idée de cet élément en lisant cet article.

Objectif de cet article

Apprenez les éléments grammaticaux de l'API Stream que vous comprenez d'une manière ou d'une autre.

Quelle est la manière d'écrire l'API Stream en premier lieu?

La façon d'écrire l'API Stream dans le cas d'un traitement tel que l'affichage uniquement des nombres pairs de 1 à 10 est la suivante.

Arrays.stream(new int[]{1,2,3,4,5,6,7,8,9,10})
     .filter( i -> (i % 2) == 0 )
     .forEach( i -> System.out.println(i));

(Il existe également une forme cool en utilisant IntStream.range, mais laissez-le tranquille ...) À propos, la manière conventionnelle d'écrire des instructions for and if est la suivante.

for(int i : new int[]{1,2,3,4,5,6,7,8,10}) {
     if((i % 2) == 0) {
         System.out.println(i);
     }
}

Avec un processus aussi court, le nombre de lignes est le même et le même aspect. Cependant, si cela devient un processus compliqué, si vous vous souvenez des méthodes telles que filter et forEach of Stream API, Je pense personnellement que ce sera plus facile à voir et à comprendre car il est clair où et ce que vous faites. Mais quand vous le regardez soudainement, quel est cet argument? Quelle est la différence entre filter et for Each? Parce que ça devient J'ai essayé de résumer un peu.

Interface fonctionnelle

La partie de l'argument que vous avez vue dans la source ci-dessus ne vous est pas familière pour la première fois.

filter( i -> (i % 2) == 0 )

En fait, c'est une façon d'écrire qui prend une interface fonctionnelle comme argument, et qui est abrégée en une expression lambda. Je pense que ceux qui travaillent principalement avant java7 le verront pour la première fois. Dans java8 et versions ultérieures, celles qui remplissent les conditions de l'interface de type de fonction sont des expressions lambda qui définissent directement le traitement de la fonction en tant qu'argument. Vous pouvez l'écrire comme ça.

Quelle est la définition d'une interface fonctionnelle?

En gros, c'est une interface dans laquelle une seule méthode abstraite est définie. Un exemple est comme ça.

@FunctionalInterface
public interface Predicate<T> {
     boolean test(T t);
}

Comme vous pouvez le voir, ce n'est qu'une interface. Il est important qu'il n'y ait qu'une seule méthode abstraite, et elle peut être écrite comme une expression lambda qui décrit uniquement le traitement sous cette forme. (Peu importe si la méthode par défaut est écrite.) À propos, Functional Interface indique uniquement explicitement qu'elle peut être utilisée avec des interfaces fonctionnelles. Ce n'est pas significatif du point de vue du processus.

Définir une interface fonctionnelle comme argument avec une expression lambda

Si vous faites référence à la méthode de filtrage, le filtre reçoit l'interface Predicate en tant qu'argument.

  Stream<T> filter(Predicate<? super T> predicate);

Dans le code source ci-dessus, il a été écrit comme ceci.

filter( i -> (i % 2) == 0 )

La méthode abstraite de Predicate est la méthode de test. Donc, spécifiez simplement le processus de réception d'un argument et de retour du booléen comme valeur de retour dans l'expression lambda. (En fait, l'exemple ci-dessus est celui-là.) Cependant, même s'il est dit que le style d'écriture ci-dessus est le style lambda, qu'est-ce que le style lambda? ?? ?? Ce sera Jetons un coup d'œil à la grammaire.

Style Lambda

La grammaire de base d'une expression lambda ressemble à ceci.

(argument) -> {En traitement}

C'est comme définir un argument sur le côté gauche de la flèche et écrire le processus sur le côté droit. En d'autres termes, si vous regardez l'expression lambda du filtre que j'ai écrit cette fois sans l'omettre, cela ressemble à ceci.

filter( (int  i) -> {return (i % 2) == 0;} )

Même si vous êtes nouveau dans ce style d'écriture, vous pourrez peut-être porter un jugement. Dans l'exemple en haut, j'ai simplement omis tout ce qui peut omettre le type d'argument et ainsi de suite. (Je pense que la règle d'omettre l'expression lambda sera à nouveau soulevée dans l'article.)

En termes simples, le type d'argument et () sont omis, et l'instruction de retour et {} sont omis. Tous sont conditionnels, mais si vous les omettez, les perspectives s'amélioreront. Vous pouvez le comprendre en étudiant la grammaire, donc je pense que c'est une bonne idée d'omettre les parties qui peuvent être omises.

Types d'interfaces fonctionnelles

Différentes interfaces fonctionnelles sont définies en standard. (À propos, Effective Java ne recommande pas de créer votre propre interface fonctionnelle, donc Lorsque vous l'utilisez, essayez d'utiliser le standard. ) Cependant, il peut être divisé en quatre systèmes. Après cela, il est dédié aux choses int, longues et primitives, et il a deux arguments. C'est une bonne idée de se souvenir des bases et de les rechercher au cas par cas. (Je ne pouvais pas le faire dans ma tête car il y en a tellement ...)

Pour l'instant, rappelons-nous l'existence et les méthodes du fournisseur, du consommateur, du prédicat et de la fonction de base.

//Interface fournisseur
//Il définit une méthode get qui ne reçoit pas d'argument et renvoie une valeur.
@FunctionalInterface
public interface Supplier<T> {
    T get();
}

//Interface consommateur
//Définit une méthode d'acceptation qui prend un argument et ne renvoie pas de valeur de retour.
@FunctionalInterface
public interface Consumer<T> {
     void accept(T t);
}

//Interface de prédicat
//Il définit une méthode de test qui prend un argument et renvoie un booléen.
@FunctionalInterface
public interface Predicate<T> {
     boolean test(T t);
};

//Interface de fonction
//Il définit une méthode apply qui prend un argument de n'importe quel type et renvoie une valeur de retour de n'importe quel type.
@FunctionalInterface
public interface Function<T, R> {
     R apply(T t);
}

Au fait, si vous voulez voir toutes sortes de choses, il est recommandé de vérifier le package "java.util.function".

Résumé des interfaces fonctionnelles

Traitement intermédiaire et traitement de résiliation

Le flux de traitement de l'API Stream est principalement divisé en trois flux.

  1. Génération de flux Cette fois également, Arrays.stream () est utilisé pour générer le flux. Cela crée juste un flux, donc je n'entrerai pas dans les détails.
  2. Traitement intermédiaire Dans ce cas, le filtre est applicable. Le traitement intermédiaire peut être effectué plusieurs fois dans un seul flux.
  3. Processus de résiliation Dans ce cas, pour Each s'applique. La terminaison ne peut être appelée qu'une seule fois dans un flux.

Le flux de traitement de Stream API est génération → traitement intermédiaire → traitement de terminaison. Mais pourquoi le processus intermédiaire peut-il être exécuté plusieurs fois et la méthode de terminaison une seule fois? Cette quantité d'informations provenant d'explications courantes peut prêter à confusion.

Traitement intermédiaire séparé et traitement de résiliation

Pour être honnête, il est difficile de se souvenir et de distinguer uniquement les méthodes de traitement intermédiaire et de traitement de résiliation. (De combien devrais-je m'en souvenir ...) Il est plus important de comprendre le flux du traitement intermédiaire et de terminaison que de se souvenir de la méthode.

Il apparaît plusieurs fois, mais à nouveau le même code source.

Arrays.stream(new int[]{1,2,3,4,5,6,7,8,9,10})
     .filter( i -> (i % 2) == 0 )
     .forEach( i -> System.out.println(i));

L'important ici est que vous pouvez appeler la méthode forEach après la méthode filter. Au fait, ça ne marche pas même si je l'écris comme ça.

//Mauvais exemple(Ou plutôt, une erreur de compilation)
Arrays.stream(new int[]{1,2,3,4,5,6,7,8,9,10})
     .forEach( i -> System.out.println(i));
     .filter( i -> (i % 2) == 0 )

En fait, la raison pour laquelle la méthode forEach peut être appelée après la méthode filter peut être comprise directement en regardant l'interface Stream.

Traitement intermédiaire et traitement de terminaison compris par l'interface Stream

Le code source ci-dessous est un extrait de la méthode abstraite de l'interface Stream.

public interface Stream<T> extends BaseStream<T, Stream<T>> {
     //La valeur de retour est Stream
     Stream<T> filter(Predicate<? super T> predicate);
     <R> Stream<R> map(Function<? super T, ? extends R> mapper);
     IntStream mapToInt(ToIntFunction<? super T> mapper);
     LongStream mapToLong(ToLongFunction<? super T> mapper);
     DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper);
     <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>>
mapper);
     IntStream flatMapToInt(Function<? super T, ? extends IntStream> mapper);
     LongStream flatMapToLong(Function<? super T, ? extends LongStream>
mapper);
     DoubleStream flatMapToDouble(Function<? super T, ? extends DoubleStream>
mapper);
     Stream<T> distinct();
     Stream<T> sorted();
     Stream<T> sorted(Comparator<? super T> comparator);
     Stream<T> peek(Consumer<? super T> consumer);
     Stream<T> limit(long maxSize);
     Stream<T> substream(long startInclusive);
     Stream<T> substream(long startInclusive, long endExclusive);

     //Diverses valeurs de retour
     void forEach(Consumer<? super T> action);
     void forEachOrdered(Consumer<? super T> action);
     Object[] toArray();
     <A> A[] toArray(IntFunction<A[]> generator);
     T reduce(T identity, BinaryOperator<T> accumulator);
     Optional<T> reduce(BinaryOperator<T> accumulator);
     <U> U reduce(U identity,
                  BiFunction<U, ? super T, U> accumulator,
                  BinaryOperator<U> combiner);
     <R> R collect(Supplier<R> resultFactory,
                   BiConsumer<R, ? super T> accumulator,
                   BiConsumer<R, R> combiner);
     <R> R collect(Collector<? super T, R> collector);
     Optional<T> min(Comparator<? super T> comparator);
     Optional<T> max(Comparator<? super T> comparator);
     long count();
     boolean anyMatch(Predicate<? super T> predicate);
     boolean allMatch(Predicate<? super T> predicate);
     boolean noneMatch(Predicate<? super T> predicate);
     Optional<T> findFirst();
     Optional<T> findAny();
}

Si vous regardez de près, la méthode de filtrage utilise Stream comme valeur de retour. Et forEach n'a pas de valeur de retour, non?

Cette! (Laquelle)

Ceci est une interface, mais si vous regardez la classe d'implémentation "Reference Pipeline" Les méthodes intermédiaires telles que la méthode de filtre créent un nouveau Stream dans la méthode et le définissent comme valeur de retour. Puisque Stream est défini comme valeur de retour, la méthode Stream peut être appelée en continu. Au contraire, comme vous pouvez le voir à partir de la valeur de retour de la méthode, la méthode de terminal telle que forEach ne définit pas Stream comme valeur de retour. Vous ne pouvez pas appeler la méthode Stream successivement.

Si vous comprenez cela, les méthodes intermédiaires et les méthodes de terminaison sont très faciles. La méthode qui retourne Stream est le processus intermédiaire On peut comprendre à peu près comme un traitement de fin que Stream n'est pas utilisé comme valeur de retour.

Résumé du traitement intermédiaire et du traitement de résiliation

Résumé

Si vous comprenez, l'API Stream ne fait pas peur.

TODO

Recommended Posts

Explorons un peu plus l'API Stream, dont je comprends que c'est une réécriture.
[Swift, un incontournable pour les débutants! ] Comprenons la mise en œuvre du traitement des communications de l'API Web
Java: dont le problème est plus rapide, en flux ou en boucle
Examinons la signification de "stream" et "collect" dans l'API Stream de Java.
[java8] Pour comprendre l'API Stream
Je voulais écrire un processus équivalent à une instruction while avec l'API Java 8 Stream
J'ai essayé de résumer l'API Stream
Kanban n'est pas la seule solution, utilisons un peu plus Microsoft Project