The classes implementing the collection interface have the stream() method, and I see that it returns the stream interface when the stream() method is called. I know there will be some internal implementations, and you can just use the stream interface method. But I'm curious. Where is the inner iteration? Collection Implementation?? or Stream Implementation?? I don't think this is the way to borrow the result of calling the splitter() method when it's needed to be repeated...
Am I asking a weird question? I just wonder where the instance exists in memory.. There's no way to find out even if I search this for 3 days, and I don't know how to observe Java memory..
import java.util.ArrayList;
import java.util.List;
import java.util.Spliterator;
import java.util.stream.Stream;
public class StreamDebugTest {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
Stream<String> stream = list.stream();
stream.map(s -> {
System.out.println("Changing: " + s);
return s.toLowerCase();
})
.forEach(s -> {
System.out.println("last: " + s);
});
Stream<String> newStream = list.stream();
Spliterator<String> spliterator = newStream.spliterator();
}
}
//================
All the brilliant terms you haven't seen in java tutorials are killing me. Is there a way to learn more about this?? Reference Pipeline was not even seen in java8 Oracle Docs...
If you take a look at the implementation of the Collection.stream()
method, you would see that this invokes the StreamSupport.stream()
method, which creates a new instance of the concrete static nested class ReferencePipeline.Head
. The two classes ReferencePipeline
and ReferencePipeline.Head
basically provide your stream implementation.
In fact, when you ask about the implementation of the collection iteration, or a better phrasing: the iteration of the stream elements, this is handled by the ReferencePipeline
class. This abstract class contains the implementation of all stream operations (forEach()
, count()
, etc.).
For example, the forEach()
operation iterates non-parallel streams via the Spliterator.forEachRemaining()
method. The count()
operation relies on the evaluateSequential()
or evaluateParallel()
to compute the value via a Spliterator
. As you can see, under the hood, the iteration actually happens in terminal operations via iterators. This is because streams are lazily evaluated, and unless their operations aren't used by a terminal operation, these are not executed at all.
Taking on from @Slaw's comment, I've decided to include a debug example of the count()
terminal operation. Supposing that we have the following line of code List.of(1,2,3).stream().count();
.
When we step into count()
, we can see that the ReferencePipeline.count()
method is invoked.
Inside this method, it is made a call to AbstractPipeline.evaluate()
by passing an implementation of the TerminalOp
interface returned from the ReduceOps.makeRefCounting()
method (this argument passage is important for later).
Once inside evaluate()
, since the stream is not parallel, the method invokes evaluateSequential()
from a TerminalOp
instance.
The instance in question is an anonymous implementation of the ReduceOp
abstract class. Specifically, the exact instance returned by ReduceOps.makeRefCounting()
at step 2.
At this point, we can see that the evaluateSequential()
being executed is the one defined in the anonymous ReduceOp
definition made in the ReduceOps.makeRefCounting()
method.
Inside of evaluateSequential()
, it is made an invocation to the PipelineHelper.exactOutputSizeIfKnown()
method, whose definition is the one inherited from AbstractPipeline.exactOutputSizeIfKnown()
.
Lastly, once we step into AbstractPipeline.exactOutputSizeIfKnown()
, we can see that the size is computed from the getExactSizeIfKnown()
method of the given Spliterator
instance.