1

it is possible, with Java 8 stream API, to create Stream that is not evaluated until is necessary?

I mean.

I have a stream that processes a list of elements, in one of the middle operations (map) I have to read go through another stream and I want to have that Stream in another variable to be used through all other first stream objects, but if there Are no objects to process I would like to avoid process second stream.

I think it's easier to check with code:

Message[] process(@Nullable Message[] messages) {
    Stream<Function> transformationsToApply =
            transformations
                    .stream()
                    .filter(transformation -> transformationIsEnabled(transformation.getLeft()))
                    .map(Pair::getRight);

    return Arrays.stream(messages != null ? messages : new Message[0])
            .filter(Objects::nonNull)
            .map(agentMessage -> {
                transformationsToApply.forEach(transformation -> processMessage(transformation, agentMessage));
                return agentMessage;
            })
            .toArray(Message[]::new);
}

My doubt is about first one stream generation, I would like to return stream based on the list that I processed, but I only to want to do if it is gonna be used (And use same for all message elemets).

Any idea..?

Stefan Zobel
  • 3,182
  • 7
  • 28
  • 38
colymore
  • 11,776
  • 13
  • 48
  • 90
  • 2
    The operation in the second stream's `.map` will cause an exception. The stream's `forEach` terminal operation would be called multiple times, which is not allowed. Why not return a stream and let the caller run `.toArray` only when necessary? – ernest_k May 22 '18 at 11:15
  • You should specify the type parameters of `Function`, using raw types will disable some compile-time type checking. – Sean Van Gorder May 22 '18 at 16:51

2 Answers2

2

Don't worry, your transformationsToApply is not evaluated until you attach a terminal operation.

Intermediate operations return a new stream. They are always lazy; executing an intermediate operation such as filter() does not actually perform any filtering, but instead creates a new stream that, when traversed, contains the elements of the initial stream that match the given predicate. Traversal of the pipeline source does not begin until the terminal operation of the pipeline is executed.

Mạnh Quyết Nguyễn
  • 17,677
  • 1
  • 23
  • 51
  • And the value of the processed stream will be used for all iterations? Or it will be calculated each time? That's my doubt and that's why I'm saving it in a variable. – colymore May 22 '18 at 13:34
  • Your expression inside `.map` will cause exception will cause exception since the stream can be consumed only once. I suggest you to store result of `transformationsToApply.toList` to a local variable and reuse it in your `.map` method – Mạnh Quyết Nguyễn May 22 '18 at 14:35
1

Streams are not stateful, so there's no easy way to have it do something only when the first element is processed. In this case, you could just check the messages parameter and return early if there's nothing to do:

Message[] process(@Nullable Message[] messages) {
    if (messages == null || messages.length == 0) return new Message[0];

    List<Function> transformationsToApply = transformations.stream()
            .filter(transformation -> transformationIsEnabled(transformation.getLeft()))
            .map(Pair::getRight)
            .collect(Collectors.toList());

    return Arrays.stream(messages)
            .filter(Objects::nonNull)
            .map(agentMessage -> {
                transformationsToApply.forEach(transformation -> processMessage(transformation, agentMessage));
                return agentMessage;
            })
            .toArray(Message[]::new);
}

I also fixed the issue with reusing the transformationsToApply stream, you need to make it a collection before you can iterate over it multiple times.

Sean Van Gorder
  • 3,393
  • 26
  • 26