使用lambdas和generics时,对方法的引用是不明确的

我收到以下代码的错误,我认为不应该存在…使用JDK 8u40编译此代码。

public class Ambiguous { public static void main(String[] args) { consumerIntFunctionTest(data -> { Arrays.sort(data); }, int[]::new); consumerIntFunctionTest(Arrays::sort, int[]::new); } private static  void consumerIntFunctionTest(final Consumer consumer, final IntFunction generator) { } private static  void consumerIntFunctionTest(final Function consumer, final IntFunction generator) { } } 

错误如下:

错误:(17,9)java:对consumerIntFunctionTest的引用与net.tuis.ubench.Ambiguous中的方法consumerIntFunctionTest(java.util.function.Consumer,java.util.function.IntFunction)和方法consumerIntFunctionTest(java.util。)都不明确。 net.tuis.ubench.Ambiguous匹配中的function.Function,java.util.function.IntFunction)

错误发生在以下行:

 consumerIntFunctionTest(Arrays::sort, int[]::new); 

我相信应该没有错误,因为所有Arrays::sort引用都是void类型,并且它们都没有返回值。 正如您所看到的,当我明确扩展Consumer lambda时,它确实有效。

这真的是javac中的错误,还是JLS声明lambda在这种情况下无法自动扩展? 如果是后者,我仍然认为这很奇怪,因为带有第一个参数Function consumerIntFunctionTest不匹配。

在你的第一个例子中

 consumerIntFunctionTest(data -> { Arrays.sort(data); }, int[]::new); 

lambda表达式具有一个与void兼容的块,可以通过表达式的结构来识别,而无需解析实际类型。

相比之下,在这个例子中

 consumerIntFunctionTest(Arrays::sort, int[]::new); 

必须解析方法引用,以确定它是否符合void函数( Consumer )或值返回函数( Function )。 这同样适用于简化的lambda表达式

 consumerIntFunctionTest(data -> Arrays.sort(data), int[]::new); 

根据已解决的目标方法,它可以是兼容的, void ,也可以是价值兼容的。

问题是解析方法需要有关所需签名的知识,这应该通过目标类型确定,但是在知道通用方法的类型参数之前,目标类型是未知的。 虽然理论上两者都可以立即确定,但是在规范中简化了(仍然非常复杂)过程,该方法首先执行重载决策,最后应用类型推断(参见JLS§15.12.2 )。 因此,类型推断可以提供的信息不能用于解决重载决策。

但请注意第15.12.2.1节中描述的第一步。 识别可能适用的方法包含:

根据以下规则,表达式可能与目标类型兼容

  • 如果满足以下所有条件,则lambda表达式(第15.27节)可能与函数接口类型(第9.8节)兼容:

    • 目标类型的函数类型的arity与lambda表达式的arity相同。

    • 如果目标类型的函数类型具有void返回,则lambda主体是语句表达式(第14.8节)或void兼容块(第15.27.2节)。

    • 如果目标类型的函数类型具有(非void)返回类型,则lambda主体是表达式或值兼容块(第15.27.2节)。

  • 方法引用表达式(第15.13节)可能与函数接口类型兼容,如果类型的函数类型arity为n,则存在至少一个具有arity n的方法引用表达式的潜在适用方法(第15.13.1节),并且以下之一是真的:

    • 方法引用表达式具有ReferenceType :: [TypeArguments]标识符的forms,并且至少一个可能适用的方法是i)static并支持arity n,或ii)not static并支持arity n-1。

    • 方法引用表达式具有一些其他forms,并且至少一个可能适用的方法不是静态的。

潜在适用性的定义超出了基本的arity检查,也考虑了function接口目标类型的存在和“形状”。 在某些涉及类型参数推断的情况下,在重载解析之前,无法正确键入作为方法调用参数出现的lambda表达式

因此,在第一个示例中,其中一个方法按lambda的形状排序,而在方法引用或lambda表达式由单个调用表达式组成的情况下,两个可能适用的方法都会忍受第一个选择过程并产生“模糊”错误在类型推断之前可以启动以帮助找到目标方法以确定它是否为void或值返回方法。

请注意,就像使用x->{ foo(); } 要使lambda表达式显式为void -compatible,您可以使用x->( foo() )使lambda表达式显式地与值兼容。


你疯狂地进一步阅读这个答案,解释说这种组合类型推断和方法重载决策的限制是一个刻意(但不容易)的决定。