HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavaMinor

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

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
applicationproductjavausingstreamsjoiningcustomerrelationship

Problem

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": ,
         "ID":  } ]
  }
]


I have done this using a nested for loop:

for (Product product : products) {
    ProductInfo prodInfo = new 
    ProductInfo();
    prodInfo.setProduct(product);
    List 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"));
            associatedCustomerList.add(associatedCustomer);
        }
        prodInfo.setCustomers(associatedCustomerList);
    }
    productInfo.add(prodInfo);
}


Here, searchResults contain the result from List. 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 prodList = products.stream()
.map(product -> {
List customerList =searchResults.getResult().stream()

Solution

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 if you want to be less strict on the value type,
// at the expense of having to do Object.toString() later
List> searchResultDetails = 
                                (List>) 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> 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 results =
    products.stream()
            .map(product -> new ProductInformationWithAssociatedCustomer(product,
                                    customerDetailsByProduct.getOrDefault(product.getId(),
                                            Collections.emptyList())))
            .collect(Collectors.toList());

Code Snippets

// 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();
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())));
// 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());

Context

StackExchange Code Review Q#161570, answer score: 2

Revisions (0)

No revisions yet.