You need to add a generic type parameter (let's call it C) after the class name, not in the implements clause.
Then, add a constructor that takes in a Supplier<C> telling you how to create a C, and store the supplier somewhere. Replace all the occurrences of new HashSet<>() with get calls to the supplier instead.
class MultiMapCollector<C extends Collection<String>> implements Collector<Entity, Map<String, C>, Map<String, C>> {
private final Supplier<C> supplier;
public MultiMapCollector(Supplier<C> supplier) {
this.supplier = supplier;
}
@Override
public Supplier<Map<String, C>> supplier() {
return HashMap::new;
}
@Override
public BiConsumer<Map<String, C>, Entity> accumulator() {
return (map, e) -> map.computeIfAbsent(e.getName(),
k -> supplier.get()).add(e.getValue());
}
@Override
public BinaryOperator<Map<String, C>> combiner() {
return (map1, map2) -> {
map2.keySet().forEach(key -> map1.computeIfAbsent(key, k -> supplier.get()).addAll(map2.get(key)));
return map1;
};
}
@Override
public Function<Map<String, C>, Map<String, C>> finisher() {
return Collections::unmodifiableMap;
}
@Override
public Set<Characteristics> characteristics() {
// now you aren't sure whether the collection is ordered or not
return Set.of();
}
}
Now every time you create a MultiMapCollector, you need to pass in a supplier:
new MultiMapCollector<>(HashSet::new)
If you want it to use a default of HashSet when you don't provide anything, add static factory methods and make the constructor private:
public static MultiMapCollector<Set<String>> get() {
return new MultiMapCollector<>(HashSet::new);
}
public static <C extends Collection<String>> MultiMapCollector<C> with(Supplier<C> supplier) {
return new MultiMapCollector<>(supplier);
}
Just like how Collectors.groupingBy is a factory method.
More generally, you can take a full downstream collector instead of just a Supplier<C>, and that is what the 2-parameter overload of Collectors.groupingBy does. groupingBy just handles the grouping, and the downstream collector (the 2nd argument) handles the groups.
Here is a sketch of how you would do this:
class MultiMapCollector<R> implements Collector<Entity, Map<String, R>, Map<String, R>> {
private final Collector<String, R, R> downstream;
private MultiMapCollector(Collector<String, R, R> downstream) {
this.downstream = downstream;
}
public static MultiMapCollector<Set<String>> get() {
return new MultiMapCollector<>(Collectors.toSet());
}
public static <R> MultiMapCollector<R> with(Collector<String, R, R> downstream) {
return new MultiMapCollector<>(downstream);
}
@Override
public Supplier<Map<String, R>> supplier() {
return HashMap::new;
}
@Override
public BiConsumer<Map<String, R>, Entity> accumulator() {
return (map, e) -> downstream.accumulator().accept(map.computeIfAbsent(e.getName(),
k -> downstream.supplier().get()), e.getValue());
}
@Override
public BinaryOperator<Map<String, R>> combiner() {
return (map1, map2) -> {
map2.keySet().forEach(key ->
downstream.combiner().apply(
map1.computeIfAbsent(key, k -> downstream.supplier().get()),
map2.get(key)
)
);
return map1;
};
}
@Override
public Function<Map<String, R>, Map<String, R>> finisher() {
return Collections::unmodifiableMap;
}
@Override
public Set<Characteristics> characteristics() {
return Set.of();
}
}