web-dev-qa-db-fra.com

Est-il possible de réutiliser un flux?

J'apprends les nouvelles fonctionnalités de Java 8 et, tout en expérimentant des flux (Java.util.stream.Stream) et des collecteurs, j'ai compris qu'un flux ne pouvait pas être utilisé deux fois.

Est-il possible de le réutiliser? 

44
Francisco

Si vous voulez avoir pour effet de réutiliser un flux, vous pouvez envelopper l'expression de flux dans un fournisseur et appeler myStreamSupplier.get() chaque fois que vous en souhaitez un nouveau. Par exemple:

Supplier<Stream<String>> sup = () -> someList.stream();
List<String> nonEmptyStrings = sup.get().filter(s -> !s.isEmpty()).collect(Collectors.toList());
Set<String> uniqueStrings = sup.get().collect(Collectors.toSet());
52
Hank D

De la documentation :

Un flux ne doit être exploité (en invoquant une opération de flux intermédiaire ou terminal) qu'une seule fois. 

Une implémentation de flux peut déclencher IllegalStateException si elle détecte que le flux est réutilisé.

Donc, la réponse est non, les flux ne doivent pas être réutilisés.

50
Vince Emigh

Comme d'autres l'ont dit, "non, vous ne pouvez pas". 

Mais il est utile de rappeler le nom pratique summaryStatistics() pour de nombreuses opérations de base:

Donc au lieu de:

List<Person> personList = getPersons();

personList.stream().mapToInt(p -> p.getAge()).average().getAsDouble();
personList.stream().mapToInt(p -> p.getAge()).min().getAsInt();
personList.stream().mapToInt(p -> p.getAge()).max().getAsInt();

Vous pouvez:

// Can also be DoubleSummaryStatistics from mapToDouble()

IntSummaryStatistics stats = personList.stream()
                                       .mapToInt(p-> p.getAge())
                                       .summaryStatistics();

stats.getAverage();
stats.getMin();
stats.getMax();
19
Andrejs

L'idée même du Stream est qu'il est unique. Cela vous permet de créer des sources non réentrables (par exemple, lire les lignes à partir de la connexion réseau) sans stockage intermédiaire. Si vous souhaitez cependant réutiliser le contenu du flux, vous pouvez le transférer dans la collection intermédiaire pour obtenir la "copie papier":

Stream<MyType> stream = // get the stream from somewhere
List<MyType> list = stream.collect(Collectors.toList()); // materialize the stream contents
list.stream().doSomething // create a new stream from the list
list.stream().doSomethingElse // create one more stream from the list

Si vous ne souhaitez pas matérialiser le flux, il est parfois possible de faire plusieurs choses avec le même flux en même temps. Par exemple, vous pouvez vous référer à this ou this question pour plus de détails.

4
Tagir Valeev

À bien y penser, cette volonté de "réutiliser" un flux n'est que la volonté de réaliser le résultat souhaité avec une opération in-line de Nice. Donc, en gros, ce dont nous parlons ici, c'est que pouvons-nous faire pour continuer le traitement après avoir écrit une opération de terminal?

1) si votre opération de terminal retourne une collection , le problème est résolu immédiatement, puisque chaque collection peut être reconvertie en flux (JDK 8).

List<Integer> l=Arrays.asList(5,10,14);
        l.stream()
            .filter(nth-> nth>5)
            .collect(Collectors.toList())
            .stream()
            .filter(nth-> nth%2==0).forEach(nth-> System.out.println(nth));

2) si vos opérations de terminal renvoient un optional , avec les améliorations apportées à JDK 9 à la classe Optional, vous pouvez transformer le résultat Optional en un flux et obtenir l’opération Nice en ligne souhaitée:

List<Integer> l=Arrays.asList(5,10,14);
        l.stream()
            .filter(nth-> nth>5)
            .findAny()
            .stream()
            .filter(nth-> nth%2==0).forEach(nth-> System.out.println(nth));

3) si votre opération de terminal renvoie quelque chose d'autre , je doute vraiment que vous deviez envisager un flux pour traiter ce résultat:

List<Integer> l=Arrays.asList(5,10,14);
        boolean allEven=l.stream()
            .filter(nth-> nth>5)
            .allMatch(nth-> nth%2==0);
        if(allEven){
            ...
        }
1
BabaNew

Comme d'autres l'ont noté, l'objet de flux lui-même ne peut pas être réutilisé.

Mais une façon d'obtenir l'effet de la réutilisation d'un flux consiste à extraire le code de création du flux dans une fonction .

Vous pouvez le faire en créant une méthode ou un objet fonction contenant le code de création de flux. Vous pouvez ensuite l'utiliser plusieurs fois.

Exemple:

public static void main(String[] args) {
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);

    // The normal way to use a stream:
    List<String> result1 = list.stream()
        .filter(i -> i % 2 == 1)
        .map(i -> i * i)
        .limit(10)
        .map(i -> "i :" + i)
        .collect(toList());

    // The stream operation can be extracted to a local function to
    // be reused on multiple sources:
    Function<List<Integer>, List<String>> listOperation = l -> l.stream()
        .filter(i -> i % 2 == 1)
        .map(i -> i * i)
        .limit(10)
        .map(i -> "i :" + i)
        .collect(toList());

    List<String> result2 = listOperation.apply(list);
    List<String> result3 = listOperation.apply(Arrays.asList(1, 2, 3));

    // Or the stream operation can be extracted to a static method,
    // if it doesn't refer to any local variables:
    List<String> result4 = streamMethod(list);

    // The stream operation can also have Stream as argument and return value,
    // so that it can be used as a component of a longer stream pipeline:
    Function<Stream<Integer>, Stream<String>> streamOperation = s -> s
        .filter(i -> i % 2 == 1)
        .map(i -> i * i)
        .limit(10)
        .map(i -> "i :" + i);

    List<String> result5 = streamOperation.apply(list.stream().map(i -> i * 2))
        .filter(s -> s.length() < 7)
        .sorted()
        .collect(toCollection(LinkedList::new));
}

public static List<String> streamMethod(List<Integer> l) {
    return l.stream()
        .filter(i -> i % 2 == 1)
        .map(i -> i * i)
        .limit(10)
        .map(i -> "i :" + i)
        .collect(toList());
}

Si, par contre, vous avez déjà un objet de flux que vous souhaitez itérer plusieurs fois, vous devez enregistrer le contenu du flux dans un objet de collection.

Vous pouvez ensuite obtenir plusieurs flux avec le même contenu que ceux de la collection.

Exemple:

public void test(Stream<Integer> stream) {
    // Create a copy of the stream elements
    List<Integer> streamCopy = stream.collect(toList());

    // Use the copy to get multiple streams
    List<Integer> result1 = streamCopy.stream() ...
    List<Integer> result2 = streamCopy.stream() ...
}
1
Lii

La Bibliothèque fonctionnelle Java fournit ses propres flux qui font ce que vous demandez, c’est-à-dire qu’ils sont mémorisés et paresseux. Vous pouvez utiliser ses méthodes de conversion pour convertir des objets Java SDK en objets FJ, par exemple. Java8.JavaStream_Stream(stream) retournera un flux FJ réutilisable à partir d'un flux JDK 8.

0
Bill Burdick