Monday, July 9, 2018

Deferred Execution with Java's Predicate

In the previous posts "Deferred Execution with Java's Supplier" and "Deferred Execution with Java's Consumer", I looked at easily deferring execution in Java via standard Java APIs that accept, respectively, Suppliers and Consumers. In this post, I take a similar look at how standard JDK-provided APIs allow for deferred execution via the standard functional interface Predicate. The Predicate is described in its Javadoc, "Represents a predicate (boolean-valued function) of one argument." In other words, a Predicate is like a JDK-supplied Function, but with its return value limited to either true or false.

Perhaps the most common application of Predicate in the standard Java APIs is in the context of filters. Several of the examples in this post will demonstrate use of Predicate in conjunction with filtering methods on instances of Optional and on instances of Stream.

Optional.filter(Predicate)

The behavior of the Optional class's filter(Predicate) method is described this way by its Javadoc documentation, "If a value is present, and the value matches the given predicate, returns an Optional describing the value, otherwise returns an empty Optional." In other words, Optional.filter(Predicate) returns an Optional that will be empty if either the original Optional was empty or if the Predicate applied to the original and present Optional resolves to false. Otherwise, if the original Optional does have a "present" value and the Predicate applied to that value returns true, the returned Optional will also have the same "present" value. This is illustrated in the next code listing (full source is available on GitHub).

Optional.filter(Predicate) Demonstrated

/**
 * Demonstrate use of {@code Optional.filter(Predicate)} on an
 * {@code Optional<Boolean>}.
 */
public static void demonstrateOptionalFilterOnBoolean()
{
   out.print("\nfalse: ");
   getOptionalBoolean(false).filter(b -> b).ifPresent(out::print);
   out.print("\ntrue:  ");
   getOptionalBoolean(true).filter(b -> b).ifPresent(out::print);
   out.print("\nnull:  ");
   getOptionalBoolean(null).filter(b -> b).ifPresent(out::print);
}

/**
 * Demonstrate use of {@code Optional.filter(Predicate)} on an
 * {@code Optional<Float>}.
 */
public static void demonstrateOptionalFilterOnFloat()
{
   out.print("\n3.14: ");
   getOptionalFloat(3.14f).filter(f -> f > 0.0).ifPresent(out::print);
   out.print("\n-2.5: ");
   getOptionalFloat(-2.5f).filter(f -> f > 0.0).ifPresent(out::print);
   out.print("\nnull: ");
   getOptionalFloat(null).filter(f -> f > 0.0).ifPresent(out::print);
}

The two methods in the above code listing demonstrate use of Optional.filter(Predicate) on a lambda expression that results in a direct boolean result and on a lambda expression that results in a boolean result based on numerical comparison. In one case, the Predicate is the boolean and in the other case the Predicate is the numeric comparison.

Stream.filter(Predicate)

The Stream interface's method filter(Predicate) works similarly to the Optional class's method of the same name. The next code listing demonstrates application of Stream.filter(Predicate).

Stream.filter(Predicate) Demonstrated

/**
 * Demonstrates use of {@code Stream.filter(Predicate}}.
 */
public static void demonstrateStreamFilter()
{
   final int maximum = 100;
   out.println("\nThe probable prime numbers between 1 and " + maximum + " are:");
   final Stream<BigInteger> bigIntegers = getConsecutiveBigIntegers(maximum);
   bigIntegers.filter(bi -> bi.isProbablePrime(100)).forEach(pp -> out.println(" " + pp));
}

The above code listing is not intended to demonstrate the best approach to identifying prime numbers in Java. Instead, it's intended to demonstrate how filter(Predicate) can be invoked on a Stream to narrow down elements of that Stream to only those matching the Predicate.

For my next illustration of Stream.filter(Predicate), I use the Pattern class's convenient method asPredicate() to supply the instance of the Predicate to be supplied to both examples using Stream.filter(Predicate).

Stream.filter(Predicate) with Pattern.asPredicate() Demonstrated

/**
 * Demonstrates use of {@code Pattern.asPredicate()} to provide
 * a {@code Predicate} that can be used with {@code Stream.filter()}.
 */
public static void demonstratePatternAsPredicateInFilter()
{
   final long count
      = getPotentialTelephoneNumbers().stream()
         .filter(PATTERN.asPredicate())
         .peek(out::println)
         .count();
   out.println(count + " valid telephone numbers.");
}

Collection.removeIf(Predicate)

The Collection interface specifies (and implements as a default method) the useful method removeIf(Predicate). There are also multiple implementations of Collection that implement their own overridden versions of removeIf(Predicate) that include ArrayDeque.removeIf(Predicate), ArrayList.removeIf(Predicate), and Vector.removeIf(Predicate).

