我必须根据属性的值过滤列表。我还必须根据其属性之一来过滤嵌套列表,对于另一个嵌套列表也是如此。我想知道这在流中如何可能。
示例:
我想从这里返回 foo 的列表。
void test() {
List<Foo> listOfFoos;
for(Foo foo : listOfFoos) {
if(foo.getType().equalsIgnoreCase("fooType")) {
// If foo matches condition, retain it
for(Bar bar : foo.getBars()) {
if(bar.getType().equalsIgnoreCase("barType")) {
// If Bar matches condition, retain this Bar
for(NestedAttribute attribute : bar.getNestedAttributes()) {
if(attribute.getId().equalsIgnoreCase("attributeID")) {
// retain this attribute and return it.
}
}
} else {
// remove bar from the list
foo.getBars().remove(bar);
}
}
}else {
// remove Foo from list
listOfFoos.remove(foo);
}
}
}
@Getter
@Setter
class Foo {
String type;
List<Bar> bars;
}
@Getter
@Setter
class Bar {
String type;
List<NestedAttribute> nestedAttributes;
}
@Getter
@Setter
class NestedAttribute {
String id;
}
我试过这个:
listOfFoos = listOfFoos.stream()
.filter(foo -> foo.getType().equalsIgnoreCase("fooType"))
.flatMap(foo -> foo.getBars().stream()
.filter(bar -> bar.getType().equalsIgnoreCase("barType"))
.flatMap(bar -> bar.getNestedAttributes().stream()
.filter(nested -> nested.getId().equalsIgnoreCase("attributeID"))
)
).collect(Collectors.toList());
您可以使用 stream 过滤器 lambda 表达式来做到这一点,但不幸的是,由此产生的内聚力不会很好:
listOfFoos.stream()
.filter(foo ->
(foo.getType().equalsIgnoreCase("fooType") && (foo.getBars().stream()
.filter((bar -> (bar.getType().equalsIgnoreCase("barType") && (bar.getNestedAttributes().stream()
.filter(nestedAttribute -> nestedAttribute.getId().equalsIgnoreCase("attributeID"))
).count() > 0)))
).count() > 0))
.collect(Collectors.toList());
我确信您可以通过流来完成此任务,但我不认为它非常适合这样做。 问题在于流和映射将现有元素替换为新元素,可能是不同类型的。 但有必要维护对先前构造的类型的访问来构建层次结构。
mapMulti
是一种可能性,但它可能会变得混乱(比下面更混乱)。
以下内容创建一个没有任何删除的新层次结构(随机访问列表中的删除可能会很昂贵,因为需要线性搜索或重复复制值)并添加包含所需类型的实例。在每个条件下,都会创建一个新实例。 此时,之前的列表会更新以反映刚刚创建的实例。
生成一些可变数据后,这似乎符合我理解的目标。
static List<Foo> test(List<Foo> listOfFoos) {
List<Foo> newFooList = new ArrayList<>();
for (Foo foo : listOfFoos) {
if (foo.getType().equalsIgnoreCase("fooType")) {
Foo newFoo = new Foo(foo.getType(), new ArrayList<>());
newFooList.add(newFoo);
for (Bar bar : foo.getBars()) {
if (bar.getType().equalsIgnoreCase("barType")) {
Bar newBar = new Bar(bar.getType(), new ArrayList<>());
newFoo.getBars.add(newBar);
for (NestedAttribute attribute : bar
.getNestedAttributes()) {
if (attribute.getId().equalsIgnoreCase(
"attributeID")) {
newBar.getNestedAttributes().add(attribute);
}
}
}
}
}
}
return newFooList;
}
您可以尝试这个选项。这不是一个流利的陈述,而是三个流利的一个。
Function<Bar, List<NestedAttribute>> filterAttributes
= bar -> bar.getNestedAttributes()
.stream()
.filter(a -> "attributeId".equals(a.getId()))
.collect(Collectors.toList());
Function<Foo, List<Bar>> filterBarsAndAttributes
= foo -> foo.getBars()
.stream()
.filter(b -> "barType".equals(b.getType()))
.peek(b -> b.setNestedAttributes(filterAttributes.apply(b)))
.collect(Collectors.toList());
listOfFoos.stream()
.forEach(f -> f.setBars(filterBarsAndAttributes.apply(f)));
我假设您想要所有“fooType”foo,其中只有“barType”栏和“attributeID”嵌套属性。
然后类似:
List<Foo> selected = listOfFoos.stream()
// keep the "footType" foos
.filter(foo -> foo.getType().equalsIgnoreCase("fooType"))
// map each foo to itself
.map(foo -> {
// ... but sneakily remove the non-"barType" bars
foo.getBars().removeIf(bar -> !bar.getType().equalsIgnoreCase("barType"))
return foo;
}
// map each foo to itself again
.map(foo -> {
// iterate over the bars
foo.getBars().forEach(bar ->
// remove the non-"attributeID" nested attributes
bar.getNestedAttributes().removeIf(nested -> !nested.getId().equalsIgnoreCase("attributeID"))
);
return foo;
}
.collect(Collectors.toList());
请注意,这实际上是在修改嵌套集合,而不仅仅是创建流。要获得过滤的嵌套集合需要这样做,或者创建新的嵌套集合。
您可以流式传输您的
List<Foo>
,然后:
filter()
每个 foo
的 type
与 fooType
不匹配。map()
每个foo
自身,同时使用removeIf()
删除所有不满足所需条件的嵌套元素。collect()
剩余的过滤元素。public static List<Foo> filterList(List<Foo> list, String fooType, String barType, String attrID) {
return list.stream()
.filter(foo -> foo.getType().equalsIgnoreCase(fooType))
.map(foo -> {
foo.getBars().removeIf(bar -> !bar.getType().equalsIgnoreCase(barType));
foo.getBars().forEach(bar -> bar.getNestedAttributes().removeIf(attr -> !attr.getId().equalsIgnoreCase(attrID)));
return foo;
})
.collect(Collectors.toList());
}
peek()
方法将每个 lambda 保持为一行。但是,我不推荐这种方法,因为 peek()
应该仅用于调试目的,如文档所述:
此方法的存在主要是为了支持调试,您希望在元素流过管道中的某个点时查看元素
public static List<Foo> filterList2(List<Foo> list, String fooType, String barType, String attrID) {
return list.stream()
.filter(foo -> foo.getType().equalsIgnoreCase(fooType))
.peek(foo -> foo.getBars().removeIf(bar -> !bar.getType().equalsIgnoreCase(barType)))
.peek(foo -> foo.getBars().forEach(bar -> bar.getNestedAttributes().removeIf(attr -> !attr.getId().equalsIgnoreCase(attrID))))
.collect(Collectors.toList());
}