1

I am trying to create a custom collector in order to count valid elements of a list. I have done it using one of the already provide collectors:

arr.stream()
    .filter(e -> e.matches("[:;][~-]?[)D]"))
    .map(e -> 1)
    .reduce(0, Integer::sum);

but as a challenge for myself, I wanted to create my own custom collector in order to understand it better. And this is where I got stuck.

It is probably something trivial but I am learning this and can't figure a supplier, an accumulator, and a combiner. I guess I still don't understand something about them. For instance, I have a similar stream:

arr1.stream()
    .filter(e -> e.matches("[:;][~-]?[)D]"))
    .map(e -> 1)
    .collect(temporary array, adding to array, reduce);

AFAIK supplier is a function without arguments, which returns something. I studied standard examples and it is usually a method reference for a new collection, e.g. ArrayList::new. I tried to use constant 0, e -> 0 because I want to return a scalar. I think it is wrong because it makes the stream returning 0. If using method reference for a temporary collection, Java complains about a mismatch of types of a supplier and returning object. I am also confused about using an accumulator if the final result is a number as a combiner would reduce all elements to a number, e.g. (a,b) -> a + b`.

I'm completely stumped.

Celdor
  • 2,437
  • 2
  • 23
  • 44
  • Not sure I understand what refrains you from writing `ArrayList arr1 = new ArrayList<>(); arr1.stream().filter(e -> e.matches("[:;][~-]?[)D]")).collect(Collectors.toList()).size();` –  Jul 02 '21 at 13:35
  • Because it is a challenge as I wrote: _as a challenge for myself, I wanted to create my own custom collector_. It's even in the title! – Celdor Jul 02 '21 at 13:38

1 Answers1

1

Probably part of your problem is that you cannot obviously create an accumulator for an Integer type since it is immutable.

You start with this:

System.out.println(IntStream.of(1,2,3).reduce(0, Integer::sum));

You can extend to this:

System.out.println(IntStream.of(1,2,3).boxed()
    .collect(Collectors.reducing(0, (i1,i2)->i1+i2)));

Or even this, which has an intermediate mapping function

System.out.println(IntStream.of(1,2,3).boxed()
        .collect(Collectors.reducing(0, i->i*2, (i1,i2)->i1+i2)));

You can get this far with your own Collector

Collector<Integer, Integer, Integer> myctry = Collector.of(
        ()->0, 
        (i1,i2)->{
            // what to do here?
        }, 
        (i1,i2)->{
            return i1+i2;
        }
    );  

The accumulator is A function that folds a value into a mutable result container with mutable being the keyword here.

So, make a mutable integer

public class MutableInteger {
    private int value;
    public MutableInteger(int value) {
        this.value = value;
    }
    public void set(int value) {
        this.value = value;
    }
    public int intValue() {
        return value;
    }
}

And now:

Collector<MutableInteger, MutableInteger, MutableInteger> myc = Collector.of(
        ()->new MutableInteger(0), 
        (i1,i2)->{
            i1.set(i1.intValue()+i2.intValue());
        }, 
        (i1,i2)->{
            i1.set(i1.intValue()+i2.intValue());
            return i1;
        }
    );

And then:

System.out.println(IntStream.of(1,2,3)
        .mapToObj(MutableInteger::new)
        .collect(myc).intValue());

Reference: Example of stream reduction with distinct combiner and accumulator

EDIT: The finisher just does whatever with the final result. If you don't set it on purpose then it is set by default to IDENTITY_FINISH which is Function.identity() which says just to return the final result as is.

EDIT: If you're really desperate:

Collector<int[], int[], int[]> mycai = Collector.of(
        ()->new int[1], 
        (i1,i2)->i1[0] += i2[0], 
        (i1,i2)->{i1[0] += i2[0]; return i1;}
    );
System.out.println(IntStream.of(1,2,3)
        .mapToObj(v->{
            int[] i = new int[1];
            i[0] = v;
            return i;
        })
        .collect(mycai)[0]);
K.Nicholas
  • 10,956
  • 4
  • 46
  • 66
  • 1
    Can you explain what is a finisher for? I wasn't even aware there was also a "finisher". Anyway, I marked this answer as accepted, although not entirely happy with having to make an extra class for it. I think I've got the point. Also, thanks for demonstrating the usage of all three operations. I'll probably come back to this answer a few times. Now, at least I can see my supplier was correct :) Thanks – Celdor Jul 02 '21 at 13:52
  • Yea, actually don't need the Characteristics. – K.Nicholas Jul 02 '21 at 13:57