I am new to Java 8, and have had trouble implementing already provided solution here on a similar kind of issue. Please help.
In Java 8 group by how to group by on three fields which returns more than one row that has to sum up on the rest of two Integer fields. Here in the below dto/pojo class need to make a sum up of incomingCount and outgoingCount fields based on the unique key of uuid, msgDate, and channel combined.
public class ReportData {
private String uuid;
private String msgDate;
private String channel;
private Integer incomingCount;
private Integer outgoingCount;
}
//Initializing List as sample.
List<ReportData> list1 = new ArrayList<>();
list1.add(new ReportData("c9c3a519","December 2023", "digital", 5, 0 ));
list1.add(new ReportData("c9c3a519","December 2023", "digital", 3, 0 ));
list1.add(new ReportData("c9c3a519","December 2023", "digital", 0, 3 ));
list1.add(new ReportData("c9c3a519","November 2023", "digital", 4, 0 ));
list1.add(new ReportData("c9c3a519","November 2023", "digital", 0, 4 ));
list1.add(new ReportData("c9c3a519","December 2023", "manual", 5, 0 ));
list1.add(new ReportData("c9c3a519","December 2023", "manual", 3, 0 ));
list1.add(new ReportData("c9c3a519","December 2023", "manual", 0, 3 ));
list1.add(new ReportData("c9c3a519","November 2023", "manual", 4, 0 ));
list1.add(new ReportData("c9c3a519","November 2023", "manual", 0, 4 ));
list1.add(new ReportData("3de4c44f","December 2023", "digital", 5, 0 ));
list1.add(new ReportData("3de4c44f","December 2023", "digital", 0, 3 ));
list1.add(new ReportData("3de4c44f","November 2023", "digital", 4, 0 ));
list1.add(new ReportData("3de4c44f","November 2023", "digital", 0, 4 ));
list1.add(new ReportData("3de4c44f","December 2023", "manual", 5, 0 ));
list1.add(new ReportData("3de4c44f","December 2023", "manual", 0, 3 ));
list1.add(new ReportData("3de4c44f","November 2023", "manual", 4, 0 ));
list1.add(new ReportData("3de4c44f","November 2023", "manual", 0, 4 ));
Output Object should have data as below:
uuid msgDate channel incomingCount outgoingCount
c9c3a519 December 2023 digital 8 3
c9c3a519 November 2023 digital 4 4
c9c3a519 December 2023 manual 8 3
c9c3a519 November 2023 manual 4 4
...
...
...
Collect the result into a Map. This example will use Collectors.toMap(keyMapper, valueMapper, mergeFunction, mapFactory).
Returns a Collector that accumulates elements into a Map whose keys and values are the result of applying the provided mapping functions to the input elements. If the mapped keys contains duplicates (according to Object.equals(Object)), the value mapping function is applied to each equal element, and the results are merged using the provided merging function. The Map is created by a provided supplier function.
Also I am using lombok annotations for brevity.
First start by creating classes to represent the keys you want to group by and the aggregated data:
@AllArgsConstructor
@Getter
public class Count {
private final int in;
private final int out;
public Count merge(Count other) {
return new Count(this.in + other.in, this.out + other.out);
}
@Override
public String toString() {
return in + " " + out;
}
}
@AllArgsConstructor
public class Key {
private final String uuid;
private final String date;
private final String channel;
@Override
public int hashCode() {
return Objects.hash(uuid, date, channel);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Key)) {
return false;
}
Key other = (Key) obj;
return uuid.equals(other.uuid) && date.equals(other.date) && channel.equals(other.channel);
}
@Override
public String toString() {
return uuid + " " + date + " " + channel;
}
}
Then extend ReportData
with 2 more methods to create the key and initial aggregation:
@AllArgsConstructor
public class ReportData {
//the fields
public Key createKey() {
return new Key(uuid, msgDate, channel);
}
public Count createCount() {
return new Count(incomingCount, outgoingCount);
}
}
And collect the data:
public class SoMain {
public static void main(String[] args) {
List<ReportData> list = new ArrayList<>();
//populate the list
Map<Key, Count> result = list.stream()
.collect(Collectors.toMap(ReportData::createKey, ReportData::createCount, Count::merge, LinkedHashMap::new));
for (Map.Entry<Key, Count> entry : result.entrySet()) {
System.out.println(entry.getKey() + " " + entry.getValue());
}
}
}
The arguments of the Collector are as follows:
ReportData::createKey
- creates the key to group by (key of the map)ReportData::createCount
- creates the initial aggregation from a single ReportData
(value of the map)Count::merge
- merges two Count
s on key collision (see the merge method)LinkedHashMap::new
- factory for a Map
to insert the results in. I want to preserve insertion order, but if you don't need to, you can just omit the parameter to use the default factory.Prints:
c9c3a519 December 2023 digital 8 3
c9c3a519 November 2023 digital 4 4
c9c3a519 December 2023 manual 8 3
c9c3a519 November 2023 manual 4 4
3de4c44f December 2023 digital 5 3
3de4c44f November 2023 digital 4 4
3de4c44f December 2023 manual 5 3
3de4c44f November 2023 manual 4 4