Pages

Sunday, January 12, 2020

Java 8 Spliterator API - Working Examples

1. Overview

In this tutorial, We'll be talking about Spliterator introduced in the new Java 8 (JDK8). Spliterator is an interface that is designed for bulk or huge datasets and provides the best performance.

Spliterator takes the data from the source and traverses it. Finally, it divides the data into partitions. Source data can be array, any collection or IO Channel such as files.

Spliterator does the partition using trySplit() method as another Spliterator. By partitioning, Operations can be performed parallel.

A Spliterator also reports a set of characteristics() of its structure, source, and elements from among ORDERED, DISTINCT, SORTED, SIZED, NONNULL, IMMUTABLE, CONCURRENT, and SUBSIZED. These may be employed by Spliterator clients to control, specialize or simplify computation.

For example, a Spliterator for a Collection would report SIZED, a Spliterator for a Set would report DISTINCT, and a Spliterator for a SortedSet would also report SORTED.

We will see its syntax and how Spliterator can be instantiated?
This has many useful methods and explanations for each method with example programs.

Java 8 Spliterator API - Working Examples




2. Spliterator Static Fields


This iterator has constants that are declared as static and final in the Spliterator interface. Below is the list of all constants.

public static final int ORDERED    = 0x00000010;
public static final int DISTINCT   = 0x00000001;
public static final int SORTED     = 0x00000004;
public static final int SIZED      = 0x00000040;
public static final int NONNULL    = 0x00000100;
public static final int IMMUTABLE  = 0x00000400;
public static final int CONCURRENT = 0x00001000;
public static final int SUBSIZED = 0x00004000;

Each of these has its own character.

SIZED – if it's capable of returning an exact number of elements with the estimateSize() method
SORTED – if it's iterating through a sorted source
SUBSIZED – if we split the instance using a trySplit() method and obtain Spliterators that are SIZED as well
CONCURRENT – if source can be safely modified concurrently
DISTINCT – if for each pair of encountered elements x, y, !x.equals(y)
IMMUTABLE – if elements held by source can't be structurally modified
NONNULL – if source holds nulls or not
ORDERED – if iterating over an ordered sequence

3. Spliterator Nested Classes


The following are the nested interfaces that are declared as static. To improve the performance, Spliterator has specialized implementations for Int, Double, Long and primitive types.


interface   Spliterator.OfDouble A Spliterator specialized for double values.
interface   Spliterator.OfInt A Spliterator specialized for int values.
interface   Spliterator.OfLong A Spliterator specialized for long values.
interface   Spliterator.OfPrimitive<T,T_CONS,T_SPLITR extends Spliterator.OfPrimitive<T,T_CONS,T_SPLITR>> A Spliterator specialized for primitive values.

4. Spliterator Methods


This interface has a total of 8 methods. Among them, 4 are default methods that have implementation already in its interface.

Default Methods:


forEachRemaining(Consumer<? super T> action)
getComparator()
getExactSizeIfKnown()
hasCharacteristics(int characteristics)

Abstract Methods:


characteristics()
estimateSize()
tryAdvance(Consumer<? super T> action)
trySplit()

5. Spliterator Method Examples


Spliterator has a total of 8 methods. We will be seeing the example program on each method and how to use them properly.

First We'll start with default methods and next is to see one by one abstract method.

5.1 hasCharacteristics() Example

Syntax:

default boolean hasCharacteristics(int characteristics);

Returns boolean if the provided character is supported by Spliterator for the underlying collection.

Example:

Below example, we will find each characteristic on ArrayList, HashSet, and TreeSet.


package com.java.w3schools.blog.java8.iterator.spliterator;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Spliterator;
import java.util.TreeSet;

public class SpliteratorExample {

 public static void main(String[] args) {

// Spliterator characteristics check for List
  List lines = new ArrayList<>();
  lines.add("This is line 1");
  Spliterator listSpliterator = lines.spliterator();
  System.out.println("ArrayList has the following characteristics : ");
  checkCharacteristics(listSpliterator);

// Spliterator characteristics check for Set
  Set setLines = new HashSet();
  setLines.add("This is line 1");
  Spliterator setSpliterator = setLines.spliterator();
  System.out.println("\nHashSet has the following characteristics : ");
  checkCharacteristics(setSpliterator);

// Spliterator characteristics check for SortedSet
  Set sortedSetLines = new TreeSet();
  sortedSetLines.add("This is line 1");
  Spliterator sortedSetSpliterator = sortedSetLines.spliterator();
  System.out.println("\nTreeSet has the following characteristics : ");
  checkCharacteristics(sortedSetSpliterator);

 }

