Skip to content

Streams and Iterables

In this short article, we'll explore various StreamSupport methods that allow us to wrap an Iterable instance as a stream of elements.

Sumit Gaur
Sumit Gaur
2 min read
Streams and Iterables
Photo by JJ Ying / Unsplash

Quoting Wikipedia, the iterator design pattern is used to traverse a container for accessing its elements. But with the introduction of streams in java8, we now can leverage parallel processing utilizing the underlying hardware to its maximum potential.

In this post, we'll explore the StreamSupport class and its various methods that help us to wrap an iterable instance as a stream of elements.

Iterables and stream operations

The Iterable interface does not have any stream() method, so while the overall purpose at a high level remains the same, i.e., to traverse the elements, we cannot directly use an iterator to generate a stream for our custom use-cases.

Having said that, we can still wrap an iterator instance using one of the utility methods in StreamSupport class to generate a stream of underlying elements in the container:

StreamSupport.stream(iterable.spliterator(), false)
Wrapping an Iterable instance as a Stream

But there could be scenarios where we have a custom iteration logic that we want to wrap as a stream like accessing a ResultSet instance while it has more records or maybe reading a String via nextLine() from a BufferredReader instance while it has data to offer.

Let's see a quick example for the same:

Imagine a custom LinkedList data structure implementation where the nodes are connected via the next reference. The following code can be used to create a stream of the underlying elements:

public Stream<E> stream(){
    return StreamSupport.stream(Spliterators.spliteratorUnknownSize(new Iterator<>(){
        Node temp = head;
        @Override
        public boolean hasNext(){
            return temp != null;
        }
        @Override
        public E next(){
            E data = temp.data;
            temp = temp.next;
            return data;
        }
    }, Spliterator.ORDERED), false);
}
Using StreamSupport to wrap an Iterator instance in a Stream - openjdk@1.16.0

The hasNext() method will check if the temp node is pointing to a null value or not, and accordingly, the presence of the next element is derived.

Similarly, we return the data at the current node every time the next() method is called. The temp reference is then shifted one node ahead to traverse the next node.

Using a Spliterator

Similarly, we can use a Spliterator to generate the stream. Using the same example:

public Stream<E> stream(){
    return StreamSupport.stream(new Spliterator<E>(){
        Node temp = head;
        @Override
        public boolean tryAdvance(Consumer<? super E> action){
            if(temp != null){
                action.accept(temp.data);
                temp = temp.next;
                return true;
            }
            return false;
        }
        @Override
        public Spliterator<E> trySplit(){
            return null;
        }
        @Override
        public long estimateSize(){
            return size;
        }
        @Override
        public int characteristics(){
            return Spliterator.ORDERED;
        }
    }, false);
}
Using StreamSupport to wrap a Spliterator instance in a Stream - openjdk@1.16.0

In the above example, we can see a similar implementation done using a Spliteraor. The tryAdvance() method will feed the current data to the Consumer instance action , moving the temp reference one node ahead afterward. In case the temp already points to a null value, false is returned.

You can check the complete example in the Github repository.

That is all for this post. If you want to share any feedback, please drop me an email, or contact me on any social platforms. I’ll try to respond at the earliest. Also, please consider subscribing for regular updates.

CollectionsDesign ForumHow To

Sumit Gaur

A passionate java developer and open source advocate, who understands the value of continuous learning and sharing knowledge.


Related Posts

Members Public

Lambda Expressions and Exception Handling

This short article will explore a few options to handle checked and unchecked exceptions in lambda expressions.

Lambda Expressions and Exception Handling
Members Public

Implementing a Custom Thread Pool

In this article, we'll explore some multithreading concepts by writing our very own custom thread pool to manage several tasks and threads.

Implementing a Custom Thread Pool
Members Public

Checked Collection - stronger type safety

CheckedCollection helps to enforce strong type safety when mixing generic and non-generic code.

Checked Collection - stronger type safety