web-dev-qa-db-fra.com

Ajout de deux flux Java 8 ou d'un élément supplémentaire à un flux

Je peux ajouter des flux ou des éléments supplémentaires, comme ceci:

Stream stream = Stream.concat(stream1, Stream.concat(stream2, Stream.of(element));

Et je peux ajouter de nouvelles choses au fur et à mesure, comme ceci:

Stream stream = Stream.concat(
                       Stream.concat(
                              stream1.filter(x -> x!=0), stream2)
                              .filter(x -> x!=1),
                                  Stream.of(element))
                                  .filter(x -> x!=2);

Mais c’est moche, parce que concat est statique. Si concat était une méthode d'instance, les exemples ci-dessus seraient beaucoup plus faciles à lire:

 Stream stream = stream1.concat(stream2).concat(element);

Et

 Stream stream = stream1
                 .filter(x -> x!=0)
                 .concat(stream2)
                 .filter(x -> x!=1)
                 .concat(element)
                 .filter(x -> x!=2);

Ma question est:

1) Y a-t-il une bonne raison pour laquelle concat est statique? Ou y a-t-il une méthode d'instance équivalente qui me manque?

2) Dans tous les cas, y a-t-il une meilleure façon de le faire?

151
MarcG

Si vous ajoutez static imports pour Stream.concat et Stream.of, le premier exemple pourrait être écrit comme suit:

Stream<Foo> stream = concat(stream1, concat(stream2, of(element)));

L'importation de méthodes static avec des noms génériques peut générer un code difficile à lire et à gérer (pollution de noms namespace). Il serait donc préférable de créer vos propres méthodes static avec des noms plus significatifs. Cependant, pour la démonstration je vais rester avec ce nom.

public static <T> Stream<T> concat(Stream<? extends T> lhs, Stream<? extends T> rhs) {
    return Stream.concat(lhs, rhs);
}
public static <T> Stream<T> concat(Stream<? extends T> lhs, T rhs) {
    return Stream.concat(lhs, Stream.of(rhs));
}

Avec ces deux méthodes statiques (éventuellement en combinaison avec des importations statiques), les deux exemples pourraient être écrits comme suit:

Stream<Foo> stream = concat(stream1, concat(stream2, element));

Stream<Foo> stream = concat(
                         concat(stream1.filter(x -> x!=0), stream2).filter(x -> x!=1),
                         element)
                     .filter(x -> x!=2);

Le code est maintenant beaucoup plus court. Cependant, je conviens que la lisibilité ne s’est pas améliorée. J'ai donc une autre solution.


Dans de nombreuses situations, Collectors peut être utilisé pour étendre la fonctionnalité des flux. Avec les deux Collectors en bas, les deux exemples pourraient être écrits comme suit:

Stream<Foo> stream = stream1.collect(concat(stream2)).collect(concat(element));

Stream<Foo> stream = stream1
                     .filter(x -> x!=0)
                     .collect(concat(stream2))
                     .filter(x -> x!=1)
                     .collect(concat(element))
                     .filter(x -> x!=2);

La seule différence entre la syntaxe souhaitée et la syntaxe ci-dessus est qu'il faut remplacer concat (...) par collect (concat (...)). Les deux méthodes statiques peuvent être implémentées comme suit (éventuellement utilisées en combinaison avec des importations statiques):

private static <T,A,R,S> Collector<T,?,S> combine(Collector<T,A,R> collector, Function<? super R, ? extends S> function) {
    return Collector.of(
        collector.supplier(),
        collector.accumulator(),
        collector.combiner(),
        collector.finisher().andThen(function));
}
public static <T> Collector<T,?,Stream<T>> concat(Stream<? extends T> other) {
    return combine(Collectors.toList(),
        list -> Stream.concat(list.stream(), other));
}
public static <T> Collector<T,?,Stream<T>> concat(T element) {
    return concat(Stream.of(element));
}

Bien entendu, cette solution présente un inconvénient qui mérite d’être mentionné. collect est une opération finale qui utilise tous les éléments du flux. De plus, le collecteur concat crée un intermédiaire ArrayList chaque fois qu'il est utilisé dans la chaîne. Les deux opérations peuvent avoir un impact significatif sur le comportement de votre programme. Cependant, si readability est plus important que performance, cela pourrait quand même être une approche très utile.

120
nosid

