2

I was going through the Predicate class introduced in java 8 which is functional interface. There is a method and inside the Predicate class which is as following for composing multiple predicates into one.

default Predicate<T> and(Predicate<? super T> other) {
    Objects.requireNonNull(other);
    return (t) -> test(t) && other.test(t);
}

I have read the concept of PECS in java but still couldn't understand why in the case of Predicate we are using ? super T. How have the java programmers decided that it would be a consumer and not a producer.

I mean why lines with compile errors should not be allowed:

public class PredicateExample {
    public static void main(String[] args) {
        Predicate<Number> pred = n -> n.intValue() > 2;
        Predicate<Integer> predInt = n -> n > 3;
        //Compile error
        //pred.and(predInt);

        Predicate<Object> predObj = o -> Integer.parseInt(o.toString()) > 4;
        pred.and(predObj); //Valid statement

        Number n = new Integer(100);
        System.out.println(pred.and(predObj).test(10));
        System.out.println(predInt.and(pred).test(10));
        //Compile error
        //System.out.println(pred.and(predInt).test(10));
    }
}
Gautham M
  • 4,816
  • 3
  • 15
  • 37
Nishant Lakhara
  • 2,295
  • 4
  • 23
  • 46
  • 3
    The goal is to create a `Predicate` that can decide something for objects of type `T`. A predicate that deals with super classes of `T` can decide things for `T` as well. For example, suppose you create a predicate for dogs, `Predicate` and then you want to combine it with a predicate that deals with `Animal`s, `Predicate`, for example a predicate that can decide whether the animal is over 10 years old. Such a predicate can be used for dogs as well. So it would be unecessary to limit yourself only to predicates for dogs, when general animal-predicates work as well. – Zabuzard May 29 '21 at 08:00
  • @Zabuzard the comment is equivalent to my answer. Thanks – Nishant Lakhara May 29 '21 at 08:05

3 Answers3

6

Predicate<T>s take in a T and give you a boolean. Yes, it is a producer of booleans, but that's not really relevant here. When applying PECS on a type parameter, you should think about whether the type is a producer or consumer of that type parameter.

Since Predicate<T> accepts a T, it's a consumer of T, so it should be ? super for the T parameter.

Another example: BiFunction<T, U, V> accepts a T and a U, and produces a V, so it's a consumer of T, consumer of U, and producer of V. Therefore, ? super for T and U, ? extends for V. As you can see, only the type parameters matter. Anything else that the type might do, doesn't.

The lines will be allowed if you flip the two sides - predInt.and(pred), which creates a Predicate<Integer>.

This is because and is declared to produce the same type of predicate as the one it is called on, so pred.and(...) can only produce a Predicate<Number>, but a Predicate<Number> doesn't make sense here - the other conjunct can only accept integers!

You can totally make it work in both orders. If you declare and as a static method:

public static <T> Predicate<T> and(Predicate<? super T> a, Predicate<? super T> b)

Then you can do both of these:

and(predInt, pred)
and(pred, predInt)

But it's less readable this way :(

Sweeper
  • 213,210
  • 22
  • 193
  • 313
4

Producer - Gives an object to you by returning it: T get()
The object must be a T or a subclass thereof, so it extends.

Consumer - Takes an object from you as method parameter: void accept(T)
The method may take a more general type, but cannot require a more specific type, so parameter can be a super.

Since the Predicate boolean test(T) method takes the object as argument, it's a consumer.

Reference: What is PECS (Producer Extends Consumer Super)?

Andreas
  • 154,647
  • 11
  • 152
  • 247
3

The problem with

    Predicate<Number> pred = n -> n.intValue() > 2;
    Predicate<Integer> predInt = n -> n > 3;
    //Compile error
    //pred.and(predInt);

is that the pred accepts all subclasses of Number (for example also Double) but predInt accepts only one specific subclass (Integer).

The following code is therefore fine:

    Number n = Double.valueOf(100);
    System.out.println(pred.test(n));

But the following code (if it would compile) would fail at runtime:

    Number n = Double.valueOf(100);
    System.out.println(pred.and(predInt).test(n));

Since generics were introduced exactly to prevent such runtime failures it must prevent the code from compiling (i.e. produce a compile error).

Thomas Kläger
  • 17,754
  • 3
  • 23
  • 34
  • If I get it right, OP is aware of all that and was specifically asking for the terminology of PECS (see the details in his question and the comment section). – Zabuzard May 29 '21 at 08:43