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());
    }
}