我不想将流拆分为两个,但我想添加一个操作,在转换之前拆分数据。
为了说明这一点,假设我有一些共同的对象:
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
的拆分创建一个逻辑,而不是最终转换项目的一个,然后在最后基于此标准收集?
如果我找对你,你想要这样的东西:
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
来说,避免遍历是没有优势的,所以这只是一个不必要的代码复杂化。
这是我的两分钱。
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
。此类分区的每个值现在都包含一个列表,其中包含布尔属性相同的对象。
正如你在评论中所说的那样,bucketing的标记只是通过将bucketing标记(boolean)和转换后的对象包装成一对Pair(在我的例子中为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();
}
}