Посмотрите, как реализовано Collectors.averagingDouble
или Collectors.averagingInt
.
public static <T> Collector<T, ?, Double>
averagingInt(ToIntFunction<? super T> mapper) {
return new CollectorImpl<>(
() -> new long[2],
(a, t) -> { a[0] += mapper.applyAsInt(t); a[1]++; },
(a, b) -> { a[0] += b[0]; a[1] += b[1]; return a; },
a -> (a[1] == 0) ? 0.0d : (double) a[0] / a[1], CH_NOID);
}
В сущности, вам нужен изменяемый тип накопления, который будет содержатьBigDecimal
, который представляет собой сумму цен на продукты, и int
, который представляет собой количество обработанных продуктов.Имея это, проблема сводится к написанию простого Collector<Product, AccumulationType, BigDecimal>
.
Я упростил пример и удалил геттеры / сеттеры и конструктор всех аргументов.Вместо вложенного класса ProductPriceSummary
вы можете использовать любой класс изменяемого держателя для 2 элементов.
class AverageProductPriceCollector implements Collector<Product, AverageProductPriceCollector.ProductPriceSummary, BigDecimal> {
static class ProductPriceSummary {
private BigDecimal sum = BigDecimal.ZERO;
private int n;
}
@Override
public Supplier<ProductPriceSummary> supplier() {
return ProductPriceSummary::new;
}
@Override
public BiConsumer<ProductPriceSummary, Product> accumulator() {
return (a, p) -> {
// if getPrize() still returns double
// a.sum = a.sum.add(BigDecimal.valueOf(p.getPrize()));
a.sum = a.sum.add(p.getPrize());
a.n += 1;
};
}
@Override
public BinaryOperator<ProductPriceSummary> combiner() {
return (a, b) -> {
ProductPriceSummary s = new ProductPriceSummary();
s.sum = a.sum.add(b.sum);
s.n = a.n + b.n;
return s;
};
}
@Override
public Function<ProductPriceSummary, BigDecimal> finisher() {
return s -> s.n == 0 ?
BigDecimal.ZERO :
s.sum.divide(BigDecimal.valueOf(s.n), RoundingMode.CEILING);
}
@Override
public Set<Characteristics> characteristics() {
return Collections.emptySet();
}
}