Saturday, July 9, 2016

Part 3 : Immutable Collectors - Bridging Google Guava and Java 8.

Part 3 Immutable Collectors - Bridging Google Guava and Java 8.

This is third part of the Collectors. First we discussed how to create custom collectors for Collections with initial capacity for list implementations. Then we discussed how to create custom collectors for Collections with initial capacity for set implementations. This is third part of creating custom collectors for Immutable Collections from Google Guava library.

Let us quickly revise what Collector is and what are its methods.

Collector: It is an interface that defines the contract for mutable reduction. Mutable reduction means that elements from stream are to be collected into some source. Typically, this source is Collection but with Collector as interface we can implement custom collectors.

Method of Collector interface:
1.     Supplier<A> supplier() – Supplier creates and returns the new mutable result container.
2.   BiConsumer<A, T> accumulator() – accumulator is used to insert a value into the mutable container.
3.  BinaryOperator<A> combiner() – combiner is used to accept two partial result and combine it together.
4.  Function<A, R> finisher() – finisher performs final transformation from intermediate accumulation type A to type R.
5. Set<Characteristics> characteristics() – characteristics is used to determine the characteristics of this Collector.

Let us write a Collector for ImmutableList and understand how we can use it.

Let us name our class as ImmutableListCollector<T>.
The method that will return the Collector is toListCollector().

The method toListCollector() is supposed to return Collector but then what will be the type of Collector?

Collector has 3 type parameters

public interface Collector<T, A, R>

T – type of input elements to reduction operation.
A – the mutable accumulation type of reduction operation.
R – the result type of reduction operation.

Let us define type parameters for ImmutableList collector.

T – as T defines the type of input elements we will have T as our first type parameter.
A – A defines mutable accumulation type which in our case will be ImmutableList.Builder<T>
R – R means result type of reduction operation which in our case will be ImmutableList<T>

Great now we have our return type Collector as
Collector<T, ImmutableList.Builder<T>, ImmutableList<T>>

We will use public static method of Collector interface called of().This method accepts parameters as Supplier, BiConsumer, BinaryOperator, Function and Characteristics. Do these parameters sound familiar? They are the abstract method’s return types. Scroll up and read about it once again to get better understanding.

Enough talk lets write some code. What I will do is I will write entire method and then explain it step by step.


/**
 * @returns a {@link Collector} that collects data in
 * {@link ImmutableList}.
 *
 * @param <T> The type of input elements for the new collector.
 * */
public static <T>
Collector<T, ImmutableList.Builder<T>, ImmutableList<T>>
toListCollector() {
             
       return
                     Collector.of(
                           ImmutableList.Builder<T>::new,
                           ImmutableList.Builder<T>::add,
                           (left, right) -> left.addAll(right.build()),
                           ImmutableList.Builder<T>::build
                     );
}

Now let us understand the above method in terms of Collector interface’s methods.

Method of Collector interface:
1.     Supplier<A> supplier() – Supplier creates and returns the new mutable result container.
Collector.of(..) method’s first parameter is a Supplier. So Supplier for this Collector will be new instance of ImmutableList.Builder<T>.

2.    BiConsumer<A, T> accumulator() – accumulator is used to insert a value into the mutable container.  Collector.of(..) method’s second parameter is BiConsumer. So BiConsumer for this Collector will add one value to the provided Supplier i.e. ImmutableList.Builder<T>::add.
3.  BinaryOperator<A> combiner() – combiner is used to accept two partial result and combine it together. Combiner combines two partial result in our case it is left and right builders. So combine them by (left, right) -> left.addAll(right.build()).

4.  Function<A, R> finisher() – finisher performs final transformation from intermediate accumulation type A to type R. The final transformation from type A i.e. ImmutableList.Builder<T> to type R i.e. ImmutableList<T>. This is done by ImmutableList.Builder<T>::build as built method in ImmutableList.Builder<T> class return ImmutableList<T>.

5. Set<Characteristics> characteristics() – characteristics is used to determine the characteristics of this Collector. We are not providing any Characteristics.



Below is entire class ImmutableListCollector<T>.

