简体   繁体   English

Java 8:Stream:消耗步骤以保存流元素的中间状态

[英]Java 8 : Stream : Consuming steps to save Intermediate States of streamed elements

First of all, sorry for that abstract title. 首先,对这个抽象标题感到抱歉。 The idea is more simple with an example. 举个例子,这个想法更简单。 Let say I have some values in a list L. I want to build parameters Request for a service, then call this service and collect all the Responses. 假设我在列表L中有一些值。我想构建请求服务的参数,然后调用该服务并收集所有响应。

Currently, I'm using this kind of code structure : 目前,我正在使用这种代码结构:

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

}

However, I find the need of two stream not efficient considering the last request has to be built before the first one can be sent. 但是,考虑到必须在发送第一个请求之前构建最后一个请求,我发现两个流的效率不高。 I would like to do something like : 我想做类似的事情:

@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);

}

However, I feel guilty to use such a "Hack" to the mapping semantic. 但是,我对将这样的“ Hack”用于映射语义感到内。 However it's the only way I've found to build 2 Correlated list with lazy loading. 但是,这是我发现用延迟加载构建2个相关列表的唯一方法。 The first solution doesn't interest me because it has to wait to build all the request before sending them. 第一个解决方案对我不感兴趣,因为它必须等待构建所有请求,然后再发送它们。 I would love to achieve something like a wiretap in EIP. 我很想在EIP中实现类似窃听的功能。 http://camel.apache.org/wire-tap.html http://camel.apache.org/wire-tap.html

I would gladly have your thoughts about a more elegant manner than modifying the semantic of the map method to achieve this. 与修改map方法的语义来实现这一目标相比,我很乐意让您想到一种更优雅的方式。

If it helps, you can find the source here : http://tinyurl.com/hojkdzu 如果有帮助,您可以在这里找到源: http : //tinyurl.com/hojkdzu

Using .peek() while requires less changes in your code, is actually quite dirty solution. 使用.peek()虽然需要在代码中进行较少的更改,但实际上是一个很肮脏的解决方案。 You need it, because you have a design flaw in your original code. 您需要它,因为您的原始代码中存在设计缺陷。 You have "parallel data structures" (probably the term is not very good): the first element in the requests list corresponds to the first element in the responses list, and so on. 您具有“并行数据结构”(可能是术语不是很好): requests列表中的第一个元素对应于responses列表中的第一个元素,依此类推。 When you have such situation, consider creating a new PoJo class instead. 遇到这种情况时,请考虑创建一个新的PoJo类。 Something like this: 像这样:

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

Now your problem magically disappears. 现在,您的问题神奇地消失了。 You can write: 你可以写:

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

commonAssertions(reqresp);

You will need to change commonAssertions method after that, but I'm pretty sure it will become simpler. 之后,您将需要更改commonAssertions方法,但是我敢肯定它会变得更简单。 Also you may find that some methods in your code use request and response together, so it's quite natural to make them as methods in RequestAndResponse class. 您还可能会发现代码中的某些方法将请求和响应一起使用,因此将它们作为RequestAndResponse类中的方法是很自然的。

Not sure what you really want to achieve here. 不知道您真正想要在这里实现什么。 But if what you want is to collect requests "as you go" then you can use peek() : 但是,如果您想要“随您走”收集请求,则可以使用peek()

final List<Request> requests = new ArrayList<>();

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

commonAssertions(requests, responses);

.peek() is pretty much what you mean in the subject of your post; .peek()与您在帖子主题中的意思.peek() it takes a Consumer , can occur at any steps in the pipeline, and here the consumer just saves the "intermediate states" into a list. 它需要一个Consumer ,它可以在管道中的任何步骤发生,并且此处消费者只是将“中间状态”保存到列表中。

BUT... the javadoc of peek() specifically mentions this: 但是... peek()的javadoc特别提到了这一点:

For parallel stream pipelines, the action may be called at whatever time and in whatever thread the element is made available by the upstream operation. 对于并行流管道,可以在任何时间和线程中通过上游操作使元素可用,以调用该操作。 If the action modifies shared state, it is responsible for providing the required synchronization. 如果操作修改了共享状态,则它负责提供所需的同步。

So, well, be careful, I guess... 所以,好吧,我想...

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM