Java flux `generate ()` comment « inclure » le premier élément « exclu »

Luca Abbati:

Supposons que ce scénario d'utilisation d'un flux Java, où les données sont ajoutées à partir d'une source de données. Source de données peut être une liste de valeurs, comme dans l'exemple ci-dessous, ou un api paginé REST. Peu importe, pour le moment.

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        final List<Boolean> dataSource = List.of(true, true, true, false, false, false, false);
        final AtomicInteger index = new AtomicInteger();
        Stream
            .generate(() -> {
                boolean value = dataSource.get(index.getAndIncrement());
                System.out.format("--> Executed expensive operation to retrieve data: %b\n", value);
                return value;
            })
            .takeWhile(value -> value == true)
            .forEach(data -> System.out.printf("--> Using: %b\n", data));
    }
}

Si vous exécutez ce code votre sortie sera

--> Executed expensive operation to retrieve data: true
--> Using: true
--> Executed expensive operation to retrieve data: true
--> Using: true
--> Executed expensive operation to retrieve data: true
--> Using: true
--> Executed expensive operation to retrieve data: false

Comme vous pouvez le voir le dernier élément, celui qui a évalué à false, ne sont ajoutés au flux, comme prévu.

Supposons maintenant que les generate()charges de la méthode des pages de données à partir d' un api REST. Dans ce cas , la valeur true/falseest une valeur à la page Nindiquant si la page N + 1existe, quelque chose comme un has_morechamp. Maintenant, je veux la dernière page retournée par l'API à ajouter au courant, mais je ne veux pas effectuer une autre opération coûteuse pour lire une page vide, parce que je sais déjà qu'il n'y a pas plus de pages.

Quelle est la manière la plus idiomatiques de le faire en utilisant l'API Java Stream? Chaque solution que je peux penser a besoin d'un appel à l'API à exécuter.


MISE À JOUR

En plus des approches énumérées dans Inclusive takeWhile () pour Streams il y a une autre façon laide pour y parvenir.

public static void main(String[] args) {
    final List<Boolean> dataSource = List.of(true, true, true, false, false, false, false);
    final AtomicInteger index = new AtomicInteger();
    final AtomicBoolean hasMore = new AtomicBoolean(true);
    Stream
        .generate(() -> {
            if (!hasMore.get()) {
                return null;
            }
            boolean value = dataSource.get(index.getAndIncrement());
            hasMore.set(value);
            System.out.format("--> Executed expensive operation to retrieve data: %b\n", value);
            return value;
        })
        .takeWhile(Objects::nonNull)
        .forEach(data -> System.out.printf("--> Using: %b\n", data));
}
Holger:

Vous utilisez le mauvais outil pour votre travail. Comme il a déjà noticable dans votre exemple de code, le Supplierpassé à Stream.generatedoit aller très loin pour maintenir l'indice dont il a besoin pour aller chercher pages.

Ce qui fait empirer les choses, est que Stream.generatecrée un non - ordonnée flux :

Renvoie un flux non ordonné séquentiel infini où chaque élément est généré par le fournisseur fourni. Ceci est approprié pour générer des flux de constantes, les flux d'éléments aléatoires, etc.

Vous n'êtes pas le retour des valeurs aléatoires ou constante ni rien d'autre qui serait indépendant de l'ordre.

Cela a un impact significatif sur la sémantiquetakeWhile :

Sinon, les rendements, si ce courant est non ordonnée, un courant constitué d'un sous-ensemble des éléments pris dans ce courant correspondant à l'attribut donné.

Cela est logique si on y pense. S'il y a au moins un élément rejeté par le prédicat, il pourrait être rencontré à une position arbitraire pour un non ordonné flux, donc un sous - ensemble arbitraire d'éléments rencontrés avant, y compris l'ensemble vide, serait un préfixe valide.

Mais puisqu'il n'y a pas « avant » ou « après » pour un flux non ordonnée, même des éléments produits par le générateur après le rejet on pourrait être inclus par le résultat.

Dans la pratique, il est rare de rencontrer de tels effets pour un flux séquentiel, mais il ne change pas le fait que Stream.generate(…) .takeWhile(…)sémantiquement mal pour votre tâche.


À partir de votre code d'exemple, je conclus que les pages ne contiennent pas leur propre numéro, ni une méthode « getNext », donc nous devons maintenir le nombre et l'état « hasNext » pour la création d'un cours d'eau.

En supposant un exemple de configuration comme

class Page {
    private String data;
    private boolean hasNext;

    public Page(String data, boolean hasNext) {
        this.data = data;
        this.hasNext = hasNext;
    }

    public String getData() {
        return data;
    }

    public boolean hasNext() {
        return hasNext;
    }

}
private static String[] SAMPLE_PAGES = { "foo", "bar", "baz" };
public static Page getPage(int index) {
    Objects.checkIndex(index, SAMPLE_PAGES.length);
    return new Page(SAMPLE_PAGES[index], index + 1 < SAMPLE_PAGES.length);
}

Vous pouvez mettre en place un flux correct comme

Stream.iterate(Map.entry(0, getPage(0)), Objects::nonNull,
        e -> e.getValue().hasNext()? Map.entry(e.getKey()+1, getPage(e.getKey()+1)): null)
    .map(Map.Entry::getValue)
    .forEach(page -> System.out.println(page.getData()));

Notez que Stream.iteratecrée un ordre courant :

Renvoie un flux séquentiel commandé produit par application itérative de la fonction suivante donnée à un élément initial, conditionnée à satisfaire le prédicat hasNext donné.

Bien sûr, les choses seraient beaucoup plus facile si la page a connu son propre numéro, par exemple

Stream.iterate(getPage(0), Objects::nonNull,
               p -> p.hasNext()? getPage(p.getPageNumber()+1): null)
    .forEach(page -> System.out.println(page.getData()));

ou s'il y avait une méthode pour obtenir à partir d'une page existante à la page suivante, par exemple

Stream.iterate(getPage(0), Objects::nonNull, p -> p.hasNext()? p.getNextPage(): null)
    .forEach(page -> System.out.println(page.getData()));

Je suppose que tu aimes

Origine http://43.154.161.224:23101/article/api/json?id=235661&siteId=1
conseillé
Classement