 private static void checkCharacteristics(Spliterator spliterator) {
  if (spliterator.hasCharacteristics(Spliterator.CONCURRENT)) {
   System.out.println("CONCURRENT");
  }

  if (spliterator.hasCharacteristics(Spliterator.DISTINCT)) {
   System.out.println("DISTINCT");
  }

  if (spliterator.hasCharacteristics(Spliterator.IMMUTABLE)) {
   System.out.println("IMMUTABLE");
  }

  if (spliterator.hasCharacteristics(Spliterator.NONNULL)) {
   System.out.println("NONNULL");
  }

  if (spliterator.hasCharacteristics(Spliterator.ORDERED)) {
   System.out.println("ORDERED");
  }

  if (spliterator.hasCharacteristics(Spliterator.SIZED)) {
   System.out.println("SIZED");
  }

  if (spliterator.hasCharacteristics(Spliterator.SORTED)) {
   System.out.println("SORTED");
  }

  if (spliterator.hasCharacteristics(Spliterator.SUBSIZED)) {
   System.out.println("SUBSIZED");
  }
 }

}

Output:

See the output for each collection characteristics are produced correctly by SplitIterator.

ArrayList has the following characteristics : 
ORDERED
SIZED
SUBSIZED

HashSet has the following characteristics : 
DISTINCT
SIZED

TreeSet has the following characteristics : 
DISTINCT
ORDERED
SIZED
SORTED

5.2 getExactSizeIfKnown() Example


getExactSizeIfKnown() method returns exact size for the collection is SIZED. Otherwise it returns -1 value.

Syntax:

default long getExactSizeIfKnown()

This is declared as a default method and returns long value for the count.

Internally this method calls estimatedSize() mehtod.

default long getExactSizeIfKnown() {
    return (characteristics() & SIZED) == 0 ? -1L : estimateSize();
}

Example:

import java.util.ArrayList;
import java.util.List;
import java.util.Spliterator;
import java.util.stream.IntStream;

/**
 * Spliterator getExactSizeIfKnown() Example
 * 
 * @author Venkatesh
 *
 */
public class SpliteratorGetExactSizeIfKnownExample {

 public static void main(String[] args) {

  List lines = new ArrayList<>();

  IntStream.range(0, 100000).forEach(i -> lines.add("Line number " + i));

  System.out.println("Original list size " + lines.size());

  Spliterator linesIterator = lines.spliterator();
  long estimatedSize = linesIterator.getExactSizeIfKnown();
  System.out.println("lines spliterator count for getExactSizeIfKnown() : " + estimatedSize);

  Spliterator splittedLinesIterator = linesIterator.trySplit();
  long splittedLinesIteratorCount = splittedLinesIterator.getExactSizeIfKnown();
  System.out.println(
    "lines splittedLinesIteratorCount count for getExactSizeIfKnown() : " + splittedLinesIteratorCount);
 }
}

Output:

Original list size 100000
lines spliterator count for getExactSizeIfKnown() : 100000
lines splittedLinesIteratorCount count for getExactSizeIfKnown() : 50000

This method works as same as estimatedSize() method.

5.3 forEachRemaining() Example


forEachRemaining() method takes action as an argument and performs the action on each element of the iterator. Action is performed in sequential order by the current thread if the collection is ORDERED. If any exception is thrown then it skips all remaining elements from the collection.

Note: Action is Consumer Functional Interface which has functional method accept(). If the specified action is null then it will throw NullPointerException

Syntax:

default void forEachRemaining(Consumer<? super T> action)

This is also a default method, just performs the given operation. It doesn't return any value.

Example:


Example:
import java.util.HashSet;
import java.util.Set;
import java.util.Spliterator;
import java.util.stream.IntStream;

