Можно ли разделить -> обработать -> собрать данные в одной операции потока - PullRequest
0 голосов
/ 26 февраля 2019

Я не хочу разбивать поток на два, но я хочу добавить операцию, которая разбивает данные до Я преобразую его.

Чтобы проиллюстрировать это, скажем, у меня есть какой-то общий объект:

public class CommonItem {
    private String name;
    private boolean isSelected;
   /* getters and setters */
}

У меня есть этот объект, представляющий различные вещи, такие как:

public class Foo {
    private String text;
    private boolean isChecked;

    public Foo(String text, boolean isChecked) {
        this.text = text;
        this.isChecked = isChecked;
    }
   /* getters and setters */
}

и

public class Bar {
    private String title;
    private boolean isTicked;

    public Bar(String title, boolean isTicked) {
        this.title = title;
        this.isTicked = isTicked;
    }
   /* getters and setters */
}

Итак, в операции Stream я могу легко преобразовать их в нужный элемент и разделить их по логическому свойству, выполнив

listOfCommonItems.stream()
    .map(item -> new Foo(item.getName(), item.isSelected()))
    .collect(Collectors.groupingBy(Foo::isChecked));

.Я хочу вывод - Map<Boolean, List<Foo>> разделить на два сегмента - те, которые проверены и те, которые нет.Тем не менее, если бы я хотел такого же рода вещи с Bar, я должен был бы выполнить другие критерии сбора

listOfCommonItems.stream()
    .map(item -> new Bar(item.getName(), item.isSelected()))
    .collect(Collectors.groupingBy(Bar::isTicked));

, чтобы получить Map<Boolean, List<Bar>>.

Я могу использовать .filter, нотогда мне нужно обработать поток дважды.Это похоже на то, если я разделю CommonItem на две части и обработаю результаты позже.И мне точно не нужны два потока, так как это один и тот же набор данных, мне просто нужно это в двух корзинах, где общие критерии предшествуют их преобразованию.

Однако можно ли как-то выполнить разбиение до сопоставления, так что я могу легко иметь единую логику для разделения, основанную на CommonItem вместо единицы для конечного преобразованного элемента, а затем собирать на основе этого критерия в конце?

Ответы [ 2 ]

0 голосов
/ 26 февраля 2019

Вот мои два цента.

List<CommonItem> listOfCommonItems = Arrays.asList(
    new CommonItem("foo", true),
    new CommonItem("bar", false),
    new CommonItem("bar", false),
    new CommonItem("foo", true),
    new CommonItem("foo", false),
    new CommonItem("bar", true),
    new CommonItem("bar", false)
);

Map<Class<?>, ? extends Map<Boolean, ? extends List<?>>> map = listOfCommonItems.stream()
    .map(item -> new SimpleEntry<>(item.isSelected(), convertCommonItem(item)))
    .collect(groupingBy(t -> t.getValue().getClass(), partitioningBy(
        Entry::getKey, mapping(t -> t.getValue(), toList()))));

    System.out.println(map);

Каждый класс (Foo, Bar, ...) сопоставлен с объектом Partition, который представляет собой Map, содержащий ключи true и false .Каждое значение такого раздела теперь содержит список с объектами, для которых логическое свойство было одинаковым.

Тег для сегментирования, как вы назвали его в комментариях, просто реализуется путем переноса обоих тегов (boolean) и преобразованный объект в своего рода пару (AbstractMap.SimpleEntry в моем случае).


Вот некоторая фиктивная реализация преобразования.Я точно не знаю, как вы планировали преобразовать общие элементы в их соответствующие классы, но вот простая реализация, обеспечивающая небольшой контекст:

private static Object convertCommonItem(CommonItem commonItem) {
    switch (commonItem.getName()) {
        case "foo":
            return new Foo(commonItem.getName(), commonItem.isSelected());
        case "bar":
            return new Bar(commonItem.getName(), commonItem.isSelected());
        default:
            throw new UnsupportedOperationException();
    }
}
0 голосов
/ 26 февраля 2019

Если я вас правильно понял, вы хотите, чтобы что-то вроде этого:

public static <T> Map<Boolean,List<T>> splitData(
    List<CommonItem> listOfCommonItems, BiFunction<String,Boolean,T> mapper) {

    return listOfCommonItems.stream()
        .collect(Collectors.partitioningBy(CommonItem::isSelected,
            Collectors.mapping(ci -> mapper.apply(ci.getName(), ci.isSelected()),
                Collectors.toList())));
}

можно было использовать как

Map<Boolean,List<Foo>> map1 = splitData(listOfCommonItems, Foo::new);
Map<Boolean,List<Bar>> map2 = splitData(listOfCommonItems, Bar::new);

Вы должны понимать, что groupingBy(Function) или partitioningBy(Predicate)короткие руки для groupingBy(Function, toList()) соотв.partitioningBy(Predicate, toList()).Таким образом, вы можете писать эти формы явно, когда вы хотите вставить дополнительные операции, такие как mapping элементы перед добавлением их в списки.

partitioningBy - это специализированная форма groupingBy для логических ключей, которая позволяетбазовая реализация для использования оптимизированного кода для этого случая.


Чтобы сделать это в одной операции потока, вам нужен тип, способный содержать результат.С таким классом, как

class Both {
    List<Foo> foos = new ArrayList<>();
    List<Bar> bars = new ArrayList<>();
    void add(CommonItem ci) {
        String name = ci.getName();
        boolean sel = ci.isSelected();
        foos.add(new Foo(name, sel));
        bars.add(new Bar(name, sel));
    }
    Both merge(Both other) {
        if(foos.isEmpty()) return other;
        foos.addAll(other.foos);
        bars.addAll(other.bars);
        return this;
    }
}

, вы можете собрать их все как

Map<Boolean, Both> map = listOfCommonItems.stream()
    .collect(Collectors.partitioningBy(CommonItem::isSelected,
        Collector.of(Both::new, Both::add, Both::merge)));

, хотя для обычного List избегать обхода нет никакого преимущества, так что этопросто будьте ненужным усложнением кода.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...