Assume we have a collection of objects that are identified by unique Strings, along with a class Tree that defines a hierarchy on them. That class is implemented using a Map from nodes (represented by their IDs) to Collections of their respective children's IDs.
class Tree {
  private Map<String, Collection<String>> edges;
  // ...
  public Stream<String> descendants(String node) {
    // To be defined.
  }
}
I would like to enable streaming a node's descendants. A simple solution is this:
private Stream<String> children(String node) {
    return edges.getOrDefault(node, Collections.emptyList()).stream();
}
public Stream<String> descendants(String node) {
    return Stream.concat(
        Stream.of(node),
        children(node).flatMap(this::descendants)
    );
}
Before continuing, I would like to make the following assertions about this solution. (Am I correct about these?)
Walking the
Streamreturned fromdescendantsconsumes resources (time and memory) – relative to the size of the tree – in the same order of complexity as hand-coding the recursion would. In particular, the intermediate objects representing the iteration state (Streams,Spliterators, ...) form a stack and therefore the memory requirement at any given time is in the same order of complexity as the tree's depth.As I understand this, as soon as I perform a terminating operation on the
Streamreturned fromdescendants, the root-level call toflatMapwill cause all containedStreams – one for each (recursive) call todescendants– to be realized immediately. Thus, the resultingStreamis only lazy on the first level of recursion, but not beyond. (Edited according to Tagir Valeevs answer.)
If I understood these points correctly, my question is this: How can I define descendants so that the resulting Stream is lazy?
I would like the solution to be as elegant as possible, in the sense that I prefer a solution which leaves the iteration state implicit. (To clarify what I mean by that: I know that I could write a Spliterator that walks the tree while maintaining an explicit stack of Spliterators on each level. I would like to avoid that.)
(Is there possibly a way in Java to formulate this as a producer-consumer workflow, like one could use in languages like Julia and Go?)