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>
Predicate<? super TestObject>
or Predicate<? extends TestObject>
Predicate<? extends TestObject>
.
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<>
? 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
.
filter
requires a Predicate<? super TestObject>
Predicate<? super TestObject>
, not Predicate<? extends TestObject>
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>
Predicate<? extends TestObject>
is completely different from Predicate<? super TestObject>
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.
This is really not about how Java handles predicates, but it's simply a question of generics.
In short, the answer is that predicate
and predicate1
aren't necessarily compatible. The type of the parameter to or
is Predicate<? super T>
Predicate<? super T>
, assuming that T
in the two Predicate
objects is the same concrete type argument. But this is not the case.
To illustrate the problem, assume:
class TestObject2 extends TestObject {
boolean isTrue() {
return true;
}
}
class TestObject3 extends TestObject {
boolean isTrue3() {
return true;
}
}
And (note that the declared types didn't change, but the actual lambda expressions did):
private static Predicate<? extends TestObject> predicate =
(TestObject2 testObject) -> testObject.isTrue();
private static Predicate<? extends TestObject> predicate1 =
(TestObject3 testObject) -> testObject.isTrue3();
In this example, or
would be getting the wrong value ( predicate
accepts a TestObject2
object, but in this case, it could be called with a TestObject3
object).
How do you think predicate1
would work in this filter
?
Stream.of(new TestObject2(), new TestObject2()).filter(predicate.or(predicate1));
The fact is that the compiler is guaranteeing that Predicate<? extends TestObject>
Predicate<? extends TestObject>
is compatible with both Predicate<TestObject2>
and Predicate<TestObject3>
(and that's as per the law of generics)
This is PECS yet again, since you consume some TestObject
via the Predicate
, you would need ? super TestObject
? super TestObject
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.