On Thu, Mar 5, 2026 at 3:13 PM Viktor Klang <[email protected]> wrote:
> >And if generate() is unordered, is it by-spec safe to depend on the > elements being delivered in the order they are generated, at all? > > Encounter order for unordered streams is described here: > https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/stream/package-summary.html#Ordering > Yeah. Specifically this statement: > if the source of a stream is a List containing [1, 2, 3], then the result of executing map(x -> x*2) must be [2, 4, 6]. However, if the source has no defined encounter order, then any permutation of the values [2, 4, 6] would be a valid result. generate()'s source isn't a List, and it's specified as "an infinite sequential unordered stream", which to me reads as "it has no defined encounter order". And if that reading is correct, then any permutation of the generated results would be a valid result? On 2026-03-06 00:04, Jige Yu wrote: > > From what I can see, many of these supplier lambdas do return null > idempotently, such as generate(queue::poll). > > But with enough usage, I suspect we'll run into scenarios that may trip if > called again after returning null. > > And if generate() is unordered, is it by-spec safe to depend on the > elements being delivered in the order they are generated, at all? > > On Thu, Mar 5, 2026 at 1:33 AM Viktor Klang <[email protected]> > wrote: > >> Is the supplier's get()-method allowed to be invoked *after* it has >> previously returned *null?* >> On 2026-03-05 06:54, Jige Yu wrote: >> >> Makes sense. >> >> What do you guys think of the idiom of >> generate(supplierThatEventuallyReturnsNull) + takeWhile() ? Should it be >> avoided? >> >> On Wed, Mar 4, 2026 at 6:59 AM Viktor Klang <[email protected]> >> wrote: >> >>> >In our codebase, I see some developers using iterate() + takeWhile() >>> and others using generate() + takeWhile(). I am debating whether to raise a >>> concern about this pattern. Most likely, people won't insert intermediary >>> operations between them, and I worry I might be overthinking it. >>> >>> In this specific case I'd argue that it's more correct (and more >>> performant, and less code) to just use the 3-arg iterate. >>> >>> >or should I reconsider my warnings about side effects being rearranged >>> in sequential streams? >>> >>> Personally I prefer my Streams correct regardless of underlying >>> implementation and regardless of whether the stream isParallel() or not. >>> On 2026-03-03 20:29, Jige Yu wrote: >>> >>> Hi Viktor, >>> >>> Thanks for the explanation! >>> >>> I also experimented with adding parallel() in the middle, and it indeed >>> threw a NullPointerException even without distinct(). >>> >>> In our codebase, I see some developers using iterate() + takeWhile() and >>> others using generate() + takeWhile(). I am debating whether to raise a >>> concern about this pattern. Most likely, people won't insert intermediary >>> operations between them, and I worry I might be overthinking it. >>> >>> However, generate(supplierThatMayReturnNull).takeWhile() seems even more >>> precarious. Since generate() is documented as unordered, could it >>> potentially return elements out of encounter order, such as swapping a >>> later null with an earlier non-null return? >>> >>> This brings me back to the rationale I’ve used to discourage side >>> effects in map() and filter(). In a sequential stream, I’ve argued that >>> relying on side effects from an earlier map() to be visible in a subsequent >>> map() is unsafe because the stream is theoretically free to process >>> multiple elements through the first map() before starting the second. >>> >>> Is that view too pedantic? If we can safely assume iterate() + >>> takeWhile() is stable in non-parallel streams, should the same logic apply >>> to subsequent map() calls with side effects (style issues aside)? >>> >>> I’m trying to find a consistent theory. Should I advise my colleagues >>> that iterate() + takeWhile() and generate() + takeWhile() are unsafe, or >>> should I reconsider my warnings about side effects being rearranged in >>> sequential streams? >>> >>> I hope that clarifies the root of my confusion. >>> >>> Best, >>> Jige Yu >>> >>> On Mon, Mar 2, 2026 at 6:08 AM Viktor Klang <[email protected]> >>> wrote: >>> >>>> Hi Jige, >>>> >>>> I think I understand what you mean. In this case you're trying to >>>> prevent a `null`-return from `nextOrNull()` to be fed into the next >>>> iteration and thus throwing a NullPointerException. >>>> >>>> Now the answer is going to be a bit nuanced than you might want to >>>> hear, but in the spirit of providing clarity, the code which you provided >>>> will "work" under the assumption that there is no "buffer" in between >>>> iterate(…) and takeWhile(…). >>>> >>>> TL;DR: use Stream.iterate(seed, e -> e != null, e -> e.nextOrNull()) >>>> Long version: >>>> Imagine we have the following: >>>> ```java >>>> record E(E e) {} >>>> Stream.iterate(new E(new E(new E(null))), e -> e.e()) >>>> .< /span>takeWhile(Objects::nonNull) >>>> .forEach(IO::println) >>>> ``` >>>> We get: >>>> ```java >>>> E[e=E[e=E[e=null]]] >>>> E[e=E[e=null]] >>>> E[e=null] >>>> ``` >>>> However, if we do: >>>> ```java >>>> Stream.iterate(new E(new E(new E(null))), e -> e.e())< /span> >>>> .gather( >>>> Gatherer.<E,ArrayList<E>,E>ofSequential( >>>> ArrayList::new, >>>> (l, e, _) -> l.add(e), >>>> (l, d) -> l.forEach(d::push) >>>> ) >>>> ) >>>> .takeWhile(Objects::nonNull) >>>> .forEach(IO::println) >>>> ``` >>>> We get: >>>> ```java >>>> Exception java.lang.NullPointerException: Cannot invoke >>>> "REPL.$JShell$16$E.e()" because "<parameter1>" is null >>>> at lambda$do_it$$0 (#5:1) >>>> at Stream$1.tryAdvance (Stream.java:1515) >>>> at ReferencePipeline.forEachWithCancel (ReferencePipeline.java:147) >>>> at AbstractPipeline.copyIntoWithCancel (AbstractPipeline.java:588) >>>> at AbstractPipeline.copyInto (AbstractPipeline.java:574) >>>> at AbstractPipeline.wrapAndCopyInto (AbstractPipeline.java:560) >>>> at ForEachOps$ForEachOp.evaluateSequential (ForEachOps.java:153) >>>> at ForEachOps$ForEachOp$OfRef.evaluateSequential (ForEachOps.java:176) >>>> at AbstractPipeline.evaluate (AbstractPipeline.java:265) >>>> at ReferencePipeline.forEach (ReferencePipeline.java:632) >>>> at (#5:9) >>>> ``` >>>> But if we introduce something like `distinct()` in between, it will >>>> "work" under sequential processing, >>>> but under parallel processing it might not, as the distinct operation >>>> will have to buffer *separately* from takeWhile: >>>> ```java >>>> Stream.iterate(new E(new E(new E(null))), e -> e.e())< /span> >>>> .distinct() >>>> .takeWhile(Objects::nonNull) >>>> .forEach(IO::println) >>>> ``` >>>> ```java >>>> E[e=E[e=E[e=null]]] >>>> E[e=E[e=null]] >>>> E[e=null] >>>> ``` >>>> Parallel: >>>> ```java >>>> Stream.iterate(new E(new E(new E(null))), e -> e.e())< /span> >>>> .parallel() >>>> .distinct() >>>> .takeWhile(Objects::nonNull) >>>> .forEach(IO::println) >>>> ``` >>>> ```java >>>> Exception java.lang.NullPointerException: Cannot invoke >>>> "REPL.$JShell$16$E.e()" because "<parameter1>" is null >>>> at lambda$do_it$$0 (#7:1) >>>> at Stream$1.tryAdvance (Stream.java:1515) >>>> at Spliterators$AbstractSpliterator.trySplit (Spliterators.java:1447) >>>> at AbstractTask.compute (AbstractTask.java:308) >>>> at CountedCompleter.exec (CountedCompleter.java:759) >>>> at ForkJoinTask.doExec (ForkJoinTask.java:511) >>>> at ForkJoinTask.invoke (ForkJoinTask.java:683) >>>> at ReduceOps$ReduceOp.evaluateParallel (ReduceOps.java:927) >>>> at DistinctOps$1.reduce (DistinctOps.java:64) >>>> at DistinctOps$1.opEvaluateParallelLazy (DistinctOps.java:110) >>>> at AbstractPipeline.sourceSpliterator (AbstractPipeline.java:495) >>>> at AbstractPipeline.evaluate (AbstractPipeline.java:264) >>>> at ReferencePipeline.forEach (ReferencePipeline.java:632) >>>> at (#7:4) >>>> ``` >>>> >>>> On 2026-03-01 06:29, Jige Yu wrote: >>>> >>>> Hi @core-libs-dev, >>>> I am looking to validate the following idiom: >>>> Stream.iterate(seed, e -> e.nextOrNull()) >>>> .takeWhile(Objects::nonNull); >>>> The intent is for the stream to call nextOrNull() repeatedly until it >>>> returns null. However, I am concerned about where the Stream specification >>>> guarantees the correctness of this approach regarding happens-before >>>> relationships. >>>> The iterate() Javadoc defines happens-before for the function passed to >>>> it, stating that the action of applying f for one element happens-before >>>> the action of applying it for subsequent elements. However, it seems silent >>>> on the happens-before relationship with downstream operations like >>>> takeWhile(). >>>> My concern stems from the general discouragement of side effects in >>>> stream operations. For example, relying on side effects between subsequent >>>> map() calls is considered brittle because a stream might invoke the first >>>> map() on multiple elements before the second map() processes the first >>>> element. >>>> If this theory holds, is there anything theoretically preventing >>>> iterate() from generating multiple elements before takeWhile() evaluates >>>> the first one? I may be overthinking this, but I would appreciate your >>>> insights into why side effects are discouraged even in ordered, sequential >>>> streams and whether this specific idiom is safe. >>>> Appreciate your help! >>>> Best regards, >>>> Jige Yu >>>> >>>> -- >>>> Cheers, >>>> √ >>>> >>>> >>>> Viktor Klang >>>> Software Architect, Java Platform Group >>>> Oracle >>>> >>>> -- >>> Cheers, >>> √ >>> >>> >>> Viktor Klang >>> Software Architect, Java Platform Group >>> Oracle >>> >>> -- >> Cheers, >> √ >> >> >> Viktor Klang >> Software Architect, Java Platform Group >> Oracle >> >> -- > Cheers, > √ > > > Viktor Klang > Software Architect, Java Platform Group > Oracle > >
