I have the following domain model:class Holding------------Account account;DataSourceProduct dataSourceProduct;class Account-------------String name;class DataSourceProduct-------------DataSource dataSource;Product product;// other product data specific to the data sourceclass Product--------------String id;    class DataSource----------------String name;I get a list of Holding data from three different data sources. I need to show the data in a grid. Each row in the grid has the same product and account and will have columns to show the product data as provided by the three different data sources. Note: One of the data sources may have a holding with a product id of null.To accomplish this, I currently break up the list of holdings into several lists where each list contains only holdings with the same product id and account. Also, I key each list by data source. So, in the end, I have the following:List<Map<DataSource,Holding>>So, we have something like this:index 0 in the list    Map:Key       Map:Value    datasource1   holding1AccordingToDs1  ----|    datasource2   holding1AccordingToDs2  ----|---> all three holdings have same product and account    datasource3   holding2AccordingToDs3  ----|index 1 in the list    Map:Key       Map:Value    datasource1   holding2AccordingToDs1  ----|    datasource2   holding2AccordingToDs2  ----|---> all three holdings have same product and accountindex 2 in the list    Map:Key       Map:Value    datasource1   holding3AccordingToDs1  ----|    datasource3   holding3AccordingToDs3  ----|---> all three holdings have To get from List<Holding> to List<Map<DataSource,Holding>> I do the following:    // these holdings could have an null instrument id    // and this data is returned from a different db than other    // holdings    List<Holding> aSourceHoldings = holdingService.getHoldings(date);    List<Holding> unMatchedHoldings = aSourceHoldings .stream()            .filter(h -> h.getDataSourceProduct().getProduct().getId() == null)            .collect(Collectors.toList());    // if null product id, no way to match to other instruments    aSourceHoldings.removeAll(unMatchedHoldings);    // get other data source holdings    List<Holding> toBeMatchedHoldings = holdingRepository.getHoldings(date);    toBeMatchedHoldings.addAll(aSourceHoldings);    // grouping by account and product    Map<Account, Map<Product, List<Holding>>> matchedHoldings =             toBeMatchedHoldings.stream()            .collect(Collectors.groupingBy(Holding::getAccount,                    Collectors.groupingBy(h -> h.getDataSourceProduct().getProduct())));List<Map<DataSource, Holding>> unifiedHoldings = new ArrayList<Map<DataSource, Holding>>();    Map<DataSource, Holding> tmp = new HashMap<DataSource, Holding>();    for (Map<Product, List<Holding>> productHoldings : matchedHoldings.values()) {        for (List<Holding> holdings : productHoldings.values()) {            for (Holding holding : holdings) {                tempMap.put(holding.getDataSourceProduct.getDataSource(), holding);             }            unifiedHoldings.add(tempMap);        }    }    return unifiedHoldings;Like data sources, the number of accounts is small (4-5) and will remain fairly static over the years. Is it possible to reduce the number of manipulations to group and get the data that I need for the UI grid?
Java 8 streams - restructure entity classes for UI Grid
java;hash map
Type inference for generic instance creationYou can rely on type inference for generic instance creation when declaring unifiedHoldings:List<Map<DataSource, List<Holding>>> unifiedHoldings = new ArrayList<>();Mutable operations on collectionsList<Holding> aSourceHoldings = holdingService.getHoldings(date);List<Holding> unMatchedHoldings = /* ... */ ;aSourceHoldings.removeAll(unMatchedHoldings);List<Holding> toBeMatchedHoldings = holdingRepository.getHoldings(date);toBeMatchedHoldings.addAll(aSourceHoldings);Calling mutable operations such as removeAll(Collection) and addAll(Collection) may throw UnsupportedOperationException if the underlying List does not return a mutable implementation. As such, in these scenarios, you should be creating your own List given the resulting elements, and then do the necessary removal/addition calls on the new instance.Account and Product usageInterestingly, it looks like Account and Product themselves aren't used in the processing, after kind of using them as 'keys' for grouping. The nested Maps looks too peculiar, which brings us to the next point...Grouping your Holding objectsI currently break up the list of holdings into several lists where each list contains only holdings with the same product id and account. Also, I key each list by data source.(emphasis mine)This looks like a good use case for a supplementary 'key' class that will allow us to grouping Holding objects with the same account, product and data source. For example:private static final class HoldingKey {    private final Account account;    private final Product product;    HoldingKey(Account account, Product product) {        this.account = Objects.requireNonNull(account);        this.product = Objects.requireNonNull(product);    }    Account getAccount() {        return account;    }    Product getProduct() {        return product;    }    @Override    public boolean equals(Object o) {        return o instanceof HoldingKey                && getAccount().equals(((HoldingKey) o).getAccount())                && getProduct().equals(((HoldingKey) o).getProduct());    }    @Override    public int hashCode() {        return Objects.hash(getAccount(), getProduct());    }    static HoldingKey toKey(Holding holding) {        return new HoldingKey(holding.getAccount(),                                 holding.getDataSourceProduct().getProduct());    }}Using the method reference HoldingKey::toKey to group Holding objects by, with a dash of Stream.concat(Stream, Stream) to combine two streams together, we can combine the processing on both the repository and service results with relative ease:    Stream<Holding> fromRepository = holdingRepository.getHoldings(date).stream();    Stream<Holding> fromService = holdingService.getHoldings(date).stream()            .filter(h -> h.getDataSourceProduct().getProduct().getId() != null);    Map<HoldingKey, List<Holding>> map = Stream.concat(fromRepository, fromService)            .collect(Collectors.groupingBy(HoldingKey::toKey));This simplifies the nested Maps we have by flattening that multi-key structure into a single HoldingKey object. Achieving what you require is a simple continuation on map's entrySet(), with a mapping function to turn List<Holding> into your required Map<DataSource, Holding>:private static final Function<List<Holding>, Map<DataSource, Holding>> MAPPER =        x -> x.stream().collect(Collectors.toMap(                y -> y.getDataSourceProduct().getDataSource(), Function.identity()));private static List<Map<DataSource, List<Holding>>> getUnifiedHoldings(Date date) {    Stream<Holding> fromRepository = holdingRepository.getHoldings(date).stream();    Stream<Holding> fromService = holdingService.getHoldings(date).stream()            .filter(h -> h.getDataSourceProduct().getProduct().getId() != null);    return Stream.concat(fromRepository, fromService)            .collect(Collectors.groupingBy(HoldingKey::toKey))            .entrySet()            .stream()            .map(MAPPER.compose(Map.Entry::getValue))            .collect(Collectors.toList());}Here, we compose() our MAPPER function with the 'extractor' function Map.Entry::getValue to derive our resulting list elements.