What is the Java Stream way to use a different filtering (if any) on a collection, based on external conditions?
List<Data> initialData = ...
List<Data> data = something.isTrue() ? initialData.stream().filter() : initialData;
CodePudding user response:
Predicate<Data> predicate = d -> true; // Initial predicate does no filtering
predicate = predicate.and(d -> d.hasData()); // Chain additional conditions, possibly in a separate method
initialData.stream().filter(predicate).collect(Collectors.toList());
CodePudding user response:
It is clear that a filter value can simply be passed as a parameter to a function.
It is different if you want to pass the field to be filtered as a parameter. For this purpose I use a functional interface.
You can define your own that suits your purposes, use one of the existing ones in import java.util.function.*;
or derive one from them.
Your filter function would then take your list of objects, a function, and a filter value. The type of filtering could also be passed.
Here is an example of a function that takes the comparison operator, the comparison field and the comparison value and returns a filtered list.
import java.util.*;
import java.util.function.*;
import java.util.stream.Collectors;
public class Test {
private static final String HOUSTON = "Houston";
private static final String CHICAGO = "Chicago";
private static final String BOSTON = "Boston";
public static void main(String[] args) {
new Test();
}
public Test() {
List<From> from = new ArrayList<>();
from.add(new From( "Ralph", "Sanders", "Main Street", HOUSTON ));
from.add(new From( "Susan", "Schulz", "Main Street", HOUSTON ));
from.add(new From( "Claudia", "Walters", "Sunset Ave", HOUSTON ));
from.add(new From( "Jack", "Kolis", "Sunset Ave", HOUSTON ));
from.add(new From( "Sophia", "Brinks", "Green Street", CHICAGO ));
from.add(new From( "Erica", "Holden", "Green Street", CHICAGO ));
from.add(new From( "Claus", "Dishman", "Irish Ave", CHICAGO ));
from.add(new From( "Julia", "Ford", "Irish Ave", CHICAGO ));
from.add(new From( "Susan", "Snow", "Tea Street", BOSTON ));
from.add(new From( "Frank", "Schulz", "Tea Street", BOSTON ));
from.add(new From( "Kirsten", "Dilling", "Harbor Ave", BOSTON ));
from.add(new From( "Kurt", "Burton", "Harbor Ave", BOSTON ));
List<From> byCity = myFilter(from, (a, b) -> a.equals(b), From::getCity, "Houston");
for(From e : byCity) System.out.println(e);
List<From> byStreet = myFilter(from, (a, b) -> a.equals(b), From::getStreet, "Sunset Ave");
for(From e : byStreet) System.out.println(e);
}
public List<From> myFilter(List<From> from, BiPredicate<String, String> predicate, Function<From, String> where, String by) {
return from.stream().filter(f -> predicate.test(where.apply(f), by)).collect(Collectors.toList());
}
}
Edit: For the sake of completeness:
public class From {
private String firstName;
private String lastName;
private String street;
private String city;
public From(String firstName, String lastName, String street, String city) {
this.firstName = firstName;
this.lastName = lastName;
this.street = street;
this.city = city;
}
public String getFirstName() { return firstName; }
public String getLastName() { return lastName; }
public String getStreet() { return street; }
public String getCity() { return city; }
@Override
public String toString() {
return "[" firstName ", " lastName ", " city ", " street "]";
}
}
CodePudding user response:
More of an addendum: keep in mind that using streams comes with a certain overhead. Yes, using it leads to nice, concise code (when you know what are doing), but there is runtime cost for establishing streams. From that point of view, it isn't necessarily always the "best" option to do things inside the stream.
Meaning: the given example isn't just about style. There might be a significant performance penalty for turning the external check into something that always streams your collection.