Java 8 Streams: Multiple Filters Vs. Complex Condition

Java 8 Streams: multiple filters vs. complex condition

The code that has to be executed for both alternatives is so similar that you can’t predict a result reliably. The underlying object structure might differ but that’s no challenge to the hotspot optimizer. So it depends on other surrounding conditions which will yield to a faster execution, if there is any difference.

Combining two filter instances creates more objects and hence more delegating code but this can change if you use method references rather than lambda expressions, e.g. replace filter(x -> x.isCool()) by filter(ItemType::isCool). That way you have eliminated the synthetic delegating method created for your lambda expression. So combining two filters using two method references might create the same or lesser delegation code than a single filter invocation using a lambda expression with &&.

But, as said, this kind of overhead will be eliminated by the HotSpot optimizer and is negligible.

In theory, two filters could be easier parallelized than a single filter but that’s only relevant for rather computational intense tasks¹.

So there is no simple answer.

The bottom line is, don’t think about such performance differences below the odor detection threshold. Use what is more readable.


¹…and would require an implementation doing parallel processing of subsequent stages, a road currently not taken by the standard Stream implementation

Java 8 Streams - Filter More Than One Condition

Simple enough

resultList.stream()
.filter(fixture -> fixture.getHome().equals(team) || fixture.getAway().equals(team)))
.collect(toList());

EDIT: This is on the assumption that order does not matter to you. If your final list needs to have home result and then away, have a look at Elliott Frisch's answer.

How to apply multiple Filters on Java Stream?

I suppose you want to keep all the TestObjects that satisfy all the conditions specified by the map?

This will do the job:

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

Translated into "English",

filter the list list by keeping all the elements x that:

  • all of the key value pairs y of filterMap must satisfy:

    • x.getProperty(y.getKey()) == y.getValue()

(I don't think I did a good job at making this human readable...) If you want a more readable solution, I recommend Jeroen Steenbeeke's answer.

java 8 stream multiple filters

You can do it like so,

Pattern comma = Pattern.compile(",");
Pattern empNum = Pattern.compile("(244|543)\\d+");
Pattern empType = Pattern.compile("(Contract|Permanent)");
try (Stream<String> stream = Files.lines(Paths.get("C:\\data\\sample.txt"))) {
List<String> result = stream.skip(2).map(l -> comma.split(l))
.filter(s -> empNum.matcher(s[2]).matches())
.filter(s -> empType.matcher(s[5]).matches())
.map(s -> Arrays.stream(s).collect(Collectors.joining(",")))
.collect(Collectors.toList());
System.out.println(result);
} catch (IOException e) {
e.printStackTrace();
}

First read the file and skip 2 header lines. Then split it using the , character. Filter it out using EmpId and EmpType. Next, merge the tokens back again to form the line, and finally collect each line into a List.

Java 8 stream filter on multiple filters

You first need to create a Predicate out of all your filters. For this, you can reduce them to a final Predicate by means of the AND operator:

Predicate<Order> predicate = filters.stream()
.map(f -> (Predicate<Order>) f::filter)
.reduce(Predicate::and)
.orElse(o -> true);

Now, you can apply this predicate to the stream of orders:

orders.stream()
.filter(predicate)
...

Performance of "filter()" method in Stream API in java

In the case of filter(cond1 && cond2 && cond3), the processing of condition stops as soon as any of the conditions evaluates to false e.g. if cond1 evaluates to false, the other conditions (cond2 and cond3) won't be processed. Similarly, if cond1 evaluates to true, the processing will proceed to the evaluation of cond2 and if this evaluates to false, the condition, cond3 will not be evaluated.

The processing of filter(cond1).filter(cond2).filter(cond3) too happens in the same manner, as can be seen from the following example:

import java.util.stream.Stream;

public class Main {
public static void main(String[] args) {
Stream.of("one", "two", "three", "four")
.filter(s -> {
System.out.println("Hello");
return s.contains("t");
})
.filter(s -> {
System.out.println("Hi");
return s.contains("f");
})
.forEach(System.out::println);
}
}

Output:

Hello
Hello
Hi
Hello
Hi
Hello

Thus, it does not make any difference and it's a matter of choice. The second one looks cleaner.


Note: For more complex expressions, you can use Predicate#and which gives you two more benefits:

  1. You can reuse the behaviour implemented by the Predicate with other streams in your code.
  2. Further clean code.

Demo:

import java.util.function.Predicate;
import java.util.stream.Stream;

public class Main {
public static void main(String[] args) {
Predicate<String> containsT = s -> s.contains("t");
Predicate<String> containsE = s -> s.contains("e");

Stream.of("one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten")
.filter(containsT.and(containsE))
.forEach(System.out::println);
}
}

Output:

three
eight
ten

However, it doesn't make any difference in the way the processing happens.

Java8 stream filter by multiple parameters

filter by the two properties and collect into a list.

List<Transfer> resultSet = 
transfers.stream().filter(t -> id.equals(t.getFromAccountID()) ||
id.equals(t.toAccountID()))
.collect(Collectors.toList());

Java Streams: Find first for multiple filter predicates

How about doing it the other way around? Streaming the products, and applying predicates on them.

List<Predicate<Product>> predicates = getPredicates();
List<Product> products = getProducts();
List<Product> filtered = products.stream().filter(product -> {
Iterator<Predicate<Product>> iterator = predicates.iterator();
while (iterator.hasNext()) {
Predicate<Product> currentPredicate = iterator.next();
if (currentPredicate.test(product)) {
iterator.remove();
return true;
}
}
return false;
}).collect(Collectors.toList());

The downside is you have to be careful which collection you use for predicates, Iterator.remove is not always supported.

Edit: Looks like i wasn't reading carefully enough. I think getting one of each would be best with loop.

List<Product> products = getProducts();
List<Predicate<Product>> predicates = getPredicates();
List<Product> matchingProducts = new ArrayList<>(predicates.size());
for (Product product : products) {
if (predicates.isEmpty()) {
break;
}
for (int predicateIndex = 0; predicateIndex < predicates.size(); predicateIndex++) {
Predicate<Product> predicate = predicates.get(predicateIndex);
if (predicate.test(product)) {
matchingProducts.add(product);
predicates.remove(predicateIndex);
break;
}
}
}

Actually managed to achieve it with a stream and takeWhile, you were correct, Benjamin.

List<Predicate<Product>> predicates = getPredicates();
List<Product> products = getProducts();
List<Product> matches = products.stream()
.takeWhile(product -> !predicates.isEmpty())
.filter(product -> {
Iterator<Predicate<Product>> iterator = predicates.iterator();
while (iterator.hasNext()) {
if (iterator.next().test(product)) {
iterator.remove();
return true;
}
}
return false;
})
.collect(Collectors.toList());

Just make sure takeWhile is before filter, otherwise last matching element gets skipped.



Related Topics



Leave a reply



Submit