5

I'm relatively new to programming and I have been wondering for past two days how to make a Predicate that is made from a custom list of other Predicates. So I've came up with some kind of solution. Below is a code snippet that should give you an idea. Because I have written it based on solely reading various pieces of documentations I have two questions: 1/ is it a good solution? 2/ is there some other, recommended solution for this problem?

public class Tester {
  private static ArrayList<Predicate<String>> testerList;

  //some Predicates of type String here...

  public static void addPredicate(Predicate<String> newPredicate) {
    if (testerList == null) 
                 {testerList = new ArrayList<Predicate<String>>();}
    testerList.add(newPredicate);
  }

  public static Predicate<String> customTesters () {
    return s -> testerList.stream().allMatch(t -> t.test(s));

  }
}
Stefan Zobel
  • 3,182
  • 7
  • 28
  • 38
Jan Maria Prokop
  • 53
  • 1
  • 1
  • 5
  • 7
    The logic is correct, but the design is awful. Why don't you just use a single method taking a List> as argument and returning the agregated predicate? Using a mutable static field is really ugly and make the Tester class impossible to use. – JB Nizet May 26 '17 at 21:58
  • @JBNizet You're right - it's easier. And prettier. I don't know much about design yet. – Jan Maria Prokop May 26 '17 at 23:55

2 Answers2

10

You could have a static method that receives many predicates and returns the predicate you want:

public static <T> Predicate<T> and(Predicate<T>... predicates) {
    // TODO Handle case when argument is null or empty or has only one element
    return s -> Arrays.stream(predicates).allMatch(t -> t.test(s));
}

A variant:

public static <T> Predicate<T> and(Predicate<T>... predicates) {
    // TODO Handle case when argument is null or empty or has only one element
    return Arrays.stream(predicates).reduce(t -> true, Predicate::and);
}

Here I'm using Stream.reduce, which takes the identity and an operator as arguments. Stream.reduce applies the Predicate::and operator to all elements of the stream to produce a result predicate, and uses the identity to operate on the first element of the stream. This is why I have used t -> true as the identity, otherwise the result predicate might end up evaluating to false.

Usage:

Predicate<String> predicate = and(s -> s.startsWith("a"), s -> s.length() > 4);
fps
  • 33,623
  • 8
  • 55
  • 110
  • Hi, maybe the clause `reduce(Predicate::and).orElse(t -> true)` is easier to understand. and it makes the matching routes shorter. – holi-java May 27 '17 at 07:40
  • @holi-java I used this variant because OP said he/she was a beginner, so I didn't want to introduce `Optional` here – fps May 27 '17 at 13:22
  • In your first snippet you probably mean `Arrays.stream(predicates)` instead of `Arrays.stream(predicates).stream()`. – Thomas Fritsch May 27 '17 at 14:44
  • @ThomasFritsch Sure, you are correct. I'll edit, thanks for pointing out that mistake. – fps May 27 '17 at 14:48
  • I don't fully understand anyway. But that's OK. I look forward to reading those documentations and tutorials. ;) – Jan Maria Prokop May 27 '17 at 20:10
  • 1
    @JanMariaProkop A reduction is a process that converts many elements into one (or into a few) by means of an operator. The typical example is a list of numbers and the sum operator, i.e. if you have the list `[1,2,3,4]` and the `+` operator (sum), reducing the list returns `1+2+3+4 = 10`. The operator takes 2 values of the list and returns a new value. Reduction is performed step by step: `(((1+2)+3)+4)`, so first step produces `1+2=3`, second step produces `(3)+3=6` and third step produces `(6)+4=10`. – fps May 27 '17 at 22:09
  • 1
    @JanMariaProkop With your list of predicates it is the same: the method receives a list of predicates and you are applying the operator `AND` to reduce the list to one final predicate, which is returned. The detail with that version of the `Stream.reduce` method is that it accepts the identity, because the stream of predicates can be empty, so the identity is returned in that case. The identity is defined with regard to the operator. It means that `identity OP element = element` for all elements of the stream. For numbers and the operator `+`, the identity is `0`. – fps May 27 '17 at 22:20
  • 1
    @JanMariaProkop For predicates and the operator `AND`, the identity is a predicate that always evaluates to `true`. If you apply the operator `AND` between any predicate `P` (which returns either `true` or `false`) and a predicate that always returns `true`, then the result will be that one of `P`, due to `AND` truth table: `P(false) AND true = false // P(true) AND true = true`. Hope it is more clear now. Here's a link to the [`Stream.reduce`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#reduce-T-java.util.function.BinaryOperator-) method. – fps May 27 '17 at 22:29
  • 1
    @FedericoPeraltaSchaffner It really is absolutely clear now. I appreciate it deeply! I would have click some kind of +1 button but there seem not to be one. – Jan Maria Prokop May 27 '17 at 23:01
  • 1
    @JanMariaProkop You can upvote the answer, appart from marking it as correct, but you need to have at least 15 reputation points. – fps May 27 '17 at 23:08
  • 1
    @FedericoPeraltaSchaffner just two more points and I will. :) Cheers! – Jan Maria Prokop May 28 '17 at 02:49
  • I prefer `.reduce(Predicate::and).orElse(t -> true)`, as it reduces the memory footprint (and occasionally even the performance), for small numbers of predicates. – Holger May 29 '17 at 13:49
  • 1
    @Holger Hi, I prefer that one too. But OP is a beginner and I didn't want to introduce `Optional`. – fps May 29 '17 at 14:16
  • This code generates a Type safety warning with message : `Potential heap pollution via varargs parameter predicates` i'm not sure if it's safe ? – Lorenzo Apr 04 '21 at 08:07
  • @Lorenzo [This](https://stackoverflow.com/questions/12462079/possible-heap-pollution-via-varargs-parameter) explains what that warning is all about and also [this](https://docs.oracle.com/javase/7/docs/api/java/lang/SafeVarargs.html) This is because generics with varargs – fps Apr 05 '21 at 15:45
2

Java Predicate has a nice function of AND which returns new Predicate which is evaluation of both predicates. You can add them all into one with this.

https://docs.oracle.com/javase/8/docs/api/java/util/function/Predicate.html#and-java.util.function.Predicate-

example :

Predicate<String> a = str -> str != null;
Predicate<String> b = str -> str.length() != 0;
Predicate<String> c = a.and(b);

c.test("Str");
//stupid test but you see the idea :)
Emil Hotkowski
  • 2,233
  • 1
  • 13
  • 19