/**
 * Spliterator forEachRemaining() Example
 * 
 * @author Venkatesh
 *
 */
public class SpliteratoForEachRemainingExample {

 public static void main(String[] args) {

  Set lines = new HashSet<>();

  IntStream.range(0, 10).forEach(i -> lines.add("Line number " + i));

  System.out.println("Original list size " + lines.size());

// Main spliterator
  Spliterator linesIterator = lines.spliterator();

// Splitted spliterator
  Spliterator splittedLinesIterator = linesIterator.trySplit();

  System.out.println("Elements processed by main iterator");
  linesIterator.forEachRemaining(value -> System.out.println(value));

  System.out.println("Elements processed by splitted iterator 1");
  splittedLinesIterator.forEachRemaining(value -> System.out.println(value));
 }
}

Output:

Original list size 10
Elements processed by main iterator
Line number 1
Line number 2
Line number 0
Elements processed by splitted iterator 1
Line number 9
Line number 7
Line number 8
Line number 5
Line number 6
Line number 3
Line number 4

5.4 getComparator() Example


getComparator() method returns Comparator that is used by SplitIterator source. If the SplitIterator's source is SORTED in the natural order, returns null.
Otherwise, if the source is not SORTED, throws IllegalStateException.

ArrayList is not sorted by default and if we call getComparator() then it throws IllegalStateException. Same for HashSet, LinkedHashSet, LinkedList which are not sorted.

Syntax:

default Comparator<? super T> getComparator()

This is also a default mehtod that returns Comparator that is used by the collection.

Example:

public class SpliteratoGetComparatorExample {

public static void main(String[] args) {

 Set<String> lines = new HashSet<>();
 lines.add("Line 1");
 
 Spliterator<String> linesSpliterator = lines.spliterator();
 Comparator<String> setComparator = (Comparator<String>) linesSpliterator.getComparator();
 System.out.println(setComparator);
 
 }
}

Output:

Because HashSet is not sorted and thrown IllegalStateException as expected.

Exception in thread "main" java.lang.IllegalStateException
at java.base/java.util.Spliterator.getComparator(Spliterator.java:465)
at com.java.w3schools.blog.java8.iterator.spliterator.SpliteratoGetComparatorExample.main(SpliteratoGetComparatorExample.java:23)

Now, We will see the example on TreeSet and what it returns.

Set<String> seasons = new TreeSet<>();
seasons.add("Summer");
seasons.add("Fall");
seasons.add("Snow");

Spliterator<String> seasonsSpliterator = seasons.spliterator();
Comparator<String> treeSetComparator = (Comparator<String>) seasonsSpliterator.getComparator();
System.out.println(seasons);
System.out.println(treeSetComparator);

Output:

[Fall, Snow, Summer]
null

Printed null because TreeSet is sorted in natural or ascending order.

Now make it to Desending order and it prints the compartor used for sorting.

Spliterator<String> reverseSpliterator = seasons2.spliterator();
Comparator<String> treeSetReverseComparator = (Comparator<String>) reverseSpliterator.getComparator();
System.out.println(seasons2);
System.out.println(treeSetReverseComparator);

Output:

[Summer, Snow, Fall]
java.util.Collections$ReverseComparator@4a574795

Note: IllegalStateException will be thrown if the spliterator does not report a characteristic of SORTED.


5.5 estimateSize() Example


estimateSize() method returns size of the underlying collection that would be processed by forEachRemaining() method. If the count is invalid or unknown or to expensive to compute then will return  Long.MAX_VALUE( 2^263-1).

Sometimes size value may be differed if the Spliterator is split by using trySplit() method. The count must be lesser than its actual size which is determined by getExactSizeIfKnown() method.

Syntax:

long estimateSize()

Returns long value that holds the size of the spliterator.

Example:

Example program to get the estimate size for list spliterator and splitted spliterator count.

When we call trySplit() method it split the current iterator into two halfs. The first half by current iterator and second half are assigned to a new SplitIterator.


import java.util.ArrayList;
import java.util.List;
import java.util.Spliterator;
import java.util.stream.IntStream;

/**
 * Spliterator estimateSize() Example
 * 
 * @author Venkatesh
 *
 */
public class SpliteratorEstimateSizeExample {

