Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How collect / reduce java 8 stream into pojo?

Look at the code:

Collection<MyDto> col = ...

MyBuilder builder = new MyBuilder(); 
for (MyDto dto: col) {
    switch (dto.getType()) {
        case FIELD1:
            builder.field1(dto.getValue());
            break:
        case FIELD2:
            builder.field2(dto.getValue());
            break:
    }    
}

Some result = builder.build();

Is there a way to do this with streams, like:

Some result = col.stream().collect(...)

Note that all stream values are collected into sigle pojo, not collection, stream or map.

like image 480
Cherry Avatar asked Sep 12 '18 14:09

Cherry


People also ask

What is reduce in Java 8 streams?

A reduction is a terminal operation that aggregates a stream into a type or a primitive. The Java 8 Stream API contains a set of predefined reduction operations, such as average , sum , min , max , and count , which return one value by combining the elements of a stream.

What is reduce () in java8?

In Java 8, the Stream. reduce() combine elements of a stream and produces a single value. A simple sum operation using a for loop.

How reduce Works Java stream?

Reducing is the repeated process of combining all elements. reduce operation applies a binary operator to each element in the stream where the first argument to the operator is the return value of the previous application and second argument is the current stream element.

How do you reduce an object in Java?

We can perform a reduction operation on elements of a Java Stream using the Stream. reduce() method that returns an Optional describing the reduced object or the reduced value itself.


2 Answers

The bottom line is that somewhere, somehow, you need to map the possible return values of MyDto.getType() to property setting methods of MyBuilder. Your code does that via a switch statement, and that's just fine. You can write the reduction as a stream-based pipeline instead, but you still need to incorporate the mapping somehow.

A pretty direct way of doing that would be to construct a literal Map, which could be made static, final, and unmodifiable. For instance, if you're starting with classes structured like so ...

class Some {
}

class MyBuilder {
    void field1(String s) { }
    void field2(String s) { }
    void field3(String s) { }
    Some build() {
        return null;
    }
}

class ValueType {}

class MyDto {
    int type;
    ValueType value;

    int getType() {
        return type;
    }

    ValueType getValue() {
        return value;
    }
}

... then you might set up the reduction you describe like this:

public class Reduction {

    // Map from DTO types to builder methods
    private final static Map<Integer, BiConsumer<MyBuilder, ValueType>> builderMethods;

    static {
        // one-time map initialization
        Map<Integer, BiConsumer<MyBuilder, ValueType>> temp = new HashMap<>();
        temp.put(FIELD1, MyBuilder::field1);
        temp.put(FIELD2, MyBuilder::field2);
        temp.put(FIELD3, MyBuilder::field3);
        builderMethods = Collections.unmodifiableMap(temp);
    }

    public Some reduce(Collection<MyDto> col) {
        return col.stream()
                  // this reduction produces the populated builder
                  .reduce(new MyBuilder(),
                          (b, d) -> { builderMethods.get(d.getType()).accept(b, d); return b; })
                  // obtain the built object
                  .build();
    }
}

That particular implementation uses a new builder every time, but it could be modified to instead use a builder passed into Reduction.reduce() via a parameter, in case you want to start with some properties pre-populated, and / or retain a record of the properties with which the returned object was built.

Finally, note well that although you can hide the details in one place or another, I don't see any scope to make the overall process any simpler than the switch-based code you started with.

like image 115
John Bollinger Avatar answered Nov 14 '22 21:11

John Bollinger


I did not compile this, but just to give you an idea:

 Map<Boolean, List<MyDto>> map = col.stream().collect(Collectors.partitioningBy(t -> t.getType() == FIELD2));

 map.get(false).forEach(x -> builder.field1(x.getValue()))

 map.get(true).forEach(x -> builder.field2(x.getValue()))
like image 45
Eugene Avatar answered Nov 14 '22 21:11

Eugene