是否可以在单个Stream操作中进行拆分->处理->收集数据

时间:2019-02-26 06:52:56

标签: java java-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操作中,我可以轻松地将它们转换为所需的项目,并通过boolean属性将它们拆分

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 :(得分:3)

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

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的一种特殊形式,它允许基础实现在这种情况下使用优化的代码。


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

答案 1 :(得分:0)

这是我的两分钱。

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

每个类(FooBar,...)映射到Partition对象,该对象是一个包含键 true false 。现在,该分区的每个值都包含一个列表,其中包含boolean属性相同的对象。

就像您在注释中所称的那样,用于存储分区的标签只是通过将存储分区标签(布尔值)和转换后的对象包装成一对(在我的情况下为Map)中来实现的。 / p>


这是转换的一些虚拟实现。我不完全知道您打算如何将常见项目转换为它们各自的类,但这是一个提供一些上下文的简单实现:

AbstractMap.SimpleEntry