Java流`generate()`how to";包括「;第一个",;不包括;要素

Java流`generate()`how to";包括「;第一个",;不包括;要素,java,java-stream,Java,Java Stream,假设Java流的这种使用场景,其中数据是从数据源添加的。数据源可以是值列表,如下面的示例所示,也可以是分页的RESTAPI。现在没关系 import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Stream; public class Main { public static void main(String[] args) { fin

假设Java流的这种使用场景,其中数据是从数据源添加的。数据源可以是值列表,如下面的示例所示,也可以是分页的RESTAPI。现在没关系

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));
    }
}
正如您所看到的,最后一个元素(计算结果为
false
)未按预期添加到流中

现在假设
generate()
方法从RESTAPI加载数据页。在这种情况下,值
true/false
是第
N
页上的一个值,指示是否存在第
N+1
页,类似于
的内容有更多的
字段。现在,我希望将API返回的最后一个页面添加到流中,但我不希望执行另一个昂贵的操作来读取空页面,因为我已经知道没有更多页面了

使用Java流API实现这一点最惯用的方法是什么?我能想到的每一种解决方法都需要执行对API的调用


更新

除了表中列出的方法外,还有另一种丑陋的方法可以实现这一点

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));
}
publicstaticvoidmain(字符串[]args){
最终列表数据源=List.of(真、真、真、假、假、假、假);
最终AtomicInteger索引=新的AtomicInteger();
最终AtomicBoolean hasMore=新的AtomicBoolean(真);
流动
.生成(()->{
如果(!hasMore.get()){
返回null;
}
布尔值=dataSource.get(index.getAndIncrement());
hasMore.set(值);
System.out.format(“-->执行了昂贵的操作以检索数据:%b\n”,值);
返回值;
})
.takeWhile(对象::非空)
.forEach(数据->系统.out.printf(“-->使用:%b\n”,数据));
}

您在工作中使用了错误的工具。正如您的代码示例中已经注意到的那样,传递给的
供应商
必须竭尽全力维护获取页面所需的索引

更糟糕的是:

返回无限顺序无序流,其中每个元素由提供的供应商生成。 这适用于生成恒定流、随机元素流等

您不会返回常量或随机值,也不会返回与顺序无关的任何其他值

这对以下方面具有重大影响:

否则,如果该流是无序的,则返回一个由从该流中提取的与给定谓词匹配的元素子集组成的流

如果你仔细想想,这是有道理的。如果至少有一个元素被谓词拒绝,它可能会在无序流的任意位置遇到,因此在此之前遇到的任意元素子集(包括空集)将是有效的前缀

但是,由于无序流没有“之前”或“之后”,因此,即使生成器在被拒绝流之后生成的元素也可以包含在结果中

实际上,对于顺序流,您不太可能遇到这样的效果,但这不会改变以下事实:
stream.generate(…).takeWhile(…)
在语义上不适合您的任务


从您的示例代码中,我得出结论,页面不包含自己的编号,也不包含“getNext”方法,因此我们必须维护编号和“hasNext”状态以创建流

假设设置示例如下

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;
    }

}
请注意:

将给定的下一个函数迭代应用于初始元素时产生的顺序流, 条件是满足给定的hasNext谓词

当然,如果页面知道自己的编号,事情会简单得多,例如

Stream.iterate(getPage(0), Objects::nonNull,
               p -> p.hasNext()? getPage(p.getPageNumber()+1): null)
    .forEach(page -> System.out.println(page.getData()));
或者如果有一种方法可以从现有页面转到下一页面,例如

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

听起来您想要构建一个迭代器,它有两个方法:
hasNext
next
。你能描述一下你是如何计算出hasNext的吗?简单地实现
迭代器
接口更适合您的用例,您不需要真正的
。1:true、2:true、3:true、4:false->1:true、2:true、3:true、4:true、5:false(true/false表示当前页面而不是下一个页面),然后使用takeWhile来获得您想要的内容。如果将此短语转换为类似的for循环方法,如果说您正在寻找类似
boolean fetchNextPage=true的内容,是否正确;对于(int idx=0;idx)执行了昂贵的操作来检索数据:%b\n,value);if(!value){fetchNextPage=false;}System.out.printf(-->使用:%b\n,value)}
?谢谢大家!事实上,在@SamuelPhilipp发布答案之前,我没有找到答案。将此作为副本关闭。感谢您的澄清和扩展的答案。
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()));
Stream.iterate(getPage(0), Objects::nonNull,
               p -> p.hasNext()? getPage(p.getPageNumber()+1): null)
    .forEach(page -> System.out.println(page.getData()));
Stream.iterate(getPage(0), Objects::nonNull, p -> p.hasNext()? p.getNextPage(): null)
    .forEach(page -> System.out.println(page.getData()));