Search code examples
javadictionaryhashmapjava-stream

Copying Map with modification on values


I have a simple DTO class:

@Builder
public class AccountDto {
  private Long id;
  private BigDecimal balance;
  private Currency currency;
}

Currency is an enum of my own which maintains about 300 different currency symbols.

For testing purposes, I want to create 10 AccountDTOs that correspond to one currency, then 10 that correspond to another, and so on and so forth. The following code generates some sequential IDs:

public static final Map<Currency, List<Long>> ACCOUNT_IDS_OF_GIVEN_CURRENCIES = Map.of(
          Currency.USD, LongStream.rangeClosed(1L, 10L).boxed().toList(),
          Currency.GBP, LongStream.rangeClosed(11L, 20L).boxed().toList(),
          Currency.EUR, LongStream.rangeClosed(21L, 30L).boxed().toList(),
          Currency.INR, LongStream.rangeClosed(31L, 40L).boxed().toList()
  );

Now, I would like to create AccountDTO instances based on these IDs that I generated into another map keyed by the exact same Currency instances. While I could do it like this:

public static final Map<Currency, List<AccountDto>> ACCOUNT_DTOS_OF_GIVEN_CURRENCIES = generateAccountDTOsBasedOnIds(ACCOUNT_IDS_OF_GIVEN_CURRENCIES);

public static Map<Currency, List<AccountDto>> generateAccountDTOsBasedOnIds(Map<Currency, List<Long>> inputMap){
    Map<Currency, List<AccountDto>> accountDtosOfGivenCurrencies = new HashMap<>();
    for(Map.Entry<Currency, List<Long>> entry : inputMap.entrySet()){
      accountDtosOfGivenCurrencies.put(entry.getKey(), entry.getValue().stream()
              .map(id->AccountDto.builder()
                      .id(id)
                      .balance(BigDecimal.TEN)
                      .currency(entry.getKey())
                      .build())
              .collect(Collectors.toList()));
    }
    return accountDtosOfGivenCurrencies;
  }

This is rather imperative and inelegant, explicitly allocating a HashMap, whereas no such thing was necessary for the ACCOUNT_IDS_OF_GIVEN_CURRENCIES Map. I am trying to do this in one fell swoop, using streams, Map.ofEntries(), entrySet(), Map.Entry.copyOf() (Java 17 feature) and other ways, but so far I have failed. The closest I've come to is the following, which fails at compile-time because Map.entry() is not an instantiable type.

public static final Map<Currency, List<AccountDto>> ACCOUNT_DTOS_OF_GIVEN_CURRENCIES = Map.ofEntries(
          ACCOUNT_IDS_OF_GIVEN_CURRENCIES.entrySet().stream().map(currencyListEntry -> Map.entry(currencyListEntry.getKey(),
                  currencyListEntry.getValue().stream().map(id->
                          AccountDto.builder()
                                  .id(id)
                                  .balance(BigDecimal.TEN)
                                  .currency(currencyListEntry.getKey())
                                  .build())
                          .toList())).toArray(Map.Entry::new));

Compile-time error in my solution.

Any ideas about how this could be solved elegantly and without an intermediate instantiation of a Map type like HashMap or TreeMap? Completely open to solutions that might use ApacheCommons, Guava, any reasonable library I could add to my pom. Thanks.


Solution

  • You can use the toMap collector.

    public static final Map<Currency, List<AccountDto>> ACCOUNT_DTOS_OF_GIVEN_CURRENCIES = 
        ACCOUNT_IDS_OF_GIVEN_CURRENCIES
            .entrySet().stream()
            .collect(Collectors.toMap(Map.Entry::getKey,
                e -> e.getValue().stream()
                    .map(id -> AccountDto.builder().id(id)
                            .balance(BigDecimal.TEN).currency(e.getKey()).build())
                            .toList()));