Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Problems understanding lower bounds when used with lambda and Functional Interface

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?

like image 297
piper1970 Avatar asked Feb 10 '16 04:02

piper1970


People also ask

How lambda expression and functional interfaces are related?

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.

Which functional interface would you use for the following no parameters returns a value?

IntConsumer functional interface represents an operation that accepts a single int-valued argument and returns no result.

Can functional interface extend another functional interface?

A functional interface can extends another interface only when it does not have any abstract method.

What is lambda expression in functional programming?

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.


2 Answers

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.

like image 179
Tagir Valeev Avatar answered Sep 19 '22 15:09

Tagir Valeev


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.

like image 21
Douglas Avatar answered Sep 20 '22 15:09

Douglas