Friday, January 14, 2022

Java Tutorial: Collector Interface

Chapters

Collector Interface

Collector is a mutable reduction operation that accumulates input elements into a mutable result container, optionally transforming the accumulated result into a final representation after all input elements have been processed. Reduction operations can be performed either sequentially or in parallel.

The class Collectors provides implementations of many common mutable reductions. Aside from pre-built methods from Collectors class, we can create our own Collector method. Note that my explanation here is simplified. If you're planning to create your own Collector then, you need to read the documentation for full details.

This example demonstrates Collector Interface methods like of(), characteristics() and finisher().
import java.util.stream.Collector;

@SuppressWarnings({"unchecked"})
public class SampleClass{

  public static void main(String[] args){
    
    Collector c = 
    Collector.of(StringBuilder::new,
                 StringBuilder::append,
                 (l,r) -> l.append(r.toString()));
    
    System.out.println("Characteristics");
    System.out.println(c.characteristics());
    
    StringBuilder builder = null;
    Object cont = c.supplier().get();
    
    if(cont instanceof StringBuilder)
       builder = (StringBuilder)cont;
    else System.exit(1);
    
    c.accumulator().accept(cont, "String1-");
    c.accumulator().accept(cont, "String2-");
    c.accumulator().accept(cont, "String3");
    
    Object s = c.finisher().apply(builder);
    System.out.println(s);
    
  }
}

Result
Characteristics
[IDENTITY_FINISH]
String1-String2-String3
In the example above, I used this form of of() method:
static <T, R> Collector<T,R,R> of(Supplier<R> supplier, BiConsumer<R,T> accumulator, BinaryOperator<R> combiner, Collector.Characteristics... characteristics)

supplier supplies our method with the specified container like StringBuilder, ArrayList, etc.
accumulator accumulates contents then put them to a container.
combiner combines two containers if two containers are in use. In the example above I only used one container. Thus, I didn't invoke combiner() method.
characteristics is the characteristics of a Collector. There are three characteristics and you can read their description in the documentation.
IDENTITY_FINISH characteristic simply means that the Function denoted by finisher parameter behaves like Function.Identity().

In the example above, the form of of() doesn't have finisher parameter. Although, I could still invoke a Function when I called the finisher(). In this type of situation, java assigns IDENTITY_FINISH characteristic to our Collector to infrom us that when we call finisher(), it will behave like Function.Identity().

Next, let's create an example where the second form of of() is used.
import java.util.stream.Collector;

@SuppressWarnings({"unchecked"})
public class SampleClass{

  public static void main(String[] args){
    
    Collector c = 
    Collector.of(StringBuilder::new,
                 StringBuilder::append,
                 (l,r) -> l.append(r.toString()),
                 Object::toString);
    
    System.out.println("Characteristics");
    System.out.println(c.characteristics());
    
    StringBuilder builder1 = null;
    Object cont1 = c.supplier().get();
    
    StringBuilder builder2 = null;
    Object cont2 = c.supplier().get();
    
    if(cont1 instanceof StringBuilder)
       builder1 = (StringBuilder)cont1;
    else System.exit(1);
    
    if(cont2 instanceof StringBuilder)
       builder2 = (StringBuilder)cont2;
    else System.exit(1);
    
    c.accumulator().accept(cont1, "String1-");
    c.accumulator().accept(cont1, "String2-");
    c.accumulator().accept(cont2, "String3-");
    c.accumulator().accept(cont2, "String4");
    
    Object combined = c.combiner().apply(cont1,cont2);
    StringBuilder result = null;
    
    if(combined instanceof StringBuilder)
       result = (StringBuilder)combined;
    else System.exit(1);
    
    Object s = c.finisher().apply(result);
    
    if(s instanceof String)
      System.out.println(s + " is a String");
    
  }
}

Result
Characteristics
[]
String1-String2-String3-String4 is a String
Next, let's put our Collector method in the collect() method of Stream class.
import java.util.stream.Collector;
import java.util.HashSet;

@SuppressWarnings({"unchecked"})
public class SampleClass{

  public static void main(String[] args){
    HashSet<String> hs =
    new HashSet<>();
    
    hs.add("A");
    hs.add("B");
    hs.add("C");
    hs.add("D");
    
    Collector c = 
    Collector.of(StringBuilder::new,
                 (t,u) -> t.append("-"+u+"-"),
                 (l,r) -> l.append(r.toString()),
                 Object::toString,
                 Collector.Characteristics.UNORDERED);
    
    System.out.println("Characteristics");
    System.out.println(c.characteristics());
    
    Object result = hs.stream().collect(c);
    System.out.println(result);
  }
}

Result
Characteristics
[UNORDERED]
-A--B--C--D-
UNORDERED characteristic indicates that the collection operation does not commit to preserving the encounter order of input elements. (This might be true if the result container has no intrinsic order, such as a Set.)

Note that the examples here are unsafe that may cause exceptions regarding raw types and object typecasting. Make sure to carefully build your Collector method. In this type of situation, @SuppressWarnings({"unchecked"}) is necessary.

No comments:

Post a Comment