Merging Predicates? How to have some neat looking filter clause in Java 8 Streams
Recently, I was reading about how the lambda’s are coded internally and it’s quite fascinating how versatile interfaces and classes are when used correctly. While reading about it, I discovered some methods in the Predicate
class which were already there but I never paid any particular attention to them.
A predicate is nothing but a boolean value function. A function that returns
true
orfalse
Conditions have been around before programming languages existed and so were predicates. Whenever I used to write filter in Java 8 streams, I knew how to combine them using AND
condition (you put one filter after another, chain them), but writing an OR
was tricky. Well, not tricky exactly but not clean.
Fizz-Buzz-FizzBuzz
Take the classic FizzBuzz problem. We print Fizz
if the number is divisible by 3, Buzz
if it’s divisible by 5 and FizzBuzz
if it’s divisible by both 3 and 5. But instead of printing let’s count them (because I want to make a point).
A typical Java 7 approach will look something like this:
private static void fizzBuzzCount() {
List<Integer> numbers = Arrays.asList(1, 12, 3, 9, 10, 15, 4, 45, 60, 9);
int threeCount = 0;
int fiveCount = 0;
int bothCount = 0;
for (Integer n : numbers) {
if (n % 3 == 0) threeCount++;
if (n % 5 == 0) fiveCount++;
if (n % 3 == 0 && n % 5 == 0) bothCount++;
}
System.out.println("Fizz: " + threeCount);
System.out.println("Buzz: " + fiveCount);
System.out.println("FizzBuzz: " + bothCount);
}
Verbose.
Lambdas made it simpler (or maybe not):
private static void fizzBuzzCountStream() {
List<Integer> numbers = Arrays.asList(1, 12, 3, 9, 10, 15, 4, 45, 60, 9);
System.out.println("Fizz: " + numbers.stream()
.filter(it -> it % 3 == 0)
.count());
System.out.println("Buzz: " + numbers.stream()
.filter(it -> it % 5 == 0)
.count());
System.out.println("FizzBuzz: " + numbers.stream()
.filter(it -> it % 3 == 0 && it % 5 == 0)
.count());
}
But there is a problem
It’s okay to put these conditions inline when we have simpler conditions. For something which is complicated, we need to refactor this to have functions. Functions don’t work really well with stream. You have ugly looking streams:
int fizzBuzzCount = numbers.stream()
.filter(it -> isDivisibleBy3(it) && isDivisibleBy5(it))
.count()
It becomes a nightmare when you have something long and complicated.
Introducing Predicates
A better way to do this in Java 8 is using Predicate
. Instead of returning a boolean, we return a predicate from that method or create it directly.
IntPredicate divisibleByThree = it -> it % 3 == 0;
IntPredicate divisibleByFive = it -> it % 5 == 0;int fizz = numbers.stream()
.filter(divisibleByThree)
.count()int fizzBuzz = numbers.stream()
.filter(divisibleByThree.and(divisibleByFive))
.count()
The first part is more readable filter(divisibleByThree).count()
. The second part is cherry on the top: filter(divisibleByThree.and(divisibleByFive)).count()
You can merge predicates!
Predicates support standard boolean operators: and
, or
and negate
so that you can have some neat looking conditions:
Example: Registered and non premium users? You got it
// some fancy code/method that returns registered and premium
Predicate<User> notPremium = premium.negate();users.stream().filter(registered.and(notPremium))
Example: Students who don’t have good attendance or good grades? Readable!
students.stream().filter(goodAttendance.or(goodGrades).negate())
There is a place to use lambda’s and there’s a place to avoid them. But having an option to write more readable code is definitely good to have.
More reading related to Java streams: