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

168

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, car concatc'est statique. Siconcat s'agissait d'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 procéder?

MarcG
la source
4
On dirait que les choses n'ont pas toujours été comme ça , mais je ne trouve tout simplement pas la raison.
Edwin Dalorzo

Réponses:

126

Si vous ajoutez des importations statiques pour Stream.concat et Stream.of , le premier exemple peut être écrit comme suit:

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

L'importation de méthodes statiques avec des noms génériques peut entraîner un code qui devient difficile à lire et à maintenir ( pollution de l'espace de noms ). Il serait donc préférable de créer vos propres méthodes statiques avec des noms plus significatifs. Cependant, pour la démonstration, je m'en tiendrai à 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 désormais nettement plus court. Cependant, je reconnais que la lisibilité ne s'est pas améliorée. J'ai donc une autre solution.


Dans de nombreuses situations, les collecteurs peuvent être utilisés pour étendre les fonctionnalités des flux. Avec les deux collecteurs 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 que vous devez 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 qu'il convient de mentionner. collect est une opération finale qui consomme tous les éléments du flux. En plus de cela, le collecteur concat crée une ArrayList intermédiaire à 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 la lisibilité est plus importante que les performances , cela peut toujours être une approche très utile.

nosid
la source
1
Je ne trouve pas le concatcollectionneur très lisible. Il semble étrange d'avoir une méthode statique à paramètre unique nommée comme ceci, et également de l'utiliser collectpour la concaténation.
Didier L
@nosid, peut-être une question légèrement orthogonale à ce fil, mais pourquoi prétendez-vous It's a bad idea to import static methods with names? Je suis vraiment intéressé - je trouve que cela rend le code plus concis et plus lisible et beaucoup de gens à qui j'avais demandé pensaient la même chose. Voulez-vous donner quelques exemples pourquoi c'est généralement mauvais?
quantum
1
@Quantum: Quelle est la signification de compare(reverse(getType(42)), of(6 * 9).hashCode())? Notez que je n'ai pas dit que les importations statiques sont une mauvaise idée, mais que les importations statiques pour des noms génériques comme ofet concatsont.
nosid
1
@nosid: Le survol de chaque déclaration dans un IDE moderne ne révélera-t-il pas rapidement la signification? Quoi qu'il en soit, je pense que cela pourrait être au mieux une déclaration de préférence personnelle, car je ne vois toujours aucune raison technique pour laquelle les importations statiques pour les noms "génériques" sont mauvaises - à moins que vous n'utilisiez Notepad ou VI (M) pour la programmation, auquel cas vous avez de plus gros problèmes.
quantum
Je ne vais pas dire que le SDK Scala est meilleur, mais ... oups je l'ai dit.
eirirlar
165

Malheureusement, cette réponse n'aide probablement que peu ou pas du tout, mais j'ai fait une analyse médico-légale 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 peux clairement voir que la méthode a été implémentée à l'origine comme 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 qui pourraient 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 de discussion que certains premiers utilisateurs du JDK 8 se sont interrogés sur le comportement de la méthode d'instance concat lorsqu'elle est utilisée avec des arguments nuls.

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

Refactorisé en Streams.concat (Stream, Stream)

Mais sans aucune explication, tout à coup, les méthodes ont été changé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 concatméthode hors Streamet dans la classe d'assistance Streams.

Refactorisé en Stream.concat (Stream, Stream)

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

Donc, en fin de compte, la raison de la conception n'est pas tout à fait claire pour moi et je n'ai pas pu 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

Cet autre fil de discussion de Michael Hixson discute / pose des questions sur d'autres façons 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?

Edwin Dalorzo
la source
6
+1 Belle recherche. Et je vais l'utiliser comme mon Stream.concat en prenant des varargs:public static <T> Stream<T> concat(Stream<T>... streams) { return Stream.of(streams).reduce(Stream.empty(), Stream::concat);}
MarcG
1
Aujourd'hui, j'ai écrit ma propre version concat, et juste après, je finance ce sujet. La signature est légèrement différente, mais grâce à cela, elle est plus générique;) par exemple, vous pouvez fusionner Stream <Integer> et Stream <Double> avec Stream <Number>. @SafeVarargs private static <T> Stream<T> concat(Stream<? extends T>... streams) { return Stream.of(streams).reduce(Stream.empty(),Stream::concat).map(Function.identity());}
kant le
@kant Pourquoi avez-vous besoin d'une Function.identity()carte? Après tout, il renvoie le même argument qu'il reçoit. Cela ne devrait avoir aucun effet sur le flux résultant. Est-ce que je manque quelque chose?
Edwin Dalorzo le
1
Avez-vous essayé de le saisir dans votre IDE? Sans .map (identity ()), vous obtiendrez une erreur de compilation. Je veux retourner Stream <T> mais instruction: return Stream.of(streams).reduce(Stream.empty(),Stream::concat)renvoie Stream <? étend T>. (Quelque chose <T> est le sous-type de Quelque chose <? étend T>, pas l'inverse, donc il ne peut pas être casté) Cast supplémentaire .map(identity())<? étend T> à <T>. Cela se produit grâce au mélange de java 8 'types de cible' d'arguments de méthode et de types de retour et de signature de la méthode map (). En fait, c'est Function. <T> identity ().
kant le
1
@kant Je ne vois pas grand chose à faire ? extends T, car vous pouvez utiliser la conversion de capture . En tout cas, voici mon extrait de code général Continuons la discussion dans le Gist.
Edwin Dalorzo le
12

Ma bibliothèque StreamEx étend les fonctionnalités de l'API Stream. En particulier, il propose des méthodes telles que l' ajout et le préfixe qui résolvent ce problème (ils utilisent en interne concat). Ces méthodes peuvent accepter un autre flux ou une autre collection ou un tableau de varargs. En utilisant ma bibliothèque, votre problème peut être résolu de cette façon (notez que cela x != 0semble é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));

Au fait, il existe également un raccourci pour votre filteropération:

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

Faites simplement:

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 équivaut à aplatir un flux.

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

Herman
la source
1

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 d'ajout / d'avance.

Des valeurs individuelles, des tableaux, des itérables, des flux ou des flux réactifs Les éditeurs peuvent être ajoutés et ajoutés en tant que 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 principal développeur de cyclops-react]

John McClean
la source
1

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 (donc ce thread), la combinaison de leurs résultats de traitement est assez facile.

La clé à résoudre est de créer votre propre collecteur et de vous assurer que la fonction fournisseur du nouveau collecteur renvoie la même collection à chaque fois ( pas 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();
    }
}
Legna
la source
0

Que diriez-vous d'écrire votre propre méthode 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.

Félix S
la source
1
Soyez prudent lors de la construction de flux à partir d'une concaténation répétée. L'accès à un élément d'un flux profondément concaténé peut entraîner des chaînes d'appels profondes, voire StackOverflowError.
Legna