 public static void main(String[] args) {
 
  List<String> lines = new ArrayList<>();
  
  IntStream.range(0, 100000).forEach(i -> lines.add("Line number " + i));
  
  System.out.println("Original list size " + lines.size());
  
  Spliterator<String> linesIterator = lines.spliterator();
  long estimatedSize = linesIterator.estimateSize();
  System.out.println("lines spliterator count for estimateSize() : " + estimatedSize);
  
  Spliterator<String> splittedLinesIterator = linesIterator.trySplit();
  long splittedLinesIteratorCount = splittedLinesIterator.estimateSize();
  System.out.println("lines splittedLinesIteratorCount count for estimateSize() : " + splittedLinesIteratorCount);
 }
}

Output:

Original list size 100000
lines spliterator count for estimateSize() : 100000
lines splittedLinesIteratorCount count for estimateSize() : 50000

5.6 trySplit() Example


trySplit() method checks if this spliterator can be partitioned. If yes, it returns a new SplitIterator that covers a portion of elements from this SplitIterator.

Syntax:

Spliterator<T> trySplit()

Returns SplitIterator.

Example:

/**
 * Spliterator trySplit() Example
 * 
 * @author Venkatesh
 *
 */
public class SpliteratoTrySplitExample {

 public static void main(String[] args) {
  
  Set<String> countires = new HashSet<>();
  countires.add("USA");
  countires.add("INDIA");
  countires.add("UK");
  countires.add("AUS");
  countires.add("NZ");
  countires.add("PAK");
  countires.add("Canada");
  
  Spliterator<String> countriesSpliterator = countires.spliterator();
  Spliterator<String> splittedIterator = countriesSpliterator.trySplit();
  
  System.out.println("countires list : " + countires);
  countriesSpliterator.forEachRemaining(value -> System.out.println(value));
  System.out.println("-----------------");
  splittedIterator.forEachRemaining(value -> System.out.println(value));  
 
 }
}

Output:

countires list : [Canada, USA, UK, PAK, NZ, INDIA, AUS]
PAK
NZ
INDIA
AUS
-----------------
Canada
USA
UK

If this Spliterator is ORDERED, the returned Spliterator must cover a strict prefix of the elements.

Set<String> countires = new TreeSet<>();

See the below output for the same above program. Just changed HashSet to TreeSet.

countires list : [AUS, Canada, INDIA, NZ, PAK, UK, USA]
UK
USA
-----------------
AUS
Canada
INDIA
NZ
PAK

5.7 tryAdvance() Example


tryAdvance() method is used to get the next element from the collection if it is available. This takes Consumer as an argument and it just performs the action.
It checks logic hasNext() and next() operations in one method to reduce the boilerplate coding. Because of this, tryAdvance() method came into existance. And also if your collection is operated by multiple threads then tryAdvance() method ensure that if one thread gets value from iterator then the current thread will get the next value. This method is also designed to work in a multithreading enviornment.

It will be fetching the elements in sequential order if single thread is accessing.
Parallel processing is achieved by multipl threads.

Syntax:

boolean tryAdvance(Consumer<? super T> action)

This method takes Consumer Functional Interface and returns true if it has next element, false otherwise.

Single Thread tryAdvance() Example:


/**
 * Spliterator tryAdvance() Example
 * 
 * @author Venkatesh
 *
 */
public class SpliteratoTryAdvanceExample {
 
 public static void main(String[] args) {
  // Set<String> countires = new TreeSet<>();
  Set<String> states = new HashSet<>();
  states.add("Alabama");
  states.add("Hawaii");
  states.add("Alaska");
  states.add("Michigan");
  states.add("Vermont");
  states.add("Wisconsin");
  states.add("Texas");
  
  System.out.println("Current thread name: " + Thread.currentThread().getName());
  System.out.println("States Set : " + states);
  Spliterator<String> statesSpliterator = states.spliterator();
  int count = 0;
  while (statesSpliterator.tryAdvance(stateName -> System.out.println("US State Name : " + stateName))) {
  count++;
  }
  
  System.out.println("Total States : " + count);
  
 }
}

Output:

