Reputation: 322
I have trouble understanding following code about two Predicate
objects. The first one uses a lower bounded wildcard, the second a upper bounded.
Predicate<? super String> p1 = s -> s.startsWith("a"); // why can I call startsWith()?
Predicate<? extends String> p2 = s -> s.startsWith("a");
p1.test("a"); // works
p2.test("a"); // doesn't work (why?)
What I don't understand about p1
is, why is it possible to call methods from the class String
, e.g. startsWith()
? Why can I only pass String
objects into p1.test()
, I expected to be able to call it for Number
and Object
objects as well.
As p1
behaves I thought p2
would, but this isn't the case. I can't even pass a String
object into p2.test()
. This doesn't makes sense to me, because we expect an object which inherits from String
(including String
).
I think it maybe has something to do with the fact, that we specify the reference type rather than the type of the object itself. But what type is then used for the object?
Upvotes: 3
Views: 113
Reputation: 45786
When you have Predicate<? super String>
that means the actual implementation of the Predicate
is guaranteed to be able to handle an instance of String
. Whether the implementation sees the String
as a String
or CharSequence
or even Object
is irrelevant, as long as it can handle the type. This provides flexibility in an API that uses the interface. For example:
void addFilter(Predicate<? super String> filter) { ... }
You could call addFilter
with a Predicate<CharSequence>
or a Predicate<Object>
instance, it doesn't matter to the API. For instance:
addFilter((CharSequence cs) -> cs.length() % 2 == 0);
However, when the API actually invokes the filter
it has to pass an instance of String
to test
. If the API were allowed to call filter.test(new Object())
then the Predicate<CharSequence>
passed to addFilter
above would fail with a ClassCastException
.
When you have Predicate<? extends CharSequence>
(using CharSequence
since String
is a final class) you cannot call test
because the implementation probably won't be able to handle any type of CharSequence
. For instance:
Predicate<? extends CharSequence> predicate = (String s) -> s.startsWith("...");
predicate.test(new StringBuilder());
A ClassCastException
would be thrown because the "real type" of predicate
is actually Predicate<String>
, not Predicate<StringBuilder>
. Thus the compiler rejects the call to test
because it's not type-safe.
I also recommend reading What is PECS (Producer Extends Consumer Super)?.
Upvotes: 2
Reputation: 178263
It's legal for you to call startsWith
for p1
, even though p1
is typed with a lower bound ? super String
, because the type argument is inferred to be String
. The lambda expression s -> s.startsWith("a");
is inferred to be a Predicate<String>
, which is legal to assign to a variable of type Predicate<? super String>
.
This compiles:
Predicate<String> ps = s -> s.startsWith("a");
Predicate<? super String> p1 = ps;
This does not:
// no "startsWith" on Object
Predicate<? super String> p1 = (Object s) -> s.startsWith("a");
The JLS reference is in Section 15.27.3, "Type of a Lambda Expression".
If T is a wildcard-parameterized functional interface type and the lambda expression is implicitly typed, then the ground target type is the non-wildcard parameterization (§9.9) of T.
Here, the ground target type is the type of the lambda expression, and T
is the target type, which here is your lower bound variable datatype. This allows the compiler to assign Predicate<String>
as the ground target type, which becomes the type of the lambda expression.
Also notice that you can't pass a superclass object to p1.test
, because you could (and you already have) assigned a Predicate<String>
to p1
, which takes a String
.
p1.test(new Object()); // Error: can't pass something higher than String
As for why you can't pass a String
to p2.test
, when you have an upper bounded wildcard such as ? extends String
, that means that the type parameter can be any class that is either String
or a subtype. (The compiler ignores that String
is final
here and there can't be any subclasses of String
.) The predicate p2
could be assigned a Predicate<SillyString>
, assuming SillyString
is a subclass of String
. But you can't pass a String
to a method that could expect a SillyString
.
Upvotes: 5