115
votes

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);
4

4 Answers

126
votes

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.
  • [..]
19
votes

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

List<String> list = Arrays.asList("a", "b", "c");        
sort(list, Comparable::<String>compareTo);
3
votes

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));

0
votes

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