使用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表达式显式地与值兼容。
你疯狂地进一步阅读这个答案,解释说这种组合类型推断和方法重载决策的限制是一个刻意(但不容易)的决定。