DEV Community

yangbongsoo
yangbongsoo

Posted on

How to handle a Map using Java stream

Let's look at the code below. I create a Product class that contains a list of Items.

@Value
@Builder
public class Product {
    long productId;

    List<Item> items;

    @Value
    @Builder
    public static class Item {
        long itemId;
    }
}
Enter fullscreen mode Exit fullscreen mode

And then I get the product list using an external API. In order to handle item objects, I use a nested for loop.

I make itemsByItemId Map object. Key is itemId and value is item object.

public class Main {
    public static void main(String[] args) {
        // get product list using an external api
        ApiClient apiClient = new ApiClient();
        List<Product> products = apiClient.getProducts().collectList().block();

        Map<Long, Item> itemsByItemId = new HashMap<>();
        for (Product product : products) {
            for (Item item : product.getItems()) {
                itemsByItemId.put(item.getItemId(), item);
            }
        }

        System.out.println(itemsByItemId);
    }
}
Enter fullscreen mode Exit fullscreen mode

However I want to use java stream api. Let's look at the code below. It looks complicated at a glance.

For each item, we create and merge Maps using Map.of, and since we need to merge again at the product level, we incorporate flatMap(map -> map.entrySet().stream()) and collect(Collectors.toMap) twice each.

public class Main {
  public static void main(String[] args) {
    // get product list using an external api
    ApiClient apiClient = new ApiClient();
    List<Product> products = apiClient.getProducts().collectList().block();

    Map<Long, Item> itemsByItemId = products.stream() // Stream<Product>
      .map(product ->
        product.getItems().stream() // Stream<Item>
          .map(item -> 
            Map.of(item.getItemId(), item)) // Stream<Map<Long, Item>>
            .flatMap(map -> map.entrySet().stream()) // Stream<Entry<Long, Item>>
            .collect(Collectors.toMap(
              Entry::getKey,
              Entry::getValue,
              (item1, item2) -> item2
          )
        )
      ) // Stream<Map<Long, Item>>
      .flatMap(map -> map.entrySet().stream()) // Stream<Entry<Long, Item>>
      .collect(Collectors.toMap(
        Entry::getKey,
        Entry::getValue,
        (item1, item2) -> item2
      )
    );

    System.out.println(itemsByItemId);
  }
}
Enter fullscreen mode Exit fullscreen mode

This is a better solution code. I create item lists using flatMap. .flatMap(product -> product.getItems().stream()) And then, I only need to create them once with Map.of.

public class Main {
  public static void main(String[] args) {
    // get product list using an external api
    ApiClient apiClient = new ApiClient();
    List<Product> products = apiClient.getProducts().collectList().block();

    Map<Long, Item> itemsByItemId = products.stream() // Stream<Product>
      .flatMap(product -> product.getItems().stream()) // Stream<Item>
      .map(item -> Map.of(item.getItemId(), item)) // Stream<Map<Long, Item>>
      .flatMap(map -> map.entrySet().stream()) // Stream<Entry<Long, Item>>
      .collect(Collectors.toMap(
          Entry::getKey,
          Entry::getValue,
          (item1, item2) -> item2
        )
      );

    System.out.println(itemsByItemId);  
  }
}
Enter fullscreen mode Exit fullscreen mode

However, in reality, since collect(Collectors.toMap) is used to create the Map, there's no need for me to manually work with Map.of. Ultimately, it resulted in much simpler code as shown below.

public class Main {
  public static void main(String[] args) {
    // get product list using an external api
    ApiClient apiClient = new ApiClient();
    List<Product> products = apiClient.getProducts().collectList().block();

    Map<Long, Item> itemsByItemId = products.stream() // Stream<Product>
      .flatMap(product -> product.getItems().stream()) // Stream<Item>
      .collect(Collectors.toMap(Item::getItemId, Function.identity()));

    System.out.println(itemsByItemId);  
  }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)

Great read:

Is it Time to go Back to the Monolith?

History repeats itself. Everything old is new again and I’ve been around long enough to see ideas discarded, rediscovered and return triumphantly to overtake the fad. In recent years SQL has made a tremendous comeback from the dead. We love relational databases all over again. I think the Monolith will have its space odyssey moment again. Microservices and serverless are trends pushed by the cloud vendors, designed to sell us more cloud computing resources.

Microservices make very little sense financially for most use cases. Yes, they can ramp down. But when they scale up, they pay the costs in dividends. The increased observability costs alone line the pockets of the “big cloud” vendors.

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay