How to handle a Map using Java stream
Tags: java
Published: 2024-02-04 16:18:41

Let's look at the code below. I create a Product class that contains a list of Items.
```java
@Value
@Builder
public class Product {
	long productId;

	List<Item> items;

	@Value
	@Builder
	public static class Item {
		long itemId;
	}
}
```

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. 
```java
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);
	}
}
```

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.
```java
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);
  }
}
```

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`.
```java
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);  
  }
}
```

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.
```java
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);  
  }
}
```