结束"无限"在满足某些条件时流

时间:2018-04-23 09:59:50

标签: java java-stream

我试图从REST风格的Web服务中提取数据,该服务在页面中提供内容。

我能够知道我到达终点的唯一方法是当我要求页面时没有结果。我想在那时终止流。

我编写了以下Java代码。第一个函数从Web服务中提取单个页面并将其作为流返回。第二个函数将流平面映射为单个流。

public Stream<ApplicationResponse> getApplications(String token, RestTemplate rt, Integer page, Integer pageSize) {
    HttpEntity<String> entity = new HttpEntity<>("parameters", getHeaders(token));
    String url = String.format("%s?PageIndex=%s&PageSize=%s", endpoint, page, pageSize);
    ResponseEntity<ApplicationCollection> ar = rt.exchange(url, HttpMethod.GET, entity, ApplicationCollection.class);
    ApplicationResponse[] res = Objects.requireNonNull(ar.getBody()).getData();

    // Do something here when res is empty, so that the stream ends

    return Arrays.stream(res);
}

public Stream<ApplicationResponse> getApplications(String token, RestTemplate rt) {
    // This function does the right thing, exept when we run out of data!
    return IntStream.iterate(1, i -> i + 1).mapToObj(i -> getApplications(token, rt, i, 500)).flatMap(Function.identity());
}

问题是,如何让它结束?

如果我用Python编写这个,我会在我知道的地方引发一个StopIteration异常,没有什么可以放到流上。有什么类似我可以做的吗?

我能想到的最好的事情是使用null,或者引发异常以表示数据的结束,然后将流包装到Iterator中,该Iterator知道在收到该信号时停止。但是,我能做些什么吗?

1 个答案:

答案 0 :(得分:2)

在Holger的评论之后,我试了一下,试了Spliterator而不是Iterator。这确实比较简单,因为nexthasNext ......有点合并到tryAdvance?它甚至足够短,只需将其内联到一个util方法,imo。

public static Stream<ApplicationResponse> getApplications(String token, RestTemplate rt)
{
    return StreamSupport.stream(new AbstractSpliterator<ApplicationResponse[]>(Long.MAX_VALUE,
                                                                               Spliterator.ORDERED
                                                                                               | Spliterator.IMMUTABLE)
    {
        private int page = 1;

        @Override
        public boolean tryAdvance(Consumer<? super ApplicationResponse[]> action)
        {
            HttpEntity<String> entity = new HttpEntity<>("parameters", getHeaders(token));
            String url = String.format("%s?PageIndex=%s&PageSize=%s", endpoint, page, 500);
            ResponseEntity<ApplicationCollection> ar = rt.exchange(url, HttpMethod.GET, entity,
                                                                   ApplicationCollection.class);
            ApplicationResponse[] res = Objects.requireNonNull(ar.getBody()).getData();

            if (res.length == 0)
                return false;

            page++;
            action.accept(res);
            return true;
        }
    }, false).flatMap(Arrays::stream);
}

您可以实现Iterator并创建它的流:

public class ResponseIterator
    implements Iterator<Stream<ApplicationResponse>>
{
    private int                   page = 1;
    private String                token;
    private RestTemplate          rt;

    private ApplicationResponse[] next;

    private ResponseIterator(String token, RestTemplate rt)
    {
        this.token = token;
        this.rt = rt;
    }

    public static Stream<ApplicationResponse> getApplications(String token, RestTemplate rt)
    {
        Iterable<Stream<ApplicationResponse>> iterable = () -> new ResponseIterator(token, rt);
        return StreamSupport.stream(iterable.spliterator(), false).flatMap(Function.identity());
    }

    @Override
    public boolean hasNext()
    {
        if (next == null)
        {
            next = getNext();
        }
        return next.length != 0;
    }

    @Override
    public Stream<ApplicationResponse> next()
    {
        if (next == null)
        {
            next = getNext();
        }
        Stream<ApplicationResponse> nextStream = Arrays.stream(next);
        next = getNext();
        return nextStream;
    }

    private ApplicationResponse[] getNext()
    {
        HttpEntity<String> entity = new HttpEntity<>("parameters", getHeaders(token));
        String url = String.format("%s?PageIndex=%s&PageSize=%s", endpoint, page, 500);
        ResponseEntity<ApplicationCollection> ar = rt.exchange(url, HttpMethod.GET, entity,
                                                               ApplicationCollection.class);
        ApplicationResponse[] res = Objects.requireNonNull(ar.getBody()).getData();
        page++;
        return res;
    }
}

它将在hasNext()中检查下一个响应是否为空,从而停止流。否则,它将流和flatMap响应。我已经硬连线pageSize,但您可以轻松地将其作为工厂方法ResponseIterator.getApplications()的第三个输入。