1

I've been playing around with streams and then I noticed that when I do the following, it won't produce an output into the console:

String[] is = {"Hello", "World", "My", "Name", "Is", "Jacky"};
Arrays.stream(is).peek(s -> System.out.println(s));

I figure this is because peek() is a non-terminating stream method, and forEach() should be used instead of peek() to terminate the stream and produce the results:

Arrays.stream(is).forEach(s -> System.out.println(s));

Is there however a way to terminate a stream 'early', using perhaps a custom terminating method (functional interface) that wouldn't do anything except for terminating the stream?.. Is there a proper way to do this with what is already present in Java?

I understand I could do something like this:

Arrays.stream(is).peek(s -> System.out.println(s)).forEach(s -> {});

But that feels wasteful.

Ivar
  • 6,138
  • 12
  • 49
  • 61
Jack Avante
  • 1,405
  • 1
  • 15
  • 32
  • The stream API is "lazy", any data passes through pipeline only when terminating statement actually "pumps" the data. So, if your purpose is to print all elements using peek(), you need such terminating statement, which will "pump" all those elements. – Daniel Zin Apr 21 '21 at 14:27
  • 2
    @DanielZ. I’m guessing OP’s question is whether there’s a no-op terminating statement. My knowledge beyond Java 1.8 is severely limited. At least for Java 1.8, the answer seems to be “no”: there’s no `sink()`/`consume()`/`terminate()` operation. Writing one is obviously trivial (as OP has shown); but what would be the “least wasteful” way (`forEach(s -> {})` actually seems like a good candidate)? – Konrad Rudolph Apr 21 '21 at 14:34
  • That is indeed what I'm trying to figure out. This is purely a question of curiosity, as there are some programming languages that contain streams similar to Java that have a no-op terminator, so I'm trying to figure out if Java has a dedicated one in any of the versions (I haven't been able to find any personally) – Jack Avante Apr 21 '21 at 14:40
  • 1
    @KonradRudolph there is no such thing. what is worse for OP, in theory, a `forEach(s -> {})` can be entirely dismissed by the compiler (it does nothing after all), and as such any other intermediate operation also, effectively making this stream a NO-OP. – Eugene Apr 21 '21 at 14:46
  • 1
    `peek` is meant to be used for debugging purposes; if you have a stream where the effect of `peek` is the reason you have the stream in the first place, then you should use `forEach` instead. See [the docs](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#peek-java.util.function.Consumer-), and [this other Stack Overflow Q&A](https://stackoverflow.com/questions/33635717/in-java-streams-is-peek-really-only-for-debugging). – kaya3 Apr 21 '21 at 14:49
  • 1
    Streams are meant/designed to produce a result with no side effects. To terminate the stream without a terminating method means you either don't want anything to happen at all, or you're using the stream for a side effect. Either of these are counter to what they're meant to be used for. – Scratte Apr 21 '21 at 14:49
  • 2
    in perspective, if you find your self in a position to need a no-op terminal operation, then according to java, you do not need streams to begin with... – Eugene Apr 21 '21 at 14:50
  • @kaya3 Is `peek` truly only meant for debugging purposes?.. What if I want to apply several different operations to the objects in a stream?.. If I avoided using `peek`, then I would have to repetitively use `forEach` followed by `collect` – Jack Avante Apr 21 '21 at 14:53
  • 2
    you _cant_ use `collect` after `forEach` – Eugene Apr 21 '21 at 14:54
  • If you want to apply several operations, you can write one `forEach` which calls each of those operations. If you also want to collect them in a list, you could just as easily use a `forEach` on the resulting list rather than the stream that produces the list. – kaya3 Apr 21 '21 at 14:55
  • 1
    If you need to apply several different operations to a stream, you should use a chain of `map` calls. Streams are designed to be *functional* (https://subscription.packtpub.com/book/application_development/9781786461483/1/ch01lvl1sec8/principles-of-functional-programming) so when you want your stream to have side effects during non-terminal operations, it's better to replace stream usage with loop. – geobreze Apr 21 '21 at 14:55
  • @geobreze To be clear, you should not use `map` as a substitute for `peek` or `forEach`, because `map` is intended for transforming the stream objects into something else, not for side-effects. – kaya3 Apr 21 '21 at 14:56
  • @Eugene Do you have a source for your claim that `forEach(s -> {})` can be elided? As far as I understand Java’s streaming API, the compiler would have to first prove that none of the operations in the stream pipeline have an observable side-effect, and that isn’t the case here. – Konrad Rudolph Apr 21 '21 at 14:59
  • 1
    @KonradRudolph It would *not* have to prove that; the implementation explicitly doesn't have to call the function passed by `peek` on every element, nor even on any of them. The docs say the function is called *"as elements are consumed from the resulting stream"*, and it can be implementation-specific whether or not a particular call will consume the elements or not. [This answer](https://stackoverflow.com/a/66047984/12299000) gives a few scenarios in which `peek` is elided. – kaya3 Apr 21 '21 at 15:02
  • 1
    @KonradRudolph that is why I said "in theory". But there are already things that the runtime does for streams. For example: `Stream.of("a").map(x -> {System.out.println(x); return x.length();}).count();` will print exactly nothing since java-9. – Eugene Apr 21 '21 at 15:04
  • @kaya3 That goes out of the window as soon as you have a terminator consuming all elements. The answer you linked doesn’t apply because `count` *does not consume all elements*. `forEach` *does*, as a matter of API documentation. The only caveat given in the documentation is that the *order* is not deterministic. – Konrad Rudolph Apr 21 '21 at 15:08
  • 1
    @KonradRudolph The docs only say that `forEach` is a "terminal operation", and the docs define this as *"Terminal operations, such as Stream.forEach or IntStream.sum, **may** traverse the stream to produce a result or a side-effect"* (emphasis mine). Please point out where in the docs it says that a `forEach` call is guaranteed to consume all elements of a stream, because as I see it, there is nothing stopping an implementation from eliding `forEach` when it provably does nothing. – kaya3 Apr 21 '21 at 15:11
  • 1
    @KonradRudolph If the action does nothing, then I don't see any problem with an implementation "performing" that action for each element of the stream by doing nothing, without consuming the elements. An implementation doing that would not contradict the docs. – kaya3 Apr 21 '21 at 15:15
  • @kaya3 Never mind, I changed my mind. `forEach` only guarantees that the action is performed on each item. But that can legitimately be understood as saying that if the action is a no-op, *no* action is performed, and thus nothing’s consumed. – Konrad Rudolph Apr 21 '21 at 15:15
  • Alright, fair enough. – kaya3 Apr 21 '21 at 15:15
  • 2
    In principle it may also be allowed for an implementation to not consume elements of the stream if `forEach` is called with a function which provably ignores its input, even if the function does do something, such as `x -> System.out.println("Hello")`. This could theoretically be implemented by calling the function `.count()` times with a `null` input, and would make it difficult to write *any* do-nothing terminal operation which formally guarantees the stream is consumed. – kaya3 Apr 21 '21 at 15:17

1 Answers1

2

There is no such thing.

There have been discussion around such functionality, for example here, where you can read that this will not happen and why.

You best option is forEach(x -> {}) or you can create such a dummy consumer yourself and use it everywhere needed:

static class Dummies {
     static <T> Consumer<T> getConsumer() {
         return x -> {};
     }
}
Eugene
  • 117,005
  • 15
  • 201
  • 306
  • 3
    I’m not sure whether the linked discussion is about what the OP intended. It’s rather the opposite, potential optimizations that would lead to *not* processing elements when using a recognizable no-op consumer. And the general answer to the OP’s actual question should be *don’t* try to enforce side effects in intermediate operations. – Holger Apr 21 '21 at 17:25