The next code listing demonstrates two examples of Collection.removeIf(Predicate) in action. The first example uses the method Predicate.negate() to negate the expected regular expression pattern so that the elements removed from the collection are those that do NOT match the regular expression. The second example performs similar functionality, but takes advantage of the JDK 11-introduced 'not' method to perform this negation.

Collection.removeIf(Predicate) with Negated Pattern.asPredicate() Demonstrated

/**
 * Demonstrates use of {@code Collection.removeIf(Predicate)}
 * in conjunction with {@code Predicate.negate()}.
 */
public static void demonstrateCollectionRemoveIf()
{
   final Set<String> telephoneNumbers = new HashSet<>(getPotentialTelephoneNumbers());
   telephoneNumbers.removeIf(PATTERN.asPredicate().negate());
   out.println(telephoneNumbers);
}

/**
 * Demonstrates use of {@code Collection.removeIf(Predicate)}
 * in conjunction with JDK 11-introduced {@code Predicate.not()}.
 */
public static void demonstrateCollectionRemoveIfWithJdk11Not()
{
   final Set<String> telephoneNumbers = new HashSet<>(getPotentialTelephoneNumbers());
   telephoneNumbers.removeIf(not(PATTERN.asPredicate()));
   out.println(telephoneNumbers);
}

Stream.allMatch(Predicate)

The Stream interface's method allMatch(Predicate) returns true if every single element in the stream matches the provided Predicate. If even a single element does not match the Predicate, the method returns false.

Stream.allMatch(Predicate) Demonstrated

/**
 * Demonstrate use of {@code Stream.allMatch(Predicate)}.
 */
public static void demonstrateStreamAllMatch()
{
   final Set<String> names = getNames();
   final boolean allNamesSixDigits = names.stream()
      .allMatch(name -> name.length() == 6);
   out.println("Are all names " + names + " six digits? " + allNamesSixDigits);
}

Stream.anyMatch(Predicate)

The Stream.anyMatch(Predicate) method returns true if at least one of its elements matches the Predicate and returns false if none of its elements match the Predicate.

Stream.anyMatch(Predicate) Demonstrated

/**
 * Demonstrate use of {@code Stream.anyMatch(Predicate)}.
 */
public static void demonstrateStreamAnyMatch()
{
   final Set<String> names = getNames();
   final boolean anyNamesSixDigits = names.stream()
      .anyMatch(name -> name.length() == 6);
   out.println("Are any names " + names + " six digits? " + anyNamesSixDigits);
}

Stream.noneMatch(Predicate)

The Stream.noneMatch(Predicate) method returns true when no elements in stream match the Predicate and returns false if at least one element in the stream DOES match the Predicate.

Stream.noneMatch(Predicate) Demonstrated

/**
 * Demonstrate use of {@code Stream.noneMatch(Predicate)}.
 */
public static void demonstrateStreamNoneMatch()
{
   final Set<String> names = getNames();
   final boolean noNamesSixDigits = names.stream()
      .noneMatch(name -> name.length() == 6);
   out.println("Are no names " + names + " six digits? " + noNamesSixDigits);
   final boolean noNamesFourDigits = names.stream()
      .noneMatch(name -> name.length() == 4);
   out.println("Are no names " + names + " four digits? " + noNamesFourDigits);
}

Collectors.partitioningBy(Predicate)

Although there are several more JDK APIs that use Predicate, I wrap this post up with discussion of and an example of using Collectors.partitioningBy(Predicate). This interesting method divides up all elements in the stream it is invoked upon into two groups with one group associated with key Boolean.TRUE (elements that match the Predicate) and with one group associated with the key Boolean.FALSE (those elements that did not match the Predicate). The next code listing takes advantage of this to divide integers up into even numbers and odd numbers.

Collectors.partitioningBy(Predicate) Demonstrated

/**
 * Demonstrate use of {@code Collectors.partitioningBy(Predicate)}.
 */
public static void demonstrateCollectorsPartitioningBy()
{
   final Map<Boolean, List<Integer>> evensAndOdds
      = getConsecutiveIntegers(100)
         .collect(Collectors.partitioningBy(integer -> integer % 2 == 0));
   out.println("Evens: " + evensAndOdds.get(Boolean.TRUE));
   out.println("Odds:  " + evensAndOdds.get(Boolean.FALSE));
}

I used several "helper" methods in the above code examples that are not shown in this post. These "helper" methods and all of the examples shown in this post are available on GitHub.

Java's standard functional interface Predicate is a specialized version of fellow built-in Java functional interface Function that arguably deserves its own specialization because the true/false return status is so commonly useful for representing the conditions in which certain functionality applies or does not apply. This post has demonstrated several instances in the JDK where Predicate is used to determine which stream elements apply, whether or not an Optional applies, and to divide up stream elements into those that satisfy the predicate and those that do not. Along the way, convenience methods such as Pattern.asPredicate() and Predicate.not() were also demonstrated.

No comments: