简体   繁体   中英

filter inner and outer list in java using streams

Is there a way to filter both inner and outer lists using some predicate and java streams? For example:

Class outerObject{
    int num;
    List<innerObject> innerObjectList;
}
Class innerObject{
    int num;
}
List <outerObject> outerObjectList

Is there a way to filter the list such that the outerObject list has field num = 10 and the list of innerObject 's for the corresponding outerObject is also filtered by corresponding field num = 10 ?

I tried:

outerObjectList.stream()
               .filter(i -> i.getNum() == 10)
               .filter(i -> i.getOrders().stream().anyMatch(s -> getNum() == 10))
               .collect(Collectors.toList());

But it does not seem to filter the inner list.

Descriptive example:

If a outer list has 3 objects with num = 10 and each of the 3 object has 1 inner object in their innerObjectList with num = 10 then it should give me a list of 3 outerObject with a list of 1 innerObject each.

It's not filtering the inner list because at no point are you altering any elements; you're just removing them from the top-level list.

To filter the inner list as well, you should have a .map operation that operates on the outerObjects, changing them to new outerObjects whose innerObjectLists are themselves filtered versions of the old ones.

outerObjectList.stream()
                .filter(i -> i.getNum() == 10)
              .filter(i -> i.getOrders().stream().anyMatch(s -> getNum() == 10))
                .collect(Collectors.toList());

Let's break this down a bit: You have two streams, an outer stream (on which you call .filter , .filter and .collect , and an inner stream, on which you call .anyMatch . All that you end up with is the collection of the outer stream, and at no point do you modify any of its members (only the structure of the stream itself, by removing them.) You don't modify the inner stream at all; you just report on a characteristic of it ("Does it have anything with getNum() == 10 ?")

If you want to filter the inner stream, you have more work to do. You could actually modify the elements of the outer stream with .peek , changing their lists to "filtered" versions; alternatively, if you want to be good and immutable, you could create new filtered versions with .map .

For the .map() approach, you need to have a lambda that creates a new OuterObject such that:

  • output.num == input.num
  • output.innerObjectList == input.innerObjectList.stream().filter(s -> s.getNum() == 10)

Does this make sense?

When you say 'filter' in the context of streams that normally means to filter the stream so that downstream operations see a subset of the stream. I gather than what you are really looking for is to remove items from the lists based on a condition. You can do this using a 'peek' operation:

public class Outer {

    private final int num;
    private final List<Inner> inners = new ArrayList<>();

    public class Inner {

        private final int num;

        public Inner(int num) {
            this.num = num;
        }

        public boolean hasNum(int num) {
            return this.num == num;
        }
    }

    public Outer(int num) {
        this.num = num;
    }

    public void addInner(int num) {
        inners.add(new Inner(num));
    }

    public void removeInners(int num) {
        inners.removeIf(i -> i.hasNum(num));
    }

    public boolean hasInner(int num) {
        return inners.stream().anyMatch(i -> i.hasNum(num));
    }

    public boolean hasNum(int num) {
        return this.num == num;
    }
}

@Test
public void testOuter() {
    List<Outer> outers = new ArrayList<>();
    outers.add(new Outer(1));
    outers.add(new Outer(1));
    outers.get(1).addInner(5);
    outers.get(1).addInner(1);
    outers.add(new Outer(2));
    outers.add(new Outer(1));
    outers.get(3).addInner(1);
    outers = outers.stream()
            .filter(o -> o.hasNum(1))
            .peek(o -> o.removeInners(1))
            .collect(Collectors.toList());
    assertThat(outers.size(), is(2));
    assertFalse(outer.stream().anyMatch(i -> i.hasInner(1)));
}

Note that I am assigning the list back to the variable after it's collected.

If you want to modify the lists in-place (not creating new lists), you can use Collection.removeIf method:

outerObjectList.removeIf(i -> i.getNum() != 10);
outerObjectList.forEach(i -> i.getOrders().removeIf(s -> s.getNum() != 10));

No Stream API is necessary for this operation.

outerObjectList.stream()
            .filter(outer -> outer.num == 10)
            .map(outerObject -> {
                outerObject.innerObjectList= outerObject.innerObjectList.stream().filter(innerObject -> innerObject.num == 10).collect(Collectors.toList());
                return outerObject;
            })
            .collect(Collectors.toList());

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM