java-predicate-chain
Java 8 Predicate Chain
2. Basic Example
@Test
public void whenFilterList_thenSuccess(){
List<String> names = Arrays.asList("Adam", "Alexander", "John", "Tom");
List<String> result = names.stream()
.filter(name -> name.startsWith("A"))
.collect(Collectors.toList());
assertEquals(2, result.size());
assertThat(result, contains("Adam","Alexander"));
}
In this example, we filtered our List of names to only leave names that start with “A” using the Predicate:
name -> name.startsWith("A")
But what if we wanted to apply multiple Predicates?
3. Multiple Filters
@Test
public void whenFilterListWithMultipleFilters_thenSuccess(){
List<String> result = names.stream()
.filter(name -> name.startsWith("A"))
.filter(name -> name.length() < 5)
.collect(Collectors.toList());
assertEquals(1, result.size());
assertThat(result, contains("Adam"));
}
We’ve now updated our example to filter our list by extracting names that start with “A” and have a length that is less than 5.
We used two filters — one for each Predicate.
4. Complex Predicate
@Test
public void whenFilterListWithComplexPredicate_thenSuccess(){
List<String> result = names.stream()
.filter(name -> name.startsWith("A") && name.length() < 5)
.collect(Collectors.toList());
assertEquals(1, result.size());
assertThat(result, contains("Adam"));
}
This option is more flexible than the first one, as we can use bitwise operations to build the Predicate as complex as we want.
5. Combining Predicates
Next, if we don’t want to build a complex Predicate using bitwise operations, Java 8 Predicate has useful methods that we can use to combine Predicates.
We’ll combine Predicates using the methods Predicate.and(), Predicate.or(), and Predicate.negate().
5.1. Predicate.and()
In this example, we’ll define our Predicates explicitly, and then we’ll combine them using Predicate.and():
@Test
public void whenFilterListWithCombinedPredicatesUsingAnd_thenSuccess(){
Predicate<String> predicate1 = str -> str.startsWith("A");
Predicate<String> predicate2 = str -> str.length() < 5;
List<String> result = names.stream()
.filter(predicate1.and(predicate2))
.collect(Collectors.toList());
assertEquals(1, result.size());
assertThat(result, contains("Adam"));
}
As we can see, the syntax is fairly intuitive, and the method names suggest the type of operation. Using and(), we’ve filtered our List by extracting only names that fulfill both conditions.
5.2. Predicate.or()
Let’s extract names start with “J”, as well as names with a length that’s less than 4:
@Test
public void whenFilterListWithCombinedPredicatesUsingOr_thenSuccess(){
Predicate<String> predicate1 = str -> str.startsWith("J");
Predicate<String> predicate2 = str -> str.length() < 4;
List<String> result = names.stream()
.filter(predicate1.or(predicate2))
.collect(Collectors.toList());
assertEquals(2, result.size());
assertThat(result, contains("John","Tom"));
}
5.3. Predicate.negate()
@Test
public void whenFilterListWithCombinedPredicatesUsingOrAndNegate_thenSuccess(){
Predicate<String> predicate1 = str -> str.startsWith("J");
Predicate<String> predicate2 = str -> str.length() < 4;
List<String> result = names.stream()
.filter(predicate1.or(predicate2.negate()))
.collect(Collectors.toList());
assertEquals(3, result.size());
assertThat(result, contains("Adam","Alexander","John"));
}
Here, we’ve used a combination of or() and negate() to filter the List by names that start with “J” or have a length that isn’t less than 4.
5.4. Combine Predicates Inline
We can also use them inline by casting the Predicate:
@Test
public void whenFilterListWithCombinedPredicatesInline_thenSuccess(){
List<String> result = names.stream()
.filter(((Predicate<String>)name -> name.startsWith("A"))
.and(name -> name.length()<5))
.collect(Collectors.toList());
assertEquals(1, result.size());
assertThat(result, contains("Adam"));
}
6. Combining a Collection of Predicates
In the following example, we have a List of Predicates that we combined using Predicate.and():
@Test
public void whenFilterListWithCollectionOfPredicatesUsingAnd_thenSuccess(){
List<Predicate<String>> allPredicates = new ArrayList<Predicate<String>>();
allPredicates.add(str -> str.startsWith("A"));
allPredicates.add(str -> str.contains("d"));
allPredicates.add(str -> str.length() > 4);
List<String> result = names.stream()
.filter(allPredicates.stream().reduce(x->true, Predicate::and))
.collect(Collectors.toList());
assertEquals(1, result.size());
assertThat(result, contains("Alexander"));
}
Note that we use our base identity as:
x->true
But that will be different if we want to combine them using Predicate.or():
@Test
public void whenFilterListWithCollectionOfPredicatesUsingOr_thenSuccess(){
List<String> result = names.stream()
.filter(allPredicates.stream().reduce(x->false, Predicate::or))
.collect(Collectors.toList());
assertEquals(2, result.size());
assertThat(result, contains("Adam","Alexander"));
}
7. Conclusion
In this article, we explored different ways to chain Predicates in Java 8, by using filter(), building complex Predicates, and combining Predicates.
The full source code is available over on GitHub.