import java.util.stream.Collector;
import java.util.stream.Collector.Characteristics;

import com.google.common.collect.ImmutableList;

public final class ImmutableListCollector<T> {
      
     /**
      * @returns a {@link Collector} that collects data in
      * {@link ImmutableList}.
      *
      * @param <T> The type of input elements for the new collector.
      * */
     public static <T> Collector<T, ImmutableList.Builder<T>, ImmutableList<T>>
     toListCollector() {
             
              return
                     Collector.of(
                           ImmutableList.Builder<T>::new,
                           ImmutableList.Builder<T>::add,
                           (left, right) -> left.addAll(right.build()),
                           ImmutableList.Builder<T>::build
                     );
       }

}

Now the question arises that we have built this Collector how to use it?

Ok Let us assume that we have List<Person> and we want an ImmutableList<String> that will hold name of all Person objects.

ImmutableList<String> names = 
                   people.stream()
                         .map(Person::getName)
                         .collect(ImmutableListCollector.toListCollector());

 You can also make a static import of class ImmutableListCollector and use the method directly in collect method.

import static ImmutableListCollector.toListCollector;

ImmutableList<String> namespeople.stream()
                                    .map(Person::getName)
                                    .collect(toListCollector());

I have written several Collectors at this GitHub repository. This article explains you to write custom collector for ImmutableList and hence you can write your own Collectors for ImmutableSet, ImmutableSortedSet, and others.

In next article we will see how to write a Collector for class that implements Map interface. For example, ImmutableMap class.

Just for the fun, I would like to provide few collectors for Google Guava.

// ImmutableSetCollector
public final class ImmutableSetCollector<T> {

    /**
     * @returns a {@link Collector} that collects data in
     * {@link ImmutableSet}.
     *
     * @param <T> The type of input elements for the new
     * collector.
     * */
       public static <T> Collector<T, ImmutableSet.Builder<T>, ImmutableSet<T>>
       toSetCollector() {
             
              return
                     Collector.of(
                            ImmutableSet.Builder<T>::new,
                            ImmutableSet.Builder<T>::add,
                            (left, right) -> left.addAll(right.build()),
                            ImmutableSet.Builder<T>::build);
       }
}


// ImmutableSortedMultisetCollector
public final class ImmutableSortedMultisetCollector<T extends Comparable<?>> {

 /**
  * @returns a {@link Collector} that collects data in
  * {@link ImmutableSortedMultiset} whose elements are
  * ordered by their natural ordering.
  *
  * @param <T> The type of input elements for the new
  * collector.
  * */
  public static <T extends Comparable<?>>
  Collector<T, ImmutableSortedMultiset.Builder<T>, ImmutableSortedMultiset<T>>
  toImmutableSortedMultisetCollector() {
         return
                Collector.of(
                       ImmutableSortedMultiset::naturalOrder,
                       (c, v) -> c.add(v),
                       (c1, c2) -> c1.addAll(c2.build()),
                       ImmutableSortedMultiset.Builder<T>::build);
     }
      
  /**
   * @returns a {@link Collector} that collects data in
   * {@link ImmutableSortedMultiset} whose elements are
   * ordered by reverse of the their natural ordering.
   *
   * @param <T> The type of input elements for the new
   * collector.
   * */
  public static <T extends Comparable<?>>
  Collector<T, ImmutableSortedMultiset.Builder<T>, ImmutableSortedMultiset<T>>
  toReverseImmutableSortedMultisetCollector() {
         return
                Collector.of(
                       ImmutableSortedMultiset::reverseOrder,
                       (c, v) -> c.add(v),
                       (c1, c2) -> c1.addAll(c2.build()),
                       ImmutableSortedMultiset.Builder<T>::build);
       }
}

// ImmutableSortedSetCollector
public final class ImmutableSortedSetCollector<T extends Comparable<?>> {

