Skip to content

Crafting Custom Spliterators. Navigating the World of Custom Data Sources

Posted on:September 1, 2023 at 09:13 PM

Greetings, fellow coders! In this lecture, we’re going to dive into the art of creating a custom Spliterator that will allow us to generate a stream of custom objects from a custom data source. Are you ready to unravel the mystery? Let’s get started!

Table of contents

Open Table of contents

Sections

The Challenge: Reading Custom Objects from a File

Imagine we have a file containing data for books, and our objective is to create a stream of book objects from this file. The catch is that the lines method from the NIO package only provides us with a stream of lines from the file. We need a Spliterator that can help us build a stream of book objects.

Bridging the Gap with Custom Spliterators

To overcome this challenge, we’ll create our own Spliterator, which we’ll aptly name the BookSpliterator. This custom Spliterator will serve as the bridge between the lines we get from the base Spliterator (which reads the file line by line) and our desired stream of book objects.

Now, let’s start crafting the BookSpliterator. First, we need to implement four key methods:

  1. tryAdvance(Consumer<? super Book> action) In this method, we call the tryAdvance method on the base Spliterator (baseSpliterator) to read lines from the stream. We extract the fields for the book object (name, author, genre, and rating) from these lines and set them in the current book object. Finally, we invoke the accept method on the provided action to accept the book object.
@Override
    public boolean tryAdvance(Consumer<? super Book> action) {
        if(this.baseSpliterator.tryAdvance(name -> this.name = name) &&
                this.baseSpliterator.tryAdvance(author -> this.author = author) &&
                this.baseSpliterator.tryAdvance(genre -> this.genre = genre) &&
                this.baseSpliterator.tryAdvance(rating -> this.rating = Double.valueOf(rating))) {

            action.accept(new Book(this.name, this.author, this.genre, this.rating));
            return true;

        }
        return false;
    }
  1. trySplit() As we don’t intend to process this Spliterator in parallel, we simply return null. Parallel processing is a topic for another day.

  2. estimateSize() The estimateSize method estimates the size of the stream that will be created. Since the base Spliterator reads lines, and each book consists of four lines, we divide the size of the base Spliterator by four to get the size of our custom Spliterator.

@Override
    public long estimateSize() {
        return baseSpliterator.estimateSize() / 4;
    }
  1. characteristics() For this method, we can directly return the characteristics of the base Spliterator. This way, our custom Spliterator inherits the same characteristics.

Using the Custom Spliterator

With our BookSpliterator in place, we can now use it to create a stream of book objects. To achieve this, we utilize the StreamSupport.stream method. It takes our BookSpliterator and a boolean parameter (parallel) that indicates whether we want to process the stream in parallel. In this case, we set it to false.

Spliterator<String> baseSpliterator = lines.spliterator();
Spliterator<Book> spliterator= new BookSpliterator(baseSpliterator);

Stream<Book> stream = StreamSupport.stream(spliterator, false);

stream.forEach(System.out::println);

Time to Witness the Magic!

Finally, we print the elements present in the stream using the forEach method. The result? A stream of book objects beautifully extracted from our custom data source, the file.

And there you have it! We’ve just crafted our very own custom Spliterator to read and process data from a unique source. This newfound knowledge opens up exciting possibilities for handling various data structures and sources efficiently.

You can find the repo for this section of the course Here