如何在Java Stream上应用多个filter?

我必须通过Map过滤对象集合,它保存对象字段名称和字段值的键值对。 我试图通过stream()。filter()应用所有filter。

对象实际上是JSON,因此Map保存其变量的名称以及它们必须包含的值以便被接受,但为了简单起见并且因为它与问题无关,我编写了一个简单的Testclass来模拟行为:

public class TestObject { private int property1; private int property2; private int property3; public TestObject(int property1, int property2, int property3) { this.property1 = property1; this.property2 = property2; this.property3 = property3; } public int getProperty(int key) { switch(key) { case 1: return property1; case 2: return property2; default: return property3; } } } 

到目前为止我尝试了什么:

 public static void main(String[] args) { List list = new ArrayList(); Map filterMap = new HashMap(); list.add(new TestObject(1, 2, 3)); list.add(new TestObject(1, 2, 4)); list.add(new TestObject(1, 4, 3)); filterMap.put(3, 3); //Filter property3 == 3 filterMap.put(2, 2); //Filter property2 == 2 //Does not apply the result filterMap.forEach((key, value) -> list.stream() .filter(testObject -> testObject.getProperty(key) == value) .collect(Collectors.toList()) ); /* Gives error: boolean can not be converted to void list = list.stream() .filter(testObject -> filterMap.forEach((key, value) -> testObject.getProperty(key) == value)) .collect(Collectors.toList() ); */ //Printing result list.forEach(obj -> System.out.println(obj.getProperty(1) + " " + obj.getProperty(2) + " " + obj.getProperty(3))); } 

我首先尝试将forEach of the Map和Collection的流放在首位,但两种解决方案都没有按预期工作。 此示例的所需输出仅用于打印具有值property1 = 1,property2 = 2和property3 = 3的对象。

如何正确应用所有filter,就像在具有固定数量的filter的代码中一个接一个地放置它们一样?

使用已知数量的filter:

 list.stream().filter(...).filter(...) 

编辑:

Sweeper在他的回答中总结了我的问题,所以只是为了澄清(可能还有未来的读者):我想保留满足所有filter的所有对象。

我想你想保留满足地图指定的所有条件的所有TestObjects

这将完成工作:

 List newList = list.stream() .filter(x -> filterMap.entrySet().stream() .allMatch(y -> x.getProperty(y.getKey()) == y.getValue() ) ) .collect(Collectors.toList()); 

翻译成“英文”,

通过保留以下所有元素xfilter列表list

  • filterMap所有键值对y必须满足:
    • x.getProperty(y.getKey()) == y.getValue()

(我认为我没有做好这个人类可读的工作……)如果你想要一个更易读的解决方案,我推荐Jeroen Steenbeeke的答案。

要将可变数量的filter步骤应用于流(仅在运行时已知),您可以使用循环来添加filter步骤。

 Stream stream = list.stream(); for (Predicate predicate: allPredicates) { stream = stream.filter(predicate); } list = stream.collect(Collectors.toList()); 

更通用的方法是创建一个多重filter( Predicate ),它使用Predicate.and(...)Predicate.or(...) 。 这适用于任何使用Predicate东西 – 首先是StreamOptional
由于结果是Predicate本身,因此可以继续使用Predicate.and(...)Predicate.or(...)或者再次使用MultiPredicate构建更复杂的谓词。

 class MultiPredicate { public static  Predicate matchingAll(Collection> predicates) { Predicate multiPredicate = to -> true; for (Predicate predicate : predicates) { multiPredicate = multiPredicate.and(predicate); } return multiPredicate; } @SafeVarargs public static  Predicate matchingAll(Predicate first, Predicate... other) { if (other == null || other.length == 0) { return first; } Predicate multiPredicate = first; for (Predicate predicate : other) { multiPredicate = multiPredicate.and(predicate); } return multiPredicate; } public static  Predicate matchingAny(Collection> predicates) { Predicate multiPredicate = to -> false; for (Predicate predicate : predicates) { multiPredicate = multiPredicate.or(predicate); } return multiPredicate; } @SafeVarargs public static  Predicate matchingAny(Predicate first, Predicate... other) { if (other == null || other.length == 0) { return first; } Predicate multiPredicate = first; for (Predicate predicate : other) { multiPredicate = multiPredicate.or(predicate); } return multiPredicate; } } 

适用于这个问题:

 public static void main(String... args) { List list = new ArrayList<>(); Map filterMap = new HashMap<>(); list.add(new TestObject(1, 2, 3)); list.add(new TestObject(1, 2, 4)); list.add(new TestObject(1, 4, 3)); filterMap.put(3, 3); // Filter property3 == 3 filterMap.put(2, 2); // Filter property2 == 2 List> filters = filterMap.entrySet().stream() .map(filterMapEntry -> mapToFilter(filterMapEntry)) .collect(Collectors.toList()); Predicate multiFilter = MultiPredicate.matchingAll(filters); List filtered = list.stream() .filter(multiFilter) .collect(Collectors.toList()); for (TestObject to : filtered) { System.out.println("(" + to.getProperty(1) + "|" + to.getProperty(2) + "|" + to.getProperty(3) + ")"); } } private static Predicate mapToFilter(Entry filterMapEntry) { return to -> to.getProperty(filterMapEntry.getKey()) == filterMapEntry.getValue(); } 

在这种情况下,所有filter必须匹配。 结果是:

 (1|2|3) 

如果我们使用MultiPredicate.matchingAny(...) ,结果是:

 (1|2|3) (1|2|4) (1|4|3) 

它与filter无关。 实际上,filter永远不会按照您的代码工作。 看着

 //Does not apply the result filterMap.forEach((key, value) -> list.stream() .filter(testObject -> testObject.getProperty(key) == value) .collect(Collectors.toList()) ); 

列表已被过滤,但此处没有任何更改。 没有删除任何元素,也没有更改任何对象地址。 尝试removeIf

 // Does not apply the result filterMap.forEach((key, value) -> list.removeIf(testObject -> testObject.getProperty(key) != value)); 

输出是

 1 2 3