Как собрать / уменьшить поток Java 8 в Pojo? - PullRequest
0 голосов
/ 12 сентября 2018

Посмотрите на код:

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

Есть ли способ сделать это с потоками, например:

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

Обратите внимание, что все значения потока собираются в sigle pojo, а неколлекция, поток или карта.

Ответы [ 5 ]

0 голосов
/ 12 сентября 2018

Ваша основная проблема заключается в том, что сопоставление каждого метода MyBuilder каждому типу MyDto является произвольным, т. Е. У Java нет способа автоматически узнать, какой метод вызывать для каждого типа: вы должны указать Java, какой ,

Итак, если каждый метод компоновщика сопоставлен с различным значением dto.getType(), самый простой способ сообщить Java, это переместить switch в общий метод внутри MyBuilder, который позволяет вам сообщить соответствующее поле, как это:

public MyBuilder fieldFromDto(MyDto dto) {
    switch (dto.getType()) {
        case FIELD1: return field1(dto.getValue);
        case FIELD2: return field2(dto.getValue);
        //...

Итак, вы могли бы сделать именно это:

MyBuilder builder = new MyBuilder();
col.stream().forEach(builder::fieldFromDto);
Some result = builder.build();

Другая возможность состояла бы в том, чтобы превратить этот переключатель в лямбда-карту (Type и Value - это типы полей MyDto):

class MyBuilder {
    public final Map<Type, Function<Value, MyBuilder>> mappings = new Map<>();
    public MyBuilder() {
        mappings.put(FIELD1, this::field1);
        mappings.put(FIELD2, this::field2);
        //...
    }

А затем используйте эти лямбды в forEach:

MyBuilder builder = new MyBuilder();
col.stream().forEach(dto -> builder.mappings.get(dto.getType()).apply(dto.getValue()));
Some result = builder.build();

Кроме этого, вы можете использовать рефлексию, как некоторые другие предложенные ответы, но тогда вам нужно убедиться, что FIELD1, FIELD2 и т. Д. Являются действительными MyBuilder именами методов, теряя некоторую гибкость.

В конце концов, я бы не советовал делать что-либо из вышеперечисленного. Потоки хороши, но иногда они не дают никаких преимуществ по сравнению с обычным циклом for и могут сделать ваш код более уродливым и трудным в обслуживании.

0 голосов
/ 12 сентября 2018

А теперь, для удручающе скучного ответа:

Не делайте этого.

Использование потоков для эффективного отображения таким образом делает ваш код менее читаемым и поддерживаемым в будущем.Для этой цели не рекомендуется использовать эту функцию Java 8.

Абсолютно можно сделать, как это сделали некоторые авторы, но это не обязательно означает, что если будет сделано.

Если говорить более кратко, ваша первоначальная предпосылка заключается в том, что вы можете захватить все свои поля в некотором виде перечисления или структуры, на которой вы можете switch, что прерывается каждый раз, когда вы вводите или удаляетеполе, которое может занять много времени, чтобы выследить.Умные способы получить поля с отражением могут быть немного более гибкими, но тогда вы находитесь в более жесткой конфигурации с отражением, чем вы можете себе представить;если вы хотите отобразить 1 к 1, это работает нормально, но если вы хотите выполнить какое-то преобразование данных, вы должны быть очень осторожны с настройкой своего картографа.

Все это говорит о том, что ...

Вместо этого используйте структуру сопоставления, например MapStruct или Dozer .

0 голосов
/ 12 сентября 2018

Если предположить, что два экземпляра MyBuilder могут быть объединены / объединены, то вы можете сделать это с помощью Collector.

public class MyCollector implements Collector<MyDto, MyBuilder, Result> {

    @Override 
    public Supplier<MyBuilder> supplier() {
        return MyBuilder::new;
    }

    @Override
    public BiConsumer<MyBuilder, MyDto> accumulator() {
        return (builder, dto) -> {
            // Add "dto" to "builder" based on type
        };
    }

    @Override
    public BinaryOperator<MyBuilder> combiner() {
        return (left, right) -> left.merge(right);
    }

    @Override
    public Function<MyBuilder, Result> finisher() {
        return MyBuilder::build;
    }

    @Override
    public Set<Characteristics> characteristics() {
        return Set.of();
    }

}

Тогда вы можете сделать:

Collection<MyDto> col = ...;
Result r = col.stream().collect(new MyCollector());

Если вы не хотите создавать собственную реализацию Collector, вы можете использовать Collector.of(...).


Другой, возможно, более ремонтопригодный способ сделать это - заставить строителя сделать всю работу. Таким образом, вся логика сопоставления находится в одном месте.

public class ResultBuilder {

    public static Collector<MyDto, ?, Result> resultCollector() {
        return Collector.of(ResultBuilder::new, ResultBuilder::add,
                ResultBuilder::merge, ResultBuilder::build);
    }

    public ResultBuilder add(MyDto dto) {
        // Do what is needed based on the type of "dto"
        return this;
    }

    public ResultBuilder merge(ResultBuilder other) {
        // Merge "other" into "this"
        return this;
    }

    public Result build() {
        // Build result and return it
    }

}

Тогда вы можете использовать строитель с или без потоков. С потоками очень похоже на ранее:

Collection<MyDto> col = ...;
Result r = col.stream().collect(ResultBuilder.resultCollector());
0 голосов
/ 12 сентября 2018

Суть в том, что где-то вам необходимо сопоставить возможные возвращаемые значения MyDto.getType() с методами установки свойств MyBuilder.Ваш код делает это с помощью оператора switch, , и это прекрасно .Вместо этого вы можете записать сокращение в виде потокового конвейера, но вам все равно нужно каким-то образом включить отображение.

Довольно прямой способ сделать это - создать литерал Map, который можно сделатьстатический, окончательный и неизменяемый.Например, если вы начинаете с классов, структурированных так ...

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

... тогда вы можете настроить сокращение, которое вы описываете, следующим образом:

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

ТоВ конкретной реализации каждый раз используется новый компоновщик, но его можно изменить, чтобы вместо него использовать компоновщик, переданный в Reduction.reduce() через параметр, в случае, если вы хотите начать с предварительно заполненными некоторыми свойствами и / или сохранить запись свойствс помощью которого был построен возвращенный объект.

Наконец, хорошо заметьте, что, хотя вы можете скрыть детали в одном месте или другом, я не вижу никакой возможности сделать весь процесс проще, чем switchна основе кода, с которого вы начали.

0 голосов
/ 12 сентября 2018

Я не скомпилировал это, но просто чтобы дать вам представление:

 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()))
...