How to use Java reduce() function on stream of objects with BinaryOperator
combinedResult
is a stream of type ObjectNode that consists of data like this for example:
{"id":"id1","version":1,"Success":4,"Exception":6,"Failure":3}
{"id":"id1","version":1,"Success":4,"Exception":6,"Failure":3}
{"id":"id1","version":2,"Exception":1,"Success":2,"Failure":2}
{"id":"id1","version":2,"Exception":1,"Success":2,"Failure":2}
I want to get a result like this:
{"id":"id1","version":1,"Success":8,"Exception":12,"Failure":6}
{"id":"id1","version":2,"Success":4,"Exception":2,"Failure":4}
I have written the below BinaryOperator function
BinaryOperator<ObjectNode> func = (o1, o2) -> {
if (o1.get("id").asText().equals(o2.get("id").asText()) &&
o1.get("version").equals(o2.get("version"))) {
ObjectNode o = Jive.newObjectNode();
o.set("id", Jive.newJsonNode(o1.get("id")));
o.set("version", Jive.newJsonNode(o1.get("version")));
o.set("Success", Jive.newJsonNode(o1.get("Success").asInt() + o2.get("Success").asInt()));
o.set("Failure", Jive.newJsonNode(o1.get("Failure").asInt() + o2.get("Failure").asInt()));
o.set("Exception", Jive.newJsonNode(o1.get("Exception").asInt() + o2.get("Exception").asInt()));
return o;
}
return o1;
};
combinedResult.stream().reduce(func)
But when I try this out, I get below result:
Optional[{"id":"id1","version":1,"Success":8,"Failure":6,"Exception":12}]
I understand that this is because I return o1
as default value in the BinaryOperator, but I don't know how to resolve this.
Solution 1:
You can use reduce method.
<U> U reduce(U identity,
BiFunction<U, ? super T, U> accumulator,
BinaryOperator<U> combiner);
Your have start identity with a empty HashMap whose key will be unique identifier on which you wanted to cumulate results. (Id + Version)
public class JavaReduce {
public static void main(String[] args) {
//DataSetup
List<ObjectNode> objectNodeList = List.of(
new ObjectNode("id1", 1, 4, 6, 3),
new ObjectNode("id1", 1, 4, 6, 3),
new ObjectNode("id2", 2, 2, 1, 2),
new ObjectNode("id2", 2, 2, 1, 2));
Map<String, ObjectNode> objectNodeCumulativeMap = objectNodeList.stream()
.reduce(new HashMap<>(), (intermediate, ObjectNode) -> {
String key = ObjectNode.getId().concat(String.valueOf(ObjectNode.getVersion()));
if(!intermediate.containsKey(key)){
intermediate.put(key, ObjectNode);
} else {
ObjectNode objectNode = intermediate.get(key);
objectNode.setSuccess(objectNode.getSuccess() + ObjectNode.getSuccess());
objectNode.setFailure(objectNode.getFailure() + ObjectNode.getFailure());
objectNode.setException(objectNode.getException() + ObjectNode.getException());
}
return intermediate;
}, (cumulative, intermediate) -> {
cumulative.putAll(intermediate);
return cumulative;
});
System.out.println(objectNodeCumulativeMap.values());
}
}
//DTO for data
@Data
@AllArgsConstructor
@NoArgsConstructor
class ObjectNode {
private String id;
private Integer version;
private Integer success;
private Integer exception;
private Integer failure;
}
But ideal way as Sudipta Bhattacharyya and Holger mentioned is to use collect
Below is snippet of solution.
public class JavaCollect {
public static void main(String[] args) {
//DataSetup
List<ObjectNode> objectNodeList = List.of(
new ObjectNode("id1", 1, 4, 6, 3),
new ObjectNode("id1", 1, 4, 6, 3),
new ObjectNode("id2", 2, 2, 1, 2),
new ObjectNode("id2", 2, 2, 1, 2));
Map<String, ObjectNode> collect = objectNodeList.stream()
.collect(groupingBy(JavaCollect::uniqueKey, collectingAndThen(toList(), JavaCollect::downstream)));
System.out.println(collect.values());
}
private static ObjectNode downstream(List<ObjectNode> list) {
ObjectNode objectNode = list.stream().findFirst().orElse(new ObjectNode());
objectNode.setSuccess(list.stream().map(ObjectNode::getSuccess).collect(summingInt(Integer::intValue)));
objectNode.setException(list.stream().map(ObjectNode::getException).collect(summingInt(Integer::intValue)));
objectNode.setFailure(list.stream().map(ObjectNode::getFailure).collect(summingInt(Integer::intValue)));
return objectNode;
}
private static String uniqueKey(ObjectNode objectNode) {
return objectNode.getId().concat(objectNode.getVersion().toString());
}
}