用于function接口的Java 8 lambda模糊方法 – 目标类型

我有以下代码:

public class LambdaTest1 { public static void method1(Predicate predicate){ System.out.println("Inside Predicate"); } public static void method1(Function function){ System.out.println("Inside Function"); } public static void main(String[] args) { method1((i) -> "Test"); } } 

这给我一个错误信息

“方法method1(谓词)对于LambdaTest1类型是不明确的”。

我可以看到,对于FunctionConsumerfunction接口,输入参数是Integer 。 但是对于Function ,返回类型是String

由于我的lambda调用具有返回值“Text” – 这应该调用我的Function函数接口而不是抛出此错误。

任何人都可以解释这种行为背后的原因吗?

另一个例子:

 public class LambdaTest1 { public static void method1(Consumer consumer){ System.out.println("Inside Consumer"); } public static void method1(Predicate predicate){ System.out.println("Inside Predicate"); } public static void main(String[] args) { List lst = new ArrayList(); method1(i -> true); method1(s -> lst.add(s)); //ambiguous error } } 

在上面的代码中,行method1(s -> lst.add(s)); 给出一个ambiguos错误,但上面的方法method1(i -> true)工作正常。

正如在这个答案中所解释的那样,Java语言设计者在选择与类型推断相结合的重载方法的过程中进行了有意识的切割。 因此,并非lambda表达式参数的每个方面都用于确定正确的重载方法。

最值得注意的是,在您的第一个示例中,lambda表达式(i) -> "Test"是一个隐式类型的lambda表达式,其返回值不考虑重载解析,而将其更改为,例如(Integer i) -> "Test"将它转换为显式类型的lambda表达式,其中包含返回值。 与Java语言规范§15.12.2.2比较。 :

阶段1:确定严格调用适用的匹配Arity方法

除非具有以下forms之一,否则参数表达式被认为可能适用的方法m 适用性相关

  • 隐式类型的lambda表达式(第15.27.1节)。

  • 显式类型化的lambda表达式,其主体是与适用性无关的表达式。

  • 一个显式类型的lambda表达式,其主体是一个块,其中至少有一个结果表达式与适用性无关。

因此,明确键入的lambda表达式可以“与适用性相关”,具体取决于它们的内容,而隐式类型的表达式通常被排除。 还有一个附录,更具体:

隐式类型的lambda表达式或不精确的方法引用表达式的含义在解析目标类型之前是足够模糊的,包含这些表达式的参数不被认为与适用性相关; 它们被简单地忽略(除了它们预期的arity),直到重载完成。

所以使用隐式类型(i) -> "Test"无助于决定是否调用method1(Predicate)method1(Function) ,因为两者都不是更具体,所以方法选择失败在尝试推断lambda表达式的函数类型之前。

另一种情况,在method1(Consumer)method1(Predicate)是不同的,因为一个方法的参数具有一个带有void返回的函数类型,另一个是一个非void返回类型,它允许选择一个通过lambda表达式的形状的适用方法,已经在链接的答案中讨论过。 i -> true仅与值兼容 ,因此不适合Consumer 。 同样, i -> {}仅与void兼容 ,因此不适合Predicate

只有少数情况,形状不明确:

  • 当块永远不会正常完成时,例如arg -> { throw new Exception(); } arg -> { throw new Exception(); }
    arg -> { for(;;); }
  • 当lambda表达式的forms为arg -> expressionexpression也是一个语句。 这样的表达陈述是
    • 作业,例如arg -> foo=arg
    • 递增/递减表达式,例如arg -> counter++
    • 方法调用,如示例中的s -> lst.add(s)
    • 实例化,例如arg -> new Foo(arg)

请注意,带括号的表达式不在此列表中,因此将s -> lst.add(s)更改为
s -> (lst.add(s))足以将它变成一个不再与void兼容的表达式。 同样,将其转换为类似s -> {lst.add(s);}的语句会阻止它与值兼容。 因此,在这种情况下选择正确的方法很容易。