122

Suppose I've a generic interface:

interface MyComparable<T extends Comparable<T>>  {
    public int compare(T obj1, T obj2);
}

And a method sort:

public static <T extends Comparable<T>> 
       void sort(List<T> list, MyComparable<T> comp) {
    // sort the list
}

I can invoke this method and pass a lambda expression as argument:

List<String> list = Arrays.asList("a", "b", "c");
sort(list, (a, b) -> a.compareTo(b));

That will work fine.

But now if I make the interface non-generic, and the method generic:

interface MyComparable {
    public <T extends Comparable<T>> int compare(T obj1, T obj2);
}

public static <T extends Comparable<T>> 
       void sort(List<T> list, MyComparable comp) {
}

And then invoke this like:

List<String> list = Arrays.asList("a", "b", "c");
sort(list, (a, b) -> a.compareTo(b));

It doesn't compile. It shows error at lambda expression saying:

"Target method is generic"

OK, when I compiled it using javac, it shows following error:

SO.java:20: error: incompatible types: cannot infer type-variable(s) T#1
        sort(list, (a, b) -> a.compareTo(b));
            ^
    (argument mismatch; invalid functional descriptor for lambda expression
      method <T#2>(T#2,T#2)int in interface MyComparable is generic)
  where T#1,T#2 are type-variables:
    T#1 extends Comparable<T#1> declared in method <T#1>sort(List<T#1>,MyComparable)
    T#2 extends Comparable<T#2> declared in method <T#2>compare(T#2,T#2)
1 error

From this error message, it seems like compiler is not able to infer the type arguments. Is that the case? If yes, then why is it happening like this?

I tried various ways, searched through the internet. Then I found this JavaCodeGeeks article, which shows a way, so I tried:

sort(list, <T extends Comparable<T>>(a, b) -> a.compareTo(b));

which again doesn't work, contrary to what that article claims that it works. Might be possible that it used to work in some initial builds.

So my question is: Is there any way to create lambda expression for a generic method? I can do this using a method reference though, by creating a method:

public static <T extends Comparable<T>> int compare(T obj1, T obj2) {
    return obj1.compareTo(obj2);
}

in some class say SO, and pass it as:

sort(list, SO::compare);
rogerdpack
  • 62,887
  • 36
  • 269
  • 388
Rohit Jain
  • 209,639
  • 45
  • 409
  • 525

5 Answers5

134

You can't use a lambda expression for a functional interface, if the method in the functional interface has type parameters. See section §15.27.3 in JLS8:

A lambda expression is compatible [..] with a target type T if T is a functional interface type (§9.8) and the expression is congruent with the function type of [..] T. [..] A lambda expression is congruent with a function type if all of the following are true:

  • The function type has no type parameters.
  • [..]
Rohit Jain
  • 209,639
  • 45
  • 409
  • 525
nosid
  • 48,932
  • 13
  • 112
  • 139
  • 55
    However, this restriction does not apply to *method references* to generic methods. You can use a method reference to a generic method with a generic functional interface. – Brian Goetz May 15 '14 at 08:57
  • 21
    I'm sure there's a good reason for this restriction. What is it? – Sandro Sep 26 '14 at 13:36
  • 8
    @Sandro: there is simply no syntax for declaring type parameters for a lambda expression. And such a syntax would be very complicated. Keep in mind that the parser still must be able to tell such a lambda expression with type parameters apart from other legal Java constructs. Thus you have to resort to method references. The target method *can* declare type parameters using an established syntax. – Holger Dec 16 '15 at 10:48
  • 3
    @Holger still, where type parameters can be auto-deduced, the compiler could decude a capure type as it does when you declare e.g. Set> and do type-checks with those captured types. Sure, that makes it impossible to give them as type-parameters in the body, but if you'd need that, resorting to method references is a good alternative – WorldSEnder Feb 14 '17 at 21:47
  • NB that lambda's are compatible with Generics in many other ways (ways that don't have method type parameters)... – rogerdpack Nov 17 '21 at 19:23
21

Using method reference, i found other way to pass the argument:

List<String> list = Arrays.asList("a", "b", "c");        
sort(list, Comparable::<String>compareTo);
Andrey
  • 2,485
  • 3
  • 21
  • 26
5

Just point compiler the proper version of generic Comparator with (Comparator<String>)

So the answer will be

sort(list, (Comparator<String>)(a, b) -> a.compareTo(b));

Aleч
  • 85
  • 1
  • 1
  • 3
    `incompatible types: java.util.Comparator cannot be converted to MyComparable` and `MyComparable` is not generic (no type) so `(MyComparable)` would not work either – user85421 Sep 20 '18 at 13:00
  • 1
    Dont know how you are typing the code @CarlosHeuberger, but it works for me very well, this is what i was looking for. – Ivan Perales M. Nov 26 '18 at 04:45
  • @IvanPeralesM. after 2 months... I copied&pasted your code and copied&pasted above line over your sort line - just that, like here: https://ideone.com/YNwBbF ! Are you sure you did type above code exactly? using `Compartor` ? – user85421 Nov 26 '18 at 12:17
  • No i dont, i use the idea behind the answer, to cast the functional to tell the compile what type it is and it worked. – Ivan Perales M. Nov 27 '18 at 15:30
  • 1
    @IvanPeralesM. well, then what is your problem with my "typing"? The answer, as it is posted, does not work. – user85421 Nov 28 '18 at 10:53
0

You mean something like this?:

<T,S>(T t, S s)->...

Of what type is this lambda? You couldn't express that in Java and therefore cannot compose this expression in a function application and expressions have to be composable.

For this need to be work you would need support for Rank2 Types in Java.

Methods are allowed to be generic but therefore you couldn't use them as expressions. They can, however be reduced to lambda expression by specializing all necessary generic types before you can pass them: ClassName::<TypeName>methodName

iconfly
  • 148
  • 2
  • 5
  • 2
    `"Of what type is this lambda? You couldn't express that in Java..."` The type would be inferred using the context, just as with any other lambda. The type of a lambda isn't explicitly expressed in the lambda itself. – Kröw Jun 25 '19 at 02:31
0
List<String> list = Arrays.asList("a", "b", "c");        
sort(list, Comparable::<String>compareTo);

The int compareTo (T o) is not a generic method invocation. Although Comparable<T> is an interface with a type. Even if compareTo had returned T, i.e. T compareTo (T o) it still is not a generic method. For it to be a generic method, it needs to include a list of type parameters, i.e. <T> T compareTo (T o).

Sanjay
  • 1