Malheureusement, cette réponse n'a probablement que peu, voire aucune aide, mais j'ai effectué une analyse judiciaire de la liste de diffusion Java Lambda pour voir si je pouvais trouver la cause de cette conception. C'est ce que j'ai découvert.

Au début il y avait une méthode d'instance pour Stream.concat (Stream)

Dans la liste de diffusion, je vois clairement que la méthode a été implémentée à l'origine en tant que méthode d'instance, comme vous pouvez le lire dans ce fil de discussion de Paul Sandoz, à propos de l'opération concat.

Dans ce document, ils discutent des problèmes pouvant résulter des cas dans lesquels le flux pourrait être infini et de ce que la concaténation signifierait dans ces cas, mais je ne pense pas que ce soit la raison de la modification.

Vous voyez dans cet autre fil que certains des premiers utilisateurs du JDK 8 ont été interrogés sur le comportement de la méthode d'instance concat lorsqu'elle est utilisée avec des arguments nuls.

Ce autre fil révèle cependant que la conception de la méthode concat était en discussion.

Refactorisé dans Streams.concat (Stream, Stream)

Mais sans aucune explication, les méthodes ont été soudainement modifiées en méthodes statiques, comme vous pouvez le voir dans ce fil sur la combinaison de flux . C’est peut-être le seul fil de discussion qui jette un peu de lumière sur ce changement, mais ce n’était pas assez clair pour que je puisse déterminer la raison du refactoring. Mais nous pouvons voir qu'ils ont fait un commit dans lequel ils ont suggéré de déplacer la méthode concat de Stream vers la classe auxiliaire Streams

Refactored to Stream.concat (Stream, Stream)

Plus tard, il a de nouveau été déplacé de Streams à Stream, mais encore une fois, aucune explication à cela.

Donc, en bout de ligne, la raison de la conception n'est pas tout à fait claire pour moi et je ne pouvais pas trouver une bonne explication. Je suppose que vous pouvez toujours poser la question dans la liste de diffusion.

Quelques alternatives pour la concaténation de flux

This autre fil de Michael Hixson discute/demande d'autres moyens de combiner/concaténer des flux

  1. Pour combiner deux flux, je devrais faire ceci:

    Stream.concat(s1, s2)
    

    pas ça:

    Stream.of(s1, s2).flatMap(x -> x)
    

    ... droite?

  2. Pour combiner plus de deux flux, je devrais faire ceci:

    Stream.of(s1, s2, s3, ...).flatMap(x -> x)
    

    pas ça:

    Stream.of(s1, s2, s3, ...).reduce(Stream.empty(), Stream::concat)
    

    ... droite?

155
Edwin Dalorzo

My StreamEx library étend les fonctionnalités de Stream API. En particulier, il propose des méthodes comme append et prepend qui résolvent ce problème (en interne, elles utilisent concat). Ces méthodes peuvent accepter un autre flux ou collection ou un tableau varargs. En utilisant ma bibliothèque, votre problème peut être résolu de cette manière (notez que x != 0 est étrange pour un flux non primitif):

Stream<Integer> stream = StreamEx.of(stream1)
             .filter(x -> !x.equals(0))
             .append(stream2)
             .filter(x -> !x.equals(1))
             .append(element)
             .filter(x -> !x.equals(2));

En passant, il existe également un raccourci pour votre opération filter:

Stream<Integer> stream = StreamEx.of(stream1).without(0)
                                 .append(stream2).without(1)
                                 .append(element).without(2);
12
Tagir Valeev

Il suffit de faire:

Stream.of(stream1, stream2, Stream.of(element)).flatMap(identity());

identity() est une importation statique de Function.identity().

Concaténer plusieurs flux en un seul flux revient à aplatir un flux.

Cependant, malheureusement, pour une raison quelconque, il n'y a pas de méthode flatten() sur Stream, vous devez donc utiliser flatMap() avec la fonction d'identité.

9
herman

En fin de compte, je ne suis pas intéressé par la combinaison de flux, mais par l’obtention du résultat combiné du traitement de chaque élément de tous ces flux.

Bien que la combinaison de flux puisse s'avérer fastidieuse (d'où ce fil), la combinaison des résultats de traitement est équitable.

La solution consiste à créer votre propre collecteur et à vous assurer que la fonction fournisseur du nouveau collecteur renvoie la même collection à chaque fois (et non une nouvelle). Le code ci-dessous illustre cette approche.

package scratchpad;

