1

I have one base class

public class BaseInfo { }

Then I have two child class from BaseInfo:

public class FunctionInfo extends BaseInfo {}
public class ComponentInfo extends BaseInfo {}

Now In rest layer there is one method using which I can get the collection of those child objects similar to below

Map<String, List<ComponentInfo>> allCompInfo  = componentLayer.findById(Id, true);  
Map<String, List<FunctionInfo>> allFunctionInfo = functionLayer.findById(Id); 

Here In my next method I have to send the allCompInfo and allFunctionInfo both to get next info. Or I have to call twice that method If I am not sending them both together. In both cases the operation complexity is same. Iterating over two collection list.

I am curious to know is there any way to collect both child object collections into the BaseInfo collection. something like

     Map<String, List<BaseInfo>> allInfo  = componentLayer.findById(Id, true);
     allInfo = functionLayer.findById(Id);

Basic upper casting I tried but the collection looks like work different way. Is there any way I can type cast a whole child collection to parent collection. It will be great help if one could help.

fascynacja
  • 1,625
  • 4
  • 17
  • 35
RCode
  • 99
  • 2
  • 2
  • 13
  • Just to be clear, you want to *merge* the two maps `allCompInfo` and `allFunctionInfo`? That is, add the key value pairs of the two maps into a single map, and where the keys collide, make a new list that contains the `BaseInfo`s of both values? – Sweeper May 09 '23 at 05:49
  • Yes. You are correct. I am trying to create a single collection called as 'BaseInfo' pulling the details from both componentLayer and FunctionLayer. – RCode May 09 '23 at 05:55

3 Answers3

1

List<Component> and List<Function> are just not subtypes of List<BaseInfo>. If they were a subtypes, everything you can do to List<BaseInfo> can also be done to List<Component>, right? But this is not the case - you can add a Function to a List<BaseInfo>, but you cannot add a Function to a List<Component>. Read more about this here.

List<Component> and List<Function> are subtypes of List<? extends BaseInfo> though. List<? extends BaseInfo> is very similar to List<BaseInfo>, but you cannot add elements to it (except null). You can combine the two maps into a Map<String, List<? extends BaseInfo>> like this:

var map = new HashMap<String, List<? extends BaseInfo>>(allFunctionInfo);
for (var entry : allCompInfo.entrySet()) {
    map.merge(entry.getKey(), entry.getValue(), (x, y) -> {
        // when keys collide, create a new List<BaseInfo> with elements from both lists
        var list = new ArrayList<BaseInfo>(x);
        list.addAll(y);
        return list;
    });
}

Alternatively, you can create copies of every List<Function> and every List<Component>. And the copies can be of type List<BaseInfo> instead:

var map = new HashMap<String, List<BaseInfo>>();
for (var entry : allCompInfo.entrySet()) {
    // new ArrayList<>(anotherList) creates a copy
    map.put(entry.getKey(), new ArrayList<>(entry.getValue()));
}
for (var entry : allFunctionInfo.entrySet()) {
    map.merge(entry.getKey(), new ArrayList<>(entry.getValue()), (x, y) -> {
        x.addAll(y);
        return x;
    });
}

You can extract this to a generic method that works on any base type and two subtypes:

private static <TBase, TSub1 extends TBase, TSub2 extends TBase> 
Map<String, List<TBase>> merge(Map<String, List<TSub1>> map1, Map<String, List<TSub2>> map2) {
    var map = new HashMap<String, List<TBase>>();
    for (var entry : map1.entrySet()) {
        map.put(entry.getKey(), new ArrayList<>(entry.getValue()));
    }
    for (var entry : map2.entrySet()) {
        map.merge(entry.getKey(), new ArrayList<>(entry.getValue()), (x, y) -> {
            x.addAll(y);
            return x;
        });
    }
    return map;
}
Sweeper
  • 213,210
  • 22
  • 193
  • 313
0

If there are no "key" collisions (https://www.baeldung.com/java-hashmap-advanced#collisions-in-the-hashmap) then you can try this approach:

Map<String, List<? extends BaseInfo>> allInfos = new HashMap<>();
allInfos.putAll(compoments);
allInfos.putAll(functions);
fascynacja
  • 1,625
  • 4
  • 17
  • 35
0

You must create a new List<BaseInfo> and copy the child class objects over.

So this answer is broadly useful, here I've used more familiar classes from the JDK instead of your custom classes: Parent class Number with child classes Integer and Long.

Given:

Map<String, List<Integer>> allCompInfo  = new HashMap<>();
Map<String, List<Long>> allFunctionInfo = new HashMap<>();

and you want to populate a Map<String, List<Number>> allInfo with them:

Either copy all values into the base map imperatively, manually handling merges:

Map<String, List<Number>> allInfo = new HashMap<>();
allCompInfo.forEach((k, v) -> v.forEach(e -> allInfo.merge(k, new ArrayList<>(List.of(e)), (a,b) -> { a.addAll(b); return a;})));
allFunctionInfo.forEach((k, v) -> v.forEach(e -> allInfo.merge(k, new ArrayList<>(List.of(e)), (a,b) -> { a.addAll(b); return a;})));

Or, do it in one line via a stream, which would scale better if you add more child classes (and it looks cooler):

Map<String, List<Number>> baseInfo = Stream.of(allCompInfo, allFunctionInfo)
   .map(Map::entrySet)
   .flatMap(Set::stream)
   .flatMap(e -> e.getValue().stream().map(v -> new AbstractMap.SimpleEntry<String, Number>(e.getKey(), v)))
   .collect(groupingBy(Map.Entry::getKey, mapping(Map.Entry::getValue, toList())));
    
Bohemian
  • 412,405
  • 93
  • 575
  • 722
  • 1
    Instead of a complicated and expensive `allInfo.merge(k, new ArrayList<>(List.of(e)), (a,b) -> { a.addAll(b); return a;})` you can use `allInfo.computeIfAbsent(k, key -> new ArrayList<>()).add(e)` – Holger May 09 '23 at 11:02
  • 1
    And the Stream operation can be simplified to `Stream.of(allCompInfo, allFunctionInfo) .flatMap(m -> m.entrySet().stream()) .collect(groupingBy(Map.Entry::getKey, flatMapping(e -> e.getValue().stream(), toList())))` – Holger May 09 '23 at 11:07