使用流筛选列表和嵌套列表

Filtering lists and nested lists using streams

提问人:CurvedHalo 提问时间:3/31/2022 最后编辑:CurvedHalo 更新时间:4/4/2022 访问量:942

问:

我必须根据属性的值筛选列表。我还必须根据嵌套列表的一个属性来过滤它,同样地,对于另一个嵌套列表。我想知道这在流中怎么可能。

例:

  • 我想过滤 Foo 的列表,只保留 Foo.type = “fooType” 的那些。
    • 在这些保留的 Foo 中,我希望过滤 Bar.type = “barType” 上的 Bar 列表,仅保留满足给定条件的 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 嵌套列表 谓词

评论

0赞 f1sh 3/31/2022
你试过用流吗?你需要。输出是什么?listOfFoos?listOfFoos.stream.filter(...)
0赞 CurvedHalo 3/31/2022
我尝试过流,是的返回一个 listOfFoos,但我得到了那些与给定场景匹配的 foo,但没有删除不匹配的嵌套值
0赞 f1sh 3/31/2022
然后只需反转 的表达式。filter
0赞 WJS 3/31/2022
为什么 Foo 实例不是 fooType?酒吧也一样。对我来说,您需要检查 Foo 以确保它们是 fooType 是没有意义的。Foo实例可以容纳多种类型,并且您希望确保它是条形类型,这更有意义(至少对我而言)。
0赞 CurvedHalo 3/31/2022
这是一个任意的例子,我希望对给定值的给定参数进行过滤。例如,我可以将“type”换成“id”

答:

1赞 Ovidijus Parsiunas 3/31/2022 #1

您可以使用流过滤器 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赞 Adriaan Koster 3/31/2022 #2

我假设你想要所有的“fooType”foos,只有“barType”条和“attributeID”嵌套的Attibutes。

然后是这样的:

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赞 dani-vta 3/31/2022 #3

这就是你要找的

public static List<Foo> filterList(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());
 }

编辑:添加了带有 toString 的类实现以进行测试打印

public class Main {
    public static void main(String[] args) {
        ArrayList barListAttrs = new ArrayList();
        barListAttrs.add(new NestedAttribute("testAttr1"));
        barListAttrs.add(new NestedAttribute("id"));
        barListAttrs.add(new NestedAttribute("testAttr2"));

        ArrayList fooListBars = new ArrayList();
        fooListBars.add(new Bar("bar", barListAttrs));
        fooListBars.add(new Bar("testBar1", new ArrayList<>()));

        List<Foo> listFoo = new ArrayList<>();
        listFoo.add(new Foo("testFoo1", new ArrayList<>()));
        listFoo.add(new Foo("foo", fooListBars));

        for (Foo f : listFoo) {
            System.out.println(f);
        }

        List<Foo> list2 = filterList(listFoo, "foo", "bar", "id");
        System.out.println("\n\n---------------- RESULT ----------------\n");

        for (Foo f : list2) {
            System.out.println(f);
        }
    }
}

class Foo {
    String type;
    List<Bar> bars;

    public Foo(String type, List<Bar> bars) {
        this.type = type;
        this.bars = bars;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public List<Bar> getBars() {
        return bars;
    }

    public void setBars(List<Bar> bars) {
        this.bars = bars;
    }

    @Override
    public String toString() {
        StringBuilder str = new StringBuilder(type);
        str.append(" [");
        for (Bar b : bars) {
            str.append(b.toString());
            str.append(" ");
        }
        str.append("]");
        return str.toString();
    }
}

class Bar {
    String type;
    List<NestedAttribute> nestedAttributes;

    public Bar(String type, List<NestedAttribute> nestedAttributes) {
        this.type = type;
        this.nestedAttributes = nestedAttributes;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public List<NestedAttribute> getNestedAttributes() {
        return nestedAttributes;
    }

    public void setNestedAttributes(List<NestedAttribute> nestedAttributes) {
        this.nestedAttributes = nestedAttributes;
    }

    @Override
    public String toString() {
        StringBuilder str = new StringBuilder(type);
        str.append(" [");
        for (NestedAttribute na : nestedAttributes) {
            str.append(na.toString());
            str.append(" ");
        }
        str.append("]");
        return str.toString();
    }
}

class NestedAttribute {
    String id;

    public NestedAttribute(String id) {
        this.id = id;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return id;
    }
}
1赞 WJS 3/31/2022 #4

我确信你可以用流来做到这一点,但我不认为它非常适合这样做。问题在于,流和地图将现有元素替换为新元素,可能是不同类型的元素。 但是,有必要保持对以前构造的类型的访问以构建层次结构。 将是一种可能性,但它可能会变得杂乱无章(比下面更是如此)。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赞 Ravi Gupta 4/1/2022 #5

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


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