Home > Software engineering >  How to create a Map whose keys could potentially have null values in Java 8
How to create a Map whose keys could potentially have null values in Java 8

Time:11-10

I am attempting to create a Map<ProductKey, ProductDetail> for a list of Products that need to have their ProductDetail added to them. The ProductDetail object is read from a database from a service ProductDetailLookUpService. I have not added the service because it needs a lot of exposure to internal info. Products may or may not have the ProductDetail and similar Products may share the same ProductDetail. Here are the basic classes that make up the relationships.


                    @Data
                    static class ProductKey{
                      @EqualsAndHashCode.Include
                      private Long productCode;
                      @EqualsAndHashCode.Include
                      private Long productDetailCode;
                    }
                   
                    @Data
                    class Product {
                      @EqualsAndHashCode.Include
                      private ProductKey productKey;
                      @EqualsAndHashCode.Include
                      private ProductDetail productDetail;
                   }

                   @Data
                   class ProductDetail{
                     @EqualsAndHashCode.Include
                     private Long productCode;
                     private String description;
                     private BigDecimal price;
                     private String category;
                  }

                  public static Optional<ProductKey> findProductKey(Long productCode, 
                    List<ProductKey> productKeys){
            
                   return  productKeys.stream().
                    filter(productKey -> productCode.equals(productKey.getProductCode()))
                    .takeWhile(productKey -> productKey != null).findFirst();
                  }


Here is my attempt to create the map of Map<ProductKey, ProductDetail>. I do not know how to deal with the potential null values when a Product does not have a ProductDetail. Also, the method productKey.getProductCode() does not seem to be recognized.

Here is my attempt:


           public static void main(String[] args) {
          //Service that uses database configuration to obtain ProductDetails based on 
          //ProductCodes
            
            ProductDetailLookUpService service = ....
            List<ProductKey> productKeyList  = service.findProductKeys();
            List<Long> productCodes = Arrays.toList(new Long []{23334L, 667777L, 
       55009L});
             List<ProductDetail> productDetailList = 
                                   service.getProductDetailList(productCodes);
             Map<Long, ProductDetail> productDetailMap = 
                                         
                          mapProductCodeToProductDetail(productDetailList);
           
            // At start Product does not have ProductDetails
            // Products may share ProductDetails
            // Products which do not yet have ProductDetails defined will return a null 
            // value in the productDetailMap
           
               Map<ProductKey, ProductDetail> map = 
                    productKeyList.stream().collect(toMap(productKey -> {
                
                Optional<ProductKey> optKey = 
                                     findProductKey(productKey.getProductCode(),            
                                                                        productKeyList)
                           optKey.isEmpty()? null :
                                                    optKey.get(), 
                                   productDetailMap.get(productKey.getProductCode());
        });
    }


    public static Optional<ProductKey> findProductKey(Long 
         productCode, List<ProductKey> productKeys){
         return  productKeys.stream().
                filter(productKey -> 
                   productCode.equals(productKey.getProductCode()))
                .takeWhile(productKey -> productKey != 
              null).findFirst();
    }

I want to use Java 8 lambda to get the map to use to add the ProductDetail to Product. Here is what I want to use below, to produce a Map<ProductKey, ProductDetail>


          Map<ProductKey, ProductDetail> hashMap = new HashMap<>();
          
       productKeyList.stream().forEach( productKey -> {
            Optional<ProductKey> optKey = 
                      findProductKey(productKey.getProductCode(), 
                                                     productKeyList);
            if(optKey.isPresent()) {
                hashMap.put(optKey.get(), 
                                productDetailMap.get(optKey.get()));
            }
        });

Finally I want to use the map to add the ProductDetail to Product like this:


   

    List<Product> products = service.readProducts();
       
      products.stream().map( product -> {                                     
       
      product.setProductDetail(hashMap.get(product.getProductKey()));
                        return product;
       }        
     ).collect(toList());


CodePudding user response:

While using Collector toMap() you need to provide at least two arguments:

  • keyMapper - a function generating a Key from the stream element;
  • valueMapper - a function that produces a Value from the stream element.

And there are overloaded versions which allows you to specify mergeFunction and mapFactory. A mergeFunction would be useful if productKeyList can contain duplicates, and consequently there would be more than one element producing the identical key.

The call findProductKey() should be done in stream (before the collector). You can place into a map() operation. Then filter out empty optionals, extract the values, and apply collect().

List<ProductKey> productKeyList = // initilizing the list
        
Map<Long, ProductDetail> productDetailMap = // initializing the map
        
Map<ProductKey, ProductDetail> prodDetailByKey = productKeyList.stream()
    .map(productKey ->
        findProductKey(productKey.getProductCode(), productKeyList)
    )
    .filter(Optional::isPresent)
    .map(Optional::get)
    .collect(Collectors.toMap(
        Function.identity(),
        productKey -> productDetailMap.get(productKey.getProductCode()),
        (left, rirgt) -> left // precaution against duplicated elements in the productKeyList
    ));

In order to set the ProductDetail from the Map to the Product with the corresponding product key Iterable.forEach() would be more suitable than a stream.

List<Product> products = service.readProducts();
    
products.forEach(product -> 
    product.setProductDetail(prodDetailByKey.get(product.getProductKey()))
);

Regarding the null Values, they can be easily eliminated from any Map:

productDetailMap.values().removeIf(Objects::isNull);

Note that if a particular product key is not present is the Map, there's little you can do. The call get() would return null and productDetail property would remain unchanged.

  • Related