Current thread name: main
States Set : [Vermont, Texas, Hawaii, Alaska, Alabama, Wisconsin, Michigan]
US State Name : Vermont
US State Name : Texas
US State Name : Hawaii
US State Name : Alaska
US State Name : Alabama
US State Name : Wisconsin
US State Name : Michigan
Total States : 7

Multiple Threads Using tryAdvance() Example:

import java.util.ArrayList;
import java.util.List;
import java.util.Spliterator;

/**
 * Spliterator tryAdvance() MultiThread Example
 * 
 * @author Venkatesh
 *
 */
public class SpliteratoTryAdvanceMultiThreadExample {

 public static void main(String[] args) {
  // Set<String> countires = new TreeSet<>();
  List<String> states = new ArrayList<>();
  states.add("Alabama");
  states.add("Hawaii");
  states.add("Alaska");
  states.add("Michigan");
  states.add("Vermont");
  states.add("Wisconsin");
  states.add("Texas");

  System.out.println("Main thread name: " + Thread.currentThread().getName());
  System.out.println("States Set : " + states);
  Spliterator<String> statesSpliterator = states.spliterator();

  // Thread 1
  Thread thread1 = new Thread(new Runnable() {

   @Override
   public void run() {

    while (statesSpliterator.tryAdvance(state -> System.out
      .println("Thread Name: " + Thread.currentThread().getName() + ", State Name : " + state))) {
     try {
      Thread.sleep(500);
     } catch (InterruptedException e) {
      e.printStackTrace();
     }
    }
    System.out.println("Thread 1 done");

   }
  });

  // Thread 2
  Thread thread2 = new Thread(new Runnable() {

   @Override
   public void run() {

    while (statesSpliterator.tryAdvance(state -> System.out
      .println("Thread Name: " + Thread.currentThread().getName() + ", State Name : " + state))) {
     try {
      Thread.sleep(500);
     } catch (InterruptedException e) {
      e.printStackTrace();
     }
    }
    System.out.println("Thread 2 done");

   }
  });

  thread1.start();
  thread2.start();
  System.out.println("Main thread done");

 }
}

Output:

See the output order of elements that processed by each thread. Thread 2 picked the elements from thread 1 already processed. We no need to write external synchronization.

Main thread name: main
States Set : [Alabama, Hawaii, Alaska, Michigan, Vermont, Wisconsin, Texas]
Main thread done
Thread Name: Thread-0, State Name : Alabama
Thread Name: Thread-1, State Name : Hawaii
Thread Name: Thread-1, State Name : Alaska
Thread Name: Thread-0, State Name : Michigan
Thread Name: Thread-1, State Name : Vermont
Thread Name: Thread-0, State Name : Wisconsin
Thread Name: Thread-1, State Name : Texas
Thread 1 done
Thread 2 done

6. Custom SplitIterator


If we want to create our own or custon SplitIterator then our class must implement SplitIterator interface and must override the following abstract mehtods.

characteristics()
estimateSize()
tryAdvance(Consumer<? super T> action)
trySplit()

Custom Spliterator Example:

/**
 * Custom Spliterator Example
 * 
 * @author Venkatesh
 *
 */
public class CustomSplitIteratorExample {

 public static void main(String[] args) {

 }
}

class Customer {
 private int id;
 private String name;

 public int getId() {
  return id;
 }

 public void setId(int id) {
  this.id = id;
 }

 public String getName() {
  return name;
 }

 public void setName(String name) {
  this.name = name;
 }

}



class CustomSpliterator implements Spliterator<Customer> {

 @Override
 public boolean tryAdvance(Consumer<? super Customer> action) {
// Related custom logic
  return false;
 }

 @Override
 public Spliterator<Customer> trySplit() {
// Related custom logic
  return null;
 }

 @Override
 public long estimateSize() {
// Related custom logic
  return 0;
 }

 @Override
 public int characteristics() {
// Related custom logic
  return 0;
 }

}

7. Conclusion


In this article, We've covered Java 8 SplitIterator usage and its methods with working examples. SplitIterator provides a specialized way of processing primitive types.

And also written code to implement custom SplitIterator.


Shown examples are over GitHub

Java 8 Examples


No comments:

Post a Comment

Please do not add any spam links in the comments section.