Java 8 потоков. Как объединить итоги BigDecimal для каждого перечислимого типа в пользовательский компонент - PullRequest
1 голос
/ 28 октября 2019

Трудно сформулировать вопрос. Вот мой фрагмент кода:

Map<PassType, PassTypeRate> ratesPerType = new HashMap<>();

entries.stream().forEach((entry) -> {
    if (!ratesPerType.containsKey(entry.getPassType())) {
        ratesPerType.put(entry.getPassType(), new PassTypeRate(BigDecimal.ZERO, BigDecimal.ZERO));
    }

    if (AgeType.ADULT.equals(entry.getAgeType())) {
        PassTypeRate passTypeRate = ratesPerType.get(entry.getPassType());
        passTypeRate.setAdultRate(passTypeRate.getAdultRate().add(entry.getRate()));
    }
    if (AgeType.CHILD.equals(entry.getAgeType())) {
        PassTypeRate passTypeRate = ratesPerType.get(entry.getPassType());
        passTypeRate.setChildRate(passTypeRate.getChildRate().add(entry.getRate()));
    }
});

Итак, entries - это список bean-компонентов, который содержит ageType, passType и rate. Мне нужно агрегировать общие показатели по возрасту и типу passType.

Компонент PassTypeRate содержит общую сумму по взрослому или ребенку за PassType.

Мой основной вопрос.... можно ли вообще переписать приведенный выше фрагмент с помощью сборщиков Java 8 или аналогичных? Я не могу понять.

Любой совет будет оценен.

Спасибо

1 Ответ

4 голосов
/ 28 октября 2019

Да, выглядит как хороший кандидат для реализации с Collectors.toMap. Взгляните на реализацию ниже (я использовал lombok (https://projectlombok.org/), чтобы сделать его более читабельным):

    Map<PassType, PassTypeRate> group(List<DataEntry> entries) {
        return entries.stream()
                .collect(Collectors.toMap(
                        DataEntry::getPassType,
                        entry -> PassTypeRate.builder()
                                .adultRate(entry.getAgeType() == ADULT ? entry.getRate() : BigDecimal.ZERO)
                                .childRate(entry.getAgeType() == CHILD ? entry.getRate() : BigDecimal.ZERO)
                                .build(),
                        (rate1, rate2) -> PassTypeRate.builder()
                                .childRate(rate1.getChildRate().add(rate2.getChildRate()))
                                .adultRate(rate1.getAdultRate().add(rate2.getAdultRate()))
                                .build()
                ));
    }

И краткое объяснение:

  • Во-первыхнам нужно определить PassType как ключ карты

  • Во-вторых, нам нужно отобразить наши DataEntry на отдельные PassTypeRate объекты. Если это запись Adult, тоnew PassTypeRate должен иметь значение в поле adultRate и ноль в childRate. И наоборот.

  • Но некоторые записи могут иметь одинаковые PassType! Поэтому мынужно определить третью функцию - функцию слияния. Как мы объединяем PassTypeRate объекты? Добавляя правильные тарифы. Функция слияния возвращает новое PassTypeRate в результате добавления двух PassTypeRate объектов.

Я также подготовил несколько тестов, чтобы проверить, работает ли решение - и кажется, что оно работает :) Тестовый пример ниже:

public class RateGroupingTest {

    private RateGrouping subject = new RateGrouping();

    @Test
    public void groups() {
        //given
        List<DataEntry> entries = List.of(
                new DataEntry(ADULT, X, new BigDecimal("3")),
                new DataEntry(ADULT, Y, new BigDecimal("5")),
                new DataEntry(ADULT, Z, new BigDecimal("7")),
                new DataEntry(CHILD, X, new BigDecimal("11")),
                new DataEntry(CHILD, Y, new BigDecimal("13")),
                new DataEntry(CHILD, Z, new BigDecimal("17")),

                new DataEntry(ADULT, X, new BigDecimal("13")),
                new DataEntry(ADULT, Y, new BigDecimal("25")),
                new DataEntry(ADULT, Z, new BigDecimal("37")),
                new DataEntry(CHILD, X, new BigDecimal("411")),
                new DataEntry(CHILD, Y, new BigDecimal("513")),
                new DataEntry(CHILD, Z, new BigDecimal("617"))
        );

        //when
        Map<PassType, PassTypeRate> actual = subject.group(entries);

        //then
        assertThat(actual.get(PassType.X))
                .isEqualTo(
                        PassTypeRate.builder()
                                .childRate(new BigDecimal("422"))
                                .adultRate(new BigDecimal("16"))
                                .build()
                );
        assertThat(actual.get(PassType.Y))
                .isEqualTo(
                        PassTypeRate.builder()
                                .childRate(new BigDecimal("526"))
                                .adultRate(new BigDecimal("30"))
                                .build()
                );
        assertThat(actual.get(PassType.Z))
                .isEqualTo(
                        PassTypeRate.builder()
                                .childRate(new BigDecimal("634"))
                                .adultRate(new BigDecimal("44"))
                                .build()
                );
    }
}
...