简体   繁体   中英

Java 8 lambdas grouping reducing and mapping

Given a List of the following Transaction class, using Java 8 lambdas, I want to obtain a List of ResultantDTO , one per account type.

public class Transaction {

    private final BigDecimal amount;
    private final String accountType;
    private final String accountNumber;

}

public class ResultantDTO {

    private final List<Transaction> transactionsForAccount;

    public ResultantDTO(List<Transaction> transactionsForAccount){
        this.transactionsForAccount = transactionsForAccount;
    }

}

So far, I use the following code to group the List<Transaction> by accountType .

Map<String, List<Transaction>> transactionsGroupedByAccountType = transactions
    .stream()
    .collect(groupingBy(Transaction::getAccountType));

How do I return a List<ResultantDTO> , passing the List from each map key into the constructor, containing one ResultantDTO per accountType ?

You can do this in single stream operation:

public List<ResultantDTO> convert(List<Transaction> transactions) {
    return transactions.stream().collect(
            collectingAndThen(
                groupingBy(
                        Transaction::getAccountType,
                        collectingAndThen(toList(), ResultantDTO::new)),
                map -> new ArrayList<>(map.values())));
}

Here collectingAndThen used twice: once for downstream Collector to convert lists to the ResultantDTO objects and once to convert the resulting map to list of its values.

Assuming you have a:

static ResultantDTO from(List<Transaction> transactions) {...}

You could write:

Map<String, List<Transaction>> transactionsGroupedByAccountType = transactions
    .stream()
    .collect(groupingBy(Transaction::getAccountType));

Map<String, ResultantDTO> result = transactionsGroupedByAccountType.entrySet().stream()
         .collect(toMap(Entry::getKey, e -> from(e.getValue)));

You may be able to do it in one stream but this is probably cleaner and simpler.

You could use the toMap collector:

Map<String, ResultantDTO> transactionsGroupedByAccountType = transactions
                .stream()
                .collect(toMap(Transaction::getAccountType,
                        t -> new ResultantDTO(t.getAmount(), 
                                              Stream.of(new SimpleEntry<>(t.getAccountNumber(), t.getAmount()))
                                                    .collect(toMap(SimpleEntry::getKey, SimpleEntry::getValue))),
                        (dt1, dt2) -> new ResultantDTO(dt1.getSumOfAmountForAccountType().add(dt2.getSumOfAmountForAccountType()), 
                                                       Stream.of(dt1.getAccountNumberToSumOfAmountForAccountNumberMap(), dt2.getAccountNumberToSumOfAmountForAccountNumberMap())
                                                             .flatMap(m -> m.entrySet().stream())
                                                             .collect(toMap(Map.Entry::getKey, Map.Entry::getValue)))));

although its not very readable. Maybe you could add a constructor that takes a single Transaction as parameter and a merger in the ResultantDTO class:

 public ResultantDTO(Transaction t) {
     ///
 }

 static ResultantDTO merger(ResultantDTO r1, ResultantDTO r2) {
     ///
 }

and there it is more readable:

Map<String, ResultantDTO> transactionsGroupedByAccountType = transactions
                .stream()
                .collect(toMap(Transaction::getAccountType,
                               ResultantDTO::new,
                               ResultantDTO::merger));

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM