While studying up on Java8 Streams, I came across the following code snippet:
Predicate<? super String> predicate = s -> s.startsWith("g");
Since the generic parameter is a lower bound, I figured this would not compile. The way I see it, if an Object is a supertype to a String, then passing in an Object type should break it, since Object does not have a startsWith() function. However, I was surprised to see it work without any problems.
Further yet, when I tweaked the predicate to take an upper bound:
<? extends String>,
it would not compile.
I thought I understood the meaning of upper and lower bounds, but obviously, I am missing something. Can anyone help explain why the lower bound works with this lambda?
A lambda expression (lambda) is a short-form replacement for an anonymous class. Lambdas simplify the use of interfaces that declare single abstract methods. Such interfaces are known as functional interfaces. A functional interface can define as many default and static methods as it requires.
IntConsumer functional interface represents an operation that accepts a single int-valued argument and returns no result.
A functional interface can extends another interface only when it does not have any abstract method.
Lambda expressions allow passing a function as an input parameter for another function, which was not possible earlier. In short, lambdas can replace anonymous classes, making the code readable and easy to understand. A lambda expression consists of two parts separated by the “->” operator.
Lambda argument type is exact, it cannot be ? super
or ? extends
. This is covered by JLS 15.27.3. Type of a Lambda Expression. It introduces the ground target type concept (which is basically the lambda type). Among other things it's stated that:
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.
Emphasis mine. So essentially when you write
Predicate<? super String> predicate = s -> s.startsWith("g");
Your lambda type is Predicate<String>
. It's the same as:
Predicate<? super String> predicate = (Predicate<String>)(s -> s.startsWith("g"));
Or even
Predicate<String> pred = (Predicate<String>)(s -> s.startsWith("g"));
Predicate<? super String> predicate = pred;
Given the fact that lambdas type arguments are concrete, after that normal type conversion rules apply: Predicate<String>
is a Predicate<? super String>
, or Predicate<? extends String>
. So both Predicate<? super String>
and Predicate<? extends String>
should compile. And both actually work for me on javac 8u25, 8u45, 8u71 as well as ecj 3.11.1.
I just tested it, the assignment itself compiles. What changes is whether you can actually call predicate.test()
.
Let's take a step back and use a placeholder GenericClass<T>
for explanation. For type arguments, Foo
extends Bar
and Bar
extends Baz
.
Extends:
When you declare a GenericClass<? extends Bar>
, you are saying "I don't know what its generic type argument actually is, but it's a subclass of Bar
." The actual instance will always have a non-wildcard type argument, but in this part of the code you don't know what the value of it is. Now consider what that means for method invocations.
You know what you've actually got is either a GenericClass<Foo>
or a GenericClass<Bar>
. Consider a method that returns T
. In the former case, its return type is Foo
. In the latter, Bar
. Either way, it's a subtype of Bar
and is safe to assign to a Bar
variable.
Consider a method that has a T
parameter. If it's a GenericClass<Foo>
, then passing it a Bar
is an error - Bar
is not a subtype of Foo
.
So, with an upper bound you can use generic return values, but not generic method parameters.
Super:
When you declare a GenericClass<? super Bar>
, you are saying "I don't know what its generic type argument actually is, but it's a superclass of Bar
." Now consider what that means for method invocations.
You know what you've actually got is either a GenericClass<Bar>
or a GenericClass<Baz>
. Consider a method that returns T
. In the former case, it returns Bar
. In the latter, Baz
. If it returns a Baz
, then assigning that value to a Bar
variable is an error. You don't know which it is, so you can't safely assume anything here.
Consider a method that has a T
parameter. If it's a GenericClass<Bar>
, then passing it a Bar
is legal. If it's a GenericClass<Baz>
, then passing it a Bar
is still legal because Bar
is a subtype of Baz
.
So, with a lower bound you can use generic method parameters, but not generic return values.
In summary: <? extends T>
means you can use generic return values but not parameters. <? super T>
means you can use generic parameters but not return values. Predicate.test()
has a generic parameter, so you need super
.
Another thing to consider: The bounds stated by a wildcard are about the actual type argument of the object. Their consequences on the types that you can use with that object are the opposite. An upper bound wildcard (extends
) is a lower bound on the types of variables you can assign return values to. A lower bound wildcard (super
) is an upper bound on the types you can pass in as parameters. predicate.test(new Object())
will not compile because, with a lower bound of String
, it will only accept subclasses of String
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With