    /**
     * @returns a {@link Collector} that collects data in
     * {@link ImmutableSortedSet} whose elements are
     * ordered by their natural ordering.
     *
     * @param <T> The type of input elements for the new
     * collector.
     * */
       public static <T extends Comparable<?>>
       Collector<T, ImmutableSortedSet.Builder<T>, ImmutableSortedSet<T>>
       toImmutableSortedSetCollector() {
              return
                     Collector.of(
                            ImmutableSortedSet::naturalOrder,
                            (c, v) -> c.add(v),
                            (c1, c2) -> c1.addAll(c2.build()),
                            ImmutableSortedSet.Builder<T>::build);
       }
      
    /**
     * @returns a {@link Collector} that collects data in
     * {@link ImmutableSortedSet} whose elements are
     * ordered by reverse of the their natural ordering.
     *
     * @param <T> The type of input elements for the new
     * collector.
     * */
       public static <T extends Comparable<?>>
       Collector<T, ImmutableSortedSet.Builder<T>, ImmutableSortedSet<T>>
       toReverseImmutableSortedSetCollector() {
              return
                     Collector.of(
                            ImmutableSortedSet::reverseOrder,
                            (c, v) -> c.add(v),
                            (c1, c2) -> c1.addAll(c2.build()),
                            ImmutableSortedSet.Builder<T>::build);
       }

}


If there are any mistakes or better way to do it, please mention in comments.

Part 2 : Writing custom collectors for Set implementations


The class CollectionCollector remains the same. We discussed class ToListCollector which creates a Collector of type ArrayList with initial capacity.

The perquisite to this article is previous article. The previous article will give you basics that we will use it in this article.
In this post will discuss ToSetCollectors for HashSet (with initial capacity), LinkedHashSet (with initial capacity), TreeSet (with Comparator).

For this custom collectors we will design 2 classes in fact, we will design just 1.
1.       CollectionCollector.java : We saw this class in previous post. You can refer previous post for this class. You can also view this class at Github.
2.       ToSetCollectors.java : This is a new class that will 3 methods. Methods are as follows:
a.       toHashSet(int initialcapacity)
b.       toLinkedHashSet(int initialCapacity)
c.       toTreeSet(Comparator<? super T> comparator)

Let us discuss ToSetCollectors class now.

Before we start discussing the methods of class ToSetCollectors we will define characteristics that we will use for Collectors.

private static final Set<Collector.Characteristics> CH_ID = Collections.unmodifiableSet(EnumSet.of(
Collector.Characteristics.IDENTITY_FINISH));
     
private static final Set<Collector.Characteristics> CH_UNORDERED_ID = Collections.unmodifiableSet(EnumSet.of(
Collector.Characteristics.UNORDERED,  Collector.Characteristics.IDENTITY_FINISH));

1.       toHashSet() method: Collectors.toSet() method allows you to collect elements from Stream in HashSet that Collector does not place any initial capacity while creating the HashSet. The capability of toHashSet method in class ToSetCollectors is that you can provide the initial capacity of HashSet.

       /**
       * Create a {@link Collector} of type {@link HashSet}
       * with initial capacity.
       *
       * @param <T> type of input elements for new Collector
       * */
      public static <T> Collector<T, ?, Set<T>> toHashSet(
                  final int initialCapacity) {
           
            return
                        CollectionCollector.toCollection(
                                    initialCapacity,
                                    HashSet::new,
                                    CH_UNORDERED_ID);
      }

The method just accepts the initial capacity as parameter. This method in turn calls CollectionCollector class’s method toCollection() that accepts initial capacity, IntFunction and Set<Collector.Characteristics> as parameters.

NOTE: IntFunction is a functional interface that represents an int-valued arguments and produces a result. And why do we care about it? Because it accepts the int-valued argument in our case it will be initial capacity and returns a result in our case it will be for example new ArrayList<>(initialCapacity).

Usage:

Assume you have List<Country> called countries. Now you want to add all Country names into a set. We can write something like this:

countries.map(Country::getName)
         .stream()
         .collect(ToSetCollectors.toHashSet(50));

