# Joining a product-application-customer relationship using Java 8 Streams

I have three classes: product, customer and application.

The schema of these classes are as follows:

class application {
String productID;
String customerID;
String applicationId;
String name;
....
// getters and setters and associated constructors
}

class product {
String name;
String ID;
...
// constructors and getters and setters
}

class customer {
String name;
String ID;
...
// constructors and getters and setters
}


The only way of connection between product and customer is through the application.

The problem in hand is to retrieve all products with their associated customers (appID and customerID) in this JSON format.

 [
{
"product": {
// product details
}
"customers": [ {
"application": <value_appID>,
"ID": <value_customerID> } ]
}
]


I have done this using a nested for loop:

for (Product product : products) {
ProductInfo prodInfo = new
ProductInfo();
prodInfo.setProduct(product);
List<CustomerInfo> associatedCustomerList = new ArrayList<>();
for (Object res : searchResults.getResult()) {
if ((product.getId()).equals((String) ((Map) res).get("productID"))) {
CustomerInfo associatedCustomer = new CustomerInfo();
associatedCustomer.setCustomerID((String) ((Map) res).get("customerID"));
associatedCustomer.setApplicationId((String) ((Map) res).get("appId"));
}
prodInfo.setCustomers(associatedCustomerList);
}
}


Here, searchResults contain the result from List<? extends Object>. CustomerDetails contain appId and customerId as attributes. ProductInformationWithAssociatedCustomer is the response class.

Furthermore, I have converted this into the following which utilizes Java 8 Stream API:

 List<ProductInfo> prodList = products.stream()
.map(product -> {
List<CustomerInfo> customerList =searchResults.getResult().stream()
.filter(searchResult -> (product.getId()).equals((String) ((Map) searchResult).get("productID")))
.map(searchResult -> new CustomerInfo((String) ((Map) searchResult).get("customerID"),(String) ((Map) searchResult).get("appId")))
.collect(Collectors.toList());
return new ProductInfo(product, customerList);
}).collect(Collectors.toList());


This code is working fine.

The questions I have are:

1. Am I making correct/ optimal use of Java 8 stream features?

2. Provided, I want to do it using Java 8 Stream APIs only, should I do it in any better way?

3. What are the factors I need to consider while optimizing this type of codes.

### Using the appropriate types

Your are repeatedly casting searchResult into a Map, the first suggestion I will make is to cast it once first to make subsequent references easier to read:

// use Map<String, Object> if you want to be less strict on the value type,
// at the expense of having to do Object.toString() later
List<Map<String, String>> searchResultDetails =
(List<Map<String, String>>) searchResults.getResult();


Rationale: Avoid code repetition and makes the one-time cast clearer to understand

### Grouping the search results into a usable Map

Instead of traversing your searchResultMap for each product, consider generating an intermediary Map that gives you the list of CustomerDetails grouped by the product ID:

Map<String, List<CustomerDetails>> customerDetailsByProduct =
searchResultDetails.stream()
.collect(Collectors.groupingBy(map -> map.get("productID"),
Collectors.mapping(map -> new CustomerDetails(map.get("customerID"),
map.get("appId")), Collectors.toList())));


Rationale: Consider when you have $n$ products and a result for each of them. Your current solution will effectively be iterating through them, and iterate through the $n$ results for the one matching product. Therefore, you should start with grouping your results first by the product ID, so that the retrieval later via the Map interface will potentially be more efficient.

### Process the product list as the final step

With the above map, it's just a matter of looping through your products and creating an instance of ProductInformationWithAssociatedCustomer with the customer details, if present:

// BTW do you really need such long class names?
List<ProductInformationWithAssociatedCustomer> results =
products.stream()
.map(product -> new ProductInformationWithAssociatedCustomer(product,
customerDetailsByProduct.getOrDefault(product.getId(),
Collections.emptyList())))
.collect(Collectors.toList());

• You have already given ample explanation as to why to do each step as suggested. Can you specify , if it is not too much of an asking , as to how to determine when and where to optimize , so that it can act as a reference for everyone. Thanks .
– Echo
Apr 24 '17 at 8:41
• @SudeshnaBora updated my answer. :) Apr 24 '17 at 13:17