Lambda表达式和通用方法


111

假设我有一个通用接口:

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

和方法sort

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

我可以调用此方法并将lambda表达式作为参数传递:

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

那会很好的。

但是现在,如果我将接口设为非泛型,并且将方法设为泛型:

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) {
}

然后像这样调用:

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

它不会编译。它在lambda表达式中显示错误:

“目标方法是通用的”

好的,当我使用编译时javac,它显示以下错误:

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

从此错误消息看来,编译器似乎无法推断类型参数。是这样吗 如果是,那为什么会这样呢?

我尝试了各种方法,通过互联网进行了搜索。然后,我找到了这篇JavaCodeGeeks文章,其中显示了一种方法,因此我尝试了:

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

再次无效,与该文章声称有效的相反。它可能曾经在某些初始版本中起作用。

所以我的问题是:有没有办法为通用方法创建lambda表达式?我可以通过创建方法使用方法参考来实现:

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

在某堂课中说SO,并将其作为:

sort(list, SO::compare);

Answers:


117

不能使用一个lambda表达式用于功能接口,如果在该方法的功能接口具有类型参数。参见JLS8中的§15.27.3

如果T是功能接口类型(第9.8节),并且该表达式与[..] T的函数类型一致,则Lambda表达式与目标类型T兼容。[..] Lambda表达式是一致的如果满足以下所有条件,则使用函数类型:

  • 函数类型没有类型参数
  • [..]

47
但是,此限制不适用于对通用方法的方法引用。您可以使用具有泛型功能接口的泛型方法的方法引用。
Brian Goetz 2014年

17
我确定有此限制的充分理由。它是什么?
桑德罗(Sandro)2014年

6
@Sandro:根本没有语法来声明lambda表达式的类型参数。这样的语法将非常复杂。请记住,解析器仍然必须能够使用其他合法的Java构造来区分类型参数的lambda表达式。因此,您必须求助于方法引用。目标方法可以使用已建立的语法声明类型参数。
Holger 2015年

2
@Holger仍然可以自动推导类型参数,就像您声明Set <?>并使用这些捕获的类型进行类型检查时,编译器可能会决定使用capure类型。当然,这使得不可能在体内将它们作为类型参数提供,但是如果您需要,可以采用方法引用作为一种不错的选择
WorldSEnder

17

使用方法引用,我发现了另一种传递参数的方法:

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

3

只需指向编译器的通用比较器的正确版本即可 (Comparator<String>)

所以答案将是

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


2
incompatible types: java.util.Comparator<java.lang.String> cannot be converted to MyComparableMyComparable不是通用的(无类型),因此(MyComparable<String>)也将无法工作
user85421

1
不知道您如何键入代码@CarlosHeuberger,但是对我来说很好,这就是我想要的。
伊万·佩拉莱斯M.18年

@IvanPeralesM。2个月后...我复制并粘贴了您的代码,并在分类行上方复制并粘贴了该行-就像这里一样:ideone.com/YNwBbF!您确定确实输入了上面的代码吗?使用Compartor
user85421 '18

不,我不,我使用答案背后的想法来强制转换功能,以告诉编译器它是什么类型,并且可以工作。
伊万·佩拉莱斯M.18年

@IvanPeralesM。好吧,那我的“打字”有什么问题?答案已发布,不起作用。
user85421

0

您的意思是这样的:

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

这个lambda是什么类型的?您无法用Java表示它,因此不能在函数应用程序中组成此表达式,并且表达式必须是可组合的。

为此,您需要支持Java中的Rank2类型

方法是通用的,但是您不能将它们用作表达式。但是,可以通过在传递它们之前对所有必需的泛型类型进行专用化,将它们简化为lambda表达式:ClassName::<TypeName>methodName


1
"Of what type is this lambda? You couldn't express that in Java..."就像使用任何其他lambda一样,将使用上下文来推断类型。Lambda的类型未在Lambda本身中明确表示。
6
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.