Huntro
Huntro

Reputation: 322

Bounded wildcards in reference types

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

Answers (2)

Slaw
Slaw

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

rgettman
rgettman

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

Related Questions