首页 > 解决方案 > 是否可以在单个 Stream 操作中拆分 -> 处理 -> 收集数据

问题描述

我不想将一个流分成两部分,但我想添加一个在转换数据之前拆分数据的操作。

为了说明这一点,假设我有一些常见的对象进来:

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而不是最终转换项目的逻辑,然后在最后根据此标准进行收集?

标签: javajava-stream

解决方案


如果我理解正确,你想要这样的东西:

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)或者是resppartitioningBy(Predicate)的简写。. 因此,当您想在将元素添加到列表之前插入其他操作(例如元素)时,可以显式编写这些表单。groupingBy(Function, toList())partitioningBy(Predicate, toList())mapping

partitioningBygroupingBy布尔键的一种特殊形式,它允许底层实现针对这种情况使用优化的代码。


要在一个 Stream 操作中执行此操作,您需要一种能够保存结果的类型。像这样的班级

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,避免遍历没有任何优势,因此这只是不必要的代码复杂化。


推荐阅读