How was it decided that Predicate "and" method would have a consumer and not producer in java?

541 views Asked by At

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));
    }
}
3

There are 3 answers

0
Sweeper On BEST ANSWER

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 :(

0
Andreas On

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)?

1
Thomas Kläger On

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).