1

Why is the following java 8 code showing a bug at the second call to get()?

    Stream<String> aStream = Stream.concat(Stream.of("A"), Stream.of("B"));
    String a = stream.findFirst().get();
    String b = stream.findFirst().get();

The "aStream" stream should see two values: "A" and "B". However, trying to read anything, after the first element has already been consumed, gives

    java.lang.IllegalStateException: stream has already been operated upon or closed

Isn't it a bug in Java 8? First, why doesn't a consumed Stream.of()-created stream return an Optional with isPresent()==false? Second, why doesn't Stream.concatenate() correctly concatenate such Stream.of()-created streams?

Stefan Zobel
  • 3,182
  • 7
  • 28
  • 38
pablo53
  • 19
  • 1
  • 1
    Should `aStream` and `stream` be the same variable? – Eran May 09 '18 at 09:26
  • It may be worth noticing that in C#, on the contrary, operations attached to a stream don’t influence the stream itself: https://ideone.com/axLtod – Vlad May 09 '18 at 10:16
  • Also [Why is this java Stream operated upon twice?](https://stackoverflow.com/q/34677708/525036), [Copy a stream to avoid “stream has already been operated upon or closed”](https://stackoverflow.com/q/23860533/525036) and [Stack Overflow search](https://stackoverflow.com/search?q=%22stream+has+already+been+operated+upon+or+closed%22) – Didier L May 09 '18 at 11:19
  • @Vlad that still doesn’t do what the OP wants, i.e. returns `"A"` two times instead of `"A"` and `"B"`. – Holger May 09 '18 at 12:08
  • 1
    Your assumption that `concat` doesn’t work correctly, is entirely unfounded. `Stream.concat(Stream.of("A"), Stream.of("B"))` behaves exactly like `Stream.of("A", "B")`. – Holger May 09 '18 at 12:10
  • @Holger: That's true; however the curious semantic difference is that the C#'s Enumerables (usually) are not affected by the readers, whereas Java Streams should be operated on only once. So Java Streams seem to be stateful, as opposed to stateless C# streams. – Vlad May 09 '18 at 14:05
  • @Vlad: A Java `Stream` is like an `Iterator`, processing it will affect the iterator’s state, but not the actual source. In contrast, C#’s Enumerable is like Java’s `Iterable`, you may initiate as many iterations as you want without affecting the source, but if the `IEnumerable` is an `ICollection`, the methods composing enumerables interfere with the methods modifying the collection and you have to learn which method does what. In the worst case, there is no way to learn, i.e. `IEnumerable.Reverse()` has exactly the same signature as `List.Reverse()` despite the entirely different semantic. – Holger May 09 '18 at 15:25
  • @Holger: It's true that functional-style `IEnumerable` doesn't like mutations. But I wonder if `Stream`s don't expose the same problem? Surely `Stream`s, created from material collections, don't copy all the content at the creation time, do they? [`Reverse` is not a problem, as `List`-version returns `void`, so if you use it as instead of the other one, the code wouldn't compile. But yes, it's a small wart, sometimes you need an upcast.] – Vlad May 09 '18 at 15:32
  • @Vlad [Why are Java Streams once-off?](https://stackoverflow.com/q/28459498/525036) – Didier L May 09 '18 at 15:54
  • @DidierL: I’ve read the answer and find the rationale not too convincing. Surely it must be possible to have completely independent iterations over the same `Iterable`. The example with reading from file is not a problem in .NET: the file is opened separately at each iteration, so simultaneous iterations are a non-issue. The network data have completely different semantics, so .NET didn’t try to shoehorn it into an `IEnumerable`, but have another top-level notion for pipeline-style data (which is coincidentally called `Stream`). – Vlad May 09 '18 at 16:04
  • @Vlad well, neither did Java try to shoehorn it into an `Iterable`, but has another top-level notion for pipeline-style data which is called Stream and that’s not a coincidence. There is a clear separation between the query framework that does not modify the source (the Stream API) and framework of potentially mutable containers (the Collection API) and it won’t happen that the same object offers both methods. – Holger May 09 '18 at 16:56
  • @Holger: The same does `IEnumerable`, LINQ never _modifies_ the original collection (and side-effecting usage [e. g. non-pure lambdas] is really frowned upon). So I see no difference here: the LINQ framework doesn't modify the source as well. In both frameworks, if underlying material container is mutating during iteration, bad things happen [am I right?]. The only difference I see is that in Java you need to construct the stream explicitly, which might be desirable from puristic point of view, but seems [for me] to be not very convenient for the real-world usage. – Vlad May 09 '18 at 17:32
  • @Vlad since this issue only applies to collections, which are also iterables (it doesn’t apply to non-iterables, e.g bitset, regex patterns, scanner, zip files, etc. or genuine stream factories like `range(a, b)`), I think the clear separation of the mutable collection API and non-modifying Stream API justifies the inconvenience of having to write `stream()`. But well, it is just my opinion. – Holger May 12 '18 at 09:02

2 Answers2

6

Stream.concatenate() does concatenate the two Streams. However, once you execute a terminal operation of the combined Stream - stream.findFirst() - you can't do anything else with that Stream. You can only run one terminal operation of a Stream. That's why it's called "terminal".

If you want to obtain more than one element of the combined Stream, use a different terminal operation, such as collect:

List<String> list = stream.collect(Collectors.toList());

To clarify, the combined Stream is a single Stream<String>, not a Stream of Streams. Therefore findFirst() consumes the entire combined Stream, not just the first Stream that was uses to create the combined Stream.

Eran
  • 387,369
  • 54
  • 702
  • 768
4

Because Stream.findFirst() is a terminal operation, and terminal operations can only be run once on a given stream.

ernest_k
  • 44,416
  • 5
  • 53
  • 99
  • Then, how to consume the first element of a given stream and receive a stream of the rest of elements? I mean, how to get the head and tail, just like it is usually done in Scala? This should be very simple use case? Why is it so hard to do that in Java? – pablo53 May 09 '18 at 10:08
  • @pablo53 If you need a given number of stream elements, then just limit and collect: `List list = aStream.limit(2).collect(Collectors.toList())`, then just use the list: `list.get(0); list.get(1)`. Streams aren't really designed to function as collections... So you may just need to collect and split the list using the normal collection API – ernest_k May 09 '18 at 10:14