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.

2 comments:

  1. Very helpful for Java 8 beginners, appreciate your great work Bharat :)

    ReplyDelete
  2. All are saying the same thing repeatedly, but in your blog I had a chance to get some useful and unique information, I love your writing style very much, I would like to suggest your blog in my dude circle, so keep on updates.school rebranding uk

    ReplyDelete

Ads Inside Post