使用流过滤列表和嵌套列表

问题描述 投票:0回答:5

我必须根据属性的值过滤列表。我还必须根据其属性之一来过滤嵌套列表,对于另一个嵌套列表也是如此。我想知道这在流中如何可能。

示例:

  • 我想过滤 Foo 列表,仅保留 Foo.type = "fooType" 的列表。
    • 在这些保留的 Foo 中,我希望过滤 Bar.type = "barType" 上的 Bar 列表,仅保留满足给定条件的那些。
      • 然后我想过滤 NestedAttribute.id = "attributeID" 上的 NestedAttribute 列表,只保留那些符合此条件的。

我想从这里返回 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());

java java-stream nested-lists predicate
5个回答
1
投票

您可以使用 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());


1
投票

我确信您可以通过流来完成此任务,但我不认为它非常适合这样做。 问题在于流和映射将现有元素替换为新元素,可能是不同类型的。 但有必要维护对先前构造的类型的访问来构建层次结构。

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

1
投票

您可以尝试这个选项。这不是一个流利的陈述,而是三个流利的一个。


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


1
投票

我假设您想要所有“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());

请注意,这实际上是在修改嵌套集合,而不仅仅是创建流。要获得过滤的嵌套集合需要这样做,或者创建新的嵌套集合。


1
投票

您可以流式传输您的

List<Foo>
,然后:

  1. filter()
    每个
    foo
    type
    fooType
    不匹配。
  2. map()
    每个
    foo
    自身,同时使用
    removeIf()
    删除所有不满足所需条件的嵌套元素。
  3. 最后,
    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());
}
© www.soinside.com 2019 - 2024. All rights reserved.