2.       toLinkedHashSet(): This method is almost same as previous method.

      /**
       * Create a {@link Collector} of type {@link LinkedHashSet}
       * with initial capacity.
       *
       * @param <T> type of input elements for new Collector
       * */
      public static <T> Collector<T, ?, Set<T>> toLinkedHashSet(
                  final int initialCapacity) {
           
            return
                        CollectionCollector.toCollection(
                                    initialCapacity,
                                    LinkedHashSet::new,
                                    CH_ID);
      }

Usage:

Assume you have List<Country> called countries. Now you want to add all Country names into an ordered set which maintains insertion order. We can write something like this:

countries.map(Country::getName)
         .stream()
         .collect(ToSetCollectors.toLinkedHashSet(50));


3.       toTreeSet(): This method accepts Comparator as a parameter and not initial capacity. TreeSet class does not have any constructor that accepts initial capacity. This method will directly call CollectionCollector class’s toCollection method with 2 parameters. The 2 parameters are Supplier and Set<Collection.Characteristics>.
      /**
       * Create a {@link Collector} of type {@link TreeSet}
       *
       * All elements inserted into implement the
       * {@link Comparator} interface.
       *
       * @param <T> type of input elements of new Collector
       * */
      public static <T> Collector<T, ?, NavigableSet<T>> toTreeSet(
                  final Comparator<? super T> comparator) {
           
            return
                        CollectionCollector.toCollection(
                                    () -> new TreeSet<>(comparator),
                                    CH_ID);
      }

Usage:

Assume you have List<Country> called countries. Now you want to add all Country names into an ordered set which maintains elements by Comparator. We can write something like this:

countries.map(Country::getName)
         .stream()
         .collect(ToSetCollectors.toLinkedHashSet(50));


Entire class ToSetCollectors is below:

import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.NavigableSet;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collector;

/**
 * {@link ToSetCollectors} is used to create {@link Collector}
 * that accepts initial capacity.
 * */
public final class ToSetCollectors {

       private static final Set<Collector.Characteristics> CH_ID =       Collections.unmodifiableSet(EnumSet.of(
Collector.Characteristics.IDENTITY_FINISH));
     
       private static final Set<Collector.Characteristics> CH_UNORDERED_ID =         Collections.unmodifiableSet(EnumSet.of(
Collector.Characteristics.UNORDERED,  Collector.Characteristics.IDENTITY_FINISH));
     
      /**
       * Create a {@link Collector} of type {@link HashSet}
       * with initial capacity.
       *
       * @param <T> type of input elements for new Collector
       * */
      public static <T> Collector<T, ?, Set<T>> toHashSet(
                  final int initialCapacity) {
           
            return
                        CollectionCollector.toCollection(
                                    initialCapacity,
                                    HashSet::new,
                                    CH_UNORDERED_ID);
      }
     
      /**
       * Create a {@link Collector} of type {@link LinkedHashSet}
       * with initial capacity.
       *
       * @param <T> type of input elements for new Collector
       * */
      public static <T> Collector<T, ?, Set<T>> toLinkedHashSet(
                  final int initialCapacity) {
           
            return
                        CollectionCollector.toCollection(
                                    initialCapacity,
                                    LinkedHashSet::new,
                                    CH_ID);
      }
     
      /**
       * Creates a {@link Collector} of type {@link TreeSet}
       *
       * All elements inserted into implement the
       * {@link Comparable} interface.
       *
       * @param <T> type of input elements for new Collector
       * */
      public static <T> Collector<T, ?, NavigableSet<T>> toTreeSet() {
           
            return
                        CollectionCollector.toCollection(
                                    TreeSet::new,
                                    CH_ID);
      }
     
      /**
       * Create a {@link Collector} of type {@link TreeSet}
       *
       * All elements inserted into implement the
       * {@link Comparator} interface.
       *
       * @param <T> type of input elements of new Collector
       * */
      public static <T> Collector<T, ?, NavigableSet<T>> toTreeSet(
                  final Comparator<? super T> comparator) {
           
            return
                        CollectionCollector.toCollection(
                                    () -> new TreeSet<>(comparator),
                                    CH_ID);
      }
}

In next post we will see Collectors of Google Guava Library.

If there are any mistakes or better way to do it, please mention in comments.


Ads Inside Post