Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java 8 how to merge multiple lists by enum type

How to merge multiple lists in Java 8 by enum type?

I have 2 lists:

List<StatusSummary> listA //4 items because we have 4 enum types
List<StatusSummary> listB //4 items because we have 4 enum types

class StatusSummary {
  private StatusEnum resourcesStatus; //4 statuses: TypeA, TypeB, TypeC, TypeD
  private Integer count;
}

I want to have one list consisting of 4 items for each resourcesStatus: TypeA, TypeB, TypeC, TypeD with the sum of elements in count field like:

List<StatusSummary> listA+B with 4 objects:
(resourcesStatus: TypeA, count: 3), (resourcesStatus: TypeB, count:99), (resourcesStatus: typeC, count:322), (resourcesStatus: TypeD, count:2)

So far I implemented something like this:

listA
    .getResourcesStatus()
    .forEach(la -> listB.getResourcesStatus().stream()
        .filter(lb -> lb.getResourcesStatus() == la.getResourcesStatus())
        .forEach(lb -> result.add(new StatusSummary()
            .count(la.getCount() + lb.getCount())
        .status(la.getStatus()))));
like image 457
Matley Avatar asked Jun 23 '26 01:06

Matley


2 Answers

You can use Collectors.toMap. Map by ResourcesStatus and merge two StatusSummary with count field in merge-function for the same ResourcesStatus then you get Map<StatusEnum, StatusSummary> and then get values of the map using .values() in ArrayList.

List<StatusSummary> newList = 
           new ArrayList<>(
                  Stream.of(listA, listB)
                 .flatMap(Collection::stream)
                 .collect(Collectors.toMap(e -> e.getResourcesStatus(), i -> i,
                   (a,b) -> new StatusSummary(a.getResourcesStatus(),
                                                a.getCount() + b.getCount())))
                 .values());
like image 114
Eklavya Avatar answered Jun 24 '26 15:06

Eklavya


Problem using forEach

As far as I see your current solution using forEach, you approach to Stream API as it would be a simple for-loop with additional features. You violate the Side-effects principle of java-stream which in a nutshell says that a stream shouldn't modify another collection while performing the actions through the pipelines. I haven't tested your code, however, this is not a way you should treat streams.

One of the possible solutions

You can use Collectors.groupingBy with Collectors.summingInt to get Map<StatusEnum, Integer> with the status and a sum of counts. Each entry then can be transformed into StatusSummary and colleted as List:

 List<StatusSummary> list = Stream.of(listA, listB)
     .flatMap(List::stream)
     .collect(Collectors.groupingBy(                        // Map<StatusSummary, Integer>
          StatusSummary::getResourcesStatus,                // .. StatusEnum as key
          Collectors.summingInt(StatusSummary::getCount)))  // .. Integer as value (sum)
     .entrySet()
     .stream()                                              // Stream<Entry<..>>
     .map(e -> new StatusSummary(e.getKey(), e.getValue())) // Stream<StatusSummary>
     .collect(Collectors.toList());                         // List<StatusSummary>

... or using Collectors.collectingAndThen (not less verbose):

List<StatusSummary> list1 = Stream.of(listA, listB, listC)
    .flatMap(List::stream)
    .collect(Collectors.collectingAndThen(
         Collectors.groupingBy(
             StatusSummary::getResourcesStatus, 
             Collectors.summingInt(StatusSummary::getCount)),
         map -> map.entrySet().stream()
             .map(e -> new StatusSummary(e.getKey(), e.getValue()))
             .collect(Collectors.toList())));
like image 22
Nikolas Charalambidis Avatar answered Jun 24 '26 17:06

Nikolas Charalambidis



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!