Группировка списка объектов по полю и применение двоичной функции в одном из полей для уменьшения его на сумму в одной строке кода с использованием потоковой java 8

#java #collections #java-8 #java-stream

Вопрос:

Давайте возьмем для примера у меня есть список транзакций, который содержит txnId, код платежа, сумму. Вывод должен быть по каждому платежному коду с указанием собранной суммы.

Я попытался сгруппировать его на основе кода платежа в качестве ключа и списка суммы для кода платежа, но я не могу его уменьшить.

 import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

class Payment {

    Integer txnId;
    String paymentCode;
    Integer amount;

    Payment(Integer txnId, String paymentCode, Integer amount) {
        this.txnId = txnId;
        this.paymentCode = paymentCode;
        this.amount = amount;
    }

    public String getPaymentCode() {
        return this.paymentCode;
    }

    public Integer getAmount() {
        return this.amount;
    }

    @Override
    public String toString() {
        return "Payment [txnId="   txnId   ", paymentCode="   paymentCode   ", amount="   amount   "]";
    }

}

public class ListGroupingSum {

    public static void main(String[] args) {

        List<Payment> payments = new ArrayList<>();
        payments.add(new Payment(100, "GPAY", 100));
        payments.add(new Payment(101, "CC", 200));
        payments.add(new Payment(102, "PAYTM", 300));
        payments.add(new Payment(103, "GPAY", 400));
        payments.add(new Payment(104, "CC", 500));

        // @formatter:off
        Map<String, List<Integer>> paymentListByCodeAndAmount = payments.stream()
                .collect(Collectors.groupingBy(Payment::getPaymentCode, Collectors.mapping(Payment::getAmount, Collectors.toList())));
        System.out.println(paymentListByCodeAndAmount);
        
        //System.out.println(paymentListByCodeAndAmount.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
        // @formatter:on
    }
}
 

Что дает результат

 {CC=[200, 500], GPAY=[100, 400], PAYTM=[300]} 
 

Но, ожидание-это

 {CC=700, GPAY=500, PAYTM=300} 
 

Ответ №1:

 Map<String, Integer> paymentListByCodeAndAmount = payments.stream()
  .collect(groupingBy(Payment::getPaymentCode, summingInt(Payment::getAmount)));
 

(с точными ожидаемыми результатами)

введите описание изображения здесь

Действительно, summingInt есть коллектор, куда groupingBy поместят каждую группу, вы можете «суммировать» или что угодно.

Например, если данные группы не являются непосредственно целыми числами, вы можете сопоставить:

 Map<String, Integer> paymentListByCodeAndAmount = payments.stream()
        .collect(groupingBy(Payment::getPaymentCode, 
                mapping(p -> p.amount * p.txnId, 
                        summingInt(x -> x))));
 

прочитано как «с платежами в виде потока, группировка по коду платежа, возьмите произведение суммы и txnId и дайте мне общую сумму».

Но, конечно, существуют миллионы комбинаций, вместо этого просто суммируйте, что вы могли бы суммировать (мин, макс, среднее значение,…) и что-то с этим сделать:

 Map<String, Double> paymentListByCodeAndAmount = payments.stream()
        .collect(groupingBy(Payment::getPaymentCode,
                collectingAndThen(summarizingInt(p -> p.amount * p.txnId),
                        IntSummaryStatistics::getAverage)));
 

прочитано как «с платежами в виде потока, группировка по коду платежа, суммируйте произведение суммы и идентификатора txnId, а затем дайте мне среднее значение».

Комментарии:

1. Именно то, чего я хотел. Есть ли какая-либо альтернатива этому, например, функциональный интерфейс BinaryOperator, который можно настроить? Прошу только о понимании

2. Я обновил свои ответы, но на самом Collectors деле это DSL , так что есть миллионы различных возможностей.