Lambda 表达式和泛型仅在方法中定义

Posted

技术标签:

【中文标题】Lambda 表达式和泛型仅在方法中定义【英文标题】:Lambda Expression and generic defined only in method 【发布时间】:2014-04-30 13:50:50 【问题描述】:

假设我有一个通用接口:

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

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

我尝试了各种方法,通过互联网搜索。然后找到了this JavaCodeGeeks article,说明了一个办法,于是试了一下:

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

【问题讨论】:

【参考方案1】:

如果功能接口中的方法具有类型参数,则不能将lambda表达式用于功能接口 。见section §15.27.3 in JLS8:

如果 T 是函数式接口类型(第 9.8 节)并且表达式是 全等的,则 lambda 表达式与目标类型 T 兼容 [..] 函数类型为 [..] T. [..] 如果满足以下所有条件,则 lambda 表达式与函数类型全等 真的:

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

【讨论】:

但是,此限制不适用于对泛型方法的方法引用。您可以使用对具有通用功能接口的通用方法的方法引用。 我确信这种限制是有充分理由的。这是什么? @Sandro:根本没有为 lambda 表达式声明类型参数的语法。而且这样的语法会非常复杂。请记住,解析器仍然必须能够区分带有类型参数的这种 lambda 表达式,而不是其他合法的 Java 构造。因此,您必须求助于方法引用。目标方法可以使用已建立的语法声明类型参数。 @Holger 仍然可以自动推断类型参数,编译器可以像您声明例如设置> 并对这些捕获的类型进行类型检查。当然,这使得在正文中将它们作为类型参数提供是不可能的,但如果您需要,诉诸方法引用是一个不错的选择 注意 lambda 在许多其他方面与泛型兼容(没有方法类型参数的方式)...【参考方案2】:

使用方法引用,我找到了其他传递参数的方法:

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

【讨论】:

【参考方案3】:

只需将编译器指向正确版本的通用比较器 (Comparator&lt;String&gt;)

所以答案是

sort(list, (Comparator&lt;String&gt;)(a, b) -&gt; a.compareTo(b));

【讨论】:

incompatible types: java.util.Comparator&lt;java.lang.String&gt; cannot be converted to MyComparableMyComparable 不是通用的(无类型),所以 (MyComparable&lt;String&gt;) 也不起作用 不知道你是如何输入代码的@CarlosHeuberger,但它对我很有用,这就是我想要的。 @IvanPeralesM。 2 个月后...我复制并粘贴了您的代码,并在您的排序行上方复制并粘贴了 - 就是这样,就像这里:ideone.com/YNwBbF!你确定你确实输入了上面的代码吗?使用Compartor ? 不,我没有,我使用答案背后的想法,转换函数来告诉编译它是什么类型并且它有效。 @IvanPeralesM。好吧,那么您对我的“打字”有什么问题?发布的答案无效。【参考方案4】:

你的意思是这样的?:

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

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

为了实现这项工作,您需要在 Java 中支持 Rank2 Types。

方法可以是泛型的,但您不能将它们用作表达式。但是,通过在传递它们之前专门化所有必要的泛型类型,可以将它们简化为 lambda 表达式:ClassName::&lt;TypeName&gt;methodName

【讨论】:

"Of what type is this lambda? You couldn't express that in Java..." 将使用上下文推断类型,就像任何其他 lambda 一样。 lambda 的类型并没有在 lambda 本身中明确表示。【参考方案5】:
List<String> list = Arrays.asList("a", "b", "c");        
sort(list, Comparable::<String>compareTo);

int compareTo (T o) 不是通用方法调用。虽然Comparable&lt;T&gt; 是一个带有类型的接口。即使compareTo 已经返回T,即T compareTo (T o),它仍然不是一个通用方法。要使其成为通用方法,它需要包含type parameters 的列表,即&lt;T&gt; T compareTo (T o)

【讨论】:

以上是关于Lambda 表达式和泛型仅在方法中定义的主要内容,如果未能解决你的问题,请参考以下文章

Java之collection集合常见数据结构List和泛型

有没有办法在 lambda 表达式中显式定义泛型参数类型?

存储库设计模式

C#委托,匿名方法,Lambda,泛型委托,表达式树代码示例

lambda表达式之进化

Java8函数式接口/Lambda表达式/接口默认方法/接口静态方法/接口冲突方法重写/lambda表达式指定泛型类型等