我试图从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知道在收到该信号时停止。但是,我能做些什么吗?
答案 0 :(得分:2)
在Holger的评论之后,我试了一下,试了Spliterator
而不是Iterator
。这确实比较简单,因为next
和hasNext
......有点合并到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()
的第三个输入。