Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java 8 Predicate - why can't wildcard generics predicates be joined?

Consider the following code:

public class Main {
    private static Predicate<? extends TestObject> predicate = testObject -> true;
    private static Predicate<? extends TestObject> predicate1 = testObject -> true;

    public static void main( String[] args ) {

         List<TestObject> objects = Lists.newArrayList( new TestObject(), new TestObject() );

         objects.stream().filter( predicate.or( predicate1 ) ).findFirst();
    }
}

It doesn't compile, giving the error:

Error:(17, 48) java: incompatible types: java.util.function.Predicate<capture#1 of ? extends test.test.TestObject> cannot be converted to java.util.function.Predicate<? super capture#2 of ? extends test.test.TestObject>

It seems like we can't join such predicates with logical operators like "or" or "and", but why can't Java deal with them?

It compiles with simple predicates like Predicate<TestObject>, but not with Predicate<? super TestObject> or Predicate<? extends TestObject>.

like image 995
staszko032 Avatar asked Aug 29 '18 15:08

staszko032


People also ask

How do you combine two predicates in Java?

If we want to combine different Predicate objects, we can use the or , and , and negate methods of the Predicate interfaces. These are default methods of the interface and will return a new Predicate . Written with Java 12.

What is the use of predicate in Java 8?

This is a functional interface and can therefore be used as the assignment target for a lambda expression or method reference. Represents a predicate (boolean-valued function) of one argument. This is a functional interface whose functional method is test(Object) .

What is negate in predicate in Java?

Predicate negate() Method negate() method returns the logical negation of an existing predicate. Predicate<Integer> isEven = i -> i % 2 == 0; Predicate<Integer> isOdd = isEven. negate(); Use these predicates as follows with the Stream filter() method. List<Integer> list = Arrays.


2 Answers

You might know that the predicates are compatible, but the compiler does not.

Imagine this example:

Predicate<? extends Collection<Object>> p1 = (Set<Object> s) -> s.isEmpty();
Predicate<? extends Collection<Object>> p2 = (List<Object> l) -> l.get(0) != null;

We developers can see that the first predicate could technically handle all collections, while the second one can handle only lists. But imagine the predicates were initialized somewhere else or would be changed in the meantime. The compiler cannot know for sure which type of collections the predicate objects were made for. As a result, you cannot use them at all:

Set<Object> set = new HashSet<>();
List<Object> list = new ArrayList<>();
p1.test(set);
p2.test(list);
p1.test(list);
p2.test(set);

All these calls won't compile, because the compiler cannot say whether the actual objects behind p1 and p2 can are exactly for those types of collections. That is the meaning of ? extends Collection<>: You know it is a specific sub type, but you cannot tell the compiler which one exactly.


To illustrate this with a simpler example:

Collection<Apple> appleBasket = ...;
appleBasket.add(new Apple());  // works
appleBasket.add(new Orange()); // does not work (obviously)

Collection<Fruit> mixedFruitBasket = ...;
mixedFruitBasket.add(new Apple());  // works
mixedFruitBasket.add(new Orange()); // works

// Now the tricky part
Collection<? extends Fruit> unknownButPureFruitBasket = ...;
unknownButPureFruitBasket.add(new Apple());  // does not work 
unknownButPureFruitBasket.add(new Orange()); // does not work

You cannot add any one fruit to a basket whose type you don't know. It could in fact be a basket that accepts all fruit, but it could be a pure Apple basket, or Orange backet, or even a Banana basket that you do not even know of yet.

Try it in your IDE:

List<? extends String> l1 = new ArrayList<>();
List<? extends String> l2 = new ArrayList<>();
l1.addAll(l2);

Eclipse tells me:

The method addAll(Collection1-of ? extends String>) in the type List<capture#1-of ? extends String> is not applicable for the arguments (List<capture#2-of ? extends String>)

Note the different types: addAll expects a collection of capture#1, l2 is a collection of capture#2.

like image 108
Malte Hartwig Avatar answered Sep 22 '22 10:09

Malte Hartwig


filter requires a Predicate<? super TestObject>, not Predicate<? extends TestObject>.

Why super TestObject? Because the TestObject here is an input parameter, a consumer. According to the PECS rule, it should be marked super.

Predicate<? extends TestObject> is completely different from Predicate<? super TestObject>. The former can accept all subclasses of TestObject and TestObject. The latter can accept all superclasses of TestObject and TestObject. Obviously they are different and non-compatible.

like image 43
Sweeper Avatar answered Sep 19 '22 10:09

Sweeper