Java8:流:使用步骤保存流元素的中间状态

Java8:流:使用步骤保存流元素的中间状态,java,java-stream,Java,Java Stream,首先,抱歉,这个抽象的标题。通过一个例子,这个想法更简单。假设我在列表L中有一些值。我想为一个服务构建参数请求,然后调用这个服务并收集所有响应 目前,我正在使用这种代码结构: private final List<String> bodies = ImmutableList.of( "1", "2", "3", "4" ); @Test public void BasicRequestResponseStreamToList() { final List&

首先,抱歉,这个抽象的标题。通过一个例子,这个想法更简单。假设我在列表L中有一些值。我想为一个服务构建参数请求,然后调用这个服务并收集所有响应

目前,我正在使用这种代码结构:

private final List<String> bodies = ImmutableList.of(
        "1", "2", "3", "4"
);

@Test
public void BasicRequestResponseStreamToList() {

    final List<Request> requests = bodies.stream()
            .map(Request::new)
            .collect(Collectors.toList());

    final List<Response> responses = requests.stream()
            .map(service::send)
            .collect(Collectors.toList());

    commonAssertions(requests, responses);

}
private final List body=ImmutableList.of(
"1", "2", "3", "4"
);
@试验
公共空间基本信使响应叛国主义者(){
最终列表请求=body.stream()
.map(请求::新建)
.collect(Collectors.toList());
最终列表响应=requests.stream()
.map(服务::发送)
.collect(Collectors.toList());
共同主张(请求、响应);
}
然而,考虑到必须在发送第一个请求之前构建最后一个请求,我发现需要两个流是不有效的。我想做一些类似的事情:

@Test
public void StatefulMapperStreamRequestResponseToList() {

    final List<Request> requests = new ArrayList<>();
    final List<Response> responses = bodies.stream()
            .map(Request::new)
            .map(x -> {
                requests.add(x);
                return service.send(x);
            })
            .collect(Collectors.toList());

    commonAssertions(requests, responses);

}
@测试
public void StatefulMapperStreamRequestResponseToList(){
最终列表请求=新建ArrayList();
最终列表响应=body.stream()
.map(请求::新建)
.map(x->{
请求。添加(x);
返回服务。发送(x);
})
.collect(Collectors.toList());
共同主张(请求、响应);
}
然而,我感到内疚使用这样一个“黑客”的映射语义。然而,这是我发现的用延迟加载构建2个相关列表的唯一方法。我对第一个解决方案不感兴趣,因为在发送请求之前,它必须等待生成所有请求。 我很想在EIP中实现类似窃听的功能

我很乐意让您考虑一种更优雅的方式,而不是修改map方法的语义来实现这一点


如果有帮助,您可以在这里找到来源:

不确定您真正想要在这里实现什么。但是,如果您希望“随走随收”,则可以使用
peek()

final List requests=new ArrayList();
最终列表响应=body.stream()
.map(请求::新建)
.peek(请求::添加)
.map(服务::发送)
.collect(Collectors.toList());
共同主张(请求、响应);
.peek()
正是你在文章主题中的意思;它需要一个
消费者
,可以在管道中的任何步骤发生,消费者只需将“中间状态”保存到列表中

但是。。。
peek()
的javadoc特别提到:

对于平行流管道,可以在上游操作使元素可用的任何时间和线程中调用该操作。如果操作修改了共享状态,则它负责提供所需的同步

所以,好吧,小心点,我想…

使用
.peek()
虽然需要对代码进行更少的更改,但实际上这是一个非常肮脏的解决方案。您需要它,因为您的原始代码中存在设计缺陷。您有“并行数据结构”(这个术语可能不是很好):
请求
列表中的第一个元素对应于
响应
列表中的第一个元素,依此类推。当您遇到这种情况时,请考虑创建一个新的POJO类。大概是这样的:

public class RequestAndResponse { // You may think up a better name
    public final Request req; // use getters if you don't like public final fields
    public final Response resp;

    public RequestAndResponse(Request req, Response resp) {
        this.req = req;
        this.resp = resp;
    }
}
现在你的问题神奇地消失了。你可以写:

List<RequestAndResponse> reqresp = bodies.stream()
        .map(Request::new)
        .map(req -> new RequestAndResponse(req, service.send(req)))
        .collect(Collectors.toList());

commonAssertions(reqresp);
List reqresp=body.stream()
.map(请求::新建)
.map(请求->新请求和响应(请求,服务发送(请求)))
.collect(Collectors.toList());
共同主张(reqresp);

在那之后,您需要更改
commonAssertions
方法,但我确信它会变得更简单。此外,您可能会发现代码中的某些方法同时使用请求和响应,因此将它们作为
RequestAndResponse
类中的方法是很自然的。

非常感谢。这正是我试图表达的,但“偷看”这个词似乎没有引起我的注意,因为我太痴迷于“窃听”这个词(并且通过了更多次基于标题的重复问题检查,而不是重复检查文档,我的错)。感谢您对有状态消费者并发性的精确性。
peek()
的javadoc还说:这种方法的存在主要是为了支持调试,所以通常最好在没有peek的情况下找到解决方案(如Tagir Valeev的解决方案)
List<RequestAndResponse> reqresp = bodies.stream()
        .map(Request::new)
        .map(req -> new RequestAndResponse(req, service.send(req)))
        .collect(Collectors.toList());

commonAssertions(reqresp);