Filtering/Removing 来自使用流的嵌套对象列表 Java

Filtering/Removing from a Nested List of Objects using Streams in Java

假设我们有一个 3 维对象列表。

  class OneDObject {
    int id;
    List<Integer> list;
    OneDObject(int id, List<Integer>list) { // Constructor }
    // Getters and Setters
  }

  class TwoDObject {
    int id;
    List<OneDObject> list;
    TwoDObject(int id, List<OneDObject> list) { // Constructor }
    // Getters and Setters
  }

  var l1 = List.of(1,2,4);
  var l2 = List.of(2,4,6);
  var obj1d1 = new OneDObject(1, l1);
  var obj1d2 = new OneDObject(2, l2);
  var l3 = List.of(obj1d1, obj1d2);
  var l4 = List.of(obj1d1);
  var obj2d1 = new TwoDObject(3, l3);
  var obj2d2 = new TwoDObject(4, l4);
  var l5 = List.of(obj2d1, obj2d2);   // 3-d list

说我想过滤“l5”,如果最里面的列表中的任何元素是奇数,那么整个列表应该被删除,如果这使得第二级列表为空,那么应该在 return.

中被删除

因此,对于给定的示例,在过滤之前如果是:

[[[1,2,4],[2,4,6]], [[1,2,4]]]

过滤后应该是:

[[[2,4,6]]]

如何在 Java 中使用 Streams 执行此操作?

这是最终代码(在 Java 中,但我认为它应该几乎可以与 Kotlin 互换)

List<TwoDObject> l6 = l5.stream()
                .peek(twoDObject -> {
                    List<OneDObject> filteredOneDObjectList = twoDObject.getList()
                            .stream()
                            .filter(oneDObject -> oneDObject.getList()
                                    .stream()
                                    .noneMatch(i -> i % 2 == 1))
                            .toList();

                    twoDObject.setList(filteredOneDObjectList);
                })
                .filter(twoDObject -> twoDObject.getList().size() > 0)
                .toList();

首先,我们通过调用 Stream#peek 遍历每个 twoDObject,然后流式传输其列表并过滤掉每个包含奇数的 oneDObject。然后将列表保存回当前的twoDObject。

最后我们过滤掉所有空的twoDObjects。


请注意,Stream#peek 通常应仅用于调试目的,而不是改变流元素。
在这种情况下也可以替换为

List<TwoDObject> l6 = l5.stream()
                .map(twoDObject -> {
                    ...

                    return twoDObject;
                })
                ...

为了实现您正在寻找的东西,我们需要混合使用功能接口的流操作和集合方法。

在下面的代码中,首先我们使用 peek 聚合操作来检查每个 TwoDbObj 实例。

然后,对于它们中的每一个,我们检查它们的 OneDbObj 列表并删除至少包含奇数的实例。

一旦 OneDbObj 列表被最里面的 Integer 列表过滤,我们只保留列表至少包含一个值(也可以写为 !isEmpty)的 TwoDbObj 实例。

List<TwoDObject> listFiltered = l5.stream()
        .peek(twoObj -> twoObj.getList().removeIf(oneObj -> oneObj.getList().stream().anyMatch(i -> i % 2 != 0))) //For each TwoDbObj we get rid of the OneDbObj list whose elements contain at least an odd number
        .filter(twoObj -> twoObj.getList().size() > 0) //Keeping only the TwoDbObj instances whose list contains any element
        .collect(Collectors.toList());

由于您需要更新列表,在下面的解决方案中,我使用 ListremoveIf 方法删除任何不符合必要条件的元素。因此,要使 removeIf 起作用,列表不应该是不可变的。所以用 var list = new ArrayList<>(List.of(...)); 替换 var list = List.of(...) 代码 (注意:空检查也被忽略了。)

现在,这个问题可以分解成几个部分:

  1. 判断列表是否有奇数元素的谓词。
Predicate<OneDObject> hasOdd = obj-> obj.getList().stream().anyMatch(i -> i % 2 != 0);
  1. 谓词从二维列表中删除对象,该列表在其一维列表中包含奇数元素。
Predicate<TwoDObject> validate2d = obj -> {
    // remove any 1d list that has atleast one odd number.
    obj.getList().removeIf(hasOdd);
    // check if there are any valid 1d lists
    return obj.getList().isEmpty();
};
  1. 现在将谓词应用于最终列表:
l5.removeIf(validate2d); // l5 will now contain only the 2d object having [2,4,6] list