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.