'Rewrite imperative code of creating and populating Map in a declarative way using Java Stream API

I have this piece of code :

Map<String, BillingAccount> billingAccountsMap = new HashMap<>();
for (BillingAccount ba : billingAccounts) {
    if (ba.getBillingAccountIdentifier() != null && ba.getBillingAccountIdentifier().getIdentifier() != null) {
        billingAccountsMap.put(ba.getBillingAccountIdentifier().getIdentifier(), ba);
    }
}

All I want is to rewrite it in a functional way with Java Stream API and collect(Collectors.toMap()), but I am a bit perplexed with the null cases.

I am using Java 11.



Solution 1:[1]

You can filter out nullable values using filter() operation and then apply collect by passing a built-in collector Collectors.toMap() into it:

Map<String, BillingAccount> billingAccountsById = billingAccounts.stream()
    .filter(account -> Objects.nonNull(account.getBillingAccountIdentifier()))
    .filter(account -> Objects.nonNull(account.getBillingAccountIdentifier().getIdentifier()))
    .collect(Collectors.toMap(
        account -> account.getBillingAccountIdentifier().getIdentifier(), // keyMapper
        Function.identity())); // valueMapper

Note that for this approach, every identifier has to be unique. Otherwise, you need to provide mergeFunction as the third argument to resolve values mapped to the same key.

Solution 2:[2]

Use Collectors.toMap(..) to convert a stream of items to a map, and filter() to remove items you don't want. In your case:

var billingAccountsMap = billingAccounts.stream()
        .filter(ba -> ba.getBillingAccountIdentifier() != null)
        .filter(ba -> ba.getBillingAccountIdentifier().getIdentifier() != null)
        .collect(Collectors.toMap(ba -> ba.getBillingAccountIdentifier().getIdentifier(), ba -> ba));

See this answer for more information.

Solution 3:[3]

Map<String, BillingAccount> billingAccountsMap = billingAccounts.stream()
        .filter(ba -> ba.getBillingAccountIdentifier() != null
                && ba.getBillingAccountIdentifier().getIdentifier() != null)
        .collect(Collectors.toMap(ba -> ba.getBillingAccountIdentifier().getIdentifier(), ba -> ba));

Solution 4:[4]

You could stream your List of BillingAccount, then use the aggregate operation filter to make sure that each BillingAccount and its nested fields are not null with a short-circuited condition. Finally, collect the results where each key is the BillingAccount's identifier while the corresponding value is the object itself.

Map<String, BillingAccount> billingAccountsMap = billingAccounts.stream()
        .filter(ba -> Objects.nonNull(ba) 
                && Objects.nonNull(ba.getBillingAccountIdentifier()) 
                && Objects.nonNull(ba.getBillingAccountIdentifier().getIdentifier()))
        .collect(Collectors.toMap(ba -> ba.getBillingAccountIdentifier().getIdentifier(), Function.identity()));

Solution 5:[5]

You can use groupingBy once filter for null checks got applied:

billingAccounts.stream()
        .filter(ba -> ba.getBillingAccountIdentifier() != null
                        && ba.getBillingAccountIdentifier().getIdentifier() != null)
                            .collect(Collectors.groupingBy(x -> x.getBillingAccountIdentifier().getIdentifier()));

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3 Dusty
Solution 4
Solution 5 Ashish Patil