import Java.util.ArrayList;
import Java.util.Arrays;
import Java.util.List;
import Java.util.stream.Collector;
import Java.util.stream.Stream;

public class CombineStreams {
    public CombineStreams() {
        super();
    }

    public static void main(String[] args) {
        List<String> resultList = new ArrayList<>();
        Collector<String, List<String>, List<String>> collector = Collector.of(
                () -> resultList,
                (list, item) -> {
                    list.add(item);
                },
                (llist, rlist) -> {
                    llist.addAll(rlist);
                    return llist;
                }
        );
        String searchString = "Wil";

        System.out.println("After processing first stream\n"
                + createFirstStream().filter(name -> name.contains(searchString)).collect(collector));
        System.out.println();

        System.out.println("After processing second stream\n"
                + createSecondStream().filter(name -> name.contains(searchString)).collect(collector));
        System.out.println();

        System.out.println("After processing third stream\n"
                + createThirdStream().filter(name -> name.contains(searchString)).collect(collector));
        System.out.println();

    }

    private static Stream<String> createFirstStream() {
        return Arrays.asList(
                "William Shakespeare",
                "Emily Dickinson",
                "H. P. Lovecraft",
                "Arthur Conan Doyle",
                "Leo Tolstoy",
                "Edgar Allan Poe",
                "Robert Ervin Howard",
                "Rabindranath Tagore",
                "Rudyard Kipling",
                "Seneca",
                "John Donne",
                "Sarah Williams",
                "Oscar Wilde",
                "Catullus",
                "Alfred Tennyson",
                "William Blake",
                "Charles Dickens",
                "John Keats",
                "Theodor Herzl"
        ).stream();
    }

    private static Stream<String> createSecondStream() {
        return Arrays.asList(
                "Percy Bysshe Shelley",
                "Ernest Hemingway",
                "Barack Obama",
                "Anton Chekhov",
                "Henry Wadsworth Longfellow",
                "Arthur Schopenhauer",
                "Jacob De Haas",
                "George Gordon Byron",
                "Jack London",
                "Robert Frost",
                "Abraham Lincoln",
                "O. Henry",
                "Ovid",
                "Robert Louis Stevenson",
                "John Masefield",
                "James Joyce",
                "Clark Ashton Smith",
                "Aristotle",
                "William Wordsworth",
                "Jane Austen"
        ).stream();
    }

    private static Stream<String> createThirdStream() {
        return Arrays.asList(
                "Niccolò Machiavelli",
                "Lewis Carroll",
                "Robert Burns",
                "Edgar Rice Burroughs",
                "Plato",
                "John Milton",
                "Ralph Waldo Emerson",
                "Margaret Thatcher",
                "Sylvie d'Avigdor",
                "Marcus Tullius Cicero",
                "Banjo Paterson",
                "Woodrow Wilson",
                "Walt Whitman",
                "Theodore Roosevelt",
                "Agatha Christie",
                "Ambrose Bierce",
                "Nikola Tesla",
                "Franz Kafka"
        ).stream();
    }
}
1
Legna

Si cela ne vous dérange pas d'utiliser des bibliothèques tierces - cyclops-react a un type de flux étendu qui vous permettra de le faire via les opérateurs append/prepend.

Valeurs individuelles, tableaux, itérables, flux ou flux réactifs Les éditeurs peuvent être ajoutés et ajoutés au début sous forme de méthodes d'instance.

Stream stream = ReactiveSeq.of(1,2)
                           .filter(x -> x!=0)
                           .append(ReactiveSeq.of(3,4))
                           .filter(x -> x!=1)
                           .append(5)
                           .filter(x -> x!=2);

[Divulgation Je suis le développeur principal de cyclops-react]

1
John McClean

Vous pouvez utiliser la méthode Streams.concat(Stream<? extends T>... streams) de Guava, qui sera très courte avec les importations statiques:

Stream stream = concat(stream1, stream2, of(element));
1
Kunda

Que diriez-vous d'écrire votre propre méthode de concat?

public static Stream<T> concat(Stream<? extends T> a, 
                               Stream<? extends T> b, 
                               Stream<? extends T> args)
{
    Stream<T> concatenated = Stream.concat(a, b);
    for (Stream<T> stream : args)
    {
        concatenated = Stream.concat(concatenated, stream);
    }
    return concatenated;
}

Cela rend au moins votre premier exemple beaucoup plus lisible.

0
Felix S